我有一个与Spring Boot API层进行通信的Angular2 SPI,使用OAuth隐式授权流并将Auth0作为我的IDP进行保护。
API层是Spring Boot 2.1.0.M2,还使用Spring Security 5.1.0.RC1和Spring Security OAuth2 2.0.5.RELEASE
除了ResourceServer之外,API方面的配置很少:
@Configuration
@EnableResourceServer
@EnableWebSecurity(debug = true)
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
private final CorsFilter corsFilter;
private final CustomAccessTokenConverter accessTokenConverter;
@Value("${oauth2.resource.id}")
private String resourceId;
@Value("${security.oauth2.resource.jwk.key-set-uri}")
private String jwks;
@Autowired
public ResourceServerConfiguration(CorsFilter corsFilter, CustomAccessTokenConverter accessTokenConverter) {
this.corsFilter = corsFilter;
this.accessTokenConverter = accessTokenConverter;
}
@Override
public void configure(ResourceServerSecurityConfigurer config) throws Exception {
config.resourceId(resourceId);
config.tokenServices(tokenServices()).resourceId(resourceId);
}
@Bean
@Primary
public DefaultTokenServices tokenServices() throws Exception {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenStore(jwkTokenStore());
return defaultTokenServices;
}
@Bean
public TokenStore jwkTokenStore() throws Exception {
return new JwkTokenStore(jwks, accessTokenConverter());
}
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setAccessTokenConverter(accessTokenConverter);
return converter;
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
.anonymous().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/secure-endpoint").authenticated()
.and()
.addFilterBefore(corsFilter, ChannelProcessingFilter.class)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
我的CorsFilter看起来像这样:
@Service
public class CorsFilter extends GenericFilterBean {
private final AllowedOriginsAccessor allowedOriginsAccessor;
@Autowired
public CorsFilter(AllowedOriginsAccessor allowedOriginsAccessor) {
this.allowedOriginsAccessor = allowedOriginsAccessor;
}
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
String allowedOrigins = allowedOriginsAccessor.getAllowedOrigins();
response.setHeader("Access-Control-Allow-Origin", allowedOrigins);
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers",
"x-requested-with, authorization, content-type, pragma, cache-control, expires");
response.setHeader("Access-Control-Allow-Credentials", "true");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
}
我的JwtAccessTokenConverter看起来像这样:
@Component
public class CustomAccessTokenConverter extends DefaultAccessTokenConverter {
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> claims) {
OAuth2Authentication authentication = super.extractAuthentication(claims);
authentication.setDetails(claims);
return authentication;
}
}
这全部有效,因为只允许来自前端的经过身份验证的请求访问/secure-endpoint
(我还用/secure-endpoint
保护了.access("#oauth2.hasScope('examplescope')")
,并验证了这仅允许经过身份验证包含examplescope
的请求。前端正在按预期方式发送Bearer令牌。
问题是我已经向访问令牌添加了一些自定义数据,我需要在API层中使用这些数据。我正在尝试使用secure-endpoint
在SecurityContextHolder.getContext().getAuthentication()
控制器中提取自定义数据,但是尽管有安全上下文,但其中的Authentication对象为null。
逐步执行代码,我可以看到在FilterChainProxy处理附加过滤器链时,安全性上下文中存在Authentication对象(并包含我期望的其他数据)。处理原始链时(即,在FilterChainProxy.doFilter中调用originalChain.doFilter(request, response)
时,它消失了,在随后的任何时候都没有。 SecurityContextImpl对象ID不变,因此看起来像相同的上下文,减去Authentication对象。
我无法弄清为什么,在何处或如何清除Authentication对象,或者如何使其保持粘贴状态,以便控制器可以使用它。如有任何帮助或指示,我们将不胜感激。
相关的日志输出(如前所述,身份验证对象可用并按预期填充,直到底部的第三条日志消息为止):
o.s.security.web.FilterChainProxy : /secure-endpoint?orderByLastModifiedAsc=false at position 1 of 11 in additional filter chain; firing Filter: 'CorsFilter'
o.s.security.web.FilterChainProxy : /secure-endpoint?orderByLastModifiedAsc=false at position 1 of 11 in additional filter chain; firing Filter: 'CorsFilter'
o.s.security.web.FilterChainProxy : /secure-endpoint?orderByLastModifiedAsc=false at position 2 of 11 in additional filter chain; firing Filter: 'WebAsyncManagerIntegrationFilter'
o.s.security.web.FilterChainProxy : /secure-endpoint?orderByLastModifiedAsc=false at position 3 of 11 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
o.s.security.web.FilterChainProxy : /secure-endpoint?orderByLastModifiedAsc=false at position 4 of 11 in additional filter chain; firing Filter: 'HeaderWriterFilter'
o.s.security.web.FilterChainProxy : /secure-endpoint?orderByLastModifiedAsc=false at position 5 of 11 in additional filter chain; firing Filter: 'LogoutFilter'
o.s.s.web.util.matcher.OrRequestMatcher : Trying to match using Ant [pattern='/logout', GET]
o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/secure-endpoint'; against '/logout'
o.s.s.web.util.matcher.OrRequestMatcher : Trying to match using Ant [pattern='/logout', POST]
o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /secure-endpoint' doesn't match 'POST /logout'
o.s.s.web.util.matcher.OrRequestMatcher : Trying to match using Ant [pattern='/logout', PUT]
o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /secure-endpoint' doesn't match 'PUT /logout'
o.s.s.web.util.matcher.OrRequestMatcher : Trying to match using Ant [pattern='/logout', DELETE]
o.s.s.w.u.matcher.AntPathRequestMatcher : Request 'GET /secure-endpoint' doesn't match 'DELETE /logout'
o.s.s.web.util.matcher.OrRequestMatcher : No matches found
o.s.security.web.FilterChainProxy : /secure-endpoint?orderByLastModifiedAsc=false at position 6 of 11 in additional filter chain; firing Filter: 'OAuth2AuthenticationProcessingFilter'
p.a.OAuth2AuthenticationProcessingFilter : Authentication success: org.springframework.security.oauth2.provider.OAuth2Authentication@2fe14c40: Principal: null; Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=0:0:0:0:0:0:0:1, tokenType=BearertokenValue=<TOKEN>; Not granted any authorities
o.s.security.web.FilterChainProxy : /secure-endpoint?orderByLastModifiedAsc=false at position 7 of 11 in additional filter chain; firing Filter: 'RequestCacheAwareFilter'
o.s.security.web.FilterChainProxy : /secure-endpoint?orderByLastModifiedAsc=false at position 8 of 11 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
o.s.security.web.FilterChainProxy : /secure-endpoint?orderByLastModifiedAsc=false at position 9 of 11 in additional filter chain; firing Filter: 'SessionManagementFilter'
s.CompositeSessionAuthenticationStrategy : Delegating to org.springframework.security.web.authentication.session.ChangeSessionIdAuthenticationStrategy@720fbae6
o.s.security.web.FilterChainProxy : /secure-endpoint?orderByLastModifiedAsc=false at position 10 of 11 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
o.s.security.web.FilterChainProxy : /secure-endpoint?orderByLastModifiedAsc=false at position 11 of 11 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
o.s.s.w.u.matcher.AntPathRequestMatcher : Checking match of request : '/secure-endpoint'; against '/secure-endpoint'
o.s.s.w.a.i.FilterSecurityInterceptor : Secure object: FilterInvocation: URL: /secure-endpoint?orderByLastModifiedAsc=false; Attributes: [#oauth2.throwOnError(authenticated)]
o.s.s.w.a.i.FilterSecurityInterceptor : Previously Authenticated: org.springframework.security.oauth2.provider.OAuth2Authentication@2fe14c40: Principal: null; Credentials: [PROTECTED]; Authenticated: true; Details: remoteAddress=0:0:0:0:0:0:0:1, tokenType=BearertokenValue=<TOKEN>; Not granted any authorities
o.s.s.access.vote.AffirmativeBased : Voter: org.springframework.security.web.access.expression.WebExpressionVoter@110090b7, returned: 1
o.s.s.w.a.i.FilterSecurityInterceptor : Authorization successful
o.s.s.w.a.i.FilterSecurityInterceptor : RunAsManager did not change Authentication object
o.s.security.web.FilterChainProxy : /secure-endpoint?orderByLastModifiedAsc=false reached end of additional filter chain; proceeding with original chain
o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match the requestMatcher org.springframework.security.web.header.writers.HstsHeaderWriter$SecureRequestMatcher@2d219451
s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed