web-dev-qa-db-fra.com

Serveur d'autorisation JWT Spring OAuth2 autonome + CORS

J'ai donc le serveur d'autorisation suivant condensé de cet exemple de Dave Syer

@SpringBootApplication
public class AuthserverApplication {

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

    /* added later
    @Configuration
    @Order(Ordered.HIGHEST_PRECEDENCE)
    protected static class MyWebSecurity extends WebSecurityConfigurerAdapter {

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http //.csrf().disable() 
                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll();
       }
    }*/

    @Configuration
    @EnableAuthorizationServer
    protected static class OAuth2AuthorizationConfig extends
                    AuthorizationServerConfigurerAdapter {

            @Autowired
            private AuthenticationManager authenticationManager;

            @Bean
            public JwtAccessTokenConverter jwtAccessTokenConverter() {
                    JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
                    KeyPair keyPair = new KeyStoreKeyFactory(
                                    new ClassPathResource("keystore.jks"), "foobar".toCharArray())
                                    .getKeyPair("test");
                    converter.setKeyPair(keyPair);
                    return converter;
            }

            @Override
            public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
                    clients.inMemory()
                                    .withClient("acme")
                                    //.secret("acmesecret")
                                    .authorizedGrantTypes(//"authorization_code", "refresh_token",
                                                    "password").scopes("openid");
            }

            @Override
            public void configure(AuthorizationServerEndpointsConfigurer endpoints)
                            throws Exception {
                    endpoints.authenticationManager(authenticationManager).accessTokenConverter(
                                    jwtAccessTokenConverter());
            }

            @Override
            public void configure(AuthorizationServerSecurityConfigurer oauthServer)
                            throws Exception {
                    oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess(
                                    "isAuthenticated()");
            }
    }
}

quand je le lance et le teste avec une boucle

curl acme@localhost:8110/oauth/token -d grant_type=password -d client_id=acme -d username=user -d password=password

Je reçois un JWT en tant que respons, mais dès que j'essaie d'accéder à l'AuthServer à partir de mon frontend (JS angulaire sur un port différent), j'obtiens une erreur CORS. Pas parce qu'il manque des en-têtes, mais parce que la demande OPTION est rejetée et qu'il manque les informations d'identification.

Request URL:http://localhost:8110/oauth/token
Request Method:OPTIONS
Status Code:401 Unauthorized
WWW-Authenticate:Bearer realm="oauth", error="unauthorized", error_description="Full authentication is required to access this resource"

Je savais déjà que je devais ajouter un CorsFilter et j'ai également trouvé cet article où j'ai utilisé l'extrait de code pour la première réponse pour permettre aux OPTIONS de demander l'accès /oauth/token Sans informations d'identification:

@Order(-1)
public class MyWebSecurity extends WebSecurityConfigurerAdapter {
   @Override
   protected void configure(HttpSecurity http) throws Exception {
       http
          .authorizeRequests()
          .antMatchers(HttpMethod.OPTIONS, "/oauth/token").permitAll();
   }
}

Après cela, j'ai obtenu avec curl l'erreur suivante:

{"timestamp":1433370068120,"status":403,"error":"Forbidden","message":"Expected CSRF token not found. Has your session expired?","path":"/oauth/token"}

Donc, pour faire simple, je viens d'ajouter http.csrf().disable() à la méthode configure de la classe MyWebSecurity, qui résout le problème avec la demande OPTION, mais donc le POST la demande ne fonctionne plus et j'obtiens There is no client authentication. Try adding an appropriate authentication filter. (également avec curl).

J'ai essayé de savoir si je dois en quelque sorte connecter la classe MyWebSecurity et l'AuthServer, mais sans aucune chance. L'exemple d'origine (lien au début) injecte également le gestionnaire d'authentification, mais cela n'a rien changé pour moi.

24
Michael K.

J'ai trouvé la raison de mon problème!

J'avais juste besoin de mettre fin à la chaîne de filtres et de retourner le résultat immédiatement si une demande OPTIONS est traitée par le CorsFilter!

SimpleCorsFilter.Java

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCorsFilter implements Filter {

    public SimpleCorsFilter() {
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest) req;
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization");

        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            chain.doFilter(req, res);
        }
    }

    @Override
    public void init(FilterConfig filterConfig) {
    }

    @Override
    public void destroy() {
    }
}

Après cela, je pouvais ignorer la demande de contrôle en amont OPTIONS dans mon AuthServer = D

Le serveur fonctionne donc comme dans l'exemple ci-dessus et vous pouvez ignorer le commentaire de bloc avec la classe MyWebSecurity au début.

90
Michael K.

J'ai trouvé une solution en utilisant la solution pour la question. Mais j'ai une autre façon de décrire la solution:

@Configuration
public class WebSecurityGlobalConfig extends WebSecurityConfigurerAdapter {
      ....
      @Override
      public void configure(WebSecurity web) throws Exception {
        web.ignoring()
          .antMatchers(HttpMethod.OPTIONS);
      }
      ...
}
24
Cyril

Je suis tombé sur un problème similaire en utilisant ce qui suit

  • Backend Spring Boot 1.5.8.RELEASE
  • Spring OAuth2 Spring OAuth 2.2.0.RELEASE W
  • Vuejs application utilisant axios bibliothèque de requêtes ajax

Avec postman tout fonctionne! Lorsque j'ai commencé à faire une demande depuis l'application Vuejs, j'ai eu les erreurs suivantes

OPTIONS http: // localhost: 8080/springboot/oauth/token 401 ()

et

XMLHttpRequest ne peut pas se charger http: // localhost: 8080/springboot/oauth/token . La réponse pour le contrôle en amont a un code d'état HTTP non valide 401

Après avoir lu un peu, j'ai découvert que je peux demander à mon Spring OAuth D'ignorer la demande OPTIONS en remplaçant configure dans ma classe d'implémentation WebSecurityConfigurerAdapter comme suit

@Override
public void configure(WebSecurity web) throws Exception {
   web.ignoring().antMatchers(HttpMethod.OPTIONS);
}

L'ajout de ce qui précède a aidé mais ensuite, je suis tombé sur l'erreur spécifique de CORS

OPTIONS http: // localhost: 8080/springboot/oauth/token 403 ()

et

XMLHttpRequest ne peut pas se charger http: // localhost: 8080/springboot/oauth/token . La réponse à la demande de contrôle en amont ne passe pas la vérification du contrôle d'accès: aucun en-tête "Access-Control-Allow-Origin" n'est présent sur la ressource demandée. L'origine ' http: // localhost: 80 ' n'est donc pas autorisée à accéder. La réponse avait le code d'état HTTP 403.

Et résolu le problème ci-dessus à l'aide d'un CorsConfig comme indiqué ci-dessous

@Configuration
public class CorsConfig {
    @Bean
    public FilterRegistrationBean corsFilterRegistrationBean() {
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration config = new CorsConfiguration();
        config.applyPermitDefaultValues();
        config.setAllowCredentials(true);
        config.setAllowedOrigins(Arrays.asList("*"));
        config.setAllowedHeaders(Arrays.asList("*"));
        config.setAllowedMethods(Arrays.asList("*"));
        config.setExposedHeaders(Arrays.asList("content-length"));
        config.setMaxAge(3600L);
        source.registerCorsConfiguration("/**", config);
        FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
        bean.setOrder(0);
        return bean;
    }
}

Après l'ajout de la classe ci-dessus, cela fonctionne comme prévu. Avant de partir prod je vais rechercher consequences sur l'utilisation

web.ignoring().antMatchers(HttpMethod.OPTIONS);

ainsi que best practices pour la configuration Cors ci-dessus. Pour l'instant, * Fait le travail mais n'est certainement pas sécurisé pour la production.

La réponse de Cyril m'a aidé partially puis je suis tombé sur l'idée de CorsConfig dans ce Github problème.

11
Raf

vous avez raison! c'est une solution, et cela a aussi fonctionné pour moi (j'ai eu le même problème)

Mais laissez-moi essayer d'utiliser une implémentation plus intelligente du filtre CORS pour Java: http://software.dzhuvinov.com/cors-filter.html

Il s'agit d'une solution très complète pour les applications Java.

En fait, vous pouvez voir ici comment votre point est résolu.

3
Gervasio Amy

Utilisation de Spring Boot 2 ici.

Je devais le faire dans mon AuthorizationServerConfigurerAdapter

@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) {

    Map<String, CorsConfiguration> corsConfigMap = new HashMap<>();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    //TODO: Make configurable
    config.setAllowedOrigins(Collections.singletonList("*"));
    config.setAllowedMethods(Collections.singletonList("*"));
    config.setAllowedHeaders(Collections.singletonList("*"));
    corsConfigMap.put("/oauth/token", config);
    endpoints.getFrameworkEndpointHandlerMapping()
            .setCorsConfigurations(corsConfigMap);

    //additional settings...
}