Standalone Spring OAuth2 JWT Authorization Server + CORS

Refresh

December 2018

Views

28.6k time

19

So I have the following Authorization Server condensed from this example from 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()");
            }
    }
}

when I run it and test it with curl

curl [email protected]:8110/oauth/token -d grant_type=password -d client_id=acme -d username=user -d password=password

I get a JWT as respons, but as soon as I try to access the AuthServer from my Frontend (Angular JS on a different port) I get CORS error. Not becauce of missing Headers, but because the OPTION request is rejected and is missing the credentials.

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"

I already knew that I have to add a CorsFilter and additionally found this post where I used the the snippet for the first Answer to let the OPTIONS request access /oauth/token without credentials:

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

After that I got with curl the following error:

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

So to make it simple I just added http.csrf().disable() to the configure method of MyWebSecurity class, which solves the Problem with the OPTION request, but therefore the POST request isn't working anymore and I get There is no client authentication. Try adding an appropriate authentication filter. (also with curl).

I tried to find out if I have to somehow connect MyWebSecurity class and the AuthServer, but without any luck. The original example (link in the beginning) injects as well the authenticationManager, but this changed nothing for me.

6 answers

6

Я наткнулся на аналогичный вопрос, используя следующие

  • Backend Spring Boot 1.5.8.RELEASE
  • Весна OAuth2 Spring OAuth 2.2.0.RELEASEж
  • Vuejsприложение с использованием axiosAjax библиотеки запроса

С postmanвсе работает! Когда я начал делать запрос с Vuejsприложением , то я получил следующие ошибки

ВАРИАНТЫ HTTP: // локальный: 8080 / springboot / OAuth / маркер 401 ()

а также

XMLHttpRequest не может загрузить HTTP: // локальный: 8080 / springboot / OAuth / маркер . Ответ на предполетной имеет недопустимый HTTP код статуса 401

После прочтения немного, я обнаружил, что я могу поручить My Spring OAuthигнорировать OPTIONSзапрос, перекрывая configureв моем WebSecurityConfigurerAdapterклассе реализации , как следовать

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

Добавление выше помогло , но потом я наткнулся на CORSконкретной ошибке

ВАРИАНТЫ HTTP: // локальный: 8080 / springboot / OAuth / маркер 403 ()

а также

XMLHttpRequest не может загрузить HTTP: // локальный: 8080 / springboot / OAuth / маркер . Ответ на предполетный запрос не проходит проверку контроля доступа: Нет «Access-Control-Разрешить-Origin» заголовок присутствует на запрошенный ресурсе. Происхождение « HTTP: // локальный: 8000 », следовательно , не имеет права доступа. Ответ был код состояния HTTP 403.

И решить вышеуказанную проблему с помощью , CorsConfigкак показано ниже

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

После добавления указанного класса, он работает , как ожидалось. Прежде чем я продолжу prodя буду исследовать consequencesиспользование

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

а также best practicesдля выше Corsконфигурации. На данный момент *делает работу , но, безусловно , не является безопасным для производства.

Ответ Кирилла помог мне , partiallyа потом я наткнулся на CorsConfigидею в этом Github вопросе.

Raf
2

ну, вы правы! это решение, и он также работал для меня (у меня был такой же вопрос)

Но позвольте мне sussgest использовать умнее CORS реализации фильтра для Java: http://software.dzhuvinov.com/cors-filter.html

Это очень полное решение для Java-приложений.

На самом деле, вы можете увидеть здесь , как решается ваша точка.

0

Использование Spring Ботинок как OAuth-сервер с Spring Security и SSL для RESTful бэкэндом. Tough время пытается телеграфировать это вместе, особенно с CORS. Я думаю, что исключение Mvc конфигурации может потеряло часть конфигурации авто CORS.

Ручное добавление фильтра CORS, как описано в других ответах работали для меня. Тем не менее, я должен был добавить:

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

С этим немного добавил, я мог бросить свой заказные CORS фильтр и добавить конфигурацию, как было предложено ответить 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<String, CorsConfiguration> getCorsConfig() {

    List<String> methodsList = Stream.of("GET", "POST", "PUT", "DELETE", "OPTIONS").collect(Collectors.toList());
    List<String> allowableOriginsList = Stream.of("*").collect(Collectors.toList());
    List<String> 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<String, CorsConfiguration> corsConfigMap = new HashMap<>();
    corsConfigMap.put("/oauth/token", config);

    return corsConfigMap;
}

Тогда не было и больше нет необходимости в использовании WebSecurityConfigurerAdapter(все теперь могут жить в AuthorizationServerConfigurerAdapter) , и этот кусок может быть удален:

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

Использование Spring Ботинок 2 здесь.

Я должен был сделать это в моем 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...
}
14

Я нашел решение, используя решение вопроса. Но у меня есть еще один способ описать решение:

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

Нашел причину моей проблемы!

Мне просто нужно, чтобы закончить FilterChain и возвращать результат немедленно, если запрос OPTIONS обрабатывается 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() {
    }
}

После этого я мог игнорировать ОПЦИИ предполетной запрос в моем AuthServer = D

Таким образом, сервер работает как в приведенном выше пропущено, и вы можете игнорировать блок комментарий с классом MyWebSecurity в начале.