@[TOc]
聊聊SpringSecurity认证流程和授权流程
认证流程
我们先聊一下SpringSecurity的认证流程
首先是AbstractAuthenticationProcessingFilter的doFilter()方法
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (!requiresAuthentication(request, response)) { chain.doFilter(request, response); return; } Authentication authResult; try { authResult = attemptAuthentication(request, response); if (authResult == null) { return; } sessionStrategy.onAuthentication(authResult, request, response); } catch (InternalAuthenticationServiceException failed) { logger.error( "An internal error occurred while trying to authenticate the user.", failed); unsuccessfulAuthentication(request, response, failed); return; } catch (AuthenticationException failed) { // Authentication failed unsuccessfulAuthentication(request, response, failed); return; } // Authentication success if (continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } successfulAuthentication(request, response, chain, authResult); }attemptAuthentication()
方法中调用抽象方法attemptAuthentication()获取认证信息,实现类是UsernamePasswordAuthenticationFilter,它的attemptAuthentication()方法:
ProviderManager的authenticate()方法中获取所有的AuthenticationProvider实例,遍历看哪个,最终找到AbstractUserDetailsAuthenticationProvider的supports()方法,发现支持UsernamePasswordAuthenticationToken,发现支持后调用AbstractUserDetailsAuthenticationProvider的authenticate()方法,方法中根据用户名查找缓存有没有,没有的话调用retrieveUser()方法,方法中调用
this.getUserDetailsService().loadUserByUsername(username);也就是实现UserDetailsService接口的自定义类loadUserByUsername()方法,获取用户信息和权限信息封装到UserDetails中并返回
继续看AbstractUserDetailsAuthenticationProvider的authenticate()方法,获取完UserDetails信息后进行预检查,看一下用户是否被冻结等等,然后进入DaoAuthenticationProvider的additionalAuthenticationChecks(),这个方法里进行密码的比对,看数据库的密码和表单密码是否一致,默认比对逻辑是BCryptPasswordEncoder的matches()方法完成的
然后是AbstractUserDetailsAuthenticationProvider的postAuthenticationChecks.check(user);进行后置检查,检查账户是否过期等,
然后调用createSuccessAuthentication()方法,创建UsernamePasswordAuthenticationToken对象,构造方法中设置authenticated为true,表示已经通过认证,返回UsernamePasswordAuthenticationToken对象
至此attemptAuthentication()
successfulAuthentication()
AbstractAuthenticationProcessingFilter的successfulAuthentication()方法中把认证信息写入SecurityContextHolder.getContext()中,然后调用AuthenticationSuccessHandler的onAuthenticationSuccess方法,我们可以实现AuthenticationSuccessHandler接口自定义认证成功的处理类
unsuccessfulAuthentication()
失败方法是unsuccessfulAuthentication(),方法中清空SecurityContextHolder保存的信息,调用AuthenticationFailureHandler的onAuthenticationFailure(),我们同样可以实现AuthenticationFailureHandler接口自定义认证失败处理类
授权流程
认证涉及到的过滤器为FilterSecurityInterceptor
-
调用invoke方法,
-
进入AbstractSecurityInterceptor的beforeInvocation()方法:
通过obtainSecurityMetadataSource()获取系统中的权限的配置
-
进入authenticateIfRequired()方法
获取Authentication信息,这里面有用户的权限列表,并返回
-
得到认证信息后调用AccessDecisionManager决策管理器的decide()方法
方法中获取投票者进行投票
如果投票不通过的话会抛出AccessDeniedException异常,被ExceptionTranslationFilter处理
-
-
总结
我们主要讲了一下SpringSecurity的表单认证流程和授权流程,主要是AbstractAuthenticationProcessingFilter过滤器的作用,我们对AbstractAuthenticationProcessingFilter进行代码分析,涉及过滤器UsernamePasswordAuthenticationFilter,并对其中的方法进行了分析,以及认证成功和认证失败的处理方法successfulAuthentication()和unsuccessfulAuthentication()进行了分析。授权流程主要涉及的是FilterSecurityInterceptor类,通过投票决策器进行投票来判断是否有相应的权限