복붙노트

[SPRING] oauth / token 요청에 대해 OPTIONS HTTP 메소드 허용

SPRING

oauth / token 요청에 대해 OPTIONS HTTP 메소드 허용

내 각도 응용 프로그램에 대한 oauth2 토큰 가져 오기를 사용하려고합니다. 구성이 잘 작동합니다 (모든 요청에 ​​대해 인증이 올바르게 작동하며 토큰 가져 오기가 정상적으로 작동 함)하지만 한 가지 문제가 있습니다.

CORS 요청은 GET 전에 OPTIONS 요청이 서버로 전송되도록 요구합니다. 이를 악화시키기 위해 요청에는 인증 헤더가 포함되어 있지 않습니다. 나는이 요청을 항상 서버에서 수행 된 인증없이 200 상태로 반환하고 싶습니다. 가능한가? 어쩌면 내가 뭔가를 놓친거야.

내 봄 보안 설정 :

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

}

각도 요청 :

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

해결법

  1. ==============================

    1.@EnableAuthorizationServer는 / oauth / token, / oauth / token_key 등의 엔드 포인트에 대한 http 보안 구성을 순서 0에서 추가합니다. 그러면 OPTIONS http 메소드에 대해서만 / oauth / token 엔드 포인트에 대한 http 보안 룰을 정의해야합니다. 더 높은 순서로.

    @EnableAuthorizationServer는 / oauth / token, / oauth / token_key 등의 엔드 포인트에 대한 http 보안 구성을 순서 0에서 추가합니다. 그러면 OPTIONS http 메소드에 대해서만 / oauth / token 엔드 포인트에 대한 http 보안 룰을 정의해야합니다. 더 높은 순서로.

    이 같은:

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

    2.idursun이 제안한 솔루션을 사용하고있었습니다. OPTION 호출은 작동하기 시작했지만 Access-Control-Allow-Origin에는 여전히 문제가있었습니다.

    idursun이 제안한 솔루션을 사용하고있었습니다. OPTION 호출은 작동하기 시작했지만 Access-Control-Allow-Origin에는 여전히 문제가있었습니다.

    이 필터 구현은 확실히 나를 위해 작동했습니다.

    독립 실행 형 스프링 OAuth2 JWT 권한 부여 서버 + CORS

  3. ==============================

    3.방금 추가합니다.

    방금 추가합니다.

    ...에서

    봄의 지원 설정

    나를 위해 일했다.

  4. ==============================

    4.다음은 스프링 부트 2에서 작동합니다. 그렇지 않으면 다른 CORS 구성을 선택하지 않습니다.

    다음은 스프링 부트 2에서 작동합니다. 그렇지 않으면 다른 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);
            }
        }
    
    
    }
    

    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);
       }
    }
    
  5. ==============================

    5.Spring-Boot 1.4.7.RELEASE와 동일한 문제점

    Spring-Boot 1.4.7.RELEASE와 동일한 문제점

    내 WebSecurityConfigurerAdapter가 SecurityProperties.ACCESS_OVERRIDE_ORDER를 사용하여 선택한 대답이 작동하지 않았습니다.

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

    따라서 이전 순서로 다음 필터 구성을 추가했습니다.

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

    그리고 일이 끝났어.

    주 : 아래와 같이 @Order (SecurityProperties.DEFAULT_FILTER_ORDER) 주석이있는 javax.servlet.Filter bean을 사용하면 동일한 결과를 얻을 수 있습니다.

    @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);
        }
      }
      // ...
    }
    
  6. from https://stackoverflow.com/questions/25136532/allow-options-http-method-for-oauth-token-request by cc-by-sa and MIT license