4.1 过滤链源码分析
4.1.1 过滤器链加载流程分析
4.1.2 过滤器链加载流程源码分析
- SpringBoot的自动配置会加载spring.factories文件,在文件中有针对Spring Security的过滤器链 的配置信息org.springframework.boot.autoconfigure.security.servlet.SecurityFilterAutoConfiguration,表明过滤器链的自动配置类是SecurityFilterAutoConfiguration类。
- SecurityFilterAutoConfiguration类 以及相关导入的类 ```java @Configuration( proxyBeanMethods = false ) @ConditionalOnWebApplication( type = Type.SERVLET ) @EnableConfigurationProperties({SecurityProperties.class}) @ConditionalOnClass({AbstractSecurityWebApplicationInitializer.class, SessionCreationPolicy.class}) // 在SecurityFilterAutoConfiguration类加载完成后加载SecurityAutoConfiguration类 @AutoConfigureAfter({SecurityAutoConfiguration.class}) public class SecurityFilterAutoConfiguration {
}
```java@Configuration(proxyBeanMethods = false)@ConditionalOnClass({DefaultAuthenticationEventPublisher.class})@EnableConfigurationProperties({SecurityProperties.class}) // 定义默认的用户名和密码@Import({SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class, SecurityDataConfiguration.class})public class SecurityAutoConfiguration {public SecurityAutoConfiguration() {}@Bean@ConditionalOnMissingBean({AuthenticationEventPublisher.class})public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {return new DefaultAuthenticationEventPublisher(publisher);}}
SecurityProperties类:定义默认的用户名和密码
@ConfigurationProperties(prefix = "spring.security")public class SecurityProperties {public static final int BASIC_AUTH_ORDER = 2147483642;public static final int IGNORED_ORDER = -2147483648;public static final int DEFAULT_FILTER_ORDER = -100;private final SecurityProperties.Filter filter = new SecurityProperties.Filter();private final SecurityProperties.User user = new SecurityProperties.User();public SecurityProperties() {}public SecurityProperties.User getUser() {return this.user;}public SecurityProperties.Filter getFilter() {return this.filter;}// 定义了自动配置的user,也就是未连接数据库我们启动时可以使用登录的用户密码public static class User {private String name = "user";private String password = UUID.randomUUID().toString();private List<String> roles = new ArrayList();private boolean passwordGenerated = true;public User() {}public String getName() {return this.name;}public void setName(String name) {this.name = name;}public String getPassword() {return this.password;}public void setPassword(String password) {if (StringUtils.hasLength(password)) {this.passwordGenerated = false;this.password = password;}}public List<String> getRoles() {return this.roles;}public void setRoles(List<String> roles) {this.roles = new ArrayList(roles);}public boolean isPasswordGenerated() {return this.passwordGenerated;}}public static class Filter {private int order = -100;private Set<DispatcherType> dispatcherTypes;public Filter() {this.dispatcherTypes = new HashSet(Arrays.asList(DispatcherType.ASYNC, DispatcherType.ERROR, DispatcherType.REQUEST));}public int getOrder() {return this.order;}public void setOrder(int order) {this.order = order;}public Set<DispatcherType> getDispatcherTypes() {return this.dispatcherTypes;}public void setDispatcherTypes(Set<DispatcherType> dispatcherTypes) {this.dispatcherTypes = dispatcherTypes;}}}
WebSecurityEnablerConfiguration类:开启web安全启动配置
@Configuration(proxyBeanMethods = false)@ConditionalOnMissingBean(name = {"springSecurityFilterChain"})@ConditionalOnClass({EnableWebSecurity.class})@ConditionalOnWebApplication(type = Type.SERVLET)@EnableWebSecurity // 开启Security安全功能class WebSecurityEnablerConfiguration {WebSecurityEnablerConfiguration() {}}
EnableWebSecurity注解类:开启security安全功能
@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.TYPE})@Documented@Import({WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class, HttpSecurityConfiguration.class})@EnableGlobalAuthentication@Configurationpublic @interface EnableWebSecurity {boolean debug() default false;}
WebSecurityConfiguration类:安全配置类,生成过滤器链
@Configuration(proxyBeanMethods = false)public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {// 此方法返回的就是过滤器链@Bean(name = {"springSecurityFilterChain"})public Filter springSecurityFilterChain() throws Exception {boolean hasConfigurers = this.webSecurityConfigurers != null && !this.webSecurityConfigurers.isEmpty();boolean hasFilterChain = !this.securityFilterChains.isEmpty();Assert.state(!hasConfigurers || !hasFilterChain, "Found WebSecurityConfigurerAdapter as well as SecurityFilterChain. Please select just one.");if (!hasConfigurers && !hasFilterChain) {WebSecurityConfigurerAdapter adapter = (WebSecurityConfigurerAdapter)this.objectObjectPostProcessor.postProcess(new WebSecurityConfigurerAdapter() {});this.webSecurity.apply(adapter);}Iterator var7 = this.securityFilterChains.iterator();while(true) {while(var7.hasNext()) {SecurityFilterChain securityFilterChain = (SecurityFilterChain)var7.next();this.webSecurity.addSecurityFilterChainBuilder(() -> {return securityFilterChain;});Iterator var5 = securityFilterChain.getFilters().iterator();while(var5.hasNext()) {Filter filter = (Filter)var5.next();if (filter instanceof FilterSecurityInterceptor) {this.webSecurity.securityInterceptor((FilterSecurityInterceptor)filter);break;}}}var7 = this.webSecurityCustomizers.iterator();while(var7.hasNext()) {WebSecurityCustomizer customizer = (WebSecurityCustomizer)var7.next();customizer.customize(this.webSecurity);}return (Filter)this.webSecurity.build();}}}
生成过滤器链的过程,就是会读取我们自己编写的配置类,设置相关的过滤器信息到configure上,还有一些默认的过滤器也设置到configure,然后对configure进行一个个初始化,生成相应的过滤器类,最后对生成的过滤器进行排序返回。
4.2 认证源码分析
4.2.1 认证流程分析
4.2.2 认证流程源码跟踪
UsernamePasswordAuthenticationFilter
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {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 {// 从request请求中通过配置的参数获取用户名密码String username = this.obtainUsername(request);username = username != null ? username : "";username = username.trim();String password = this.obtainPassword(request);password = password != null ? password : "";// 创建UsernamePasswordAuthenticationTokenUsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);this.setDetails(request, authRequest);// 调用认证管理器的认证方法// 验证完成后就到父类的doFilter()方法,负责把认证信息放入SercurityContext// 然后调用SuceessHandler来处理登录成功逻辑return this.getAuthenticationManager().authenticate(authRequest);}}}
ProviderManager
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {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;int currentPosition = 0;int size = this.providers.size();// 获取providers,发现两个都不支持解析UsernamePasswordAuthenticationTokenIterator var9 = this.getProviders().iterator();while(var9.hasNext()) {AuthenticationProvider provider = (AuthenticationProvider)var9.next();// 判断支不支持if (provider.supports(toTest)) {if (logger.isTraceEnabled()) {Log var10000 = logger;String var10002 = provider.getClass().getSimpleName();++currentPosition;var10000.trace(LogMessage.format("Authenticating request with %s (%d/%d)", var10002, currentPosition, size));}try {// 验证方法result = provider.authenticate(authentication);if (result != null) {this.copyDetails(authentication, result);break;}} catch (InternalAuthenticationServiceException | AccountStatusException var14) {this.prepareException(var14, authentication);throw var14;} catch (AuthenticationException var15) {lastException = var15;}}}if (result == null && this.parent != null) {try {// 判断两个认证器都不支持,所以交给父类去验证// 父类同样是执行这个方法,发现Providers是DaoAuthenticationProvider// provider.supports(toTest)判断时是// 支持UsernamePasswordAuthenticationToken的parentResult = this.parent.authenticate(authentication);result = parentResult;} catch (ProviderNotFoundException var12) {} catch (AuthenticationException var13) {parentException = var13;lastException = var13;}}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;}}}
AbstractUserDetailsAuthenticationProvider: ProviderManager中调用的的result = provider.authenticate(authentication)方法
public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider, InitializingBean, MessageSourceAware {public Authentication authenticate(Authentication authentication) throws AuthenticationException {Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication, () -> {return this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported");});// 获取用户名String username = this.determineUsername(authentication);boolean cacheWasUsed = true;// 通过用户名查询缓存中的user信息UserDetails user = this.userCache.getUserFromCache(username);if (user == null) {cacheWasUsed = false;try {// 检查user信息user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication);} catch (UsernameNotFoundException var6) {this.logger.debug("Failed to find user '" + username + "'");if (!this.hideUserNotFoundExceptions) {throw var6;}throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");}try {// 检查用户是否锁定、启用等等this.preAuthenticationChecks.check(user);// 检查用户是否合法,比对参数:user是数据库查询的,authentication是前端传的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) {// 检查无误则把user放入缓存this.userCache.putUserInCache(user);}Object principalToReturn = user;if (this.forcePrincipalAsString) {principalToReturn = user.getUsername();}// 创建新的UsernamePasswordAuthenticationToken,已认证的,有授权信息的return this.createSuccessAuthentication(principalToReturn, authentication, user);}}
DaoAuthenticationProvider: 上一步代码调用的检查user方法user = this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication)
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {this.prepareTimingAttackProtection();try {// 调用我们自定义的MyUserDetailsService来获取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);}}// 上一步代码调用的protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {if (authentication.getCredentials() == null) {this.logger.debug("Failed to authenticate since no credentials provided");throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));} else {// 获取密码并比对数据库与前端的密码String presentedPassword = authentication.getCredentials().toString();if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {this.logger.debug("Failed to authenticate since password does not match stored value");throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));}}}}
4.2.3 总结
- 调用UsernamePasswordAuthenticationFilter类,从request中通过配置的参数取出账号密码,然后用账号密码生成UsernamePasswordAuthenticationToken,此时的token是未认证的。
- 调用认证管理器ProviderManager的认证方法authenticate(Authentication authentication)进行认证。
- 先匹配provider,通过比对发现DaoAuthenticationProvider支持UsernamePasswordAuthenticationToken的解析,然后调用provider.authenticate(authentication)进行验证。
- 验证其实是调用AbstractUserDetailsAuthenticationProvider中的authenticate(authentication)方法
- 获取用户名,查询user有没有缓存,没有则调用this.retrieveUser(username, (UsernamePasswordAuthenticationToken)authentication)进行user检查。
- 对user进行前置检查,主要检查用户是否锁定,是否启用等等。
- this.additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken)authentication)检查用户是否合法,两个参数分别数据库查询的user和前端传的认证信息,来进行密码的比对。
- 检查无误把user放入缓存。
- 创建新的UsernamePasswordAuthenticationToken,已认证的,有授权信息的。
- 上一步的this.retrieveUser和this.additionalAuthenticationChecks都是在DaoAuthenticationProvider类中实现的。
- this.retrieveUser:调用我们自定义的UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username),得到userDetails信息并返回。
- this.additionalAuthenticationChecks:获取密码并比对前端与数据库的密码是否一致。
- 验证完成后就到UsernamePasswordAuthenticationFilter父类的doFilter()方法,负责把认证信息放入SercurityContext,以便后续我们可以通过SercurityContext获取认证信息,然后调用SuceessHandler来处理登录成功逻辑。
4.3 rememberMe源码分析
4.4 Csrf源码分析
4.4.1 csrf流程分析

4.4.2 csrf源码跟踪
CsrfFilter
public final class CsrfFilter extends OncePerRequestFilter {protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {request.setAttribute(HttpServletResponse.class.getName(), response);CsrfToken csrfToken = this.tokenRepository.loadToken(request);boolean missingToken = csrfToken == null;if (missingToken) {// 生成token并封装,token值就是通过UUID生成csrfToken = this.tokenRepository.generateToken(request);this.tokenRepository.saveToken(csrfToken, request, response);}// 把token放入request请求域中request.setAttribute(CsrfToken.class.getName(), csrfToken);request.setAttribute(csrfToken.getParameterName(), csrfToken);if (!this.requireCsrfProtectionMatcher.matches(request)) {if (this.logger.isTraceEnabled()) {this.logger.trace("Did not protect against CSRF since request did not match " + this.requireCsrfProtectionMatcher);}// 执行doFilter放行,放行到页面时要取出csrfToken的值,其实就是执行getToken方法// 其实就是执行SaveOnAccessCsrfToken的getToken方法filterChain.doFilter(request, response);} else {String actualToken = request.getHeader(csrfToken.getHeaderName());if (actualToken == null) {actualToken = request.getParameter(csrfToken.getParameterName());}if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {this.logger.debug(LogMessage.of(() -> {return "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request);}));AccessDeniedException exception = !missingToken ? new InvalidCsrfTokenException(csrfToken, actualToken) : new MissingCsrfTokenException(actualToken);this.accessDeniedHandler.handle(request, response, (AccessDeniedException)exception);} else {filterChain.doFilter(request, response);}}}}
LazyCsrfTokenRepository
public final class LazyCsrfTokenRepository implements CsrfTokenRepository {private static final class SaveOnAccessCsrfToken implements CsrfToken {public String getToken() {// 把token存到session当中this.saveTokenIfNecessary();// 获取token返回return this.delegate.getToken();}}}
第一次请求总结:第一次请求页面的时候,会通过CsrfFilter来拦截请求,生成token并且封装放入request请求域中(token值是通过UUID生成),然后页面通过${_csrf.token}取值的时候,其实就是调用SaveOnAccessCsrfToken的getToken方法,将token保存到session域中,然后返回token值,这样,我们的页面上就有csrfToken的值了,下次请求就会带上这个token值。
第二次请求总结:this.tokenRepository.loadToken(request)可以从session中获取到csrfToken的值并返回,然后通过页面传过来的token值与session域中的token值进行比对,然后放行。
4.5 授权源码分析
在整个过滤器链中, FilterSecurityInterceptor是来处理整个用户授权流程的, 也是距离用户API最后一
个非常重要的过滤器链。
4.5.1 授权流程分析

- AffirmativeBased(基于肯定)的逻辑是: 一票通过权
- ConsensusBased(基于共识)的逻辑是: 赞成票多于反对票则表示通过,反对票多于赞成票则将抛出 AccessDeniedException
- UnanimousBased(基于一致)的逻辑:一票否决权
总结:FilterSecurityInterceptor类通过读取系统配置,获得了用户权限,然后交给AccessDecisionManager类去调用决策器AccessDecisionVoter进行权限认证,决策策略如上三种所示,决策通过则可以调用API,决策不通过则抛出异常,可以被异常过滤器ExceptionTranslationFilter捕获。
4.5.2 授权源码跟踪
FilterSecurityInterceptor
public class FilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {this.invoke(new FilterInvocation(request, response, chain));}public void invoke(FilterInvocation filterInvocation) throws IOException, ServletException {if (this.isApplied(filterInvocation) && this.observeOncePerRequest) {filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());} else {if (filterInvocation.getRequest() != null && this.observeOncePerRequest) {filterInvocation.getRequest().setAttribute("__spring_security_filterSecurityInterceptor_filterApplied", Boolean.TRUE);}// 前置处理InterceptorStatusToken token = super.beforeInvocation(filterInvocation);try {filterInvocation.getChain().doFilter(filterInvocation.getRequest(), filterInvocation.getResponse());} finally {super.finallyInvocation(token);}super.afterInvocation(token, (Object)null);}}}
AbstractSecurityInterceptor
public abstract class AbstractSecurityInterceptor implements InitializingBean, ApplicationEventPublisherAware, MessageSourceAware {protected InterceptorStatusToken beforeInvocation(Object object) {Assert.notNull(object, "Object was null");if (!this.getSecureObjectClass().isAssignableFrom(object.getClass())) {throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + this.getSecureObjectClass());} else {// 获取我们在配置类中编写的权限列表(antMatch的配置)// getAttributes()通过遍历权限列表,匹配到与当前请求url相同的权限,并返回Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object);if (CollectionUtils.isEmpty(attributes)) {Assert.isTrue(!this.rejectPublicInvocations, () -> {return "Secure object invocation " + object + " was denied as public invocations are not allowed via this interceptor. This indicates a configuration error because the rejectPublicInvocations property is set to 'true'";});if (this.logger.isDebugEnabled()) {this.logger.debug(LogMessage.format("Authorized public object %s", object));}this.publishEvent(new PublicInvocationEvent(object));return null;} else {if (SecurityContextHolder.getContext().getAuthentication() == null) {this.credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes);}// 从SecurityContext上下文中获取用户的所有权限信息Authentication authenticated = this.authenticateIfRequired();if (this.logger.isTraceEnabled()) {this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes));}// 通过this.accessDecisionManager.decide(authenticated, object, attributes)调用决策器去决定权限是否通过,默认是AffirmativeBased(一票通过),投票的返回值0表示弃权,1表示通过,-1表示不通过,通过则返回放行this.attemptAuthorization(object, attributes, authenticated);if (this.logger.isDebugEnabled()) {this.logger.debug(LogMessage.format("Authorized %s with attributes %s", object, attributes));}if (this.publishAuthorizationSuccess) {this.publishEvent(new AuthorizedEvent(object, attributes, authenticated));}Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes);if (runAs != null) {SecurityContext origCtx = SecurityContextHolder.getContext();SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext());SecurityContextHolder.getContext().setAuthentication(runAs);if (this.logger.isDebugEnabled()) {this.logger.debug(LogMessage.format("Switched to RunAs authentication %s", runAs));}return new InterceptorStatusToken(origCtx, true, attributes, object);} else {this.logger.trace("Did not switch RunAs authentication since RunAsManager returned null");return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object);}}}}}
