首页 > 其他分享 >Spring-Security登录认证授权原理

Spring-Security登录认证授权原理

时间:2023-01-09 10:08:36浏览次数:64  
标签:调用 SecurityContext Spring UsernamePasswordAuthenticationToken 认证 获取 Security 方法

spring-security源码下载地址:

https://github.com/spring-projects/spring-security

Spring-Security源码解读:

  • 1.使用ctrl+shift+n组合键查找UsernamePasswordAuthenticationFilter过滤器,该过滤器是用来处理用户认证逻辑的,进入后如图: image.png

  • (1)可以看到它默认的登录请求url是"/login",并且只允许POST方式的请求

  • (2)obtainUsername()方法点进去发现它默认是根据参数名为"username"和"password"来获取用户名和密码的

  • (3)通过构造方法实例化一个UsernamePasswordAuthenticationToken对象,此时调用的是UsernamePasswordAuthenticationToken的两个参数的构造函数,如图: image.png

其中super(null)调用的是父类的构造方法,传入的是权限集合,因为目前还没有认证通过,所以不知道有什么权限信息,这里设置为null,然后将用户名和密码分别赋值给principal和credentials,同样因为此时还未进行身份认证,所以setAuthenticated(false)

  • (4)setDetails(request, authRequest)是将当前的请求信息设置到UsernamePasswordAuthenticationToken中

  • (5)通过调用getAuthenticationManager()来获取AuthenticationManager,通过调用它的authenticate方法来查找支持该token(UsernamePasswordAuthenticationToken)认证方式的provider,然后调用该provider的authenticate方法进行认证

  • 2.AuthenticationManager是用来管理AuthenticationProvider的接口,通过查找后进入,然后使用ctrl+H组合键查看它的继承关系,找到ProviderManager实现类,它实现了AuthenticationManager接口,查看它的authenticate方法,它里面有段这样的代码:

for (AuthenticationProvider provider : getProviders()) {
        if (!provider.supports(toTest)) {
            continue;
        }
    ...
    try {
            result = provider.authenticate(authentication);
    ...
    }
}

通过for循环遍历AuthenticationProvider对象的集合,找到支持当前认证方式的AuthenticationProvider,找到之后调用该AuthenticationProvider的authenticate方法进行认证处理:

result = provider.authenticate(authentication);
  • 3.AuthenticationProvider接口,就是进行身份认证的接口,它里面有两个方法:authenticate认证方法和supports是否支持某种类型token的方法,通过ctrl+h查看继承关系,找到AbstractUserDetailsAuthenticationProvider抽象类,它实现了AuthenticationProvider接口,它的supports方法如下:
    public boolean supports(Class<?> authentication) {
        return (UsernamePasswordAuthenticationToken.class
            .isAssignableFrom(authentication));
        }

说明它是支持UsernamePasswordAuthenticationToken类型的AuthenticationProvider

 

再看它的authenticate认证方法,其中有一段这样的代码:

boolean cacheWasUsed = true;
    UserDetails user = this.userCache.getUserFromCache(username);
 
    if (user == null) {
        cacheWasUsed = false;
 
        try {
            user = retrieveUser(username,
                    (UsernamePasswordAuthenticationToken) authentication);
        }
    ...
    }

如果从缓存中没有获取到UserDetails,那么它调用retrieveUser方法来获取用户信息UserDetails,这里的retrieveUser是抽象方法,等一会我们看它的子类实现。

 

用户信息UserDetails是个接口,我们进入查看,它包含以下6个接口方法:

Collection<? extends GrantedAuthority> getAuthorities();//获取权限集合
String getPassword();  //获取密码
String getUsername();   //获取用户名
boolean isAccountNonExpired(); //账户未过期
boolean isAccountNonLocked();   //账户未锁定
boolean isCredentialsNonExpired(); //密码未过期
boolean isEnabled();    //账户可用

查看它的继承关系发现User类实现了该接口,并实现了该接口的所有方法

接着AbstractUserDetailsAuthenticationProvider往下看,找到下面的代码:

    preAuthenticationChecks.check(user);
    additionalAuthenticationChecks(user,
                (UsernamePasswordAuthenticationToken) authentication);

preAuthenticationChecks预检查,在最下面的内部类DefaultPreAuthenticationChecks中可以看到,它会检查上面提到的三个boolean方法,即检查账户未锁定、账户可用、账户未过期,如果上面的方法只要有一个返回false,就会抛出异常,那么认证就会失败。

 

additionalAuthenticationChecks是附加检查,是个抽象方法,等下看子类的具体实现。

 

下面还有个postAuthenticationChecks.check(user)后检查,在最下面的DefaultPostAuthenticationChecks内部类中可以看到,它会检查密码未过期,如果为false就会抛出异常

 

如果上面的检查都通过并且没有异常,表示认证通过,会调用下面的方法:

createSuccessAuthentication(principalToReturn, authentication, user);

跟进发现此时通过构造方法实例化对象UsernamePasswordAuthenticationToken时,调用的是三个参数的构造方法:

    public UsernamePasswordAuthenticationToken(Object principal, Object credentials,
        Collection<? extends GrantedAuthority> authorities) {
    super(authorities);
    this.principal = principal;
    this.credentials = credentials;
    super.setAuthenticated(true); // must use super, as we override
}

此时会调用父类的构造方法设置权限信息,并调用父类的setAuthenticated(true)方法,到这里就表示认证通过了。

 

下面我们看看AbstractUserDetailsAuthenticationProvider的子类,同ctrl+h可查看继承关系,找到DaoAuthenticationProvider

  • 4.DaoAuthenticationProvider类

  • (1)查看additionalAuthenticationChecks附加检查方法,它主要是检查用户密码的正确性,如果密码为空或者错误都会抛出异常

  • (2)获取用户信息UserDetails的retrieveUser方法,主要看下面这段代码:

UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);

它是调用了getUserDetailsService先获取到UserDetailsService对象,通过调用UserDetailsService对象的loadUserByUsername方法获取用户信息UserDetails

 

找到UserDetailsService,发现它是一个接口,查看继承关系,有很多实现,都是spring-security提供的实现类,并不满足我们的需要,我们想自己制定获取用户信息的逻辑,所以我们可以实现这个接口。比如从我们的数据库中查找用户信息

  • 5.SecurityContextPersistenceFilter过滤器

那么用户认证成功之后,又是怎么保存认证信息的呢,在下一次请求过来是如何判断该用户是否已经认证了呢?

 

请求进来时会经过SecurityContextPersistenceFilter过滤器,进入SecurityContextPersistenceFilter过滤器并找到以下代码:

SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

从session中获取SecurityContext对象,如果没有就实例化一个SecurityContext对象

SecurityContextHolder.setContext(contextBeforeChainExecution);

将SecurityContext对象设置到SecurityContextHolder中

chain.doFilter(holder.getRequest(), holder.getResponse());

表示放行,执行下一个过滤器

执行完后面的过滤并经过servlet处理之后,响应给浏览器之前再次经过此过滤器。查看以下代码:

SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
SecurityContextHolder.clearContext();
this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());

通过SecurityContextHolder获取SecurityContext对象,然后清除SecurityContext,最后将获取的SecurityContext对象放入session中

 

其中SecurityContextHolder是与ThreadLocal绑定的,即本线程内所有的方法都可以获得SecurityContext对象,而SecurityContext对象中包含了Authentication对象,即用户的认证信息,spring-security判断用户是否认证主要是根据SecurityContext中的Authentication对象来判断。Authentication对象的详细信息如图: image.png

最后整个过程的流程大致如下图:

spring-securityx.png

原文出处:

Spring-Security登录认证授权原理

标签:调用,SecurityContext,Spring,UsernamePasswordAuthenticationToken,认证,获取,Security,方法
From: https://blog.51cto.com/u_14014612/5996837

相关文章

  • SpringBoot——动态数据源(多数据源自动切换)
    前言日常的业务开发项目中只会配置一套数据源,如果需要获取其他系统的数据往往是通过调用接口,或者是通过第三方工具比如kettle将数据同步到自己的数据库中进行访问。 但......
  • Springboot使用不了jsp的一些bug
    因为在SpringBoot中不推荐使用JSP作为动态页面,我们要想使用JSP编写动态页面,需要手动添加webapp目录。形成这个样子就算成功了(不必须有web.xml) 由于SpringBoot自带tomca......
  • 实现分页几种常见方式(SpringBoot)
    实现分页作用:分页展示,减少数据的处理量封装一个MyBatisUtils工具类一、直接使用sql中的关键字limit(物理分页)使用Mybatis实现分页,核心仍为SQL写mybatis的接口UserMap......
  • VsCode新建Java、SpringBoot、Python、JavaWeb项目的基本步骤
    新建Java项目选中正上方的搜索框,按下F1快捷键,输入createJava,即可出现这样的一个命令:选中这个:然后为新创建的项目选择一个合适的位置就好啦!新建SpringBoot项目选中......
  • Element前端框架的简介+基于springboot框架的CRUD
    1ElementUI简介基于Vue的一套桌面端组件库,提前封装好的UI模板,方便开发者快速搭建一个网站前端界面。官网:https://element.eleme.cn/2ElementUI安装首先创建......
  • springboot的配置文件
    springboot为了简化spring,是一个用于方便使用spring的工具今天,这里分享springboot的配置文件一、分类springboot的配置文件有3种,分别以properties,yml,yaml后缀名结......
  • SpringMVC(一)
    SpringMVC1.简介与入门1.1什么是MVCMVC是一种软件架构的思想,将软件按照模型、视图、控制器来划分M:Model,模型层,指工程中的JavaBean,作用是处理数据JavaBean分为两类......
  • SpringMVC拦截器使用
    SpringMVC拦截器拦截器是用来干什么的?在一个登录功能中,如果用户没有登录却尝试通过地址栏直接访问内部服务器资源,这显然是非法的。怎样对这些的非法访问进行拦截?SpringM......
  • Spring事务管理
    一个转账的问题创建表 添加数据 实体类逆向生成实体类,并添加无参构造、带参构造、toStringpackagecom.qzcsbj.bean;publicclassAccount{privatelon......
  • SpringBoot——Swagger2的集成和使用
    前言现在都奉行前后端分离开发和微服务大行其道,分微服务及前后端分离后,前后端开发的沟通成本就增加了。所以一款强大的RESTfulAPI文档就至关重要了。而目前在后端领域,基本......