傳統狀況下,在過濾器中作權限驗證,Spring Secuirty也是在Filter中進行權限驗證。css
package com.awizdata.edubank.config; import com.awizdata.edubank.security.JwtAuthenticationTokenFilter; import com.awizdata.edubank.security.JwtLoginFilter; import com.awizdata.edubank.security.JwtUserDetailsServiceImpl; import com.awizdata.edubank.security.matches.SkipPathRequestMatcher; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import java.util.Arrays; import java.util.List; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { public static final String TOKEN_BASED_ENTRY_POINT = "/limit/**"; public static final String TOKEN_AUTH_ENTRY_POINT = "/auth/**"; public static final String TOKEN_OPEN_ENTRY_POINT = "/open/**"; @Bean public SkipPathRequestMatcher skipPathRequestMatcher() { List<String> pathsToSkip = Arrays.asList(TOKEN_AUTH_ENTRY_POINT); return new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_ENTRY_POINT); } @Bean public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() { return new JwtAuthenticationTokenFilter(skipPathRequestMatcher()); } //建立登陸過濾器 @Bean public JwtLoginFilter jwtLoginFilter() { return new JwtLoginFilter(authenticationManager()); } //重寫userDetailsService,查詢數據庫進行用戶名密碼驗證 @Override @Bean public UserDetailsService userDetailsService() { return new JwtUserDetailsServiceImpl(); } @Bean public DaoAuthenticationProvider daoAuthenticationProvider() { DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(); daoAuthenticationProvider.setPasswordEncoder(this.passwordEncoder()); daoAuthenticationProvider.setUserDetailsService(this.userDetailsService()); return daoAuthenticationProvider; } @Override @Bean public AuthenticationManager authenticationManager() { return new ProviderManager(Arrays.asList(daoAuthenticationProvider())); } // 裝載BCrypt密碼編碼器 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity .cors().and() // 因爲使用的是JWT,咱們這裏不須要csrf .csrf().disable() // 基於token,因此不須要session, Spring Security永遠不會建立HttpSession,它不會使用HttpSession來獲取SecurityContext .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests() .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 容許對於網站靜態資源的無受權訪問 .antMatchers(HttpMethod.GET, "/", "/error", "/*.html", "/favicon.ico", "/**/*.html", "/**/*.css", "/**/*.js" ).permitAll() // 對於獲取token的rest api要容許匿名訪問 .antMatchers(TOKEN_AUTH_ENTRY_POINT, TOKEN_OPEN_ENTRY_POINT).permitAll() // 除上面外的全部請求所有須要鑑權認證 .anyRequest().authenticated() .and() //使用自定義過濾器 .addFilter(this.jwtLoginFilter()) .addFilterAfter(jwtAuthenticationTokenFilter(), JwtLoginFilter.class); // 禁用緩存 httpSecurity.headers().cacheControl(); } }
用戶登陸訪問首先訪問此過濾器,並調用doFilter方法html
1 public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { 2 HttpServletRequest request = (HttpServletRequest)req; 3 HttpServletResponse response = (HttpServletResponse)res; 4 //判斷訪問是否須要過濾 5 if(!this.requiresAuthentication(request, response)) { 6 chain.doFilter(request, response); 7 } else { 8 if(this.logger.isDebugEnabled()) { 9 this.logger.debug("Request is to process authentication"); 10 } 11 12 Authentication authResult; 13 //調用子類UsernamePasswordAuthenticationFilter實現 14 try { 15 authResult = this.attemptAuthentication(request, response); 16 if(authResult == null) { 17 return; 18 } 19 //session控制策略,由於用jwt,故不進行深刻分析 20 this.sessionStrategy.onAuthentication(authResult, request, response); 21 } catch (InternalAuthenticationServiceException var8) { 22 this.logger.error("An internal error occurred while trying to authenticate the user.", var8); 23 //驗證失敗,調用子類unsuccessfulAuthentication方法 24 this.unsuccessfulAuthentication(request, response, var8); 25 return; 26 } catch (AuthenticationException var9) { 27 //驗證失敗,調用子類unsuccessfulAuthentication方法 28 this.unsuccessfulAuthentication(request, response, var9); 29 return; 30 } 31 32 if(this.continueChainBeforeSuccessfulAuthentication) { 33 chain.doFilter(request, response); 34 } 35 //驗證成功,調用子類successfulAuthentication方法 36 this.successfulAuthentication(request, response, chain, authResult); 37 } 38 }
AbstractAuthenticationProcessingFilter 的 doFilter 方法中調用了 UsernamePasswordAuthenticationFilter 的 attemptAuthentication方法java
1 public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { 2 public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; 3 public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; 4 private String usernameParameter = "username"; 5 private String passwordParameter = "password"; 6 private boolean postOnly = true; 7 8 //攔截POST形式的/login請求 9 public UsernamePasswordAuthenticationFilter() { 10 super(new AntPathRequestMatcher("/login", "POST")); 11 } 12 13 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { 14 if(this.postOnly && !request.getMethod().equals("POST")) { 15 throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod()); 16 } else { 17 //獲取Parameter中的用戶名和密碼 18 String username = this.obtainUsername(request); 19 String password = this.obtainPassword(request); 20 if(username == null) { 21 username = ""; 22 } 23 24 if(password == null) { 25 password = ""; 26 } 27 28 username = username.trim(); 29 //封裝用戶名和密碼 30 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password); 31 this.setDetails(request, authRequest); 32 //調用子類getAuthenticationManager()方法獲取AuthenticationManager並調用起authenticate方法進行驗證 33 return this.getAuthenticationManager().authenticate(authRequest); 34 } 35 } 36 37 protected String obtainPassword(HttpServletRequest request) { 38 return request.getParameter(this.passwordParameter); 39 } 40 41 protected String obtainUsername(HttpServletRequest request) { 42 return request.getParameter(this.usernameParameter); 43 } 44 45 //記錄remoteAddress、sessionId 46 protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) { 47 authRequest.setDetails(this.authenticationDetailsSource.buildDetails(request)); 48 } 49 50 public void setUsernameParameter(String usernameParameter) { 51 Assert.hasText(usernameParameter, "Username parameter must not be empty or null"); 52 this.usernameParameter = usernameParameter; 53 } 54 55 public void setPasswordParameter(String passwordParameter) { 56 Assert.hasText(passwordParameter, "Password parameter must not be empty or null"); 57 this.passwordParameter = passwordParameter; 58 } 59 60 public void setPostOnly(boolean postOnly) { 61 this.postOnly = postOnly; 62 } 63 64 public final String getUsernameParameter() { 65 return this.usernameParameter; 66 } 67 68 public final String getPasswordParameter() { 69 return this.passwordParameter; 70 } 71 }
UsernamePasswordAuthenticationFilter 中調用this.getAuthenticationManager().authenticate(authRequest);從WebSecurityConfig(見下圖) 能夠發現this.getAuthenticationManager().authenticate(authRequest)調用ProviderManager的authenticate方法,
並在authenticate方法中調用了daoAuthenticationProvider的authenticate方法
1 // 2 // Source code recreated from a .class file by IntelliJ IDEA 3 // (powered by Fernflower decompiler) 4 // 5 6 package org.springframework.security.authentication; 7 8 import java.util.Collections; 9 import java.util.Iterator; 10 import java.util.List; 11 import org.apache.commons.logging.Log; 12 import org.apache.commons.logging.LogFactory; 13 import org.springframework.beans.factory.InitializingBean; 14 import org.springframework.context.MessageSource; 15 import org.springframework.context.MessageSourceAware; 16 import org.springframework.context.support.MessageSourceAccessor; 17 import org.springframework.security.authentication.AbstractAuthenticationToken; 18 import org.springframework.security.authentication.AccountStatusException; 19 import org.springframework.security.authentication.AuthenticationEventPublisher; 20 import org.springframework.security.authentication.AuthenticationManager; 21 import org.springframework.security.authentication.AuthenticationProvider; 22 import org.springframework.security.authentication.InternalAuthenticationServiceException; 23 import org.springframework.security.authentication.ProviderNotFoundException; 24 import org.springframework.security.core.Authentication; 25 import org.springframework.security.core.AuthenticationException; 26 import org.springframework.security.core.CredentialsContainer; 27 import org.springframework.security.core.SpringSecurityMessageSource; 28 import org.springframework.util.Assert; 29 30 public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean { 31 private static final Log logger = LogFactory.getLog(ProviderManager.class); 32 private AuthenticationEventPublisher eventPublisher; 33 private List<AuthenticationProvider> providers; 34 protected MessageSourceAccessor messages; 35 private AuthenticationManager parent; 36 private boolean eraseCredentialsAfterAuthentication; 37 38 public ProviderManager(List<AuthenticationProvider> providers) { 39 this(providers, (AuthenticationManager)null); 40 } 41 42 public ProviderManager(List<AuthenticationProvider> providers, AuthenticationManager parent) { 43 this.eventPublisher = new ProviderManager.NullEventPublisher(); 44 this.providers = Collections.emptyList(); 45 this.messages = SpringSecurityMessageSource.getAccessor(); 46 this.eraseCredentialsAfterAuthentication = true; 47 Assert.notNull(providers, "providers list cannot be null"); 48 this.providers = providers; 49 this.parent = parent; 50 this.checkState(); 51 } 52 53 public void afterPropertiesSet() throws Exception { 54 this.checkState(); 55 } 56 57 private void checkState() { 58 if(this.parent == null && this.providers.isEmpty()) { 59 throw new IllegalArgumentException("A parent AuthenticationManager or a list of AuthenticationProviders is required"); 60 } 61 } 62 63 public Authentication authenticate(Authentication authentication) throws AuthenticationException { 64 65 Class toTest = authentication.getClass();//org.springframework.security.authentication.UsernamePasswordAuthenticationToken 66 Object lastException = null; 67 Authentication result = null; 68 boolean debug = logger.isDebugEnabled(); 69 //得到authenticationManager列表這裏只有daoAuthenticationProvider 70 Iterator e = this.getProviders().iterator(); 71 72 while(e.hasNext()) { 73 AuthenticationProvider provider = (AuthenticationProvider)e.next(); 74 //authenticationProvider是否爲UsernamePasswordAuthenticationToken或其子類 75 if(provider.supports(toTest)) { 76 if(debug) { 77 logger.debug("Authentication attempt using " + provider.getClass().getName()); 78 } 79 80 try { 81 result = provider.authenticate(authentication); 82 //copy Details到authentication 83 if(result != null) { 84 this.copyDetails(authentication, result); 85 break; 86 } 87 } catch (AccountStatusException var11) { 88 this.prepareException(var11, authentication); 89 throw var11; 90 } catch (InternalAuthenticationServiceException var12) { 91 this.prepareException(var12, authentication); 92 throw var12; 93 } catch (AuthenticationException var13) { 94 lastException = var13; 95 } 96 } 97 } 98 99 //未設置AuthenticationProvider的狀況下,調用parent進行校驗,這裏parent未指定 100 if(result == null && this.parent != null) { 101 try { 102 result = this.parent.authenticate(authentication); 103 } catch (ProviderNotFoundException var9) { 104 ; 105 } catch (AuthenticationException var10) { 106 lastException = var10; 107 } 108 } 109 110 if(result != null) { 111 if(this.eraseCredentialsAfterAuthentication && result instanceof CredentialsContainer) { 112 ((CredentialsContainer)result).eraseCredentials(); 113 } 114 115 //NullEventPublisher實現此方法,但未作任何處理 116 this.eventPublisher.publishAuthenticationSuccess(result); 117 return result; 118 } else { 119 if(lastException == null) { 120 lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound", new Object[]{toTest.getName()}, "No AuthenticationProvider found for {0}")); 121 } 122 123 this.prepareException((AuthenticationException)lastException, authentication); 124 throw lastException; 125 } 126 } 127 128 private void prepareException(AuthenticationException ex, Authentication auth) { 129 this.eventPublisher.publishAuthenticationFailure(ex, auth); 130 } 131 132 private void copyDetails(Authentication source, Authentication dest) { 133 if(dest instanceof AbstractAuthenticationToken && dest.getDetails() == null) { 134 AbstractAuthenticationToken token = (AbstractAuthenticationToken)dest; 135 token.setDetails(source.getDetails()); 136 } 137 138 } 139 140 public List<AuthenticationProvider> getProviders() { 141 return this.providers; 142 } 143 144 public void setMessageSource(MessageSource messageSource) { 145 this.messages = new MessageSourceAccessor(messageSource); 146 } 147 148 public void setAuthenticationEventPublisher(AuthenticationEventPublisher eventPublisher) { 149 Assert.notNull(eventPublisher, "AuthenticationEventPublisher cannot be null"); 150 this.eventPublisher = eventPublisher; 151 } 152 153 public void setEraseCredentialsAfterAuthentication(boolean eraseSecretData) { 154 this.eraseCredentialsAfterAuthentication = eraseSecretData; 155 } 156 157 public boolean isEraseCredentialsAfterAuthentication() { 158 return this.eraseCredentialsAfterAuthentication; 159 } 160 161 private static final class NullEventPublisher implements AuthenticationEventPublisher { 162 private NullEventPublisher() { 163 } 164 165 public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) { 166 } 167 168 public void publishAuthenticationSuccess(Authentication authentication) { 169 } 170 } 171 }
daoAuthenticationProvider類並未實現authenticate方法,故這裏調用了其父類(抽象類)AbstractUserDetailsAuthenticationProvider的authenticate方法
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.security.authentication.dao; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.MessageSource; import org.springframework.context.MessageSourceAware; import org.springframework.context.support.MessageSourceAccessor; import org.springframework.security.authentication.AccountExpiredException; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.CredentialsExpiredException; import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.SpringSecurityMessageSource; import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper; import org.springframework.security.core.userdetails.UserCache; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsChecker; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.core.userdetails.cache.NullUserCache; import org.springframework.util.Assert; public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware { protected final Log logger = LogFactory.getLog(this.getClass()); protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); private UserCache userCache = new NullUserCache(); private boolean forcePrincipalAsString = false; protected boolean hideUserNotFoundExceptions = true; private UserDetailsChecker preAuthenticationChecks = new AbstractUserDetailsAuthenticationProvider.DefaultPreAuthenticationChecks(); private UserDetailsChecker postAuthenticationChecks = new AbstractUserDetailsAuthenticationProvider.DefaultPostAuthenticationChecks(); private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper(); public AbstractUserDetailsAuthenticationProvider() { } protected abstract void additionalAuthenticationChecks(UserDetails var1, UsernamePasswordAuthenticationToken var2) throws AuthenticationException; public final void afterPropertiesSet() throws Exception { Assert.notNull(this.userCache, "A user cache must be set"); Assert.notNull(this.messages, "A message source must be set"); this.doAfterPropertiesSet(); } public Authentication authenticate(Authentication authentication) throws AuthenticationException { //類型判斷 Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported")); //提取username String username = authentication.getPrincipal() == null?"NONE_PROVIDED":authentication.getName(); //使用緩存 boolean cacheWasUsed = true; //根據username從緩存中獲取User信息 UserDetails user = this.userCache.getUserFromCache(username); //緩存中不存在對應User信息,則從新從數據庫加載 if(user == null) { cacheWasUsed = false; try { //從數據庫加載User信息 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); //判斷密碼是否匹配(DaoAuthenticationProvider中實現) 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); } protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) { UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(principal, authentication.getCredentials(), this.authoritiesMapper.mapAuthorities(user.getAuthorities())); result.setDetails(authentication.getDetails()); return result; } protected void doAfterPropertiesSet() throws Exception { } public UserCache getUserCache() { return this.userCache; } public boolean isForcePrincipalAsString() { return this.forcePrincipalAsString; } public boolean isHideUserNotFoundExceptions() { return this.hideUserNotFoundExceptions; } protected abstract UserDetails retrieveUser(String var1, UsernamePasswordAuthenticationToken var2) throws AuthenticationException; public void setForcePrincipalAsString(boolean forcePrincipalAsString) { this.forcePrincipalAsString = forcePrincipalAsString; } public void setHideUserNotFoundExceptions(boolean hideUserNotFoundExceptions) { this.hideUserNotFoundExceptions = hideUserNotFoundExceptions; } public void setMessageSource(MessageSource messageSource) { this.messages = new MessageSourceAccessor(messageSource); } public void setUserCache(UserCache userCache) { this.userCache = userCache; } public boolean supports(Class<?> authentication) { return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication); } protected UserDetailsChecker getPreAuthenticationChecks() { return this.preAuthenticationChecks; } public void setPreAuthenticationChecks(UserDetailsChecker preAuthenticationChecks) { this.preAuthenticationChecks = preAuthenticationChecks; } protected UserDetailsChecker getPostAuthenticationChecks() { return this.postAuthenticationChecks; } public void setPostAuthenticationChecks(UserDetailsChecker postAuthenticationChecks) { this.postAuthenticationChecks = postAuthenticationChecks; } public void setAuthoritiesMapper(GrantedAuthoritiesMapper authoritiesMapper) { this.authoritiesMapper = authoritiesMapper; } private class DefaultPostAuthenticationChecks implements UserDetailsChecker { private DefaultPostAuthenticationChecks() { } public void check(UserDetails user) { if(!user.isCredentialsNonExpired()) { AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account credentials have expired"); throw new CredentialsExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.credentialsExpired", "User credentials have expired")); } } } private class DefaultPreAuthenticationChecks implements UserDetailsChecker { private DefaultPreAuthenticationChecks() { } public void check(UserDetails user) { if(!user.isAccountNonLocked()) { AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is locked"); throw new LockedException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.locked", "User account is locked")); } else if(!user.isEnabled()) { AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is disabled"); throw new DisabledException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.disabled", "User is disabled")); } else if(!user.isAccountNonExpired()) { AbstractUserDetailsAuthenticationProvider.this.logger.debug("User account is expired"); throw new AccountExpiredException(AbstractUserDetailsAuthenticationProvider.this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.expired", "User account has expired")); } } } }
抽象類AbstractUserDetailsAuthenticationProvider中 核心方法additionalAuthenticationChecks retrieveUser 均在DaoAuthenticationProvider 中實現web
1 public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider { 2 private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword"; 3 private PasswordEncoder passwordEncoder; 4 private volatile String userNotFoundEncodedPassword; 5 private UserDetailsService userDetailsService; 6 7 public DaoAuthenticationProvider() { 8 this.setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder()); 9 } 10 11 //驗證密碼是否正確 12 protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { 13 if(authentication.getCredentials() == null) { 14 this.logger.debug("Authentication failed: no credentials provided"); 15 throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); 16 } else { 17 String presentedPassword = authentication.getCredentials().toString(); 18 if(!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { 19 this.logger.debug("Authentication failed: password does not match stored value"); 20 throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); 21 } 22 } 23 } 24 25 protected void doAfterPropertiesSet() throws Exception { 26 Assert.notNull(this.userDetailsService, "A UserDetailsService must be set"); 27 } 28 29 //根據username 從新請求User信息 30 protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { 31 this.prepareTimingAttackProtection(); 32 33 try { 34 //這裏本身作實現 35 UserDetails ex = this.getUserDetailsService().loadUserByUsername(username); 36 if(ex == null) { 37 throw new InternalAuthenticationServiceException("UserDetailsService returned null, which is an interface contract violation"); 38 } else { 39 return ex; 40 } 41 } catch (UsernameNotFoundException var4) { 42 this.mitigateAgainstTimingAttack(authentication); 43 throw var4; 44 } catch (InternalAuthenticationServiceException var5) { 45 throw var5; 46 } catch (Exception var6) { 47 throw new InternalAuthenticationServiceException(var6.getMessage(), var6); 48 } 49 }
1 @Service 2 public class JwtUserDetailsServiceImpl implements UserDetailsService { 3 4 @Autowired 5 private RcyUserDAO rcyUserDAO; 6 7 @Override 8 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 9 RcyUserPO user = rcyUserDAO.findByUsername(username); 10 if (user == null) { 11 throw new UsernameNotFoundException(String.format("No user found with username '%s'.", username)); 12 } else { 13 return JwtUserFactory.create(user); 14 } 15 } 16 }
驗證成功調用JwtLoginFilter successfulAuthentication方法,並生成token返回給前臺spring
驗證失敗調用unsuccessfulAuthentication,根據異常給前臺作不一樣提示數據庫
1 @Component 2 public class JwtLoginFilter extends UsernamePasswordAuthenticationFilter { 3 4 @Autowired 5 private JwtTokenUtil jwtTokenUtil; 6 @Autowired 7 private ObjectMapper mapper; 8 9 public JwtLoginFilter(AuthenticationManager authenticationManager) { 10 super.setAuthenticationManager(authenticationManager); 11 } 12 13 @Value("${jwt.tokenHead}") 14 private String tokenHead; 15 16 //登陸驗證成功 17 @Override 18 protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { 19 BackResult<Map<String, String>> result = new BackResult<>(); 20 result.setCode(RESPONSE_CODE.BACK_CODE_SUCCESS.value); 21 22 Map<String, String> data = new HashMap<>(); 23 24 //獲取User信息 25 UserDetails userDetails = (UserDetails) authResult.getPrincipal(); 26 ((JwtUser)userDetails).setLoginWay(this.getLoginWay(request)); 27 28 //生成token 29 String token = jwtTokenUtil.generateToken(userDetails); 30 data.put("token", tokenHead + token); 31 data.put("userType", ((JwtUser)userDetails).getUserType()); 32 data.put("userId",((JwtUser)userDetails).getId()); 33 data.put("roles", StringUtils.collectionToDelimitedString(userDetails.getAuthorities(), ",")); 34 35 36 result.setData(data); 37 38 response.setContentType(MediaType.APPLICATION_JSON_VALUE); 39 response.setCharacterEncoding("UTF-8"); 40 mapper.writeValue(response.getWriter(), result); 41 } 42 43 /** 44 * 獲取登陸途徑 45 * @param request 46 * @return 47 */ 48 private String getLoginWay(HttpServletRequest request) { 49 String loginWayParam = "loginWay"; 50 String loginWay = request.getParameter(loginWayParam); 51 if (JwtUser.LOGIN_WAY_APP.equals(loginWay)) { 52 return JwtUser.LOGIN_WAY_APP; 53 } else { 54 return JwtUser.LOGIN_WAY_PC; 55 } 56 } 57 58 //登陸驗證失敗 59 @Override 60 protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { 61 BackResult<String> result = new BackResult<>(); 62 result.setCode(RESPONSE_CODE.BACK_CODE_FAIL.value); 63 //根據不一樣的異常作不一樣提示 64 String exceptions; 65 if (failed instanceof BadCredentialsException) { 66 exceptions = "username or password err"; 67 } else { 68 exceptions = "login failed"; 69 } 70 // 忽略用戶不存在問題 71 result.setExceptions("login failed"); 72 73 response.setContentType(MediaType.APPLICATION_JSON_VALUE); 74 response.setCharacterEncoding("UTF-8"); 75 mapper.writeValue(response.getWriter(), result); 76 } 77 }
至此,Spring Security登陸校驗涉及源碼分析完畢apache
咱們總結一下,使用Spring Security作登陸校驗須要作的工做api
1 建立過濾器JwtLoginFilter 該類繼承UsernamePasswordAuthenticationFilter 並重寫successfulAuthentication unsuccessfulAuthentication方法緩存
2 配置WebSecurityConfig 該類繼承WebSecurityConfigurerAdapter,並在configure方法中註冊過濾器JwtLoginFiltersession
3 註冊Bean JwtLoginFilter、DaoAuthenticationProvider、AuthenticationManager 、UserDetailsService並對UserDetailsService作具體實現