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
Spring OAuth2 with JWT - Cannot convert access token to JSON When Separating Auth and Resource Servers
提问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:下载您在您的帖子(链接)中引用的源代码并将资源服务器组件分离为一个独立的应用程序
Have it cross checked if you have all the below entries in the application.properties
如果您在 application.properties 中拥有以下所有条目,请对其进行交叉检查
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
在此之后,当我使用 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, setVerifierKey
will be used when an asymmetric key is used for encryption.
首先,您必须验证 JWT 使用的是非对称密钥还是对称密钥。正如@Child 所说,setVerifierKey
将在使用非对称密钥进行加密时使用。
Second, make sure PublicKey
has 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 setVerifierKey
is 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;
}