【security】spring security原理分析 从源码入手

账号密码认证部分

进入认证过滤器doFilter方法:

AbstractAuthenticationProcessingFilter.doFilter()

在这里插入图片描述

/*
* 判断当前filter是否可以处理当前请求,若不行,则交给下一个filter去处理。
*  踩坑:如果登陆接口 错误使用了GET方法  而不是POST方法 
*  你就会惊喜的发现 attemptAuthentication 方法不执行 直接到下一个过滤器的authenticate方法
*/
if (!requiresAuthentication(request, response)) {
    chain.doFilter(request, response);
    return;
}

这里也可以实现UsernamePasswordAuthenticationFilter类 并重写attemptAuthentication方法

// doFilter方法里面 调用了子类(UsernamePasswordAuthenticationFilter)的方法
authResult = this.attemptAuthentication(request, response);

UsernamePasswordAuthenticationFilter attemptAuthentication方法的处理

在这里插入图片描述

// 认证请求的方式必须为POST
if (postOnly && !request.getMethod().equals("POST")) {
    throw new AuthenticationServiceException(
            "Authentication method not supported: " + request.getMethod());
}

// 接下来 对账号密码为空的处理
username = username != null ? username : "";
username.trim()
.....
 
 // Authenticaiton类的实现类UsernamePasswordAuthenticationToken           
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// 构造方法
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) {
    super((Collection)null);
    this.principal = principal;
    this.credentials = credentials;
    this.setAuthenticated(false);
}

// 构造器设置权限为null?super((Collection)null);
// 授权为false?this.setAuthenticated(false);

// 这里是刚刚登陆过来,账号密码还没验证,所以这里需要放行


// super: 

  public AbstractAuthenticationToken(Collection<? extends GrantedAuthority> authorities) {
        if (authorities == null) {
            this.authorities = AuthorityUtils.NO_AUTHORITIES;
        } else {
            Iterator var2 = authorities.iterator();

            while(var2.hasNext()) {
                GrantedAuthority a = (GrantedAuthority)var2.next();
                Assert.notNull(a, "Authorities collection cannot contain any null elements");
            }

            this.authorities = Collections.unmodifiableList(new ArrayList(authorities));
        }
    }


// 现在回到attemptAuthentication方法:
return this.getAuthenticationManager().authenticate(authRequest);

this.getAuthenticationManager() 获取到ProviderManager类

在这里插入图片描述



ProviderManager.authenticate(authentication)方法做了什么?

//全局变量 private List<AuthenticationProvider> providers;

// 获取所有的providers  所以AuthenticationProvider类也是可以由我们重写
 Iterator var9 = this.getProviders().iterator();
 
    Authentication authResult = null;

    while(var9.hasNext()) {
            AuthenticationProvider provider = (AuthenticationProvider)var9.next();
            // 如果有支持当前token的 
            if (provider.supports(toTest)) {
                    // 核心:调用处理 在authenticate方法里面进行密码校验
                    authResult = provider.authenticate(authentication);
               
            }
        }
        // return authResult

这里我们再分析一下核心步骤: provider.authenticate(authentication);

// provider默认是调用AbstractUserDetailsAuthenticationProvider类的

AbstractUserDetailsAuthenticationProvider
	.authenticate(Authentication authentication)


在这里插入图片描述

this.retrieveUser
  (username, (UsernamePasswordAuthenticationToken)authentication);
会去调用子类DaoAuthenticationProvider的retrieveUser方法

在retrieveUser方法里面会去调用这个我们最熟悉的方法:

userDetailsService.loadUserByUsername
在这里插入图片描述

将结果逐级返回后 至此 attemptAuthentication方法结束 , 别忘了这是在doFilter方法调用的,

回到doFilter里面:

// 最终认证成功后,会处理一些与session相关的方法(比如将认证信息存到session等操作)。
sessionStrategy.onAuthentication(authResult, request, response);
// doFilter 接下来的操作:
this.successfulAuthentication(request, response, chain, authResult);

最终认证成功后的相关回调方法,主要将当前的认证信息放到SecurityContextHolder中并调用成功处理器做相应的操作。

在这里插入图片描述

protected void successfulAuthentication(HttpServletRequest request,
    HttpServletResponse response, FilterChain chain, Authentication authResult)
        throws IOException, ServletException {


    
    // 核心:将当前的认证信息放到SecurityContextHolder中
    SecurityContextHolder.getContext().setAuthentication(authResult);
    
    rememberMeServices.loginSuccess(request, response, authResult);
    // Fire event
    if (this.eventPublisher != null) {
        eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                authResult, this.getClass()));
    }
    // 调用成功处理器,可以自己实现AuthenticationSuccessHandler接口重写方法写自己的逻辑
    successHandler.onAuthenticationSuccess(request, response, authResult);
}

总结:

进入校验过滤器 doFilter方法

判断当前filter是否可以处理

默认调用子类 账号密码校验过滤器的attemptAuthentication方法
attemptAuthentication中校验是否为post请求,且对空账号密码进行处理

进入账号密码校验Token构造方法,构造方法会设置不需要权限 进行放行
(因为此时没有去匹配账号密码 必须放进去匹配一次)

调用provider.authenticate()
遍历并取出一个可以提供支持的provider并调用

调用retrieveUser方法

调用我们熟悉的 userDetails.loadUserByUsername

自定义账号密码校验规则
返回结果
结果做些session处理

结果成功处理: 存入securityContentHolder中
默认是使用threadLocal 实现的

token认证部分

实现BasicAuthenticationFilter 过滤器,在doFilterInternal方法进行校验
(token的储存是在账号密码认证成功后执行的 并将token返回至header)

public class TokenAuthenticationFilter extends BasicAuthenticationFilter {

    public TokenAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    public TokenAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
        super(authenticationManager, authenticationEntryPoint);
    }


    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {
        String token = request.getHeader("Authorization");

        // 模拟从redis获取token :   TokenUtils.getTokenList()

        if (!TokenUtils.getTokenList().contains(token)) {
            throw new RemoteException("token校验异常");
        }
        // 模拟 getUser by token
        UserVO userVO = new UserVO();
        userVO.setUsername("qiuhuanhen");
        userVO.setPassword("123456");

        UsernamePasswordAuthenticationToken authentication =
                new UsernamePasswordAuthenticationToken(userVO, "", new ArrayList<>());
        SecurityContextHolder.getContext().setAuthentication(authentication);
        chain.doFilter(request, response);

    }


    @Override
    protected void onSuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) throws IOException {

        System.out.println("token 校验 成功");
        super.onSuccessfulAuthentication(request, response, authResult);
    }

    @Override
    protected void onUnsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException {

        System.out.println("token 校验 失败");
        super.onUnsuccessfulAuthentication(request, response, failed);
    }
}

快速入门demo

写了个简单的登录认证demo
https://gitee.com/qiuhuanhen/springboot-security-login

注意事项: 见 README 文件

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

孟秋与你

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值