У меня есть Angular2 SPI, разговаривающий со слоем API Spring Boot, защищенный с помощью OAuth-неявного потока предоставления и использующий Auth0 в качестве моего IDP.
Уровень API - это Spring Boot 2.1.0.M2, а такжеиспользование Spring Security 5.1.0.RC1 и Spring Security OAuth2 2.0.5.RELEASE
На стороне API очень мало настроек, кроме ResourceServer:
@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
(I 'мы также защитили /secure-endpoint
с помощью .access("#oauth2.hasScope('examplescope')")
и убедились, что это разрешает только аутентифицированные запросы, содержащие examplescope
through).Внешний интерфейс отправляет токен Bearer, как и ожидалось.
Проблема в том, что я добавил некоторые пользовательские данные в токен доступа, которые мне нужно использовать на уровне API.Я пытаюсь извлечь пользовательские данные в моем secure-endpoint
контроллере, используя SecurityContextHolder.getContext().getAuthentication()
, но, несмотря на наличие контекста безопасности, объект аутентификации внутри него является нулевым.
Пошаговый код, который я вижучто объект Authentication существует в контексте безопасности (и содержит ожидаемые мной дополнительные данные), пока FilterChainProxy обрабатывает цепочку дополнительных фильтров.Он исчезает при обработке исходной цепочки (т. Е. Когда * вызывается originalChain.doFilter(request, response)
в FilterChainProxy.doFilter), и отсутствует в любой последующей точке.Идентификатор объекта SecurityContextImpl не изменяется, поэтому он выглядит как один и тот же контекст, за исключением объекта аутентификации.
Я не могу понять, почему, где и как очищается объект аутентификации, или как заставить его закрепиться, чтобы он был доступен для контроллера.Любая помощь или указание по этому вопросу будут с благодарностью приняты.
Соответствующие выходные данные журнала (и, как уже упоминалось, объект аутентификации доступен и заполняется, как ожидается, до 3-го сообщения журнала снизу):
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