web-dev-qa-db-fra.com

Autoriser la méthode HTTP OPTIONS pour la demande oauth/token

J'essaie d'activer l'extraction de jetons oauth2 pour mon application angulaire. Ma configuration fonctionne correctement (l'authentification fonctionne correctement pour toutes les demandes, l'extraction de jetons fonctionne également), mais il y a un problème.

Les demandes CORS nécessitent qu’avant d’obtenir GET, une demande OPTIONS soit envoyée au serveur. Pour aggraver les choses, cette demande ne contient aucun en-tête d’authentification .. Je voudrais que cette demande revienne toujours avec un statut 200 sans aucune authentification effectuée sur le serveur. C'est possible? Peut-être que je manque quelque chose

ma configuration de sécurité de printemps:

@Configuration
@EnableWebSecurity
@EnableAuthorizationServer
public class SecurityConfig extends WebSecurityConfigurerAdapter {

private static final Logger log = LoggerFactory.getLogger(SecurityConfig.class);

@Inject
private UserService userService;

@Bean
public TokenStore tokenStore() {
    return new InMemoryTokenStore();
}

@Bean
public DefaultTokenServices tokenServices() {
    DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
    defaultTokenServices.setTokenStore(tokenStore());
    return defaultTokenServices;
}

@Bean
public WebResponseExceptionTranslator webResponseExceptionTranslator() {
    return new DefaultWebResponseExceptionTranslator() {

        @Override
        public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
            ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);
            OAuth2Exception body = responseEntity.getBody();
            HttpHeaders headers = new HttpHeaders();
            headers.setAll(responseEntity.getHeaders().toSingleValueMap());
            headers.set("Access-Control-Allow-Origin", "*");
            headers.set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
            headers.set("Access-Control-Max-Age", "3600");
            headers.set("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
            return new ResponseEntity<>(body, headers, responseEntity.getStatusCode());
        }

    };
}

@Bean
public AuthorizationServerConfigurer authorizationServerConfigurer() {
    return new AuthorizationServerConfigurer() {

        @Override
        public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
            OAuth2AuthenticationEntryPoint oAuth2AuthenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
            oAuth2AuthenticationEntryPoint.setExceptionTranslator(webResponseExceptionTranslator());
            security.authenticationEntryPoint(oAuth2AuthenticationEntryPoint);
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients.inMemory()
                    .withClient("secret-client")
                    .secret("secret")
                    .authorizedGrantTypes("password", "authorization_code", "refresh_token", "implicit")
                    .authorities("ROLE_LOGIN")
                    .scopes("read", "write", "trust")
                    .accessTokenValiditySeconds(60 * 60 * 12); // 12 hours
        }

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

@Override
protected AuthenticationManager authenticationManager() throws Exception {
    return new AuthenticationManager() {

        @Override
        public Authentication authenticate(Authentication authentication) throws AuthenticationException {
            log.warn("FIX ME: REMOVE AFTER DEBUG!!!!!!!!!!!!");                
            log.debug("authenticate: " + authentication.getPrincipal() + ":" + authentication.getCredentials());
            final Collection<GrantedAuthority> authorities = new ArrayList<>();
            WomarUser user = userService.findUser(authentication.getPrincipal().toString(), authentication.getCredentials().toString());
            for (UserRole userRole : user.getRoles()) {
                authorities.add(new SimpleGrantedAuthority(userRole.getName()));

            }
            return new UsernamePasswordAuthenticationToken(user.getLogin(), user.getPassword(), authorities);
        }

    };
}

@Bean
public OAuth2AuthenticationManager auth2AuthenticationManager() {
    OAuth2AuthenticationManager oAuth2AuthenticationManager = new OAuth2AuthenticationManager();
    oAuth2AuthenticationManager.setTokenServices(tokenServices());
    return oAuth2AuthenticationManager;
}

@Bean
public OAuth2AuthenticationProcessingFilter auth2AuthenticationProcessingFilter() throws Exception {
    OAuth2AuthenticationProcessingFilter oAuth2AuthenticationProcessingFilter = new OAuth2AuthenticationProcessingFilter();
    oAuth2AuthenticationProcessingFilter.setAuthenticationManager(auth2AuthenticationManager());
    return oAuth2AuthenticationProcessingFilter;
}

@Override
protected void configure(HttpSecurity http) throws Exception {

    OAuth2AuthenticationEntryPoint oAuth2AuthenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
    oAuth2AuthenticationEntryPoint.setRealmName("realmName");
    oAuth2AuthenticationEntryPoint.setTypeName("Basic");
    oAuth2AuthenticationEntryPoint.setExceptionTranslator(webResponseExceptionTranslator());
    http
            .antMatcher("/**").httpBasic()
            .authenticationEntryPoint(oAuth2AuthenticationEntryPoint)
            .and().addFilterBefore(auth2AuthenticationProcessingFilter(), BasicAuthenticationFilter.class)
            .authorizeRequests()
            .antMatchers("/rest/womar/admin/**").hasRole("ADMIN")
            .antMatchers("/rest/womar/**").hasRole("USER");
}

}

demande angulaire:

var config = {
params: {
    grant_type: 'password',
    username: login,
    password: password

},
headers: {
    Authorization: 'Basic ' + Base64.encode('secret-client' + ':' + 'secret')
}
};
$http.get("http://localhost:8080/oauth/token", config)
    .success(function(data, status) {
        $log.log('success');
        $log.log(data);
        $log.log(status);
    })
    .error(function(data, status) {
        $log.log('error');
        $log.log(data);
        $log.log(status);
    });
33
Wojtek Wysocki

@EnableAuthorizationServer ajoute la configuration de sécurité http pour les noeuds finaux tels que /oauth/token, /oauth/token_key etc. à l'ordre 0. Vous devez donc définir une règle de sécurité http pour le noeud final /oauth/token uniquement pour la méthode http OPTIONS qui est d'un ordre supérieur. 

Quelque chose comme ça:

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

J'utilisais la solution proposée par idursun. L'appel OPTION a commencé à fonctionner, mais il restait des problèmes avec Access-Control-Allow-Origin.

Cette implémentation de filtre a définitivement fonctionné pour moi:

Serveur d'autorisations Spring OAuth2 JWT autonome + CORS

4
camposer

Je viens d'ajouter 

@Order (Ordered.HIGHEST_PRECEDENCE)

dans 

la classe publique OAuth2SecurityConfig étend WebSecurityConfigurerAdapter {....}

et config le support du printemps

@Haricot
public CorsConfigurationSource corsConfigurationSource () {
Configuration CorsConfiguration = new CorsConfiguration ();
configuration.setAllowedOrigins (Arrays.asList (""));
configuration.setAllowedMethods (Arrays.asList ("
"));
configuration.setAllowedHeaders (Arrays.asList ("*"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource ();
source.registerCorsConfiguration ("/ **", configuration);
retourner la source;
}

Travaillé pour moi.

2
Guilherme Dalmarco

Ce qui suit fonctionne pour Spring Boot 2. Sinon, il ne prend pas d’autres configurations CORS.

@Configuration
@EnableAuthorizationServer
public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    // this is a Spring ConfigurationProperty use any way to get the CORS values
    @Autowired
    private CorsProperties corsProperties;

    // other things
    //...

    @Override
    public void configure(
            AuthorizationServerEndpointsConfigurer endpoints) {
        endpoints
                .tokenStore(tokenStore())
                .authenticationManager(authenticationManager);
        if (corsProperties.getAllowedOrigins() != null) {
            Map<String, CorsConfiguration> corsConfigMap = new HashMap<>();
            Arrays.asList(corsProperties.getAllowedOrigins().split(",")).stream()
                    .filter(StringUtils::isNotBlank).forEach(s -> {
                CorsConfiguration config = new CorsConfiguration();
                config.setAllowCredentials(true);
                config.addAllowedOrigin(s.trim());
                if (corsProperties.getAllowedMethods() != null) {
                    config.setAllowedMethods(Arrays.asList(corsProperties.getAllowedMethods().split(",")));
                }
                if (corsProperties.getAllowedHeaders() != null) {
                    config.setAllowedHeaders(Arrays.asList(corsProperties.getAllowedHeaders().split(",")));
                }
                // here the /oauth/token is used
                corsConfigMap.put("/oauth/token", config);
            });
            endpoints.getFrameworkEndpointHandlerMapping()
                    .setCorsConfigurations(corsConfigMap);
        }
    }


}

Et en plus de l’allocation déjà mentionnée de la demande OPTIONS:

@Order(-1)
@Configuration
public class MyWebSecurity extends WebSecurityConfigurerAdapter {
   @Override
   protected void configure(HttpSecurity http) throws Exception {
       http
          authorizeRequests()
            .antMatchers("/**/oauth/token").permitAll()
            .and().httpBasic().realmName(securityRealm)
            // would throw a 403 otherwise
            .and().csrf().disable()
            // optional, but with a token a sesion is not needed anymore
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
   }
}
1
k_o_

Même problème avec Spring-Boot 1.4.7.RELEASE

Ma WebSecurityConfigurerAdapter utilisait SecurityProperties.ACCESS_OVERRIDE_ORDER donc, la réponse sélectionnée ne fonctionnait pas.

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class AuthServerSecurityConfig extends WebSecurityConfigurerAdapter 

Ainsi, j'ai ajouté la configuration de filtre suivante avec l'ordre précédent:

  @Bean
  public FilterRegistrationBean corsFilter() {
    FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(corsConfigurationSource()));
    bean.setOrder(SecurityProperties.DEFAULT_FILTER_ORDER);
    return bean;
  }

  @Bean
  public CorsConfigurationSource corsConfigurationSource() {
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    CorsConfiguration config = new CorsConfiguration();
    config.setAllowCredentials(true);
    config.addAllowedOrigin("*");
    config.addAllowedHeader("*");
    config.addAllowedMethod("*");
    source.registerCorsConfiguration("/**", config);
    return source;
  }

et il a fait le travail.

Remarque : un résultat équivalent peut être obtenu avec un bean javax.servlet.Filter avec l'annotation @Order(SecurityProperties.DEFAULT_FILTER_ORDER) comme ci-dessous:

@Component
@Order(SecurityProperties.DEFAULT_FILTER_ORDER)
public class CorsFilter implements Filter {

  @Override
  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    final HttpServletResponse response = (HttpServletResponse) res;

    response.setHeader("Access-Control-Allow-Origin"  , "*"                               );
    response.setHeader("Access-Control-Allow-Methods" , "POST, PUT, GET, OPTIONS, DELETE" );
    response.setHeader("Access-Control-Allow-Headers" , "Authorization, Content-Type"     );
    response.setHeader("Access-Control-Max-Age"       , "3600"                            );

    if("OPTIONS".equalsIgnoreCase(((HttpServletRequest) req).getMethod())) {
      response.setStatus(HttpServletResponse.SC_OK);
    }
    else {
      chain.doFilter(req, res);
    }
  }
  // ...
}
0
Reginaldo Santos