java 为什么我的令牌被拒绝?什么是资源 ID?“无效令牌不包含资源 ID (oauth2-resource)”

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/47996344/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 09:54:45  来源:igfitidea点击:

Why is my token being rejected? What is a resource ID? "Invalid token does not contain resource id (oauth2-resource)"

javaspringoauth-2.0jwtspring-security-oauth2

提问by Rico Kahler

I'm trying to configure OAuth2 for a spring project. I'm using a shared UAA (oauth implementation from cloud foundry) instance my work place provides (so I'm not trying to create an authorization server and the authorization server is separate from the resource server). The frontend is a single-page-application and it gets token directly from the authorization server using the implicit grant. I have the SPA setup where it adds the Authorization: Bearer <TOKEN>header on each web API call to microservices.

我正在尝试为 spring 项目配置 OAuth2。我正在使用我的工作场所提供的共享 UAA(来自 Cloud Foundry 的 oauth 实现)实例(因此我没有尝试创建授权服务器并且授权服务器与资源服务器分开)。前端是一个单页应用程序,它使用隐式授权直接从授权服务器获取令牌。我有 SPA 设置,它Authorization: Bearer <TOKEN>在每个 Web API 调用上添加标题到微服务。

My issue is now with the microservices.

我现在的问题是微服务。

I'm trying to use this shared authorization server to authenticate the microservices. I might have a misunderstanding here, buy my current understanding is that these microservices play the role of the resource server because they host the endpoints the SPA uses to get data.

我正在尝试使用此共享授权服务器来验证微服务。我在这里可能有一个误解,我目前的理解是这些微服务扮演资源服务器的角色,因为它们托管着 SPA 用来获取数据的端点。

So I tried to configure a microservice like so:

所以我尝试像这样配置一个微服务:

@Configuration
@EnableResourceServer
public class OAuth2ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
        .authorizeRequests()
        .antMatchers("/api/**").authenticated();
    }

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(accessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setVerifierKey("-----BEGIN PUBLIC KEY-----<key omitted>-----END PUBLIC KEY-----");
        return converter;
    }

    @Bean
    @Primary
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        return defaultTokenServices;
    }


    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
         resources.tokenServices(tokenServices());
    }
}

Now whenever I hit a /api/**with the Authorization: Bearer <TOKEN>, I get a 403with this error:

现在,每当我用 打 a/api/**Authorization: Bearer <TOKEN>,我都会收到403此错误:

{
    "error": "access_denied",
    "error_description": "Invalid token does not contain resource id (oauth2-resource)"
}


So here are my questions:

所以这里是我的问题:

  • How do I configure these microservices to validate the token and insert a Principalin controller methods?I currently have it setup where the SPA has and sends the token and I also have the public key used to verify the signature of the token. I have also used jwt.ioto test the token and it says "Signature Verified".
  • What is a resource id? Why do I need it and why does it cause the error above? Is that a Spring only thing??
  • 如何配置这些微服务以验证令牌并Principal在控制器方法中插入 a ?我目前在 SPA 拥有和发送令牌的地方设置了它,我还有用于验证令牌签名的公钥。我还使用jwt.io来测试令牌,它显示“签名验证”。
  • 什么是资源 ID?为什么我需要它,为什么会导致上述错误?那是春天唯一的东西吗??

Thanks!

谢谢!

回答by tsolakp

Spring OAuth expects "aud" claimin JWT token. That claim's value should match to the resourceIdvalue you specify your Spring app (if not specified it defaults to "oauth2-resource").

Spring OAuth 需要JWT 令牌中的“aud”声明。该声明的值应与resourceId您指定的 Spring 应用程序的值匹配(如果未指定,则默认为“oauth2-resource”)。

To fix your issue you need to:

要解决您的问题,您需要:

1) Log into your shared UAA and make sure it does include "aud" claim.

1) 登录您的共享 UAA 并确保其中包含“aud”声明。

2) Change the value of that "aud" claim to be "oauth2-resource" or preferably in your Spring app update resourceIdto that claim's value like this:

2) 将该“aud”声明的值更改为“oauth2-resource”,或者最好在您的 Spring 应用程序更新中resourceId更改为该声明的值,如下所示:

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
         resources.tokenServices(tokenServices());
         resources.resourceId(value from the aud claim you got from UAA server);
    }

回答by onlyme

I add a similar issue. In my case, I used jdbc authentification and my authorization server and resource server was two separate API.

我添加了一个类似的问题。就我而言,我使用了 jdbc 身份验证,而我的授权服务器和资源服务器是两个独立的 API。

  • Authentorization server

       @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
    oauthServer.tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .passwordEncoder(oauthClientPasswordEncoder);
    

    }

    /**
    * Define the client details service. The client may be define either as in memory or in database.
     * Here client with be fetch from the specify database
      */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
       clients.jdbc(dataSource);
    }
    
    /**
    * Define the authorization by providing authentificationManager
    * And the token enhancement
     */
     @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
    endpoints.tokenStore(tokenStore())
                .tokenEnhancer(getTokenEnhancer())
                .authenticationManager(authenticationManager).userDetailsService(userDetailsService);
     }
    
  • Resource server

    public class OAuth2ResourceServerConfig extends 
        ResourceServerConfigurerAdapter {
    
        private TokenExtractor tokenExtractor = new BearerTokenExtractor();
    
        @Autowired
        private DataSource dataSource;
    
        @Bean
        public TokenStore tokenStore() {
          return new JdbcTokenStore(dataSource);
        }
    
         @Override
         public void configure(HttpSecurity http) throws Exception {
               http.addFilterAfter(new OncePerRequestFilter() {
               @Override
               protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                FilterChain filterChain) throws ServletException, IOException {
            // We don't want to allow access to a resource with no token so clear
            // the security context in case it is actually an OAuth2Authentication
            if (tokenExtractor.extract(request) == null) {
                SecurityContextHolder.clearContext();
            }
            filterChain.doFilter(request, response);
        }
    }, AbstractPreAuthenticatedProcessingFilter.class);
    http.csrf().disable();
    http.authorizeRequests().anyRequest().authenticated();
     }
    
      @Bean
      public AccessTokenConverter accessTokenConverter() {
         return new DefaultAccessTokenConverter();
      }
    
      @Bean
      public RemoteTokenServices remoteTokenServices(final @Value("${auth.server.url}") String checkTokenUrl,
        final @Value("${auth.resource.server.clientId}") String clientId,
        final @Value("${auth.resource.server.clientsecret}") String clientSecret) {
    
           final RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
           remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl);
           remoteTokenServices.setClientId(clientId);
           remoteTokenServices.setClientSecret(clientSecret);
          remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
    return remoteTokenServices;
       }
    
  • 授权服务器

       @Override
    public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
    oauthServer.tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()")
                .passwordEncoder(oauthClientPasswordEncoder);
    

    }

    /**
    * Define the client details service. The client may be define either as in memory or in database.
     * Here client with be fetch from the specify database
      */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
       clients.jdbc(dataSource);
    }
    
    /**
    * Define the authorization by providing authentificationManager
    * And the token enhancement
     */
     @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
    endpoints.tokenStore(tokenStore())
                .tokenEnhancer(getTokenEnhancer())
                .authenticationManager(authenticationManager).userDetailsService(userDetailsService);
     }
    
  • 资源服务器

    public class OAuth2ResourceServerConfig extends 
        ResourceServerConfigurerAdapter {
    
        private TokenExtractor tokenExtractor = new BearerTokenExtractor();
    
        @Autowired
        private DataSource dataSource;
    
        @Bean
        public TokenStore tokenStore() {
          return new JdbcTokenStore(dataSource);
        }
    
         @Override
         public void configure(HttpSecurity http) throws Exception {
               http.addFilterAfter(new OncePerRequestFilter() {
               @Override
               protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                FilterChain filterChain) throws ServletException, IOException {
            // We don't want to allow access to a resource with no token so clear
            // the security context in case it is actually an OAuth2Authentication
            if (tokenExtractor.extract(request) == null) {
                SecurityContextHolder.clearContext();
            }
            filterChain.doFilter(request, response);
        }
    }, AbstractPreAuthenticatedProcessingFilter.class);
    http.csrf().disable();
    http.authorizeRequests().anyRequest().authenticated();
     }
    
      @Bean
      public AccessTokenConverter accessTokenConverter() {
         return new DefaultAccessTokenConverter();
      }
    
      @Bean
      public RemoteTokenServices remoteTokenServices(final @Value("${auth.server.url}") String checkTokenUrl,
        final @Value("${auth.resource.server.clientId}") String clientId,
        final @Value("${auth.resource.server.clientsecret}") String clientSecret) {
    
           final RemoteTokenServices remoteTokenServices = new RemoteTokenServices();
           remoteTokenServices.setCheckTokenEndpointUrl(checkTokenUrl);
           remoteTokenServices.setClientId(clientId);
           remoteTokenServices.setClientSecret(clientSecret);
          remoteTokenServices.setAccessTokenConverter(accessTokenConverter());
    return remoteTokenServices;
       }
    

With this configuration, I was getting

有了这个配置,我得到了

    {
       "error": "access_denied",
       "error_description": "Invalid token does not contain resource id 
       (xxxxx)"
     }

To solve this, I had to add

为了解决这个问题,我不得不添加

    private String resourceIds= "xxxxx". !! maked sure that this resourceids is store in oauth_client_details for the clientid I used to get the token
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
          resources.resourceId(resourceIds).tokenStore(tokenStore());
      }