Servidor de Autorização JWT com OAuth2 Spring Independente + CORS

Então eu tenho o seguinte servidor de autorização condensado deste exemplo 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()"); } } } 

quando eu corro e testo com curl

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

Eu recebo um JWT como respons, mas assim que eu tento acessar o AuthServer do meu Frontend (Angular JS em uma porta diferente) eu recebo o erro CORS. Não se trata de falta de headers, mas porque a solicitação OPTION é rejeitada e está faltando as credenciais.

 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" 

Eu já sabia que tinha que adicionar um CorsFilter e além disso encontrei este post onde eu usei o snippet para o primeiro Resposta para deixar o OPTIONS solicitar access /oauth/token sem credenciais:

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

Depois disso, recebi com o seguinte erro:

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

Então, para simplificar, eu adicionei http.csrf().disable() ao método configure da class MyWebSecurity, que resolve o problema com a requisição OPTION, mas, portanto, a requisição POST não está mais funcionando e eu recebo There is no client authentication. Try adding an appropriate authentication filter. There is no client authentication. Try adding an appropriate authentication filter. (também com curl).

Eu tentei descobrir se eu tenho que de alguma forma conectar a class MyWebSecurity e o AuthServer, mas sem qualquer sorte. O exemplo original (link no início) também injeta o authenticationManager, mas isso não mudou nada para mim.

Encontrei o motivo do meu problema!

Eu só precisava terminar a cadeia de filtros e retornar o resultado imediatamente se uma solicitação OPTIONS for processada pelo 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() { } } 

Depois disso, eu poderia ignorar a solicitação de preflight OPTIONS no meu AuthServer = D

Então o servidor funciona como no recorte acima e você pode ignorar o comentário do bloco com a class MyWebSecurity no começo.

Eu encontrei uma solução usando a solução para a pergunta. Mas tenho outra maneira de descrever a solução:

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

Me deparei com problema semelhante usando seguinte

  • Backend Spring Boot 1.5.8.RELEASE
  • spring OAuth2 Spring OAuth 2.2.0.RELEASE w
  • Aplicativo Vuejs usando axios ajax request library

Com postman tudo funciona! Quando comecei a fazer o pedido do aplicativo Vuejs , recebi os seguintes erros

OPÇÕES http: // localhost: 8080 / springboot / oauth / token 401 ()

e

XMLHttpRequest não pode carregar http: // localhost: 8080 / springboot / oauth / token . A resposta da preflight tem um código de status HTTP inválido 401

Depois de ler um pouco, descobri que posso instruir o meu Spring OAuth a ignorar a solicitação OPTIONS substituindo o configure na minha class de implementação WebSecurityConfigurerAdapter seguinte forma

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

Adição do acima ajudou, mas depois, me deparei com o erro específico CORS

OPÇÕES http: // localhost: 8080 / springboot / oauth / token 403 ()

e

XMLHttpRequest não pode carregar http: // localhost: 8080 / springboot / oauth / token . A resposta à solicitação de comprovação não passa na verificação de controle de access: Nenhum header ‘Access-Control-Allow-Origin’ está presente no recurso solicitado. A origem ‘ http: // localhost: 8000 ‘ não tem, portanto, access permitido. A resposta teve o código de status HTTP 403.

E resolvido o problema acima com a ajuda de um CorsConfig como mostrado abaixo

 @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; } } 

Após a adição da class acima, isso funciona como esperado. Antes de eu ir prod vou pesquisar as consequences de usar

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

bem como as best practices para a configuração Cors acima. Por enquanto, * faz o trabalho, mas definitivamente não é seguro para a produção.

A resposta de Cyril me ajudou partially e então me deparei com a idéia do CorsConfig nesta edição do Github .

bem, você está certo! essa é uma solução, e funcionou também para mim (eu tive o mesmo problema)

Mas deixe-me convencer a usar uma implementação de filtro CORS mais inteligente para o Java: http://software.dzhuvinov.com/cors-filter.html

Esta é uma solução muito completa para aplicativos Java.

Na verdade, você pode ver aqui como seu ponto está resolvido.

Usando Spring Boot 2 aqui.

Eu tive que fazer isso no meu AuthorizationServerConfigurerAdapter

 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { Map 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... } 

Usando Spring Boot como um servidor OAuth com Spring Security e SSL para um backend RESTful. Tempo difícil tentando conectar isso, especialmente com o CORS. Eu acho que a exclusão da configuração do mvc pode perder parte da configuração do auto CORS.

A adição manual de um filtro CORS, conforme descrito nas outras respostas, funcionou para mim. No entanto, eu também tive que adicionar:

 @Override public void configure(AuthorizationServerSecurityConfigurer oauthServer) { // Disable /oauth/token Http Basic Auth oauthServer.allowFormAuthenticationForClients(); } 

Com esse bit adicionado, eu poderia soltar meu filtro CORS personalizado e adicionar a configuração como sugerido pela resposta de Sebastiaan:

 @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) { // Workaround for https://github.com/spring-projects/spring-boot/issues/1801 endpoints.authenticationManager(authentication -> authenticationManager.getOrBuild().authenticate(authentication)); endpoints.getFrameworkEndpointHandlerMapping().setCorsConfigurations(getCorsConfig()); } private Map getCorsConfig() { List methodsList = Stream.of("GET", "POST", "PUT", "DELETE", "OPTIONS").collect(Collectors.toList()); List allowableOriginsList = Stream.of("*").collect(Collectors.toList()); List allowableHeadersList = Stream.of("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") .collect(Collectors.toList()); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.setAllowedOrigins(allowableOriginsList); config.setAllowedMethods(methodsList); config.setAllowedHeaders(allowableHeadersList); Map corsConfigMap = new HashMap<>(); corsConfigMap.put("/oauth/token", config); return corsConfigMap; } 

Então, também não havia mais a necessidade de usar o WebSecurityConfigurerAdapter (tudo pode agora residir no AuthorizationServerConfigurerAdapter ) e esse fragment pode ser removido:

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