Spring Security 认证流程
继承WebSecurityConfigurerAdapter ,在其中进行安全认证的配置
- CustomizeAuthenticationFilter extends UsernamePasswordAuthenticationFilter
判断数据格式是否为json格式.如果为json格式,则执行自定义解析,否则执行父类默认的解析.
过程:
解析登录表单中的账户名密码,封装到Spring Security的UsernamePasswordAuthenticationToken(principal,credentials),并且调用父类AbstractAuthenticationToken的构造器,设置权限为null,setAuthenticated(false)(即未认证).然后调用AuthenticationManager的authenticate()进行验证.
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
} else {
String username = this.obtainUsername(request);
String password = this.obtainPassword(request);
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
}
2.ProviderManager
进入AuthenticationManager的实现类ProviderManager中.首先遍历自身支持的登录方式(AuthenticationProvider),确认是否支持UsernamePasswordAuthenticationToken的登录方式.
只有登录方式被支持,才会进行验证.进入
AuthenticationProvider.authenticate()(子类AbstractUserDetailsAuthenticationProvider).
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Class<? extends Authentication> toTest = authentication.getClass();
AuthenticationException lastException = null;
Authentication result = null;
boolean debug = logger.isDebugEnabled();
Iterator var6 = this.getProviders().iterator();
while(var6.hasNext()) {
AuthenticationProvider provider = (AuthenticationProvider)var6.next();
if (provider.supports(toTest)) { //验证是否支持登录方式
if (debug) {
logger.debug("Authentication attempt using " + provider.getClass().getName());
}
try {
result = provider.authenticate(authentication); //进入身份验证
if (result != null) {
this.copyDetails(authentication, result);
break;
}
} catch (AccountStatusException var11) {
this.prepareException(var11, authentication);
throw var11;
} catch (InternalAuthenticationServiceException var12) {
this.prepareException(var12, authentication);
throw var12;
} catch (AuthenticationException var13) {
lastException = var13;
}
}
}
3.AbstractUserDetailsAuthenticationProvider
(1)在AbstractUserDetailsAuthenticationProvider的authenticate()中,首先获取UserCache中的UserDetails,如果为空说明未验证.
(2)利用retrieveUser()方法获取用户信息,由DaoAuthenticationProvider获得UserDetailsService,调用loadUserByUsername()获取数据库中的用户信息(该部分非常关键,自定义实现类,注册到验证体系中).
(3)而后对UserDetail进行三步验证.this.preAuthenticationChecks.check(user);验证用户是否锁定,是否过期,是否冻结.
(4)DaoAuthenticationProvider实现additionalAuthenticationChecks(),通过默认的编码格式验证密码.(可以实现PasswordEncoder,自定义密码的编码格式,注册到体系中)
(5)然后this.postAuthenticationChecks.check(user);验证用户密码是否过期.并将其存入缓存中.
(6)createSuccessAuthentication将验证用户信息封装成UsernamePasswordAuthenticationToken.
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName();
boolean cacheWasUsed = true;
UserDetails user = this.userCache.getUserFromCache(username);
if (user == null) {
cacheWasUsed = false;
try {
//1.获取用户信息
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
} catch (UsernameNotFoundException var6) {
this.logger.debug("User '" + username + "' not found");
if (this.hideUserNotFoundExceptions) {
throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
throw var6;
}
Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
}
try {
//2.检查由DefaultPreAuthenticationChecks类实现(主要判断当前用户是否锁定,过期,可用)
this.preAuthenticationChecks.check(user);
//3.验证密码
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
} catch (AuthenticationException var7) {
if (!cacheWasUsed) {
throw var7;
}
cacheWasUsed = false;
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
this.preAuthenticationChecks.check(user);
this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication);
}
//4.验证用户密码是否过期
this.postAuthenticationChecks.check(user);
if (!cacheWasUsed) {
this.userCache.putUserInCache(user);
}
Object principalToReturn = user;
if (this.forcePrincipalAsString) {
principalToReturn = user.getUsername();
}
return this.createSuccessAuthentication(principalToReturn, authentication, user);
}
4.AbstractAuthenticationProcessingFilter
验证成功后,进入该类的successfulAuthentication,将认证结果保存起来,然后返回实现AuthenticationSuccessHandler的自定义类AuthenticationSuccessHandlerImpl.
项目中,AuthenticationSuccessHandlerImpl主要移除现有的token,并创建新的token返回给客户端.
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Authentication success. Updating SecurityContextHolder to contain: " + authResult);
}
SecurityContextHolder.getContext().setAuthentication(authResult);
this.rememberMeServices.loginSuccess(request, response, authResult);
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
总结
UserDetailsService接口作为桥梁,是DaoAuthenticationProvier与特定用户信息来源进行解耦的地方,UserDetailsService由UserDetails和UserDetailsManager所构成;UserDetails和UserDetailsManager各司其责,一个是对基本用户信息进行封装,一个是对基本用户信息进行管理;
特别注意,UserDetailsService、UserDetails以及UserDetailsManager都是可被用户自定义的扩展点,我们可以继承这些接口提供自己的读取用户来源和管理用户的方法
另外:
项目中主要注册了;
拒绝访问处理器:AccessDeniedHandler
认证失败处理器:SimpleUrlAuthenticationFailureHandler
认证成功处理器:AuthenticationSuccessHandler
用户密码编码格式:PasswordEncoder
登出成功处理器:LogoutSuccessHandler
认证失败终点(非登录过程):AuthenticationEntryPoint
请求拦截认证:OncePerRequestFilter
客户登录认证:UsernamePasswordAuthenticationFilter
加载方法:
AuthenticationManagerBuilder --userDetailsService()注册获取用户信息的UserDetailsService
@Override
protected void configure(HttpSecurity http) throws Exception {
//@formatter:off
http
.csrf().disable().cors()
.and()
.httpBasic().authenticationEntryPoint(unauthorisedEntryPoint) //注册认证失败终点处理器
.and()
.exceptionHandling()
.accessDeniedHandler(authenticationAccessDeniedHandler()) //注册认证失败处理器
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(logoutSuccessHandler())//登出
.and()
.authorizeRequests()//配置认证请求
//注意顺序!按顺序依次认证
.antMatchers("/address").authenticated()
.antMatchers("/datastation/**").permitAll()
.and()
.addFilterBefore(tokenAuthenticationFilter, BasicAuthenticationFilter.class)//注册请求拦截认证的策略
.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) //注册登录拦截认证
;
//@formatter:on
}
@Bean
CustomizeAuthenticationFilter customAuthenticationFilter() throws Exception {
CustomizeAuthenticationFilter filter = new CustomizeAuthenticationFilter();
//注册认证成功和失败的处理器
filter.setAuthenticationSuccessHandler(authenticationSuccessHandler());
filter.setAuthenticationFailureHandler(authenticationFailureHandler());
filter.setFilterProcessesUrl("/login");//配置登录认证路径
filter.setAuthenticationManager(authenticationManagerBean());
return filter;
}
上一篇: 怎样快速瘦肚子,这里有妙方请拿走
下一篇: 介绍卸妆油的正确与错误使用方法