Spring OAuth2 с JWT-не удается преобразовать токен доступа в JSON при разделении серверов Auth и ресурсов
Я хочу использовать Spring Boot для создания сервера аутентификации OAuth2, который может использоваться несколькими серверами ресурсов. Следовательно, мне нужно создать два сервера в качестве независимых приложений. Мои основные рекомендации были в этой статье и вопрос переполнения стека.
в указанной статье оба типа серверов объединены в одно приложение. Мне трудно их разделить.
я могу получить токен, использующий следующее:
curl testjwtclientid:XY7kmzoNzl100@localhost:8080/oauth/token -d grant_type=password -d username=john.doe -d password=jwtpass
этот вызов возвращает:
{"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"}
однако всякий раз, когда я пытаюсь использовать токен для контакта с моим сервером ресурсов, я получаю сообщение об ошибке:
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"}
конфигурация сервера Auth (из статьи):
@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:
@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;
}
}
конфигурация сервера ресурсов:
@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();
}
}
Безопасность Ресурсов Сервера 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;
}
}
точка входа:
@SpringBootApplication
@EnableResourceServer
public class ResourceApp {
public static void main(String[] args) {
SpringApplication.run(ResourceApp.class, args);
}
}
Спасибо за любую помощь.
EDIT:
{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
если я удалю часть носителя (за один из ответов здесь), я получаю следующее:
2 ответов
проблема в том, что на сервере ресурсов вы должны использовать ключ-верификатор вместо ключа подписи.
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(signingKey);
return converter;
}
изменить 01/05: Скачал исходный код, который вы указали в своем сообщении (ссылке) и разделил компонент сервера ресурсов на независимое приложение
проверьте его, если у вас есть все приведенные ниже записи в приложение.свойства
Я подозреваю, что вы могли пропустить некоторые записи конфигурации в приложении.свойства
после этого, когда я ударил сервер ресурсов с токеном JWT, он возвращает правильный ответ
Одно Уточнение: также в этом примере, они используют симметричный ключ для шифрования токенов JWT. Следовательно, даже в сервере ресурсов, в метод accessTokenConverter, setSigningKey следует использовать.setVerifierKey будет использоваться, когда асимметричный ключ используется для шифрования
Я видел, что вы опубликовали еще один вопрос по той же теме. Ваше понимание правильно. Токенов JWT может использоваться несколькими серверами ресурсов.
HTH
на сервере ресурсов и сервере Oauth вы должны использовать как ключ верификатора, так и ключ подписи.
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(signingKey);
converter.setSigningKey(signingKey);
return converter;
}