为了账号安全,请及时绑定邮箱和手机立即绑定

SpringSecurity认证流程源码讲解

标签:
Java SpringBoot

技术之路不进即退,无岸,又何谈回头。

从三个方面来说明

  1. 认证流程说明
  2. 认证结果如何在多个请求之间共享
  3. 获取认证用户信息
认证流程说明(SpringSecurity基本原理)

springSecurity其实就是一组过滤器,绿色的过滤器是由自己控制的,可以选择它是否出现。但是其他颜色就不可以了,并且他们的顺序也是死的。在springboot启动的时候会自动的进行加载。如下图所示:在这一组过滤器中,一个块就代表着一种方式,像表单和http Basic认证。橘色的框会根据之前绿色过滤器进行相应的判断,然后抛出异常。(例如身份认证没有通过) 然后由蓝色框会捕获抛出来的异常。

PS:一般来说链上的过滤器一般会有十几种

图片描述

判断逻辑都是在 InterceptorStatusToken token = super.beforeInvocation(fi);(FilterSecurityInterceptor 124)执行的,然后 fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); 这里实际是调用我们的API了,
举例

在这里我们就那上面那个实例来进行描述。在config中开启了任何url进行过滤之后。我们访问这样/mqtt/messagepub/new url会直接跳到橘色块,然后抛出未进行身份登录的异常,然后由蓝色框进行捕获,处理之后直接重定向到一个页面,进行登录。在登陆成功之后又会重新到橘色框,此次便不在抛异常了。

我们将依据下面这幅图来进行详细描述

1-认证处理流程

2-认证结果如何在多个请求之间共享

3-获取认证用户信息

图片描述
在过滤器中依据用户名和密码构建了一个UsernamePasswordAuthenticationToken(Authentication的一个实现)对象,其实就是一个Authentication的实现,他封装了我们需要的认证信息。之后会调用AuthenticationManager.。这个类其实并不会去验证我们的信息,信息验证的逻辑都是在AuthenticationProvider里面,而Manager的作用则是去管理Provider,管理的方式是通过for循环去遍历(因为不同的登录逻辑是不一样的,比如表单登录、第三方登录。换句话说 不同的Provider支持的是不同的Authentication)。这里我们举一个实际的例子去看。
在 AuthenticationManager调用DaoAuthenticationProvider。而DaoAuthenticationProvider继承了AbstractUserDetailsAuthenticationProvider ,从而也就获得了其中的authenticate方法去进行验证。

  result = provider.authenticate(authentication);

AbstractUserDetailsAuthenticationProvider通过方法 retrieveUser拿到UserDetails这个类的信息(用户的信息),然后调用preAuthenticationChecks去进行预检查(里面有三个校验,没有密码过期的校验)。然后调用additionalAuthenticationChecks(附加检查),去进行密码的加密解密。
最后的postAuthenticationChecks区进行的密码是否过期的校验。
Details四个Blooen验证工过之后则会再次调用UsernamePasswordAuthenticationToken,当然这次调用与之前是不同的,之前的调用的是两个参数的,并没有获得用户权限的信息,因此调用父类方法的时候传的是空(super())。这次调用则传了相应的用户权限信息(三个参数,多了权限信息)。

public Authentication authenticate(Authentication authentication)
            throws AuthenticationException {
……
user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
            }
            catch (UsernameNotFoundException notFound) {
                logger.debug("User '" + username + "' not found");

                if (hideUserNotFoundExceptions) {
                    throw new BadCredentialsException(messages.getMessage(
                            "AbstractUserDetailsAuthenticationProvider.badCredentials",
                            "Bad credentials"));
                }
……
    try {
            preAuthenticationChecks.check(user);
            additionalAuthenticationChecks(user,
                    (UsernamePasswordAuthenticationToken) authentication);
        }
        catch (AuthenticationException exception) {
            if (cacheWasUsed) {
                // There was a problem, so try again after checking
                // we're using latest data (i.e. not from the cache)
                cacheWasUsed = false;
                user = retrieveUser(username,
                        (UsernamePasswordAuthenticationToken) authentication);
                preAuthenticationChecks.check(user);
                additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
            }
            else {
                throw exception;
            }
        }

        postAuthenticationChecks.check(user);

在这里我们对 认证流程说明 做一个简单的总结。
AuthenticationProvider去调用UserDetailsServicen拿到用户信息,然后做一些检查。最后把用户信息拼装到一个已经认证了的Authentication里面。然后Authentication则会沿着调用线最后返回到UernamePassword……Filter。

第二点

一说到共享,我感觉大家等会想到Session 那么问题来了。把什么放到session,有什么时候从session读取?

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

        if (logger.isDebugEnabled()) {
            logger.debug("Authentication success. Updating SecurityContextHolder to contain: "
                    + authResult);
        }

         //这里是重点
        SecurityContextHolder.getContext().setAuthentication(authResult);

        rememberMeServices.loginSuccess(request, response, authResult);

        // Fire event
        if (this.eventPublisher != null) {
            eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(
                    authResult, this.getClass()));
        }

        successHandler.onAuthenticationSuccess(request, response, authResult);

从上面我们可以了解 实际上这里是把我们认证成功的authtication放到securitycont里面,然后再放到SecourityContextHolder里面。
图片描述
SecurityContextImpl implements SecurityContext,他重写了hashcode和equals保证
authentication的唯一性。而SecurityContextHolder是对threadLocal的一个封装。目的是为了方便SecurityContextPersistrncrFilter随时可以读取到。而这个过滤器在整个过滤器的最前面。因为在过滤器的最前端,所以请求最先进到这个过滤器,当完成相应之后最后经过它。所以他的作用

1---当请求进来的时候检查seesion,里面是否有SecurityContext,如果有就拿出来放到线程里,如果没有则直接放过。

2---当完成相应后,线程里有认证信息则放到Session里。

第三点
 @GetMapping("/me")
    public Object getCurrentUser(){
        return SecurityContextHolder.getContext().getAuthentication();
    }
//只获得UserDetails 信息
@GetMapping("/me1")
    public Object getCurrentUser(@AuthenticationPrincipal UserDetails user){
        return user;
    }
点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
9
获赞与收藏
50

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消