Security认证流程源码分析

简介: Security认证流程源码分析

一、security原理

security是通过一系列filter完成认证和授权的

security默认提供了30多个过滤器,spring boot在对security进行自动化配置时,会创建一个名为springsecuritychian过滤链并注入到spring容器中,filterchianproxy做为顶层管理者统一管理springsecuritychian,filterchianproxy本身通过DelegatinFilterProxy整合到原生web过滤链中

过滤器列表

过滤器

作用

默认开启

ChannelProcessingFilter 过滤请求协议 https http 默认no

过滤请求协议 https http

no

WebAsyncManagerIntegrationFilter

将WebAsyncManager和security上下文结合

yes

SecurityContextPersistenceFilter

处理请求前将安全新加载到securitycontextholder

yes

HeaderWriterFilter

处理头信息加入到响应中

yes

CorsFilter

处理跨域问题

no

CsrfFilter

处理csrf攻击

yes

LogoutFilter

处理注销登录

yes

OAuth2AuthorizationRequestRedirectFilter

处理outh2认证重定向

no

Saml2WebSsoAuthenticationRequestFilter

处理saml认证

no

X509AuthenticationFilter

处理x509认证

no

AbstractPreAuthenticatedProcessingFilter

处理预认证问题

no

CasAuthenticationFilter

处理cas认证

no

OAuth2LoginAuthenticationFilter

处理oauth2认证

no

Saml2WebSsoAuthenticationFilter

处理saml认证

no

UsernamePasswordAuthenticationFilter

处理表单认证

yes

OpenIDAuthenticationFilter

处理openid认证

no

DefaultLoginPageGeneratingFilter

配置默认登录界面

yes

DefaultLogoutPageGeneratingFilter

默认注销页面

yes

ConcurrentSessionFilter

处理session有效期

no

DigestAuthenticationFilter

处理http摘要认证

no

BearerTokenAuthenticationFilter

处理oauth2的accesstoken

no

BasicAuthenticationFilter

处理httpbasic认证

yes

RequestCacheAwareFilter

处理请求缓存

yes

SecurityContextHolderAwareRequestFilter

包装原始请求

yes

JaasApiIntegrationFilter

处理jaas认证

no

RememberMeAuthenticationFilter

处理rememberme登录

no

AnonymousAuthenticationFilter

配置匿名认证

yes

OAuth2AuthorizationCodeGrantFilter

处理oauth2认证授权码

no

SessionManagementFilter

处理session并发问题

yes

ExceptionTranslationFilter

处理认证授权中的异常

yes

FilterSecurityInterceptor

处理授权相关

yes

SwitchUserFilter

处理账户切换

no

显示详细信息

二、认证流程分析

1、前端输入完用户名密码之后,会进入UsernamePasswordAuthenticationFilter类中去获取用户名和密码,然后去构建一个UsernamePasswordAuthenticationToken对象。

UsernamePasswordAuthenticationToken这个类是实现了Authentication接口,在调用UsernamePasswordAuthenticationToken的构造函数的时候先调用父类AbstractAuthenticationToken的构造方法,传递一个null,因为在认证的时候并不知道这个用户有什么权限,之后去给用户名密码赋值,最后有一个setAuthenticated(false)方法,代表未认证,源码如下:

实例化UsernamePasswordAuthenticationToken之后调用了setDetails(request,authRequest)将请求的信息设到UsernamePasswordAuthenticationToken中去,包括ip、session等内容

2、然后执行

this.getAuthenticationManager().authenticate(authRequest)

AuthenticationManager本身不包含验证的逻辑,它的作用是管理AuthenticationProvider。

3、authenticate这个方法是在ProviderManager类上的,这个类实现了AuthenticationManager接口,在authenticate方法中有一个for循环,拿到所有的AuthenticationProvider,真正校验的逻辑是写在AuthenticationProvider中的,为什么是一个集合去进行循环?是因为不同的登陆方式认证逻辑是不一样的,可能是微信等社交平台登陆,也可能是用户名密码登陆。AuthenticationManager其实是将AuthenticationProvider收集起来,然后登陆的时候挨个去AuthenticationProvider中问你这种验证逻辑支不支持此次登陆的方式,根据传进来的Authentication类型会挑出一个适合的provider来进行校验处理。

然后去调用provider的验证方法authenticate方法,authenticate是DaoAuthenticationProvider类中的一个方法,DaoAuthenticationProvider继承了AbstractUserDetailsAuthenticationProvider。实际上authenticate的校验逻辑写在了AbstractUserDetailsAuthenticationProvider抽象类中,首先实例化UserDetails对象,调用了retrieveUser方法获取到了一个user对象,retrieveUser是一个抽象方法。

AbstractUserDetailsAuthenticationProvider的 authenticate 方法的一部分:

注意:如果自己自定义了一个MyAuthenticationProvider继承了AuthenticationProvider,这里就会走自己的认证类。

4、DaoAuthenticationProvider实现了 retrieveUser 方法,在实现的方法中实例化了UserDetails对象

注意:如果自己自定义了UserDetailsServiceImpl 实现了 UserDetailsService就会走自己的方法。

5、也就是相当于自定义验证逻辑的那个类,去实现UserDetailService类,这个返回结果就是我们自己在数据库中根据username查询出来的用户信息。在AbstractUserDetailsAuthenticationProvider中如果没拿到信息就会抛出异常,如果查到了就会去调用preAuthenticationChecks的check方法去进行预检查。

AbstractUserDetailsAuthenticationProvider的 authenticate 方法的一部分:

在预检查中进行了三个检查,因为UserDetail类中有四个布尔类型,去检查其中的三个,用户是否锁定、用户是否过期,用户是否可用。

预检查之后紧接着去调用了additionalAuthenticationChecks方法去进行附加检查,这个方法也是一个抽象方法,在DaoAuthenticationProvider中去具体实现,在里面进行了加密解密去校验当前的密码是否匹配。比对密码的过程,用到了PasswordEncoder和SaltSource,密码加密和盐的概念。

如果通过了预检查和附加检查,还会进行厚检查,检查4个布尔中的最后一个。

所有的检查都通过,则认为用户认证是成功的。用户认证成功之后,会将这些认证信息和user传递进去,调用createSuccessAuthentication方法。

createSuccessAuthentication方法中同样会实例化一个user,但是这个方法不会调用之前传两个参数的函数,而是会调用三个参数的构造函数。这个时候,在调super的构造函数中不会再传null,会将authorities权限设进去,之后将用户密码设进去,最后setAuthenticated(true),代表验证已经通过。

最后创建一个authentication会沿着验证的这条线返回回去。如果验证成功,则在这条路中调用我们系统的业务逻辑。如果在任何一处发生问题,就会抛出异常,调用我们自己定义的认证失败的处理器。

三、总结

1、UserDetails与UserDetailsService区别

UserDetails这个接口,它代表了最详细的用户信息,这个接口涵盖了一些必要的用户信息字段,具体的实现类对它进行了扩展。

public interface UserDetails extends Serializable {
 
   Collection<? extends GrantedAuthority> getAuthorities();
 
   String getPassword();
 
   String getUsername();
 
   boolean isAccountNonExpired();
 
   boolean isAccountNonLocked();
 
   boolean isCredentialsNonExpired();
 
   boolean isEnabled();
}

它和Authentication接口很类似,比如它们都拥有username,authorities,区分他们也是本文的重点内容之一。Authentication的getCredentials()与UserDetails中的getPassword()需要被区分对待,前者是用户提交的密码凭证,后者是用户正确的密码,认证器其实就是对这两者的比对。Authentication中的getAuthorities()实际是由UserDetails的getAuthorities()传递而形成的。还记得Authentication接口中的getUserDetails()方法吗?其中的UserDetails用户详细信息便是经过了AuthenticationProvider之后被填充的。!UserDetailsService只负责从特定的地方(通常是数据库)加载用户信息,仅此而已,记住这一点,可以避免走很多弯路

public interface UserDetailsService {

  UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

}

2、authenticationmanager ,providermanager, authenticationprovider关系

security中支持多种不同的认证方式,比如用户名和密码认证、手机号验证码认证等,不同的认证方式对应了不同authenticationprovider所以一个完整的流程可能由多个authenticationprovider提供,多个authenticationprovider组成一个列表,这个列表将由providermanager代理,换句话说providermanager中存在一个列表,在providermanager中遍历列表中每一个authenticationprovider去执行身份认证,最终得到认证结果。

providermanager本身也可以再配置一个authenticationmanager作为parent,这样当providermanager认证失败就可以进入到parent中再次认证,理论上providermanager的parent可以是任意类型的authenticationmanager,但是通常由providermanager来扮演parent角色,也就是providermanager是providermanager的parent。

providermanager本身也可以有多个,多个providermanager共用一个parent,有时一个应用程序有受保护资源的逻辑组,比如所有符合路径模式的资源/api/**.每个组可以有自己专用的authenticationmanager,通常每个组都是一个providermanager,他们共享一个父级,然后父级是一种全局资源,作为所有提供者的后备资源,由此三者关系如图

3、用户信息获取

3.1、业务代码中获取

security会将登录的用户信息数据保存在session中,security在此基础上做了改进,通过一个线程将登陆成的信息保存到SecurityContextHolder中,

SecurityContextHolder中数据保存默认是通过Theadlocal实现的,使用Theadlocal创建的变量只能通过当前线程访问,不能被其他线程或者 当前线程的子线程访问,就是用户数据和请求线程是绑定在一起的,请求完成后,security会将在session中登录信息清空,以后有请求时security会从session信息保存到SecurityContextHolder方便该请求后续处理

实际上SecurityContextHolder存储的是SecurityContext ,在SecurityContext存储的是Authentication

SecurityContextHolder源码如下

public class SecurityContextHolder {
    //存储策略是只有当前请求线程才能获取用户信息 开启子线程也是无法拿到用户信息
    public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
    //多线程存储策略 如果业务子线程中也可以获取用户信息 使用这个
    public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
    //是将数据保存到一个静态变量中
    public static final String MODE_GLOBAL = "MODE_GLOBAL";
    private static final String MODE_PRE_INITIALIZED = "MODE_PRE_INITIALIZED";
    public static final String SYSTEM_PROPERTY = "spring.security.strategy";
    private static String strategyName = System.getProperty("spring.security.strategy");
    private static SecurityContextHolderStrategy strategy;
    private static int initializeCount = 0;
}

SecurityContentHolderStrategy源码如下

public interface SecurityContextHolderStrategy {
 
  /**
   * 清除存储的securityContext
   */
  void clearContext();
 
  /**
   * 获取存储的securityContext
   */
  SecurityContext getContext();
 
  /**
   *设置存储的securityContext
   */
  void setContext(SecurityContext context);
 
  /**
   * 创建一个空的存储的securityContext
   */
  SecurityContext createEmptyContext();
 
}

代码中获取

@GetMapping("/index")
    public String index(){
       Authentication authentication =  SecurityContextHolder.getContext().getAuthentication();
       User user =  (User)authentication.getPrincipal();
       //身份信息
        user.getUsername();
        //权限信息
        user.getAuthorities();
        return "index";
    }

其他方式

方法二:
 @GetMapping("/me")
    public Object getCurrentUser(Authentication authentication){
        return authentication;
    }
 
方法三:
@GetMapping("/me")
    public Object getCurrentUser(@AuthenticationPrincipal UserDetails userDetails){
        return userDetails;
    }

3.2、页面中获取

Thymeleaf对Spring Security的支持都放在thymeleaf-extras-springsecurityX中,目前最新版本为5。所以需要在项目中添加此jar包的依赖和thymeleaf的依赖。后面的X要根据所有的springboot版本来决定

引入依赖

 <!--thymeleaf与security整合包-->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
            <version>3.0.4.RELEASE</version>
        </dependency>
        <!--thymeleaf启动器-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

html页面

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    登录账号:<span sec:authentication="name">123</span><br/>
    登录账号:<span sec:authentication="principal.username">456</span><br/>
    凭证:<span sec:authentication="credentials">456</span><br/>
    权限和角色:<span sec:authentication="authorities">456</span><br/>
    客户端地址:<span sec:authentication="details.remoteAddress">456</span><br/>
    sessionId:<span sec:authentication="details.sessionId">456</span><br/>
</body>
</html>


相关文章
|
2天前
|
安全 Java 数据库
后端进阶之路——Spring Security构建强大的身份验证和授权系统(四)
后端进阶之路——Spring Security构建强大的身份验证和授权系统(四)
|
2天前
|
安全 Java 数据安全/隐私保护
Spring Security OAuth 认证流程浅析:授权码模式
【1月更文挑战第16天】上一篇[Spring Security OAuth 认证流程浅析:密码模式],简单分析了密码模式授权流程的源码,这篇来试着分析 OAuth 中最具代表性的授权码模式。
82 4
|
2天前
|
安全 Java 数据安全/隐私保护
Spring Security OAuth 认证流程浅析:密码模式
【1月更文挑战第15天】从 Spring Security OAuth 源码分析其实现原理。这篇我们先分析最常用也相对简单的密码模式。
90 0
|
9月前
|
安全 Java 数据库
SpringSecurity-4-认证流程源码解析
SpringSecurity-4-认证流程源码解析
49 0
|
2天前
|
存储 安全 Java
Spring Security 认证流程
【1月更文挑战第13天】本文以用户名/密码验证方式为例,讲解 Spring Security 的认证流程,在此之前,需要你了解 Spring Security 用户名/密码认证的基本配置。
55 0
|
2天前
|
SQL 安全 前端开发
Security登录认证流程分析
Security登录认证流程分析
37 5
|
10月前
|
存储 Java 数据库
三.SpringSecurity基础-认证原理
SpringSecurity基础-认证原理
|
存储 NoSQL 数据库
认证授权流程及原理分析
认证授权流程及原理分析
172 0
Spring-security-oauth2 源码分析 登陆流程 /oauth/check_token (二)
Spring-security-oauth2 源码分析 登陆流程 /oauth/check_token (二)
Spring-security-oauth2 源码分析 登陆流程 /oauth/check_token (二)
|
安全 Java 数据库
spring Security 认证流程分析|学习笔记
快速学习 spring Security 认证流程分析
123 0
spring Security 认证流程分析|学习笔记