如何从外部服务生成的传入JWT中提取信息?

时间:2018-11-12 22:08:12

标签: jwt spring-security-oauth2 okta

如何从外部服务生成的传入JWT中提取信息? (Okta

我需要基于JWT中的字段之一执行用户信息的数据库查找。 (我也想要基于JWT范围的方法级安全性。)

秘密似乎在于使用AccessTokenConverterextractAuthentication(),然后使用它来查找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中提取任何内容。我无法验证任何建议的答案。

2 个答案:

答案 0 :(得分:0)

您正在使用Spring Boot吗?

Spring Security 5.1支持JWT访问令牌。例如,您可以仅提供一个新的JwtDecoderhttps://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模型