Spring Auhorization Server 过滤器链配置
前言
1.filter
1.1 Servlet
1.2 Spring Security Filter
即
这么多Filter,他们之间是如何协调工作的呢?
1.3 结论
2.Spring Security Filter 原理
2.1 FilterChainProxy
package org.springframework.security.web;
public class FilterChainProxy extends GenericFilterBean {
private List<SecurityFilterChain> filterChains;
public FilterChainProxy() {
}
public FilterChainProxy(SecurityFilterChain chain) {
this(Arrays.asList(chain));
}
public FilterChainProxy(List<SecurityFilterChain> filterChains) {
this.filterChains = filterChains;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
//❶执行真正过滤逻辑
doFilterInternal(request, response, chain);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
接着看doFilterInternal
package org.springframework.security.web;
public class FilterChainProxy extends GenericFilterBean {
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);
//❶获取所有的filter
List<Filter> filters = getFilters(firewallRequest);
VirtualFilterChain virtualFilterChain = new VirtualFilterChain(firewallRequest, chain, filters);
//❷内部代理类FilterChain执行过滤逻辑
virtualFilterChain.doFilter(firewallRequest, firewallResponse);
}
❶getFilters 获取所有的filter
package org.springframework.security.web;
public class FilterChainProxy extends GenericFilterBean {
private List<Filter> getFilters(HttpServletRequest request) {
int count = 0;
for (SecurityFilterChain chain : this.filterChains) {
//❶获取匹配的FilterChain
if (chain.matches(request)) {
//❷找到即返回该FilterChain的所有Filter
return chain.getFilters();
}
}
return null;
}
❶chain.matches获取匹配的FilterChain
2.2 SecurityFilterChain
package org.springframework.security.web;
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
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);
}
每个SecurityFilterChain内部都维护着一个RequestMatcher,来做请求路径的匹配。
这样每个每个请求只会被一个SecurityFilterChain处理。
回到doFilterInternal
❷交给virtualFilterChain来处理SecurityFilterChain的内部filter
2.3 VirtualFilterChain
它是FilterChainProxy的静态内部类
package org.springframework.security.web;
public class FilterChainProxy extends GenericFilterBean {
private static final class VirtualFilterChain implements FilterChain {
private final FilterChain originalChain;
private final List<Filter> additionalFilters;
private final FirewalledRequest firewalledRequest;
private final int size;
private int currentPosition = 0;
@Override
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
if (this.currentPosition == this.size) {
this.firewalledRequest.reset();
this.originalChain.doFilter(request, response);
return;
}
//记录当前执行的offset
this.currentPosition++;
//❶获取下一个filter
Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
//❷执行filter,并将自身(FilterChain 传递下去,保证接下来的filter都能执行到)
nextFilter.doFilter(request, response, this);
}
}
❶通过集合的index获取接下来要执行的Filter
❷执行Filter ,并将自身VirtualFilterChain传递下去,保证下次还能执行VirtualFilterChain的doFilter方法,保证每个Filter都能执行到。
所以这个Filter的过程就是
如果前一个FilterChain已经匹配,后面的FilterChain就不会再执行了。
2.4 FilterChain 和SecurityFilterChain的区别
上面出现了两个FilterChain,我们来简单对比一下
首先看Servlet的FilterChain
package javax.servlet;
public interface FilterChain {
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException;
是一个接口,然后只有一个方法。然后上面说的VirtualFilterChain实现了它
接着看Spring Security 的SecurityFilterChain
package org.springframework.security.web;
public interface SecurityFilterChain {
boolean matches(HttpServletRequest request);
List<Filter> getFilters();
}
package javax.servlet;
public interface Filter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException;
3.Spring Authorization Server 的过滤器链
3.1 认证服务的FilterChain
我们首先看认证服务器的配置,同样还是以开源项目PIG来介绍。
@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer();
// 个性化认证授权端点
http.apply(authorizationServerConfigurer.tokenEndpoint((tokenEndpoint) -> {
// 注入自定义的授权认证Converter
tokenEndpoint.accessTokenRequestConverter(accessTokenRequestConverter())
// 登录成功处理器
.accessTokenResponseHandler(new PigAuthenticationSuccessEventHandler())
// 登录失败处理器
.errorResponseHandler(new PigAuthenticationFailureEventHandler());
// 个性化客户端认证
}).clientAuthentication(oAuth2ClientAuthenticationConfigurer ->
// 处理客户端认证异常
oAuth2ClientAuthenticationConfigurer.errorResponseHandler(new PigAuthenticationFailureEventHandler()))
// 授权码端点个性化confirm页面
.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint
.consentPage(SecurityConstants.CUSTOM_CONSENT_PAGE_URI)));
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
DefaultSecurityFilterChain securityFilterChain = http.requestMatcher(endpointsMatcher)
.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
// redis存储token的实现
.apply(authorizationServerConfigurer.authorizationService(authorizationService)
//token端口配置
.authorizationServerSettings(AuthorizationServerSettings.builder()
.issuer(SecurityConstants.PROJECT_LICENSE).build()))
// 授权码登录的登录页个性化
.and().apply(new FormIdentityLoginConfigurer()).and().build();
// 注入自定义授权模式实现
addCustomOAuth2GrantAuthenticationProvider(http);
return securityFilterChain;
}
其中 AuthorizationServerSettings.builder()
public final class AuthorizationServerSettings extends AbstractSettings {
public static Builder builder() {
return new Builder()
.authorizationEndpoint("/oauth2/authorize")
.tokenEndpoint("/oauth2/token")
.jwkSetEndpoint("/oauth2/jwks")
.tokenRevocationEndpoint("/oauth2/revoke")
.tokenIntrospectionEndpoint("/oauth2/introspect")
.oidcClientRegistrationEndpoint("/connect/register")
.oidcUserInfoEndpoint("/userinfo");
}
用来创建SecurtiyFilterChain 的RequestMatcher,也就是说只有这些uri才能被认证服务器的SecurityFilterChain匹配到,其他的请求会继续寻找匹配的SecurityFilterChain.
关于SecurtiyFilterChain的生成使用了创建者模式
DefaultSecurityFilterChain securityFilterChain = http.requestMatcher(endpointsMatcher)
.authorizeRequests(authorizeRequests -> authorizeRequests.anyRequest().authenticated())
// redis存储token的实现
.apply(authorizationServerConfigurer.authorizationService(authorizationService)
//token端口配置
.authorizationServerSettings(AuthorizationServerSettings.builder()
.issuer(SecurityConstants.PROJECT_LICENSE).build()))
// 授权码登录的登录页个性化
.and().apply(new FormIdentityLoginConfigurer()).and().build();
package org.springframework.security.config.annotation;
public abstract class AbstractSecurityBuilder<O> implements SecurityBuilder<O> {
private AtomicBoolean building = new AtomicBoolean();
private O object;
@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;
}
接着调用doBuild()方法来执行真正的生成方法
package org.springframework.security.config.annotation;
public abstract class AbstractConfiguredSecurityBuilder<O, B extends SecurityBuilder<O>>
extends AbstractSecurityBuilder<O> {
@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;
}
}
}
最后调用performBuild()方法
实现有
这里配置的入参用的是HttpSecurity
package org.springframework.security.config.annotation.web.builders;
public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<DefaultSecurityFilterChain, HttpSecurity>
implements SecurityBuilder<DefaultSecurityFilterChain>, HttpSecurityBuilder<HttpSecurity> {
private List<OrderedFilter> filters = new ArrayList<>();
private RequestMatcher requestMatcher = AnyRequestMatcher.INSTANCE;
private FilterOrderRegistration filterOrders = new FilterOrderRegistration();
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);
//❶Filter排序
List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
for (Filter filter : this.filters) {
sortedFilters.add(((OrderedFilter) filter).filter);
}
//❷ 创建SecurityFilterChain
return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
}
最后我们通过断点看看最后生成的SecurityFilterChain包含的filter(19个)
我们再看看requestMatcher(7个)
也就是说该SecurityFilterChain是为这7个uri服务的。
这些都是可以自定义配置的。
那么其他的请求呢,比如访问某个资源?我们继续往下看。
3.2 资源服务的FilterChain
同样还是以开源项目PIG来介绍。
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
// 开放自定义的部分端点
.authorizeRequests(authorizeRequests -> authorizeRequests.antMatchers("/token/*").permitAll()
.anyRequest().authenticated())
.headers()
.frameOptions()
.sameOrigin()// 避免iframe同源无法登录
.and()
// 表单登录个性化
.apply(new FormIdentityLoginConfigurer());
// 处理 UsernamePasswordAuthenticationToken
http.authenticationProvider(new PigDaoAuthenticationProvider());
return http.build();
}
public final class FormIdentityLoginConfigurer
extends AbstractHttpConfigurer<FormIdentityLoginConfigurer, HttpSecurity> {
@Override
public void init(HttpSecurity http) throws Exception {
http.formLogin(formLogin -> {
formLogin.loginPage("/token/login");
formLogin.loginProcessingUrl("/token/form");
formLogin.failureHandler(new FormAuthenticationFailureHandler());
}).logout() // SSO登出成功处理
.logoutSuccessHandler(new SsoLogoutSuccessHandler()).deleteCookies("JSESSIONID")
.invalidateHttpSession(true).and().csrf().disable();
}
} .logoutSuccessHandler(new SsoLogoutSuccessHandler()).deleteCookies("JSESSIONID")
.invalidateHttpSession(true).and().csrf().disable();
}
}
anyRequest().authenticated(),可以看到除了开放的端点外,其他所有的端点都要鉴权。
最后也是通过HttpSecurity的build()方法生成。上面已经分析了该方法。我们自己通过断点看看最终的Filter.
requestMatcher
3.3 最终的SecurityFilterChain
可以看到认证服务器的排第一,静态资源服务器排第二,资源服务器排第三。
那么顺序是如何定的呢?我们再回到代码
认证服务器,order是最高,排在最前。
静态资源服务器,order为0,排在第二
而资源服务器是默认的order(最低优先级),所以最靠后
4. 总结
当我们理清了认证服务和资源服务的过滤器链以后,接下来看具体的源码就会容易许多。同时在配置中,我们也尽量去分开配置,以免造成权限问题。

原文始发于微信公众号(小李的源码图):拥抱Spring全新OAuth解决方案(二)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/145268.html