Spring Security 登录校验 源码解析
传统情况下,在过滤器中做权限验证,Spring Secuirty也是在Filter中进行权限验证。
创建并注册过滤器
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(); } }
2 Spring Security登录校验流程图
Spring Security登录验证的本质:用户登录后获得用户名和密码,根据用户名获取用户信息,并比对密码判断用户能否验证通过,并在此过程中对帐号是否已被锁定、账号是否可用、帐号是否已经过期、用户凭证是否已经过期。下面根据代码跟踪进一步分析验证过程
3源码分析
1 AbstractAuthenticationProcessingFilter.class
用户登录访问首先访问此过滤器,并调用doFilter方法
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 }
2 UsernamePasswordAuthenticationFilter
AbstractAuthenticationProcessingFilter 的 doFilter 方法中调用了 UsernamePasswordAuthenticationFilter 的 attemptAuthentication方法
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 }
3 ProviderManager
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 }
4 AbstractUserDetailsAuthenticationProvider
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")); } } } }
5 DaoAuthenticationProvider
抽象类AbstractUserDetailsAuthenticationProvider中 核心方法additionalAuthenticationChecks retrieveUser 均在DaoAuthenticationProvider 中实现
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 }
6 UserDetailsService 实现
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 }
7 JwtLoginFilter
验证成功调用JwtLoginFilter successfulAuthentication方法,并生成token返回给前台
验证失败调用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登录校验涉及源码分析完毕
我们总结一下,使用Spring Security做登录校验需要做的工作
1 创建过滤器JwtLoginFilter 该类继承UsernamePasswordAuthenticationFilter 并重写successfulAuthentication unsuccessfulAuthentication方法
2 配置WebSecurityConfig 该类继承WebSecurityConfigurerAdapter,并在configure方法中注册过滤器JwtLoginFilter
3 注册Bean JwtLoginFilter、DaoAuthenticationProvider、AuthenticationManager 、UserDetailsService并对UserDetailsService做具体实现