文章目录
- 前言
- 认证入口(过滤器)
- 认证管理器
- 认证器说明
- 默认认证器的实现
- 总结
前言
通过上文了解SpringSecurity核心组件后,就可以进一步了解其认证的实现流程了。
认证入口(过滤器)
在SpringSecurity中处理认证逻辑是在UsernamePasswordAuthenticationFilter
这个过滤器中实现的。UsernamePasswordAuthenticationFilter
继承于AbstractAuthenticationProcessingFilter
这个父类。
而在UsernamePasswordAuthenticationFilter
没有实现doFilter方法,所以认证的逻辑需要先看AbstractAuthenticationProcessingFilter
中的doFilter方法。
上面的核心代码是
Authentication authenticationResult = attemptAuthentication(request, response);
attemptAuthentication
方法的作用是获取Authentication
对象对应的其实就是认证过程,进入到UsernamePasswordAuthenticationFilter
中来查看具体的实现。
@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)throws AuthenticationException {if (this.postOnly && !request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}String username = obtainUsername(request);username = (username != null) ? username : "";username = username.trim();String password = obtainPassword(request);password = (password != null) ? password : "";UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);// Allow subclasses to set the "details" propertysetDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}
上面代码的含义非常清晰
- 该方法只支持POST方式提交的请求
- 获取账号和密码
- 通过账号密码获取了
UsernamePasswordAuthenticationToken
对象 - 设置请求的详细信息
- 通过
AuthenticationManager
来完成认证操作
在上面的逻辑中出现了一个对象AuthenticationManager
认证管理器
AuthenticationManager
接口中就定义了一个方法authenticate
方法,处理认证的请求。
public interface AuthenticationManager {Authentication authenticate(Authentication authentication) throws AuthenticationException;}
认证器说明
AuthenticationManager
的默认实现是ProviderManager
,而在ProviderManager
的authenticate
方法中实现的操作是循环遍历成员变量List<AuthenticationProvider> providers
。该providers
中如果有一个AuthenticationProvider
的supports
函数返回true,那么就会调用该AuthenticationProvider
的authenticate
函数认证,如果认证成功则整个认证过程结束。如果不成功,则继续使用下一个合适的AuthenticationProvider
进行认证,只要有一个认证成功则为认证成功。
在当前环境下默认的实现提供是
默认认证器的实现
进入到AbstractUserDetailsAuthenticationProvider
中的认证方法
然后进入到retrieveUser
方法中,具体的实现是DaoAuthenticationProvider
,getUserDetailsService
会获取到我们自定义的UserServiceImpl
对象,也就是会走我们自定义的认证方法了。
@Overrideprotected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)throws AuthenticationException {prepareTimingAttackProtection();try {UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);if (loadedUser == null) {throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation");}return loadedUser;}catch (UsernameNotFoundException ex) {mitigateAgainstTimingAttack(authentication);throw ex;}catch (InternalAuthenticationServiceException ex) {throw ex;}catch (Exception ex) {throw new InternalAuthenticationServiceException(ex.getMessage(), ex);}}
如果账号存在就会开始密码的验证,不过在密码验证前还是会完成一个检查
然后就是具体的密码验证
additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
具体的验证的逻辑
protected void additionalAuthenticationChecks(UserDetails userDetails,UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {// 密码为空if (authentication.getCredentials() == null) {this.logger.debug("Failed to authenticate since no credentials provided");throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}// 获取表单提交的密码String presentedPassword = authentication.getCredentials().toString();// 表单提交的密码和数据库查询的密码 比较是否相对if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {this.logger.debug("Failed to authenticate since password does not match stored value");throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}}
上面的逻辑会通过对应的密码编码器来处理,如果是非加密的情况会通过NoOpPasswordEncoder来处理
public boolean matches(CharSequence rawPassword, String encodedPassword) {return rawPassword.toString().equals(encodedPassword);}
如果有加密处理,就选择对应的加密对象来处理,比如SpringSecurity 入门使用的BCryptPasswordEncoder来处理
总结
Spring Security认证流程:
- 请求首先会进入过滤器,它的作用在于验证系统设置受限资源的过滤器。
- 请求被过滤器接收后,会将其传递给AuthenticationProvider。AuthenticationProvider需要用户的信息以及UserDetailsService的认证实现方式。
- UserDetailsService是被UserDetail继承的,UserDetail封装了User用户信息。在这个阶段,用户信息被封装进AuthenticationProvider。
- 接着,封装了用户信息的AuthenticationProvider会被传递给AuthenticationManager中的ProviderManager。
ProviderManager会对AuthenticationProvider进行再次审核,最终返回过滤器。
通过Spring Security的认证实现,可以看到虽然代码简洁明了,但是其扩展性极强。Spring Security支持多种认证和授权机制,包括用户名密码认证、JWT令牌认证、OAuth2认证等等。同时,Spring Security还提供了多种默认配置,可以根据需要进行调整和扩展。