SpringSecurity-认证流程源码级详解

自定义用户认证逻辑

处理用户信息获取逻辑:UserDetailsService

处理用户校验逻辑:UserDetails

处理密码加密解密:PasswordEncoder

认证处理流程

IMG_256

以表单认证为例:从发起认证请求到认证过滤器,接着认证成功后,响应从认证过滤器返回的整个过程。SpringSecurity做了什么,设计到了哪些类?他们之间如何调用?
SpringSecurity认证流程中涉及到的主要的类和接口如下:

IMG_256

  1. debug启动,浏览器登录:

IMG_256

2. 进入UsernamePasswordAuthenticationFilter:

IMG_256

IMG_256

IMG_256

3.AuthenticationManager

AuthenticationManager自身并不包含验证逻辑,用来管理AuthenticationProvider;

AuthenticationManager有很多实现类,主要是ProviderManager:

IMG_256

IMG_256

ProviderManager在public Authentication authenticate(Authentication authentication)会进行for循环,获取所有AuthenticationProvider,所有校验逻辑是在AuthenticationProvider里面的,为什么这里是一个集合:是因为不同的登录方式其认证逻辑是不一样的,我们现在是用户名密码登录,是需要去校验密码。如果是微信登录,则又是不一样的。AuthenticationManager作用就是把所有AuthenticationProvider搜集起来,认证时候,挨个去问,你当前的provider支不支持我现在的的登录方式(其实就是做循环,然后调用supports方法)。

IMG_256

IMG_2564.AuthenticationProvider

进入DaoAuthenticationProvider,DaoAuthenticationProvider会调用其父类的AbstractUserDetailsAuthenticationProvider的authenticate方法

IMG_256

IMG_256

附加检查:在DaoAuthenticationProvider--->检查密码是否匹配

IMG_256

然后进行后检查:后检查主要检查4个boolean中最后一个:

IMG_256

如果上面检验全部没问题的话,就认为认证是合法的,然后我们再创建一个已经授权的Authentication

IMG_256

IMG_256

IMG_2565.UserDetailsService

自定义用户认证,实现UserDetailsService接口

6.UserDetails

User是UserDetails实现类

7.登录成功之后走到我们自定义的成功处理器

进入AbstractAuthenticationProcessingFilter

IMG_256

进入自定义成功处理器:

IMG_256

认证结果在多个请求之间共享

多个请求共享肯定是放到session里,那么SpringSecurity是什么时候放到了session里面?什么时候又从session里面读取出来的?

IMG_256

AbstractAuthenticationProcessingFilter里面successfulAuthentication有一个:

SecurityContextHolder.getContext().setAuthentication(authResult);

IMG_256

其实是把认证成功的Authentication放到: SecurityContext里面,然后SecurityContext放到SecurityContextHolder里面。

IMG_256

SecurityContext其实很简单,他就是一个接口,其唯一的实现类是:

IMG_256

复制代码
package org.springframework.security.core.context;

import org.springframework.security.core.Authentication;

public class SecurityContextImpl implements SecurityContext {
    private static final long serialVersionUID = 420L;
    private Authentication authentication;

    public SecurityContextImpl() {
    }

    public boolean equals(Object obj) {
        if (obj instanceof SecurityContextImpl) {
            SecurityContextImpl test = (SecurityContextImpl)obj;
            if (this.getAuthentication() == null && test.getAuthentication() == null) {
                return true;
            }

            if (this.getAuthentication() != null && test.getAuthentication() != null && this.getAuthentication().equals(test.getAuthentication())) {
                return true;
            }
        }

        return false;
    }

    public Authentication getAuthentication() {
        return this.authentication;
    }

    public int hashCode() {
        return this.authentication == null ? -1 : this.authentication.hashCode();
    }

    public void setAuthentication(Authentication authentication) {
        this.authentication = authentication;
    }

    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(super.toString());
        if (this.authentication == null) {
            sb.append(": Null authentication");
        } else {
            sb.append(": Authentication: ").append(this.authentication);
        }

        return sb.toString();
    }
}
复制代码

SecurityContext说明:是对Authentication的包装,并且重写其hashCode和equals,保证其唯一性。

SecurityContextHolder:

  实际上是ThreaadLocal封装,ThreadLocal是跟线程绑定的一个Map,在这个线程里面存放的东西可以在另一个线程读取出来,认证结果存入ThreaadLocalMap,可以让不同方法直接使用,避免重复认证,参数传递的麻烦。

IMG_256

最后:SecurityContextHolder会交给SecurityContextPersistenceFilter过滤器:他的位置在过滤器链的最前端:

IMG_256

SecurityContextPersistenceFilter过滤器作用:

  1. 当请求进来的时候:检查session里面是否有SecurityContext,如果有将其拿出来,然后放到线程里面,如果没有就过去了;响应时候:从线程里面(因为其在过滤器链上最前端,请求先经过此过滤器,然后最后会从这个过滤器出去)
  2. 当整个请求响应回来以后:最后一个过他的时候,他检查线程,如果线程里面有:SecurityContext,就拿出来放到session里面去。

这样不同的请求可以从线程里面拿到认证信息。拿到以后放到线程里面,因为请求和响应是在一个线程中。

获取认证用户信息

@GetMapping("/me")
public Object getCurrentUser(){
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    Object u = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    return authentication;
}

或参数中直接写Authentication authentication

@GetMapping("/me2")
public Object getCurrentUser(Authentication authentication){//框架会注入
    //Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    //Object u = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    Object u = authentication.getPrincipal();
    return authentication;
}

只需要UserDetails

@GetMapping("/me3")
public Object getCurrentUser(@AuthenticationPrincipal UserDetails userDetails){//框架会注入
    String username = userDetails.getUsername();
    Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
    boolean accountNonExpired = userDetails.isAccountNonExpired();
    return userDetails;
}

 

posted @   wangzhilei  阅读(90)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示