http://localhost:8081/api/login
spring后台代码的执行流程是:
1、拦截
UsernamePasswordAuthenticationFilter类的attemptAuthentication方法:
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); // 1 } }
通过这个方法得到客户端post过来的账号和密码,存入Authentication
标红的authenticate是认证的关键方法
return this.getAuthenticationManager().authenticate(authRequest);
这个方法实际上会跳转到下面ProviderManager的authenticate方法:public Authentication authenticate(Authentication authentication) throws AuthenticationException {
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); // 3 // 7 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); // 2 } catch (ProviderNotFoundException var9) { ; } catch (AuthenticationException var10) { lastException = var10; } } if (result != null) { if (this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) { ((CredentialsContainer)result).eraseCredentials(); } this.eventPublisher.publishAuthenticationSuccess(result); // 8 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; } }
其中又调用了provider类的auth方法:
result = provider.authenticate(authentication);
3、鉴权
AbstractUserDetailsAuthenticationProvider类的authenticate方法
public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 4 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 { user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication); // 5 } 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); 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(); } return this.createSuccessAuthentication(principalToReturn, authentication, user); //6 }
这一步就是验证账户密码的关键的步骤,
Authetication里面存了客户端的发过来的用户和密码,
服务端数据库的用户和密码存入UserDetails
先从缓存里面拿,如果有,就返回,
UserDetails user = this.userCache.getUserFromCache(username);
如果缓存里面没有原始的账户和密码,就查数据库:
user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);
校验
真正校验账户和密码是在这一步:
DaoAuthenticationProvider
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 { String presentedPassword = authentication.getCredentials().toString(); 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")); } } }
数据库的原始密码就是:
String presentedPassword = authentication.getCredentials().toString();
最后到了AbstractAuthenticationProcessingFilter
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); //9 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); // 10 } }
到了第10步,spring security的逻辑就走完了,接下来是其他的filter的跳出逻辑了,略过。
MySecurityConfig:
protected void configure(AuthenticationManagerBuilder auth) throws Exception
public void configure(HttpSecurity http) throws Exception
WebSecurityConfigurerAdapter:
protected final HttpSecurity getHttp() throws Exception
public void init(final WebSecurity web) throws Exception
AbstractConfiguredSecurityBuilder:
private void init() throws Exception
protected final O doBuild() throws Exception
AbstractSecurityBuilder:build
WebSecurityConfiguration|springSecurityFilterChain
AbstractUserDetailsAuthenticationProvider
UsernamePasswordAuthenticationFilter