Je cherche à utiliser Spring Boot pour créer un serveur d'authentification OAuth2 qui pourrait être utilisé par plusieurs serveurs de ressources. Par conséquent, je dois créer les deux serveurs en tant qu'applications indépendantes. Mes références principales ont été cet article et ceci question de débordement de pile .
L'article référencé combine les deux types de serveurs dans une seule application. J'ai du mal à les séparer.
Je peux récupérer un jeton à l'aide des éléments suivants:
curl testjwtclientid:XY7kmzoNzl100@localhost:8080/oauth/token -d grant_type=password -d username=john.doe -d password=jwtpass
Cet appel renvoie:
{"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"}
Cependant, chaque fois que j'essaie d'utiliser le jeton pour contacter mon serveur de ressources, je reçois une erreur:
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"}
Configuration du serveur d'authentification (tirée de l'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);
}
}
Configuration de sécurité du serveur d'authentification:
@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 du serveur de ressources:
@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();
}
}
Configuration de la sécurité du serveur de ressources:
@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;
}
}
Le point d'entrée:
@SpringBootApplication
@EnableResourceServer
public class ResourceApp {
public static void main(String[] args) {
SpringApplication.run(ResourceApp.class, args);
}
}
Merci pour toute aide.
MODIFIER:
{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}
Si je supprime la partie au porteur (pour l'une des réponses ici ), je reçois ce qui suit:
Le problème est que, dans le serveur de ressources, vous devez utiliser la clé de vérification au lieu de la clé de signature.
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setVerifierKey(signingKey);
return converter;
}
Edit 01/05: Téléchargé le code source que vous avez référé dans votre message ( link ) et séparé le composant Resource Server dans une application indépendante
Faites-le vérifier si vous avez toutes les entrées ci-dessous dans l'application.properties
Je soupçonne que vous avez peut-être manqué certaines entrées de configuration dans l'application.properties
Après cela, lorsque je frappe le serveur de ressources avec le jeton JWT, il renvoie une réponse appropriée
Une précision: Toujours dans cet exemple, ils utilisent une clé symétrique pour crypter le jeton JWT. Par conséquent, même dans le serveur de ressources, dans la méthode accessTokenConverter, setSigningKey doit être utilisé.setVerifierKey sera utilisé lorsqu'une clé asymétrique est utilisée pour le chiffrement
J'ai vu que vous aviez posté une autre question sur le même sujet. Votre compréhension est correcte. Le jeton JWT peut être utilisé par plusieurs serveurs de ressources.
Vous devez d'abord vérifier si le JWT utilise une clé asymétrique ou une clé symétrique. Comme l'a dit @Child, setVerifierKey
sera utilisé lorsqu'une clé asymétrique est utilisée pour le chiffrement.
Deuxièmement, assurez-vous que PublicKey
a été encodé en chaîne de la bonne manière:
import Java.security.PublicKey;
import Java.util.Base64;
PublicKey publicKey = getPublicKey();
String strPublicKey = Base64.getEncoder().encodeToString(publicKey.getEncoded());`
Troisièmement, assurez-vous que la clé de chaîne passée au setVerifierKey
est formatée comme ci-dessous (vous pouvez le tester ici ):
String verifierKey = String.format("-----BEGIN PUBLIC KEY-----\n%s\n-----END PUBLIC KEY-----", strPublicKey);
converter.setVerifierKey(verifierKey);
En cas de doute, je recommande cet article .