java 带有 JWT 的 Spring OAuth2 - 分离身份验证和资源服务器时无法将访问令牌转换为 JSON

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/48084965/
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:57:34  来源:igfitidea点击:

Spring OAuth2 with JWT - Cannot convert access token to JSON When Separating Auth and Resource Servers

javaspringspring-securityoauthjwt

提问by KellyM

I am looking to use Spring Boot to create an OAuth2 Authentication server that could be used by multiple Resource server. Consequently, I am needing to create the two servers as independent apps. My primary references have been this articleand this Stack Overflow question.

我希望使用 Spring Boot 创建一个可以被多个资源服务器使用的 OAuth2 身份验证服务器。因此,我需要将两台服务器创建为独立的应用程序。我的主要参考资料是这篇文章和这个Stack Overflow 问题

The referenced article combines both server types into a single app. I am having difficulty separating them.

引用的文章将两种服务器类型组合到一个应用程序中。我很难将它们分开。

I am able to retrieve a token using the following:

我可以使用以下方法检索令牌:

curl testjwtclientid:XY7kmzoNzl100@localhost:8080/oauth/token -d grant_type=password -d username=john.doe -d password=jwtpass

curl testjwtclientid:XY7kmzoNzl100@localhost:8080/oauth/token -d grant_type=password -d username=john.doe -d password=jwtpass

This call returns:

此调用返回:

{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidGVzdGp3dHJlc291cmNlaWQiXSwidXNlcl9uYW1lIjoiam9obi5kb2UiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNTE1MDUzOTMxLCJhdXRob3JpdGllcyI6WyJTVEFOREFSRF
9VU0VSIl0sImp0aSI6IjBhY2ZlOTA5LTI1Y2MtNGFmZS1iMjk5LTI3MmExNDRiNzFhZCIsImNsaWVudF9pZCI6InRlc3Rqd3RjbGllbnRpZCJ9.ctWt8uNR55HS2PH0OihcVnXuPuw_Z33_zk6wE1qx_5U","token_type":"bearer","expires_in":43199,"scope":"read w
rite","jti":"0acfe909-25cc-4afe-b299-272a144b71ad"}

However, whenever I attempt to use the token to contact my resource server, I receive an error:

但是,每当我尝试使用令牌联系我的资源服务器时,都会收到错误消息:

    curl localhost:8090/springjwt/test -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsidGVzdGp3dHJlc291cmNlaWQiXSwidXNlcl9uYW1lIjoiam9obi5kb2UiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiZXhwIjoxNTE1MDUzOTMxLCJhdXRob3JpdGllcyI6WyJTVEFOREFSRF9VU0VSIl0sImp0aSI6IjBhY2ZlOTA5LTI1Y2MtNGFmZS1iMjk5LTI3MmExNDRiNzFhZCIsImNsaWVudF9pZCI6InRlc3Rqd3RjbGllbnRpZCJ9.ctWt8uNR55HS2PH0OihcVnXuPuw_Z33_zk6wE1qx_5U"

{"error":"invalid_token","error_description":"Cannot convert access token to JSON"}

{"error":"invalid_token","error_description":"Cannot convert access token to JSON"}

Auth Server config (from article):

身份验证服务器配置(来自文章):

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Value("${security.jwt.client-id}")
    private String clientId;

    @Value("${security.jwt.client-secret}")
    private String clientSecret;

    @Value("${security.jwt.grant-type}")
    private String grantType;

    @Value("${security.jwt.scope-read}")
    private String scopeRead;

    @Value("${security.jwt.scope-write}")
    private String scopeWrite = "write";

    @Value("${security.jwt.resource-ids}")
    private String resourceIds;

    @Autowired
    private TokenStore tokenStore;

    @Autowired
    private JwtAccessTokenConverter accessTokenConverter;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    public void configure(ClientDetailsServiceConfigurer configurer) throws Exception {
        configurer
                .inMemory()
                .withClient(clientId)
                .secret(clientSecret)
                .authorizedGrantTypes(grantType)
                .scopes(scopeRead, scopeWrite)
                .resourceIds(resourceIds);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        enhancerChain.setTokenEnhancers(Arrays.asList(accessTokenConverter));
        endpoints.tokenStore(tokenStore)
                .accessTokenConverter(accessTokenConverter)
                .tokenEnhancer(enhancerChain)
                .authenticationManager(authenticationManager);
    }

}

Auth Server security config:

身份验证服务器安全配置:

 @Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${security.signing-key}")
    private String signingKey;

    @Value("${security.encoding-strength}")
    private Integer encodingStrength;

    @Value("${security.security-realm}")
    private String securityRealm;

    @Autowired
    private UserDetailsService userDetailsService;

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(new ShaPasswordEncoder(encodingStrength));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .httpBasic()
                .realmName(securityRealm)
                .and()
                .csrf()
                .disable();

    }

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(signingKey);
        return converter;
    }

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

    @Bean
    @Primary //Making this primary to avoid any accidental duplication with another token service instance of the same name
    public DefaultTokenServices tokenServices() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }
}

Resource Server config:

资源服务器配置:

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Autowired
    private ResourceServerTokenServices tokenServices;

    @Value("${security.jwt.resource-ids}")
    private String resourceIds;

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

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().and().authorizeRequests().antMatchers("/actuator/**", "/api-docs/**").permitAll()
                .antMatchers("/springjwt/**").authenticated();
    }
}

Resource Server Security config:

资源服务器安全配置:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Value("${security.signing-key}")
    private String signingKey;

    @Value("${security.encoding-strength}")
    private Integer clientID;

    @Value("${security.security-realm}")
    private String securityRealm;

    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setSigningKey(signingKey);
        return converter;
    }

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

    @Bean ResourceServerTokenServices tokenService() {
        DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
        defaultTokenServices.setTokenStore(tokenStore());
        defaultTokenServices.setSupportRefreshToken(true);
        return defaultTokenServices;
    }
    @Override
    public AuthenticationManager authenticationManager() throws Exception {
        OAuth2AuthenticationManager authManager = new OAuth2AuthenticationManager();
        authManager.setTokenServices(tokenService());
        return authManager;
    }

}

The entry point:

入口点:

    @SpringBootApplication
@EnableResourceServer
public class ResourceApp {

    public static void main(String[] args) {
        SpringApplication.run(ResourceApp.class, args);
    }
}

Thanks for any help.

谢谢你的帮助。

EDIT:

编辑:

{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}

{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}

If I remove the Bearer portion (per one of the responses here), I receive the following:

如果我删除 Bearer 部分(根据此处的回复之一),我会收到以下信息:

采纳答案by Chids

The issue is, in the Resource Server you should use verifier key instead of signing key.

问题是,在资源服务器中,您应该使用验证密钥而不是签名密钥。

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    converter.setVerifierKey(signingKey);
    return converter;
}

Edit 01/05:Downloaded the source code that you have referred in your post (link) and separated the Resource Server Component into an independent App

编辑 01/05:下载您在您的帖子(链接)中引用的源代码并将资源服务器组件分离为一个独立的应用程序

enter image description here

在此处输入图片说明

Have it cross checked if you have all the below entries in the application.properties

如果您在 application.properties 中拥有以下所有条目,请对其进行交叉检查

enter image description here

在此处输入图片说明

I am suspecting that you might have missed some config entries in the application.properties

我怀疑您可能错过了 application.properties 中的一些配置条目

After this, when I hit the Resource Server with the JWT token, it returns proper response enter image description here

在此之后,当我使用 JWT 令牌访问资源服务器时,它会返回正确的响应 在此处输入图片说明

One Clarification:Also in this example, they are using symmetric Key for encrypting the JWT token. Hence, even in the Resource Server, in the accessTokenConverter method, setSigningKey should be used.setVerifierKey will be used when an asymmetric key is used for encryption

一个澄清:同样在这个例子中,他们使用对称密钥来加密 JWT 令牌。因此,即使在资源服务器中,在 accessTokenConverter 方法中,也应使用 setSigningKey。当使用非对称密钥进行加密时,将使用 setVerifierKey

I saw you had posted another question on the same topic. Your understanding is correct. JWT token can be used by multiple Resource Servers.

我看到你发布了关于同一主题的另一个问题。你的理解是正确的。JWT 令牌可以被多个资源服务器使用。

回答by mogav

First you must verify if the JWT is using asymmetric key or symmetric key. As @Child said, setVerifierKeywill be used when an asymmetric key is used for encryption.

首先,您必须验证 JWT 使用的是非对称密钥还是对称密钥。正如@Child 所说,setVerifierKey将在使用非对称密钥进行加密时使用。

Second, make sure PublicKeyhas been encoded to string in the correct way:

其次,确保PublicKey已以正确的方式编码为字符串:

import java.security.PublicKey;
import java.util.Base64;

PublicKey publicKey = getPublicKey();
String strPublicKey = Base64.getEncoder().encodeToString(publicKey.getEncoded());`

Third, make sure that the string-key passed to the setVerifierKeyis formatted as below (you can test it here):

第三,确保传递给 的字符串键的setVerifierKey格式如下(您可以在这里测试):

String verifierKey = String.format("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", strPublicKey);
converter.setVerifierKey(verifierKey);

If in doubt, I recommend this article.

如果有疑问,我推荐这篇文章

回答by Ashish Sharma

In the Resource Server and Oauth Server both you should use both verifier key and signing key.

在 Resource Server 和 Oauth Server 中,您都应该使用验证密钥和签名密钥。

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
    converter.setVerifierKey(signingKey);
    converter.setSigningKey(signingKey);
    return converter;
}