web-dev-qa-db-fra.com

Spring OAuth2 avec JWT - Impossible de convertir le jeton d'accès en JSON lors de la séparation des serveurs d'authentification et de ressources

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:

9
KellyM

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

enter image description here

Faites-le vérifier si vous avez toutes les entrées ci-dessous dans l'application.properties

enter image description here

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 enter image description here

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.

6
Chids

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 .

3
mogav