SpringSecurity详解
Spring Security 详解
一 Spring Security 的设计思想
Spring Security:请求安全为核心的企业级安全组件
基于 Servlet 过滤器链模型
完整覆盖认证、授权、会话、加密、防护等企业安全场景
通过注解 / DSL 配置方式嵌入 Spring Boot 全家桶
与 Spring Boot、Spring Cloud、Spring Gateway 等深度整合
设计思路:
所有安全逻辑必须进入 FilterChainProxy ,通过 SecurityFilterChain 逐步处理,如用户名
密码过滤器、认证处理器、授权管理器等。
二 Spring Security 的加载与执行过程
流程图
执行时序图
1. SecurityConfigurer.java
SecurityConfigurer 用来配置 SecurityBuilder。首先必须先 init(SecurityBuilder)方法 被调用,之后才可以调用 configure(SecurityBuilder)方法配置一个SecurityBuilder。
- init(SecurityBuilder)方法 : 初始化SecurityBuilder,并且设置一些共享的状态(shared state)
- configure(SecurityBuilder): 设置一些属性, 后面如果我们要自定义一些XxxxxConfiguration.java ,那么就是需要自定义实现此方法
/*** Initialize the {@link SecurityBuilder}. Here only shared state should be created* and modified, but not properties on the {@link SecurityBuilder} used for building* the object. This ensures that the {@link #configure(SecurityBuilder)} method uses* the correct shared objects when building. Configurers should be applied here.* @param builder* @throws Exception*/
void init(B builder) throws Exception;/*** Configure the {@link SecurityBuilder} by setting the necessary properties on the* {@link SecurityBuilder}.* @param builder* @throws Exception*/
void configure(B builder) throws Exception;
2. SecurityBuilder.java
SecurityBuilder<T>
最底层接口,负责构建一个对象
O build() throws Exception;
3. AbstractSecurityBuilder.java
AbstractSecurityBuilder<O> (抽象类)
这里保证一个对象只能build一次,否则报错throw new AlreadyBuiltException("This object has already been built");
@Override
public final O build() throws Exception {if (this.building.compareAndSet(false, true)) {this.object = doBuild();return this.object;}throw new AlreadyBuiltException("This object has already been built");
}
// 真正的构建过程交给子类实现
protected abstract O doBuild() throws Exception;
4. AbstractConfiguredSecurityBuilder.java
AbstractConfiguredSecurityBuilder<O, B> (核心抽象类)
private final LinkedHashMap<Class<? extends SecurityConfigurer<O, B>>, List<SecurityConfigurer<O, B>>> configurers = new LinkedHashMap<>();
- 我们说
SecurityConfigurer
是用来配置 一个SecurityBuilder
的,那么一个SecurityBuilder
需要一堆的configurers,所以AbstractConfiguredSecurityBuilder
有一个configurers用来维护SecurityConfigurer
private final Map<Class<?>, Object> sharedObjects = new HashMap<>();
- 我们在自定义的一些过滤器或者配置时,有一个最常用的方法,就是getSharedObject(), 就是这里的这个Map
@Override
protected final O doBuild() throws Exception {synchronized (this.configurers) {// 初始化中this.buildState = BuildState.INITIALIZING;beforeInit();init();// 配置this.buildState = BuildState.CONFIGURING;beforeConfigure();configure();// 构建中this.buildState = BuildState.BUILDING;O result = performBuild();// 构建完成this.buildState = BuildState.BUILT;return result;}
}
注意:这里是最典型的一个模板的使用方式
/*** Invoked prior to invoking each {@link SecurityConfigurer#init(SecurityBuilder)}* method. Subclasses may override this method to hook into the lifecycle without* using a {@link SecurityConfigurer}.*/
protected void beforeInit() throws Exception {
}/*** Invoked prior to invoking each* {@link SecurityConfigurer#configure(SecurityBuilder)} method. Subclasses may* override this method to hook into the lifecycle without using a* {@link SecurityConfigurer}.*/
protected void beforeConfigure() throws Exception {
}/*** Subclasses must implement this method to build the object that is being returned.* 子类必须实现此方法以构建返回的对象。* @return the Object to be buit or null if the implementation allows it* @return 待构建的对象,或若实现允许则返回null*/
protected abstract O performBuild() throws Exception;@SuppressWarnings("unchecked")
private void init() throws Exception {Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();for (SecurityConfigurer<O, B> configurer : configurers) {configurer.init((B) this);}for (SecurityConfigurer<O, B> configurer : this.configurersAddedInInitializing) {configurer.init((B) this);}
}@SuppressWarnings("unchecked")
private void configure() throws Exception {Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();for (SecurityConfigurer<O, B> configurer : configurers) {configurer.configure((B) this);}
}private Collection<SecurityConfigurer<O, B>> getConfigurers() {List<SecurityConfigurer<O, B>> result = new ArrayList<>();for (List<SecurityConfigurer<O, B>> configs : this.configurers.values()) {result.addAll(configs);}return result;
}
add(configurer): Adds SecurityConfigurer ensuring that it is allowed and invoking SecurityConfigurer.ini()
我们在配置类中添加配置,本质上就是这个add()
private <C extends SecurityConfigurer<O, B>> void add(C configurer) {Assert.notNull(configurer, "configurer cannot be null");Class<? extends SecurityConfigurer<O, B>> clazz = (Class<? extends SecurityConfigurer<O, B>>) configurer.getClass();synchronized (this.configurers) {// 见doBuild() - 这里就是如果已经 配置了之后的,不允许再次add了if (this.buildState.isConfigured()) {throw new IllegalStateException("Cannot apply " + configurer + " to already built object");}List<SecurityConfigurer<O, B>> configs = null;if (this.allowConfigurersOfSameType) {configs = this.configurers.get(clazz);}configs = (configs != null) ? configs : new ArrayList<>(1);configs.add(configurer);this.configurers.put(clazz, configs);// 判断是否正在配置,如果正在配置,就加载到configurersAddedInInitializing中if (this.buildState.isInitializing()) {this.configurersAddedInInitializing.add(configurer);}}
}
5. HttpSecurity.java
SecurityBuilder的子类,负责构建一个DefaultSecurityFilterChain对象, 他是SecurityFilterChain接口的具体实现, 而SecurityFilterChain接口,我们就可以理解成一个List<Filter>
。
这里最核心的逻辑就是实现了父类(抽象类)AbstractConfiguredSecurityBuilder.java 的performBuild
@Override
protected DefaultSecurityFilterChain performBuild() {ExpressionUrlAuthorizationConfigurer<?> expressionConfigurer = getConfigurer(ExpressionUrlAuthorizationConfigurer.class);AuthorizeHttpRequestsConfigurer<?> httpConfigurer = getConfigurer(AuthorizeHttpRequestsConfigurer.class);boolean oneConfigurerPresent = expressionConfigurer == null ^ httpConfigurer == null;Assert.state((expressionConfigurer == null && httpConfigurer == null) || oneConfigurerPresent,"authorizeHttpRequests cannot be used in conjunction with authorizeRequests. Please select just one.");this.filters.sort(OrderComparator.INSTANCE);List<Filter> sortedFilters = new ArrayList<>(this.filters.size());for (Filter filter : this.filters) {sortedFilters.add(((OrderedFilter) filter).filter);}return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
}
6. WebSecurity.java
SecurityBuilder的子类,负责构建一个FilterChainProxy对象。FilterChainProxy我们就可以理解成一个List<SecurityFilterChain>
@Override
protected Filter performBuild() throws Exception {Assert.state(!this.securityFilterChainBuilders.isEmpty(),() -> "At least one SecurityBuilder<? extends SecurityFilterChain> needs to be specified. "+ "Typically this is done by exposing a SecurityFilterChain bean. "+ "More advanced users can invoke " + WebSecurity.class.getSimpleName()+ ".addSecurityFilterChainBuilder directly");int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();// 4. 看这里List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries = new ArrayList<>();for (RequestMatcher ignoredRequest : this.ignoredRequests) {WebSecurity.this.logger.warn("You are asking Spring Security to ignore " + ignoredRequest+ ". This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.");SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);// 5. 看这里securityFilterChains.add(securityFilterChain);requestMatcherPrivilegeEvaluatorsEntries.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));}for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();// 6. 看这里securityFilterChains.add(securityFilterChain);requestMatcherPrivilegeEvaluatorsEntries.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));}if (this.privilegeEvaluator == null) {this.privilegeEvaluator = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(requestMatcherPrivilegeEvaluatorsEntries);}// 3. 看这里FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);if (this.httpFirewall != null) {filterChainProxy.setFirewall(this.httpFirewall);}if (this.requestRejectedHandler != null) {filterChainProxy.setRequestRejectedHandler(this.requestRejectedHandler);}filterChainProxy.afterPropertiesSet();// 2. 看这里Filter result = filterChainProxy;if (this.debugEnabled) {this.logger.warn("\n\n" + "********************************************************************\n"+ "********** Security debugging is enabled. *************\n"+ "********** This may include sensitive information. *************\n"+ "********** Do not use in a production system! *************\n"+ "********************************************************************\n\n");result = new DebugFilter(filterChainProxy);}this.postBuildAction.run();// 1. 看这里return result;
}
注意:最终返回的FilterChainProxy,也就是Filter 会在org.springframework.web.filter.DelegatingFilterProxy中执行doFilter,而DelegatingFilterProxy 则是容器提供的
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)throws ServletException, IOException {// Lazily initialize the delegate if necessary.Filter delegateToUse = this.delegate;if (delegateToUse == null) {synchronized (this.delegateMonitor) {delegateToUse = this.delegate;if (delegateToUse == null) {WebApplicationContext wac = findWebApplicationContext();if (wac == null) {throw new IllegalStateException("No WebApplicationContext found: " +"no ContextLoaderListener or DispatcherServlet registered?");}delegateToUse = initDelegate(wac);}this.delegate = delegateToUse;}}// Let the delegate perform the actual doFilter operation.invokeDelegate(delegateToUse, request, response, filterChain);
}/*** 实际上,使用给定的请求和响应调用委托过滤器。* Actually invoke the delegate Filter with the given request and response.* @param delegate 委托代理筛选器* @param request 请求当前HTTP请求* @param response 响应当前HTTP响应* @param filterChain 当前的filterChain
*/
protected void invokeDelegate(Filter delegate, ServletRequest request, ServletResponse response, FilterChain filterChain)throws ServletException, IOException {delegate.doFilter(request, response, filterChain);
}
7. SecurityFilterChain.java
一个接口,作用是定义一个过滤器链,该链能够与HttpServletRequest进行匹配,以判断其是否适用于该请求。之后用于配置一个FilterChainProxy。
boolean matches(HttpServletRequest request);List<Filter> getFilters();
DefaultSecurityfilterChain.java
是 SecurityFilterChain.java的实现
最主要就是 :
① 维护一个requestMatcher,之后实现matches: requestMatcher跟传入的request进行匹配
②实现getFilters, 如果matches返回true, 则返回一堆的Filters
private final RequestMatcher requestMatcher;private final List<Filter> filters;@Override
public List<Filter> getFilters() {return this.filters;
}@Override
public boolean matches(HttpServletRequest request) {return this.requestMatcher.matches(request);
}
8. FilterChainProxy.java
本质上就是一个Filter:Filter就要看doFilter,这里的功能就是控制每个filter只能执行一遍,最主要的方法就是doFilterInternal
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;if (!clearContext) {doFilterInternal(request, response, chain);return;}try {request.setAttribute(FILTER_APPLIED, Boolean.TRUE);doFilterInternal(request, response, chain);}catch (Exception ex) {Throwable[] causeChain = this.throwableAnalyzer.determineCauseChain(ex);Throwable requestRejectedException = this.throwableAnalyzer.getFirstThrowableOfType(RequestRejectedException.class, causeChain);if (!(requestRejectedException instanceof RequestRejectedException)) {throw ex;}this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response,(RequestRejectedException) requestRejectedException);}finally {this.securityContextHolderStrategy.clearContext();request.removeAttribute(FILTER_APPLIED);}
}
获取Filters, 之后构建一个虚拟的FilterChain,VirtualFilterChain
private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain)throws IOException, ServletException {FirewalledRequest firewallRequest = this.firewall.getFirewalledRequest((HttpServletRequest) request);HttpServletResponse firewallResponse = this.firewall.getFirewalledResponse((HttpServletResponse) response);List<Filter> filters = getFilters(firewallRequest);if (filters == null || filters.size() == 0) {if (logger.isTraceEnabled()) {logger.trace(LogMessage.of(() -> "No security for " + requestLine(firewallRequest)));}firewallRequest.reset();chain.doFilter(firewallRequest, firewallResponse);return;}if (logger.isDebugEnabled()) {logger.debug(LogMessage.of(() -> "Securing " + requestLine(firewallRequest)));}VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}
获取Filters(这里可以看到一个逻辑,就是只会执行对一个匹配的SecurityFilterChain,所以如果有多个SecurityFilterChain的情况时,我们可以时用@Order注解控制一下顺序)
private List<Filter> getFilters(HttpServletRequest request) {int count = 0;// 如果有多个SecurityFilterChain,执行第一个成功匹配的for (SecurityFilterChain chain : this.filterChains) {if (logger.isTraceEnabled()) {logger.trace(LogMessage.format("Trying to match request against %s (%d/%d)", chain, ++count,this.filterChains.size()));}if (chain.matches(request)) {return chain.getFilters();}}return null;
}
注意:
private List<SecurityFilterChain> filterChains;
这里我们结合设计模式理解,这里就是一个典型的策略模式,我们不管有多少个SecurityFilterChain。我们只返回匹配成功的chain.getFilters();
FilterChainProxy 的 内部类 VirtualFilterChain, 执行获取到的Filters中的每个Filter对象的doFilter
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {if (this.currentPosition == this.size) {if (logger.isDebugEnabled()) {logger.debug(LogMessage.of(() -> "Secured " + requestLine(this.firewalledRequest)));}// Deactivate path stripping as we exit the security filter chainthis.firewalledRequest.reset();this.originalChain.doFilter(request, response);return;}this.currentPosition++;Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);if (logger.isTraceEnabled()) {logger.trace(LogMessage.format("Invoking %s (%d/%d)", nextFilter.getClass().getSimpleName(),this.currentPosition, this.size));}nextFilter.doFilter(request, response, this);
}
三 Spring Security 中认证的加载与执行过程
认证执行流程图
类加载的时序图(以账号密码为例)
1. AuthenticationManager.java
是一个接口,只有一个作用, 尝试对传递的Authentication对象进行身份验证,如果成功,则返回完全填充的身份验证对象(包括授予的权限)。自定义实现的AuthenticationManager必须遵守以下原则:
- 如果帐户被禁用,并且AuthenticationManager可以测试此状态,则必须引发DisabledException。
- 如果帐户被锁定,并且AuthenticationManager可以测试帐户锁定,则必须引发LockedException。
- 如果提供了不正确的凭据,则必须引发BadCredentialsException。虽然上述例外是可选的,但AuthenticationManager必须始终测试凭据。
- 如果适用上面的异常,需要按上面表示的顺序抛出(即,如果帐户被禁用或锁定,则身份验证请求立即被拒绝,并且不执行凭据验证过程)。这可以防止对禁用或锁定的帐户测试凭据。
Authentication authenticate(Authentication authentication) throws AuthenticationException;
2. ProviderManagerBuilder.java
用于在创建ProviderManager的SecurityBuilder上操作的接口, 主要就是添加自定义 认证器的
3. AuthenticationManagerBuilder.java
SecurityBuilder用于创建AuthenticationManager。可以更简单的构建内存中身份验证、LDAP身份验证、基于JDBC的身份验证 的 Authentication,并且添加UserDetailsService和AuthenticationProvider,
// implements ProviderManagerBuilder<AuthenticationManagerBuilder>
public class AuthenticationManagerBuilderextends AbstractConfiguredSecurityBuilder<AuthenticationManager, AuthenticationManagerBuilder>implements ProviderManagerBuilder<AuthenticationManagerBuilder> {private List<AuthenticationProvider> authenticationProviders = new ArrayList<>();/*** 我们在配置类中,可以通过此方法把自定义的authenticationProvider,添加到认证管理器中,这个是ProviderManagerBuilder接口方法的实现* 基于传入的自定义AuthenticationProvider添加身份验证。由于AuthenticationProvider实现未知,因此所有自定义都必须在外部完成,并立即返回 * ProviderManagerBuilder。请注意,如果在添加AuthenticationProvider时发生错误,则会引发异常。* 返回一个允许向ProviderManagerBuilder提供进一步身份验证的ProviderManagerBuilder*/@Overridepublic AuthenticationManagerBuilder authenticationProvider(AuthenticationProvider authenticationProvider) {this.authenticationProviders.add(authenticationProvider);return this;}@Overrideprotected ProviderManager performBuild() throws Exception {if (!isConfigured()) {this.logger.debug("No authenticationProviders and no parentAuthenticationManager defined. Returning null.");return null;}// ProviderManager维护的是 所有认证处理器,即authenticationProvider,并且执行认证的逻辑ProviderManager providerManager = new ProviderManager(this.authenticationProviders,this.parentAuthenticationManager);if (this.eraseCredentials != null) {providerManager.setEraseCredentialsAfterAuthentication(this.eraseCredentials);}if (this.eventPublisher != null) {providerManager.setAuthenticationEventPublisher(this.eventPublisher);}providerManager = postProcess(providerManager);return providerManager;}public boolean isConfigured() {return !this.authenticationProviders.isEmpty() || this.parentAuthenticationManager != null;}
4. ProviderManager.java
ProviderManager维护的是 所有认证处理器,即authenticationProvider,并且执行认证的逻辑。
验证管理器,这个类实现了AuthenticationManager
接口的authenticate
方法 ,作用是通过AuthenticationProviders列表重复身份验证请求。AuthenticationProvider通常按顺序尝试,直到提供非空响应。非空响应表示提供程序有权决定身份验证请求,并且不会尝试其他提供程序。如果后续提供程序成功地对请求进行身份验证,则会忽略先前的身份验证异常,并使用成功的身份验证。如果没有后续提供程序提供非null响应或新的AuthenticationException,则将使用收到的最后一个AuthenticationException。如果没有提供程序返回非空响应,或者指示它甚至可以处理身份验证,则ProviderManager将抛出ProviderNotFoundException。还可以设置父AuthenticationManager,如果没有配置的提供程序可以执行身份验证,也将尝试此操作。这旨在支持命名空间配置选项,但不是通常需要的功能。此过程的例外情况是提供程序抛出AccountStatusException,在这种情况下,不会查询列表中的其他提供程序。身份验证后,如果凭据实现CredentialsContainer接口,则将从返回的authentication对象中清除凭据。可以通过修改eraseCredentialsAfterAuthentication属性来控制此行为。
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();for (AuthenticationProvider provider : getProviders()) {if (!provider.supports(toTest)) {continue;}if (logger.isTraceEnabled()) {logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",provider.getClass().getSimpleName(), ++currentPosition, size));}try {result = provider.authenticate(authentication);if (result != null) {copyDetails(authentication, result);break;}}catch (AccountStatusException | InternalAuthenticationServiceException ex) {prepareException(ex, authentication);// SEC-546: Avoid polling additional providers if auth failure is due to// invalid account statusthrow ex;}catch (AuthenticationException ex) {// 这里存在一个问题, 就是如果在自定义的authenticationProvider中抛出BadCredentialsException时,// 也就是AuthenticationException,这里为了可以继续执行其他的认证方式,只记录了异常,没有抛出异常// 导致了一个 authenticationProvider 可能执行两边的问题,应为父类中也存在相同的authenticationProviderlastException = ex;}}if (result == null && this.parent != null) {// Allow the parent to try.try {parentResult = this.parent.authenticate(authentication);result = parentResult;}catch (ProviderNotFoundException ex) {// ignore as we will throw below if no other exception occurred prior to// calling parent and the parent// may throw ProviderNotFound even though a provider in the child already// handled the request}catch (AuthenticationException ex) {parentException = ex;lastException = ex;}}if (result != null) {if (this.eraseCredentialsAfterAuthentication && (result instanceof CredentialsContainer)) {// Authentication is complete. Remove credentials and other secret data// from authentication((CredentialsContainer) result).eraseCredentials();}// If the parent AuthenticationManager was attempted and successful then it// will publish an AuthenticationSuccessEvent// This check prevents a duplicate AuthenticationSuccessEvent if the parent// AuthenticationManager already published itif (parentResult == null) {this.eventPublisher.publishAuthenticationSuccess(result);}return result;}// Parent was null, or didn't authenticate (or throw an exception).if (lastException == null) {lastException = new ProviderNotFoundException(this.messages.getMessage("ProviderManager.providerNotFound",new Object[] { toTest.getName() }, "No AuthenticationProvider found for {0}"));}// If the parent AuthenticationManager was attempted and failed then it will// publish an AbstractAuthenticationFailureEvent// This check prevents a duplicate AbstractAuthenticationFailureEvent if the// parent AuthenticationManager already published itif (parentException == null) {prepareException(lastException, authentication);}throw lastException;
}
5. AuthenticationProvider.java
执行认证的逻辑,如果我们要自定义认证逻辑时,可以实现这个接口
/*** Performs authentication with the same contract as* {@link org.springframework.security.authentication.AuthenticationManager#authenticate(Authentication)}* .* @param authentication the authentication request object.* @return a fully authenticated object including credentials. May return* <code>null</code> if the <code>AuthenticationProvider</code> is unable to support* authentication of the passed <code>Authentication</code> object. In such a case,* the next <code>AuthenticationProvider</code> that supports the presented* <code>Authentication</code> class will be tried.* @throws AuthenticationException if authentication fails.*/
Authentication authenticate(Authentication authentication) throws AuthenticationException;/*** Returns <code>true</code> if this <Code>AuthenticationProvider</code> supports the* indicated <Code>Authentication</code> object.* <p>* Returning <code>true</code> does not guarantee an* <code>AuthenticationProvider</code> will be able to authenticate the presented* instance of the <code>Authentication</code> class. It simply indicates it can* support closer evaluation of it. An <code>AuthenticationProvider</code> can still* return <code>null</code> from the {@link #authenticate(Authentication)} method to* indicate another <code>AuthenticationProvider</code> should be tried.* </p>* <p>* Selection of an <code>AuthenticationProvider</code> capable of performing* authentication is conducted at runtime the <code>ProviderManager</code>.* </p>* @param authentication* @return <code>true</code> if the implementation can more closely evaluate the* <code>Authentication</code> class presented*/
boolean supports(Class<?> authentication);
6. Authentication.java
一个接口,构建一个认证器,不同的认证方式自定义自己的认证器,并且需要在AuthenticationProvider接口的supports方法的实现类中验证自定义的认证器,并且需要把Authentication持久化,见官方注释:
四 Spring Security 的 默认 Filter(14个)
Spring Security不同的版本默认的Filter的个数也不一样当前说的这14个是5.7+的版本中的默认个数
1. HttpSecurityConfiguration.java
随着SpringBoot项目启动的时候加载带有@Configuration
的HttpSecurityConfiguration
, HttpSecurityConfiguration
中的主要的逻辑就是:
@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {WebSecurityConfigurerAdapter.LazyPasswordEncoder passwordEncoder = new WebSecurityConfigurerAdapter.LazyPasswordEncoder(this.context);AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(this.objectPostProcessor, passwordEncoder);authenticationBuilder.parentAuthenticationManager(authenticationManager());authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());// @formatter:offhttp.csrf(withDefaults()).addFilter(new WebAsyncManagerIntegrationFilter()).exceptionHandling(withDefaults()).headers(withDefaults()).sessionManagement(withDefaults()).securityContext(withDefaults()).requestCache(withDefaults()).anonymous(withDefaults()).servletApi(withDefaults()).apply(new DefaultLoginPageConfigurer<>());http.logout(withDefaults());// @formatter:onapplyDefaultConfigurers(http);return http;
}
`httpSecurity加载默认的Filter, 本质是去加载Configurer, Configurer是 配置 Filter,filter里面的doFilter, 就是去做这个Filter的主要逻辑,这里使用CSRF做一个举例, 显卡 HttpSecurity中的方法csrf:
public HttpSecurity csrf(Customizer<CsrfConfigurer<HttpSecurity>> csrfCustomizer) throws Exception {ApplicationContext context = getContext();csrfCustomizer.customize(getOrApply(new CsrfConfigurer<>(context)));return HttpSecurity.this;
}
CSRF 的配置:CsrfConfigurer.java
public void configure(H http) {CsrfFilter filter = new CsrfFilter(this.csrfTokenRepository);RequestMatcher requireCsrfProtectionMatcher = getRequireCsrfProtectionMatcher();if (requireCsrfProtectionMatcher != null) {filter.setRequireCsrfProtectionMatcher(requireCsrfProtectionMatcher);}AccessDeniedHandler accessDeniedHandler = createAccessDeniedHandler(http);if (accessDeniedHandler != null) {filter.setAccessDeniedHandler(accessDeniedHandler);}LogoutConfigurer<H> logoutConfigurer = http.getConfigurer(LogoutConfigurer.class);if (logoutConfigurer != null) {logoutConfigurer.addLogoutHandler(new CsrfLogoutHandler(this.csrfTokenRepository));}SessionManagementConfigurer<H> sessionConfigurer = http.getConfigurer(SessionManagementConfigurer.class);if (sessionConfigurer != null) {sessionConfigurer.addSessionAuthenticationStrategy(getSessionAuthenticationStrategy());}// 后置处理器filter = postProcess(filter);http.addFilter(filter);
}
CSRF的过滤器:CsrfFilter.java
@Override
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) {csrfToken = this.tokenRepository.generateToken(request);this.tokenRepository.saveToken(csrfToken, request, response);}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);}filterChain.doFilter(request, response);return;}String actualToken = request.getHeader(csrfToken.getHeaderName());if (actualToken == null) {actualToken = request.getParameter(csrfToken.getParameterName());}// **重点, 安全验证!!** :Constant time comparison to prevent against timing attacks.if (!equalsConstantTime(csrfToken.getToken(), actualToken)) {this.logger.debug(LogMessage.of(() -> "Invalid CSRF token found for " + UrlUtils.buildFullRequestUrl(request)));AccessDeniedException exception = (!missingToken) ? new InvalidCsrfTokenException(csrfToken, actualToken): new MissingCsrfTokenException(actualToken);this.accessDeniedHandler.handle(request, response, exception);return;}filterChain.doFilter(request, response);
}
所有的过滤器:
过滤器顺序 | 过滤器类名 | 用途 | 配置方法 | 关键配置属性 |
---|---|---|---|---|
1 | WebAsyncManagerIntegrationFilter | 确保安全上下文在异步请求中传播 | 自动启用 | 无显式配置 |
2 | SecurityContextPersistenceFilter | 在请求间存储安全上下文 | .securityContext() | requireExplicitSave (是否显式保存上下文) |
3 | HeaderWriterFilter | 添加安全相关的HTTP响应头 | .headers() | contentSecurityPolicy , frameOptions , xssProtection , hsts |
4 | CsrfFilter | 防止CSRF攻击 | .csrf() | csrfTokenRepository , ignoringAntMatchers |
5 | LogoutFilter | 处理用户退出登录 | .logout() | logoutUrl , logoutSuccessUrl , deleteCookies , invalidateHttpSession |
6 | UsernamePasswordAuthenticationFilter | 处理表单登录认证 | .formLogin() | loginPage , loginProcessingUrl , defaultSuccessUrl , failureUrl |
7 | DefaultLoginPageGeneratingFilter | 生成默认登录页 | 自动启用(当未自定义登录页时) | 无显式配置 |
8 | DefaultLogoutPageGeneratingFilter | 生成默认退出页 | 自动启用(当未自定义退出页时) | 无显式配置 |
9 | BasicAuthenticationFilter | 处理HTTP基本认证 | .httpBasic() | realmName |
10 | RequestCacheAwareFilter | 缓存请求以便认证后重定向 | 自动启用 | requestCache (可通过.requestCache() 配置) |
11 | SecurityContextHolderAwareRequestFilter | 包装HttpServletRequest添加安全方法 | 自动启用 | 无显式配置 |
12 | AnonymousAuthenticationFilter | 为未认证用户提供匿名身份 | .anonymous() | principal , authorities |
13 | SessionManagementFilter | 管理用户会话 | .sessionManagement() | sessionFixation , maximumSessions , sessionAuthenticationStrategy |
14 | ExceptionTranslationFilter | 处理认证和授权异常 | 自动启用 | authenticationEntryPoint , accessDeniedHandler |
15 | FilterSecurityInterceptor | 进行授权决策 | .authorizeRequests() | securityMetadataSource , accessDecisionManager |
特殊过滤器说明
过滤器 | 触发条件 |
---|---|
RememberMeAuthenticationFilter | 配置了记住我功能时自动添加 |
五 Spring Security 的配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;@Configuration
@EnableWebSecurity
public class SecurityConfig {// 主安全配置@Bean@Order(1)public DefaultSecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http// 1. WebAsyncManagerIntegrationFilter - 处理异步请求的安全上下文// 自动配置,无需显式配置// 2. SecurityContextPersistenceFilter - 安全上下文存储.securityContext(context -> context.requireExplicitSave(false) // 是否要求显式保存安全上下文)// 3. HeaderWriterFilter - 安全响应头设置.headers(headers -> headers.contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'"))// 防止点击劫持.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin).httpStrictTransportSecurity(hsts -> hsts.includeSubDomains(true).maxAgeInSeconds(31536000) // 1年HSTS)// XSS防护配置.xssProtection(xss -> xss.xssProtectionEnabled(true).block(true)))// 4. CsrfFilter - CSRF保护.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // 使用Cookie存储CSRF令牌.ignoringAntMatchers("/api/public/**") // 忽略某些路径)// 5. LogoutFilter - 退出登录处理.logout(logout -> logout.logoutUrl("/logout") // 退出登录URL.logoutSuccessUrl("/login?logout") // 退出成功后跳转.deleteCookies("JSESSIONID") // 删除Cookie.invalidateHttpSession(true) // 使会话失效)// 6. UsernamePasswordAuthenticationFilter - 表单登录处理.formLogin(form -> form.loginPage("/login") // 自定义登录页.loginProcessingUrl("/login-process") // 登录处理URL.defaultSuccessUrl("/home", true) // 登录成功跳转.failureUrl("/login?error=true") // 登录失败跳转.permitAll() // 允许所有用户访问登录页)// 7. DefaultLoginPageGeneratingFilter - 默认登录页生成// 当不自定义登录页时自动生成,此处已自定义故不启用// 8. DefaultLogoutPageGeneratingFilter - 默认退出页生成// 同上,已自定义退出处理// 9. BasicAuthenticationFilter - HTTP基本认证.httpBasic(basic -> basic.realmName("My App") // 设置领域名称)// 10. RequestCacheAwareFilter - 请求缓存// 自动配置,用于在认证后重定向到原始请求// 11. SecurityContextHolderAwareRequestFilter - 包装请求对象// 自动配置,使HttpServletRequest具有安全相关方法// 12. AnonymousAuthenticationFilter - 匿名用户处理.anonymous(anonymous -> anonymous.principal("guest") // 匿名用户主体名称.authorities("ROLE_GUEST") // 匿名用户权限)// 13. SessionManagementFilter - 会话管理.sessionManagement(session -> session.sessionFixation().migrateSession() // 会话固定保护.maximumSessions(1) // 每个用户最多1个会话.maxSessionsPreventsLogin(false) // 不阻止新登录,会使旧会话失效.expiredUrl("/login?expired") // 会话过期跳转)// 14. ExceptionTranslationFilter - 异常处理// 自动配置,处理认证和授权异常// 15. AuthorizationFilter - 授权决策.authorizeHttpRequests(auth -> auth.antMatchers("/", "/home", "/public/**").permitAll() // 允许匿名访问.antMatchers("/admin/**").hasRole("ADMIN") // 需要ADMIN角色.antMatchers("/user/**").hasAnyRole("USER", "ADMIN") // 需要USER或ADMIN角色.anyRequest().authenticated() // 其他请求需要认证);return http.build();}// 添加自定义过滤器示例@Bean@Order(2)public SecurityFilterChain customFilterChain(HttpSecurity http) throws Exception {http.antMatcher("/api/**").addFilterBefore(new CustomAuthenticationFilter(), BasicAuthenticationFilter.class).authorizeRequests().anyRequest().authenticated();return http.build();}
}
六 Spring Security 授权
使用场景 | 推荐授权方式 | 理由 | 配置示例 |
---|---|---|---|
Spring Security 5.5+ | AuthorizationFilter | 现代API,性能好,未来主流 | .authorizeHttpRequests() |
Spring Security 3.2+ | FilterSecurityInterceptor | 兼容性好,文档丰富 | .authorizeRequests() |
REST API | 方法级注解 | 细粒度控制,业务逻辑集成 | @PreAuthorize |
复杂业务规则 | 自定义AuthorizationManager | 灵活,可动态配置 | 实现AuthorizationManager |
微服务架构 | 多过滤器链 | 不同路径不同策略 | 多个@Bean + @Order |
方式一 Spring Security 5.4+ (推荐):
@Configuration
@EnableWebSecurity
public class SecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(authz -> authz// 静态资源允许匿名访问.requestMatchers("/css/**", "/js/**", "/images/**").permitAll()// 公开页面.requestMatchers("/", "/home", "/about", "/contact").permitAll()// API文档允许匿名.requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll()// 管理接口需要管理员角色.requestMatchers("/admin/**").hasRole("ADMIN")// 用户接口需要用户角色.requestMatchers("/user/**").hasRole("USER")// 财务接口需要财务角色.requestMatchers("/finance/**").hasAnyRole("FINANCE", "ADMIN")// API接口需要认证.requestMatchers("/api/**").authenticated()// 数据库管理接口需要特定权限.requestMatchers("/db/**").access(new WebExpressionAuthorizationManager("hasRole('ADMIN') and hasIpAddress('192.168.1.0/24')"))// 其他所有请求需要认证.anyRequest().authenticated())// 自定义访问拒绝处理.exceptionHandling(exception -> exception.accessDeniedHandler(new CustomAccessDeniedHandler()));return http.build();}
}
方式二 方法级注解:
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MethodSecurityConfig {@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(authz -> authz.anyRequest().permitAll() // 过滤器级放行,依赖方法级安全);return http.build();}
}
// 可以加在 Controller层、Servcie层
@Service
public class UserService {@PreAuthorize("hasRole('ADMIN') or #userId == authentication.principal.id")public User updateUser(Long userId, User user) {// 管理员或用户自己可以更新}@PostAuthorize("returnObject.owner == authentication.name")public Document getDocument(Long id) {// 只能查看自己拥有的文档}@PreFilter("filterObject.owner == authentication.name")public void updateDocuments(List<Document> documents) {// 只能更新自己拥有的文档列表}
}
方式三 多过滤器链 - 分层安全配置:
@Configuration
@EnableWebSecurity
public class MultiLayerSecurityConfig {@Bean@Order(1) // 高优先级:公开资源public SecurityFilterChain publicFilterChain(HttpSecurity http) throws Exception {http.securityMatcher("/public/**", "/static/**", "/webjars/**").authorizeHttpRequests(authz -> authz.anyRequest().permitAll()).csrf().disable();return http.build();}@Bean@Order(2) // API接口public SecurityFilterChain apiFilterChain(HttpSecurity http) throws Exception {http.securityMatcher("/api/**").authorizeHttpRequests(authz -> authz.anyRequest().authenticated()).httpBasic(withDefaults()).sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS));return http.build();}@Bean@Order(3) // Web界面(默认)public SecurityFilterChain webFilterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(authz -> authz.anyRequest().authenticated()).formLogin(withDefaults());return http.build();}
}