Spring Security是基于spring的应用程序提供声明式安全保护的安全性框架,它提供了完整的安全性解决方案,能够在web请求级别和方法调用级别处理身份证验证和授权.它充分使用了依赖注入和面向切面的技术。.
SecurityContextPersistenceFilter有两个主要任务:
在请求到达时处理之前,从SecurityContextRepository中获取安全上下文信息填充到SecurityContextHolder;在请求处理结束后返回响应时,将SecurityContextHolder中的安全上下文信息保存回SecurityContextRepository,并清空SecurityContextHolder。 通过SecurityContextPersistenceFilter的这种机制,在整个请求处理过程中,开发人员都可以通过使用SecurityContextHolder获取当前访问用户的安全上下文信息。缺省情况下,SecurityContextPersistenceFilter使用的SecurityContextRepository是HttpSessionSecurityContextRepository,也就是将安全上下文的信息保存在用户的会话中。 为了解决不同Serlvet容器上,尤其是weblogic上的兼容性问题,此Filter必须在整个request处理过程中被调用最多一次。 该Filter也必须在任何认证机制逻辑发生之前被调用。因为这些认证机制都依赖于SecurityContextHolder所包含的安全上下文对象。
源码解析:
// 确保该Filter在一个request处理过程中最多被调到用一次的机制: // 一旦该Fitler被调用过,他会在当前request增加该属性值为true,利用此标记 // 可以避免Filter被调用二次。 static final String FILTER_APPLIED = "__spring_security_scpf_applied"; // 安全上下文存储库 private SecurityContextRepository repo; private boolean forceEagerSessionCreation; // 默认使用http session 作为安全上下文对象存储 public SecurityContextPersistenceFilter() { this(new HttpSessionSecurityContextRepository()); } public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)res; //判断同一请求中是否已经执行过此Filter if (request.getAttribute("__spring_security_scpf_applied") != null) { chain.doFilter(request, response); } else { boolean debug = this.logger.isDebugEnabled(); // 设置Filter访问标记 request.setAttribute("__spring_security_scpf_applied", Boolean.TRUE); if (this.forceEagerSessionCreation) { HttpSession session = request.getSession(); if (debug && session.isNew()) { this.logger.debug("Eagerly created session: " + session.getId()); } } HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response); // 从安全上下文存储库(缺省是http session)中读取安全上下文对象 SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder); boolean var13 = false; try { var13 = true; // 设置安全上下文对象到SecurityContextHolder然后才继续Filter chain的调用 SecurityContextHolder.setContext(contextBeforeChainExecution); chain.doFilter(holder.getRequest(), holder.getResponse()); var13 = false; } finally { if (var13) { // 当前请求已经被处理完成了,清除SecurityContextHolder并将最新的 // 安全上下文对象保存回安全上下文存储库(缺省是http session) SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext(); SecurityContextHolder.clearContext(); this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse()); request.removeAttribute("__spring_security_scpf_applied"); if (debug) { this.logger.debug("SecurityContextHolder now cleared, as request processing completed"); } } } // 当前请求已经被处理完成了,清除SecurityContextHolder并将最新的 // 安全上下文对象保存回安全上下文存储库(缺省是http session) SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext(); SecurityContextHolder.clearContext(); this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse()); // 移除request中设置的标识 request.removeAttribute("__spring_security_scpf_applied"); if (debug) { this.logger.debug("SecurityContextHolder now cleared, as request processing completed"); } } }是处理Form登陆的过滤器,与Form登陆有关的所有操作都是在该类及其子类中进行的。
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { // 参数校验 HttpServletRequest request = (HttpServletRequest)req; HttpServletResponse response = (HttpServletResponse)res; 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 { // 子类进行身份校验处理 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); } } /** * 登陆失败处理 */ protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { SecurityContextHolder.clearContext(); if (this.logger.isDebugEnabled()) { this.logger.debug("Authentication request failed: " + failed.toString(), failed); this.logger.debug("Updated SecurityContextHolder to contain null Authentication"); this.logger.debug("Delegating to authentication failure handler " + this.failureHandler); } this.rememberMeServices.loginFail(request, response); //表明验证身份信息失败之后调用类 failureHandler的onAuthenticationFailure() 方法。而 failureHandler 是 AuthenticationFailureHandler 的实例变量。可以定义自定义的failureHandler进行失败处理。 this.failureHandler.onAuthenticationFailure(request, response, failed); } /** * 登陆成功处理 */ 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())); } //可以定义自定义的successHandler进行成功处理。 this.successHandler.onAuthenticationSuccess(request, response, authResult); }AbstractAuthenticationProcessingFilter的子类,执行此类中的attemptAuthentication方法
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { // 校验是否为POST请求 if (this.postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); } else { // 获取Reuqest中的用户名与密码 String username = this.obtainUsername(request); String password = this.obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); // 根据用户输入的用户名、密码构建了 UsernamePasswordAuthenticationToken,并将其交给 AuthenticationManager 来进行认证处理。 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); this.setDetails(request, authRequest); // 接下来AuthenticationManager 来进行认证处理 return this.getAuthenticationManager().authenticate(authRequest); } }UsernamePasswordAuthenticationToken 构造方法
public UsernamePasswordAuthenticationToken(Object principal, Object credentials) { // 此时未做认证,首先设置权限为null super((Collection)null); this.principal = principal; this.credentials = credentials; // 没有认证设置为false this.setAuthenticated(false); }AuthenticationManager 本身不包含认证逻辑,其核心是用来管理所有的 AuthenticationProvider,通过交由合适的 AuthenticationProvider 来实现认证。
该类是 AuthenticationManager 的实现类
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Class<? extends Authentication> toTest = authentication.getClass(); AuthenticationException lastException = null; AuthenticationException parentException = null; Authentication result = null; Authentication parentResult = null; boolean debug = logger.isDebugEnabled(); // 获取所有的AuthenticationProvider Iterator var8 = this.getProviders().iterator(); // 遍历所有的AuthenticationProvider获取当前Authentication所支持的AuthenticationProvider while(var8.hasNext()) { AuthenticationProvider provider = (AuthenticationProvider)var8.next(); if (provider.supports(toTest)) { if (debug) { logger.debug("Authentication attempt using " + provider.getClass().getName()); } try { // 通过provider来进行认证授权 result = provider.authenticate(authentication); if (result != null) { this.copyDetails(authentication, result); break; } } catch (AccountStatusException var13) { this.prepareException(var13, authentication); throw var13; } catch (InternalAuthenticationServiceException var14) { this.prepareException(var14, authentication); throw var14; } catch (AuthenticationException var15) { lastException = var15; } } } if (result == null && this.parent != null) { try { result = parentResult = this.parent.authenticate(authentication); } catch (ProviderNotFoundException var11) { } catch (AuthenticationException var12) { parentException = var12; lastException = var12; } } if (result != null) { if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) { ((CredentialsContainer)result).eraseCredentials(); } if (parentResult == null) { 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}")); } if (parentException == null) { this.prepareException((AuthenticationException)lastException, authentication); } throw lastException; } }Spring Security 支持多种认证逻辑,每一种认证逻辑的认证方式其实就是一种 AuthenticationProvider。通过 getProviders() 方法就能获取所有的 AuthenticationProvider,通过 provider.supports() 来判断 provider 是否支持当前的认证逻辑。(也可以自定义AuthenticationProvider实现AuthenticationProvider接口,在配置文件中配置生效,下一篇文章会详细讲解) 当选择好一个合适的 AuthenticationProvider 后,通过 provider.authenticate(authentication) 来让 AuthenticationProvider 进行认证。
传统表单登录的 AuthenticationProvider 主要是由 AbstractUserDetailsAuthenticationProvider 来进行处理的,我们来看下它的 authenticate()方法。 首先通过 retrieveUser() 方法读取到数据库中的用户信息:
// 校验用户信息 user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { this.prepareTimingAttackProtection(); try { // 读取框架中的UserDetailService,进行用户、角色校验,并返回UserDetails UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username); if (loadedUser == null) { throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation"); } else { return loadedUser; } } catch (UsernameNotFoundException var4) { this.mitigateAgainstTimingAttack(authentication); throw var4; } catch (InternalAuthenticationServiceException var5) { throw var5; } catch (Exception var6) { throw new InternalAuthenticationServiceException(var6.getMessage(), var6); } }返回到AbstractUserDetailsAuthenticationProvider.authenticate方法的中
public Authentication authenticate(Authentication authentication) throws AuthenticationException { Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> { return 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 { 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); // 附加校验 校验密码是否为null 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(); } // 当以上校验均通过是,创建success的认证,否则抛出异常 return this.createSuccessAuthentication(principalToReturn, authentication, user); }在 createSuccessAuthentication 方法中,我们发现它重新 new 了一个 UsernamePasswordAuthenticationToken,因为到这里认证已经通过了,所以将 authorities 注入进去,并设置 authenticated 为 true,即需要认证。
参考文章: https://blog.csdn.net/andy_zhang2007/article/details/84726992 https://blog.csdn.net/yuanlaijike/article/details/84703690