如何从外部服务生成的传入JWT中提取信息? (Okta)
我需要基于JWT中的字段之一执行用户信息的数据库查找。 (我也想要基于JWT范围的方法级安全性。)
秘密似乎在于使用AccessTokenConverter
到extractAuthentication()
,然后使用它来查找UserDetails
。我被困住了,因为我能找到的每个示例都包括设置一个我没有的授权服务器,并且我无法确定JwtAccessTokenConverter
是否可以在资源服务器上工作。
我的资源服务器运行并处理请求,但是自定义JwtAccessTokenConverter
在传入请求期间从未被调用;
我所有的请求都带有anonymousUser主体。
我正在使用Spring 5.1.1。
我的资源服务器配置
@Configuration
@EnableResourceServer
public class OauthResourceConfig extends ResourceServerConfigurerAdapter {
@Value("${oauth2.audience}")
String audience;
@Value("${oauth2.baseUrl}/v1/keys")
String jwksUrl;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.httpBasic().disable()
.authorizeRequests()
.anyRequest().authenticated()
.antMatchers("/api/**").permitAll();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources
.tokenServices(tokenServices())
.resourceId(audience);
}
@Primary
@Bean
public DefaultTokenServices tokenServices() throws Exception {
DefaultTokenServices tokenServices = new DefaultTokenServices();
tokenServices.setTokenStore(tokenStore());
return tokenServices;
}
@Bean
public TokenStore tokenStore() {
return new JwkTokenStore(jwksUrl, accessTokenConverter());
}
@Bean
public AccessTokenConverter accessTokenConverter() {
return new CustomJwtAccessTokenConverter();
}
}
我的自定义访问令牌转换器
public class CustomJwtAccessTokenConverter extends JwtAccessTokenConverter {
@Override
public OAuth2Authentication extractAuthentication(Map<String, ?> map) {
OAuth2Authentication authentication = super.extractAuthentication(map);
Authentication userAuthentication = authentication.getUserAuthentication();
if (userAuthentication != null) {
LinkedHashMap userDetails = (LinkedHashMap) map.get("userDetails");
if (userDetails != null) {
... Do the database lookup here ...
Collection<? extends GrantedAuthority> authorities = userAuthentication.getAuthorities();
userAuthentication = new UsernamePasswordAuthenticationToken(extendedPrincipal,
userAuthentication.getCredentials(), authorities);
}
}
return new OAuth2Authentication(authentication.getOAuth2Request(), userAuthentication);
}
}
还有我的资源
@GET
@PreAuthorize("#oauth2.hasScope('openid')")
public Response getRecallsByVin(@QueryParam("vin") String vin,
@QueryParam("page") Integer pageNumber,
@QueryParam("pageSize") Integer pageSize) {
List<VehicleNhtsaCampaign> nhtsaCampaignList;
List<OpenRecallsDto> nhtsaCampaignDtoList;
SecurityContext securityContext = SecurityContextHolder.getContext();
Object principal = securityContext.getAuthentication().getPrincipal();
... More irrelevant code follows ...
首先,@PreAuthorize
注释没有做任何事情。如果我将其更改为@PreAuthorize("#oauth2.hasScope('FooBar')")
,它仍然允许请求进入。
第二,我需要从JWT中获取其他信息,以便可以在数据库中进行用户查找。我认为,通过在资源服务器配置中添加accessTokenConverter()
,JWT将被解析并放入securityContext.getAuthentication()
响应中。相反,我得到的只是“ anonymousUser”。
更新:后来我发现我需要的数据来自一个自定义标头,因此我不需要从JWT中提取任何内容。我无法验证任何建议的答案。
答案 0 :(得分:0)
您正在使用Spring Boot吗?
Spring Security 5.1支持JWT访问令牌。例如,您可以仅提供一个新的JwtDecoder
:
https://github.com/okta/okta-spring-boot/blob/spring-boot-2.1/oauth2/src/main/java/com/okta/spring/boot/oauth/OktaOAuth2ResourceServerAutoConfig.java#L62-L84
答案 1 :(得分:0)
您可以创建一个过滤器来验证令牌并将令牌设置为SecurityContextHolder
。这是我在项目中使用jsonwebtoken
依赖项所做的:
public class JWTFilter extends GenericFilterBean {
private String secretKey = 'yoursecret';
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String jwt = resolveToken(httpServletRequest);
if (validateToken(jwt)) {
Authentication authentication = getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(servletRequest, servletResponse);
}
private String resolveToken(HttpServletRequest request){
String bearerToken = request.getHeader("Authorization");
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7, bearerToken.length());
}
return null;
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
} catch (MalformedJwtException e) {
} catch (ExpiredJwtException e) {
} catch (UnsupportedJwtException e) {
} catch (IllegalArgumentException e) {
}
return false;
}
}
然后您可以从SecurityContextHolder
访问令牌。
为了更简洁地访问令牌字段,我从http://www.jsonschema2pojo.org/创建了令牌的POJO模型