如何在Spring Boot应用程序中添加自定义OpenId过滤器?

时间:2019-01-18 09:57:02

标签: spring-boot spring-security-oauth2 openid-connect

我正在尝试实现OpenId Connect身份验证的后端。这是一个无状态的API,因此我添加了一个处理Bearer令牌的过滤器。

我已经创建了处理身份验证的OpenIdConnect过滤器,并将其添加到了WebSecurityConfigurerAdapter中。

public class OpenIdConnectFilter extends 
   AbstractAuthenticationProcessingFilter {
 @Value("${auth0.clientId}")
 private String clientId;

 @Value("${auth0.issuer}")
 private String issuer;

 @Value("${auth0.keyUrl}")
 private String jwkUrl;

private TokenExtractor tokenExtractor = new BearerTokenExtractor();

public OpenIdConnectFilter() {
    super("/connect/**");
    setAuthenticationManager(new NoopAuthenticationManager());
}

@Bean
public FilterRegistrationBean registration(OpenIdConnectFilter filter) {
  FilterRegistrationBean registration = new FilterRegistrationBean(filter);
  registration.setEnabled(false);
  return registration;
}

@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {


    try {
      Authentication authentication = tokenExtractor.extract(request);

      String accessToken = (String) authentication.getPrincipal();
        String kid = JwtHelper.headers(accessToken)
            .get("kid");
        final Jwt tokenDecoded = JwtHelper.decodeAndVerify(accessToken, verifier(kid));
        final Map<String, Object> authInfo = new ObjectMapper().readValue(tokenDecoded.getClaims(), Map.class);
        verifyClaims(authInfo);
        Set<String> scopes = new HashSet<String>(Arrays.asList(((String) authInfo.get("scope")).split(" ")));
        int expires = (Integer) authInfo.get("exp");
        OpenIdToken openIdToken = new OpenIdToken(accessToken, scopes, Long.valueOf(expires), authInfo);
        final OpenIdUserDetails user = new OpenIdUserDetails((String) authInfo.get("sub"), "Test", openIdToken);

        return new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());
    } catch (final Exception e) {
        throw new BadCredentialsException("Could not obtain user details from token", e);
    }

}

public void verifyClaims(Map claims) {
    int exp = (int) claims.get("exp");
    Date expireDate = new Date(exp * 1000L);
    Date now = new Date();
    if (expireDate.before(now) || !claims.get("iss").equals(issuer) || !claims.get("azp").equals(clientId)) {
        throw new RuntimeException("Invalid claims");
    }
}


private RsaVerifier verifier(String kid) throws Exception {
    JwkProvider provider = new UrlJwkProvider(new URL(jwkUrl));
    Jwk jwk = provider.get(kid);
    return new RsaVerifier((RSAPublicKey) jwk.getPublicKey());
}

这是安全配置:

@Configuration
@EnableWebSecurity
public class OpenIdConnectWebServerConfig extends 
WebSecurityConfigurerAdapter {

@Bean
public OpenIdConnectFilter myFilter() {
   final OpenIdConnectFilter filter = new OpenIdConnectFilter();
   return filter;
 }

@Override
protected void configure(HttpSecurity http) throws Exception {
  http.cors();
  http.antMatcher("/connect/**").authorizeRequests() 
  .antMatchers(HttpMethod.GET, "/connect/public").permitAll()
  .antMatchers(HttpMethod.GET, "/connect/private").authenticated()
  .antMatchers(HttpMethod.GET, "/connect/private- 
       messages").hasAuthority("read:messages")
  .antMatchers(HttpMethod.GET, "/connect/private- 
      roles").hasAuthority("read:roles")
  .and()
    .addFilterBefore(myFilter(), 
       UsernamePasswordAuthenticationFilter.class);
}

其他端点如下:

  @RequestMapping(value = "/connect/public", method = RequestMethod.GET, 
     produces = "application/json")
  @ResponseBody
   public String publicEndpoint() throws JSONException {
    return new JSONObject()
          .put("message", "All good. You DO NOT need to be authenticated to 
         call /api/public.")
          .toString();
  }

   @RequestMapping(value = "/connect/private", method = RequestMethod.GET, 
      produces = "application/json")
     @ResponseBody
     public String privateEndpoint() throws JSONException {
      return new JSONObject()
          .put("message", "All good. You can see this because you are 
     Authenticated.")
          .toString();

}

如果我完全删除用于配置的过滤器以及@Bean定义,则配置将按预期工作:/ connect / public可访问,而/ connect / private被禁止。

如果我保留@Bean定义并将其添加到过滤器链中,则对于/ connect / public和/ connect / private上的请求,响应均返回Not Found状态:

"timestamp": "18.01.2019 09:46:11",
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/

调试时,我注意到过滤器正在处理令牌并返回Authentication的实现。

  1. 过滤器是否正确添加到过滤器链中并位于正确的位置?
  2. 为什么应该在/ connect / public路径上同时调用该过滤器,所以该过滤器应该是公共的。它是否适用于所有与super(“ / connect / **”)调用匹配的路径?

  3. 为什么在/ connect / private发出请求时,为什么路径返回为“ /”

似乎过滤器有问题,导致每次应用过滤器时,响应变得混乱。

0 个答案:

没有答案