赞
踩
Spring Security 是一个和Spring无缝衔接的重量级安全权限框架,相信各位小码农们对于Security 是比较熟悉的,重量级,上手快,和Spring无缝衔接,自从SpringBoot框架横空出世后,Security的使用变得更加容易了。本文旨在从实际应用角度出发,阅读 Spring Security 源码,分析其实现原理。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)res; //1. 判断请求是否需要认证,如果不需要认证则跳过,否则继续向下执行 if (!this.requiresAuthentication(request, response)) { chain.doFilter(request, response); } else { if (this.logger.isDebugEnabled()) { this.logger.debug("Request is to process authentication"); } Authentication authResult; try { //2.认证主要是这个方法 authResult = this.attemptAuthentication(request, response); if (authResult == null) { return; } //3.认证信息会话 this.sessionStrategy.onAuthentication(authResult, request, response); } catch (InternalAuthenticationServiceException var8) { this.logger.error("An internal error occurred while trying to authenticate the user.", var8); this.unsuccessfulAuthentication(request, response, var8); return; } catch (AuthenticationException var9) { this.unsuccessfulAuthentication(request, response, var9); return; } if (this.continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } this.successfulAuthentication(request, response, chain, authResult); } }
(3)密码模式的认证器org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter#attemptAuthentication
//如果不是POST请求则抛出异常 if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } else { //调用request.getParameter()获取用户名和密码 String username = this.obtainUsername(request); String password = this.obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); //token类型,比较重要有三个字段principal和credentials,authorities,principal默认为用户名,credentials默认为密码,此时认证信息默认为fasle,各位可以点进去看一下构造器 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); //一些url请求信息,跳过 this.setDetails(request, authRequest); //这个时候认证流程的主角登场了AuthenticationManager,后面会详细讲一下AuthenticationManager初始化构建过程,由实现类ProviderManager管理认证流程 return this.getAuthenticationManager().authenticate(authRequest); }
List<AuthenticationProvider> providers;
public Authentication authenticate(Authentication authentication) throws AuthenticationException { //1. token类型 Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; Authentication result = null; boolean debug = logger.isDebugEnabled(); //AuthenticationProvider认证器集合 Iterator var6 = this.getProviders().iterator(); while(var6.hasNext()) { AuthenticationProvider provider = (AuthenticationProvider)var6.next(); //这一步supports就是使用token类型找到匹配的AuthenticationProvider认证器 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; } } } if (result == null && this.parent != null) { try { result = this.parent.authenticate(authentication); } catch (ProviderNotFoundException var9) { } catch (AuthenticationException var10) { lastException = var10; } } if (result != null) { if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) { ((CredentialsContainer)result).eraseCredentials(); } this.eventPublisher.publishAuthenticationSuccess(result); return result; } else { if (lastException == null) { lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}")); } this.prepareException((AuthenticationException)lastException, authentication); throw lastException; } }
(2)可以实现AuthenticationProvider自定义认证器,但是要重写supports方法,该方法根据token类型选择认证器。
默认就是前面提到的UsernamePasswordAuthenticationToken,记得自定义token,可以选择继承AbstractAuthenticationToken
public boolean supports(Class<?> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); //从token中获取用户名 String username = authentication.getPrincipal() == null ? "NONE_PROVIDED" : authentication.getName(); boolean cacheWasUsed = true; UserDetails user = this.userCache.getUserFromCache(username); if (user == null) { cacheWasUsed = false; try { //提到的UserDetailService使用的地方,用来获取用户信息,自定义逻辑 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 { //判断用户是否可用,锁定,过期 this.preAuthenticationChecks.check(user); //校验密码,也是平时很疑惑Security到底怎么做到自动校验用户名密码的位置 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); } //后置校验,校验用户凭证是否过期 this.postAuthenticationChecks.check(user); if (!cacheWasUsed) { this.userCache.putUserInCache(user); } Object principalToReturn = user; if (this.forcePrincipalAsString) { principalToReturn = user.getUsername(); } //最后校验成功返回UsernamePasswordAuthenticationToken return this.createSuccessAuthentication(principalToReturn, authentication, user); }
(3)调用UserDetailService获取用户信息,可以自定义逻辑
org.springframework.security.authentication.dao.DaoAuthenticationProvider#retrieveUser
(4)前置校验,校验用户是否可用,锁定,过期org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks#check
(5)校验密码,也是平时很疑惑Security到底怎么做到自动校验用户名密码的位置
org.springframework.security.authentication.dao.DaoAuthenticationProvider#additionalAuthenticationChecks
protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { //盐值 Object salt = null; if (this.saltSource != null) { salt = this.saltSource.getSalt(userDetails); } if (authentication.getCredentials() == null) { this.logger.debug("Authentication failed: no credentials provided"); throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } else { //从token获取前端传的密码 String presentedPassword = authentication.getCredentials().toString(); //开始校验密码,PasswordEncoder自定义设置加解密,userDetails则是UserDetailService.loadUserByUsername获取用户数据。最后两个密码进行校验 if (!this.passwordEncoder.isPasswordValid(userDetails.getPassword(), presentedPassword, salt)) { this.logger.debug("Authentication failed: password does not match stored value"); throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } } }
(6)后置校验,用户凭证是否过期
org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.DefaultPostAuthenticationChecks#check
(7)最后校验成功返回UsernamePasswordAuthenticationToken
org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider#createSuccessAuthentication
3. 最后了,认证流程已经结束了,还有我们使用Security时经常要配置SuccessHandler或者FailureHandler,这也是最后一步所要看的东西。重新回到第一步的AbstractAuthenticationProcessingFilter。前面已经看到,在这个首要的过滤器中,我们使用
attemptAuthentication进行认证。再把代码复制一遍过来,看一下成功和失败的逻辑分别是什么
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { -------------------------代码已省略 Authentication authResult; try { //这一步进行认证 authResult = this.attemptAuthentication(request, response); if (authResult == null) { return; } this.sessionStrategy.onAuthentication(authResult, request, response); } catch (InternalAuthenticationServiceException var8) { this.logger.error("An internal error occurred while trying to authenticate the user.", var8); //认证失败逻辑 this.unsuccessfulAuthentication(request, response, var8); return; } catch (AuthenticationException var9) { this.unsuccessfulAuthentication(request, response, var9); return; } if (this.continueChainBeforeSuccessfulAuthentication) { chain.doFilter(request, response); } //认证成功逻辑 this.successfulAuthentication(request, response, chain, authResult); } }
(2)认证失败逻辑,可以看到会把SecurityContextHolder当前线程信息清除(即是 ThreadLocal),最后调用AuthenticationFailureHandler,可以自定义实现FailureHandler
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#unsuccessfulAuthentication
(3)认证成功逻辑,首先在当前线程保存认证信息,其次发布事件通知,可以自定义。最后调用SuccessHandler,自定义实现业务逻辑
org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter#successfulAuthentication
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中,当前线程
SecurityContextHolder.getContext().setAuthentication(authResult);
this.rememberMeServices.loginSuccess(request, response, authResult);
//发布事件通知,可以自定义实现逻辑,和SuccessHandler一样的效果
if (this.eventPublisher != null) {
this.eventPublisher.publishEvent(new InteractiveAuthenticationSuccessEvent(authResult, this.getClass()));
}
//执行SuccessHandler,可以自定义实现该方法业务逻辑
this.successHandler.onAuthenticationSuccess(request, response, authResult);
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。