Spring 安全 OAuth2:'#oauth2.xxx' 表达式未通过映射到同一 FilterChain 的多个 RequestMatchers 进行评估
Spring Security OAuth2: '#oauth2.xxx' expressions not evaluted with multiple RequestMatchers mapped to the same FilterChain
在多模块应用程序中,我定义了 5 个 RequestMatchers 映射到同一个 FilterChain,如下所示:
@Configuration
public class Module1SecurityFilterChain extends ResourceServerConfigurerAdapter {
@Override
public void configure( HttpSecurity http ) throws Exception {
http.sessionManagement().sessionCreationPolicy( STATELESS );
http.requestMatchers().antMatchers( "/module1/**")
.and()
.authorizeRequests()
.antMatchers( "/module1/resource").authenticated()
.antMatchers( "/module1/test" ).access( "#oauth2.isClient()")
.anyRequest().access( "#oauth2.hasScope('webclient')" );
}
}
和模块 2:
@Configuration
public class Module2SecurityFilterChain extends ResourceServerConfigurerAdapter {
@Override
public void configure( HttpSecurity http ) throws Exception {
http.sessionManagement().sessionCreationPolicy( STATELESS );
http.requestMatchers().antMatchers( "/module2/**")
.and()
.authorizeRequests()
.antMatchers( "/module2/resource").authenticated()
.antMatchers( "/module2/test" ).access( "#oauth2.isClient()")
.anyRequest().access( "#oauth2.hasScope('webclient')" );
}
}
并启用方法安全性:
@Configuration
@EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true )
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
问题是所有 #oauth2.xx
表达式仅针对第一个模块 requestmatcher /module1/**
进行评估,而在其他模块中被忽略。当我对用户进行身份验证并尝试访问 /module1/test
时,访问被拒绝,而当访问 /module2/test
时,访问被授予(也应该被拒绝)。
有人可以向我解释为什么以及如何解决这个问题吗?我知道 Spring 安全一点也不容易...
再次感谢。
编辑
@Darren Forsythe(感谢您的评论)
创建的过滤器链是:
INFO | o.s.s.w.DefaultSecurityFilterChain | Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern='/oauth/token'], Ant [pattern='/oauth/token_key'], Ant [pattern='/oauth/check_token']]], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@f55a810b, org.springframework.security.web.context.SecurityContextPersistenceFilter@85021903, org.springframework.security.web.header.HeaderWriterFilter@1d0744d1, org.springframework.security.web.authentication.logout.LogoutFilter@2d15146a, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@c38f3266, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@8f9bf85, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@74a71be5, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@e4eb6cc, org.springframework.security.web.session.SessionManagementFilter@22f6b39a, org.springframework.security.web.access.ExceptionTranslationFilter@960c464f, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@f7a19dc5]
INFO | o.s.s.w.DefaultSecurityFilterChain | Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern='/module1/**'], Ant [pattern='/module2/**'], Ant [pattern='/module3/**'], Ant [pattern='/module4/**'], Ant [pattern='/module5/**'], Ant [pattern='/module6/**']]], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@38ef2427, org.springframework.security.web.context.SecurityContextPersistenceFilter@a26ff7af, org.springframework.security.web.header.HeaderWriterFilter@5344e710, org.springframework.security.web.authentication.logout.LogoutFilter@da0534c8, org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter@2956c7ab, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5682f610, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@f4cbf7a4, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@d1b1395a, org.springframework.security.web.session.SessionManagementFilter@d352f8ab, org.springframework.security.web.access.ExceptionTranslationFilter@9bb1d86, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@73c7a695]
INFO | o.s.s.w.DefaultSecurityFilterChain | Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@1cc2056f, org.springframework.security.web.context.SecurityContextPersistenceFilter@259d95db, org.springframework.security.web.header.HeaderWriterFilter@de089e0b, org.springframework.security.web.authentication.logout.LogoutFilter@8b86b4c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@96304ca8, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@1d5b7e4b, org.springframework.security.web.session.SessionManagementFilter@bd586b4d, org.springframework.security.web.access.ExceptionTranslationFilter@7cff2571]
如您所见,所有模块的 url 都映射到具有此过滤器列表的相同过滤器链:
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
OAuth2AuthenticationProcessingFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
我不明白的是为什么对于其他模块 #oauth2.xx
表达式没有被评估,因为 FilterChain 是相同的?
请求匹配器(在 antMatchers
、anyRequest
等中指定)由过滤器链按照它们指定的顺序进行处理。因为多个 ResourceServiceConfiguredAdapter
实例只是从 HttpSecurity
的同一个实例进行配置,所以匹配器会针对您的每个请求进行如下处理:
if (uri == "/module1/resource") {
// ...
} else if (uri == "/module1/test") {
// ...
} else if (true) { // anyRequest
// ...
} else if (uri = "/module2/resource") {
// ...
} else if (uri = "/module2/test") {
// ...
}
如您所见,最后两个 if 条件永远不会被命中。
您可以考虑以下两点:
替换anyRequest()
anyRequest
通常非常方便;但是,在这种情况下,您实际上并不意味着 "any request" 因为您试图将范围缩小到某些模块路径。你可以改为:
http
.requestMatchers()
.antMatchers("/module2/**")
.and()
.authorizeRequests()
.antMatchers("/module2/resource").authenticated()
.antMatchers("/module2/test").access( "#oauth2.isClient()")
.antMatchers("/module2/**").access( "#oauth2.hasScope('webclient')" );
这样,模块就不会越界并尝试指定它可能不知道的行为。
说实话,调用 anyRequest
通常是无害的,因为您已经使用 requestMatchers
缩小了过滤器链的范围。但是,因为您正在使用多个适配器组合单个 HttpSecurity
,所以存在这种隐藏的复杂性。
oauth2ResourceServer()
- Spring 安全 5.1+
如果您使用 Spring Security 5.1,那么 WebSecurityConfigurerAdapter
中实际上内置了支持,因此您不需要再使用 ResourceServerConfigurerAdapter
,至少对于 JWT 编码而言在这一点上的令牌。这也很好,因为两个 WebSecurityConfigurerAdapter
被视为单独的过滤器链。
根据您需要的 OAuth 2.0 功能,您也许可以这样做:
@EnableWebSecurity
public class Module1Config extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/module1/**")
.and()
.authorizeRequests()
.antMatchers("/module1/resource").authenticated()
.anyRequest.hasRole("SCOPE_webclient")
.oauth2ResourceServer()
.jwt();
}
}
这是目前 Spring 安全性的一个活跃开发领域 - 将功能移植到 WebSecurityConfigurerAdapter
;所以一定要联系你的具体用例,以确保它在尚未到位的情况下得到优先考虑。
在多模块应用程序中,我定义了 5 个 RequestMatchers 映射到同一个 FilterChain,如下所示:
@Configuration
public class Module1SecurityFilterChain extends ResourceServerConfigurerAdapter {
@Override
public void configure( HttpSecurity http ) throws Exception {
http.sessionManagement().sessionCreationPolicy( STATELESS );
http.requestMatchers().antMatchers( "/module1/**")
.and()
.authorizeRequests()
.antMatchers( "/module1/resource").authenticated()
.antMatchers( "/module1/test" ).access( "#oauth2.isClient()")
.anyRequest().access( "#oauth2.hasScope('webclient')" );
}
}
和模块 2:
@Configuration
public class Module2SecurityFilterChain extends ResourceServerConfigurerAdapter {
@Override
public void configure( HttpSecurity http ) throws Exception {
http.sessionManagement().sessionCreationPolicy( STATELESS );
http.requestMatchers().antMatchers( "/module2/**")
.and()
.authorizeRequests()
.antMatchers( "/module2/resource").authenticated()
.antMatchers( "/module2/test" ).access( "#oauth2.isClient()")
.anyRequest().access( "#oauth2.hasScope('webclient')" );
}
}
并启用方法安全性:
@Configuration
@EnableGlobalMethodSecurity( prePostEnabled = true, securedEnabled = true )
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
问题是所有 #oauth2.xx
表达式仅针对第一个模块 requestmatcher /module1/**
进行评估,而在其他模块中被忽略。当我对用户进行身份验证并尝试访问 /module1/test
时,访问被拒绝,而当访问 /module2/test
时,访问被授予(也应该被拒绝)。
有人可以向我解释为什么以及如何解决这个问题吗?我知道 Spring 安全一点也不容易... 再次感谢。
编辑 @Darren Forsythe(感谢您的评论) 创建的过滤器链是:
INFO | o.s.s.w.DefaultSecurityFilterChain | Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern='/oauth/token'], Ant [pattern='/oauth/token_key'], Ant [pattern='/oauth/check_token']]], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@f55a810b, org.springframework.security.web.context.SecurityContextPersistenceFilter@85021903, org.springframework.security.web.header.HeaderWriterFilter@1d0744d1, org.springframework.security.web.authentication.logout.LogoutFilter@2d15146a, org.springframework.security.web.authentication.www.BasicAuthenticationFilter@c38f3266, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@8f9bf85, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@74a71be5, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@e4eb6cc, org.springframework.security.web.session.SessionManagementFilter@22f6b39a, org.springframework.security.web.access.ExceptionTranslationFilter@960c464f, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@f7a19dc5]
INFO | o.s.s.w.DefaultSecurityFilterChain | Creating filter chain: OrRequestMatcher [requestMatchers=[Ant [pattern='/module1/**'], Ant [pattern='/module2/**'], Ant [pattern='/module3/**'], Ant [pattern='/module4/**'], Ant [pattern='/module5/**'], Ant [pattern='/module6/**']]], [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@38ef2427, org.springframework.security.web.context.SecurityContextPersistenceFilter@a26ff7af, org.springframework.security.web.header.HeaderWriterFilter@5344e710, org.springframework.security.web.authentication.logout.LogoutFilter@da0534c8, org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationProcessingFilter@2956c7ab, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5682f610, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@f4cbf7a4, org.springframework.security.web.authentication.AnonymousAuthenticationFilter@d1b1395a, org.springframework.security.web.session.SessionManagementFilter@d352f8ab, org.springframework.security.web.access.ExceptionTranslationFilter@9bb1d86, org.springframework.security.web.access.intercept.FilterSecurityInterceptor@73c7a695]
INFO | o.s.s.w.DefaultSecurityFilterChain | Creating filter chain: any request, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@1cc2056f, org.springframework.security.web.context.SecurityContextPersistenceFilter@259d95db, org.springframework.security.web.header.HeaderWriterFilter@de089e0b, org.springframework.security.web.authentication.logout.LogoutFilter@8b86b4c, org.springframework.security.web.savedrequest.RequestCacheAwareFilter@96304ca8, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@1d5b7e4b, org.springframework.security.web.session.SessionManagementFilter@bd586b4d, org.springframework.security.web.access.ExceptionTranslationFilter@7cff2571]
如您所见,所有模块的 url 都映射到具有此过滤器列表的相同过滤器链:
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
LogoutFilter
OAuth2AuthenticationProcessingFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
我不明白的是为什么对于其他模块 #oauth2.xx
表达式没有被评估,因为 FilterChain 是相同的?
请求匹配器(在 antMatchers
、anyRequest
等中指定)由过滤器链按照它们指定的顺序进行处理。因为多个 ResourceServiceConfiguredAdapter
实例只是从 HttpSecurity
的同一个实例进行配置,所以匹配器会针对您的每个请求进行如下处理:
if (uri == "/module1/resource") {
// ...
} else if (uri == "/module1/test") {
// ...
} else if (true) { // anyRequest
// ...
} else if (uri = "/module2/resource") {
// ...
} else if (uri = "/module2/test") {
// ...
}
如您所见,最后两个 if 条件永远不会被命中。
您可以考虑以下两点:
替换anyRequest()
anyRequest
通常非常方便;但是,在这种情况下,您实际上并不意味着 "any request" 因为您试图将范围缩小到某些模块路径。你可以改为:
http
.requestMatchers()
.antMatchers("/module2/**")
.and()
.authorizeRequests()
.antMatchers("/module2/resource").authenticated()
.antMatchers("/module2/test").access( "#oauth2.isClient()")
.antMatchers("/module2/**").access( "#oauth2.hasScope('webclient')" );
这样,模块就不会越界并尝试指定它可能不知道的行为。
说实话,调用 anyRequest
通常是无害的,因为您已经使用 requestMatchers
缩小了过滤器链的范围。但是,因为您正在使用多个适配器组合单个 HttpSecurity
,所以存在这种隐藏的复杂性。
oauth2ResourceServer()
- Spring 安全 5.1+
如果您使用 Spring Security 5.1,那么 WebSecurityConfigurerAdapter
中实际上内置了支持,因此您不需要再使用 ResourceServerConfigurerAdapter
,至少对于 JWT 编码而言在这一点上的令牌。这也很好,因为两个 WebSecurityConfigurerAdapter
被视为单独的过滤器链。
根据您需要的 OAuth 2.0 功能,您也许可以这样做:
@EnableWebSecurity
public class Module1Config extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.requestMatchers()
.antMatchers("/module1/**")
.and()
.authorizeRequests()
.antMatchers("/module1/resource").authenticated()
.anyRequest.hasRole("SCOPE_webclient")
.oauth2ResourceServer()
.jwt();
}
}
这是目前 Spring 安全性的一个活跃开发领域 - 将功能移植到 WebSecurityConfigurerAdapter
;所以一定要联系你的具体用例,以确保它在尚未到位的情况下得到优先考虑。