Spring 安全,启用 Oauth2 时出现 cors 错误
Spring security, cors error when enable Oauth2
我在查询 oauth/token 端点时遇到错误。
我已经为我的资源配置了 cors 启用/也尝试允许所有资源,但没有任何效果。
XMLHttpRequest cannot load http://localhost:8080/oauth/token. Response
to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:1111' is therefore not allowed
access. The response had HTTP status code 401.
vendor.js:1837 ERROR SyntaxError: Unexpected token u in JSON at position 0
at JSON.parse (<anonymous>)
at CatchSubscriber.selector (app.js:7000)
at CatchSubscriber.error (vendor.js:36672)
at MapSubscriber.Subscriber._error (vendor.js:282)
at MapSubscriber.Subscriber.error (vendor.js:256)
at XMLHttpRequest.onError (vendor.js:25571)
at ZoneDelegate.invokeTask (polyfills.js:15307)
at Object.onInvokeTask (vendor.js:4893)
at ZoneDelegate.invokeTask (polyfills.js:15306)
at Zone.runTask (polyfills.js:15074)
defaultErrorLogger @ vendor.js:1837
ErrorHandler.handleError @ vendor.js:1897
next @ vendor.js:5531
schedulerFn @ vendor.js:4604
SafeSubscriber.__tryOrUnsub @ vendor.js:392
SafeSubscriber.next @ vendor.js:339
Subscriber._next @ vendor.js:279
Subscriber.next @ vendor.js:243
Subject.next @ vendor.js:14989
EventEmitter.emit @ vendor.js:4590
NgZone.triggerError @ vendor.js:4962
onHandleError @ vendor.js:4923
ZoneDelegate.handleError @ polyfills.js:15278
Zone.runTask @ polyfills.js:15077
ZoneTask.invoke @ polyfills.js:15369
有了 Postman,一切都完美无缺。
我的cors安全配置:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedHeaders("*")
.allowedMethods("*")
.allowCredentials(true);
}
}
还尝试在允许的来源中添加 http://localhost:1111
Postman 中的代码:
require 'uri'
require 'net/http'
url = URI("http://localhost:8080/oauth/token")
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url)
request["content-type"] = 'application/x-www-form-urlencoded'
request["authorization"] = 'Basic Y2hhdHRpbzpzZWNyZXRzZWNyZXQ='
request["cache-control"] = 'no-cache'
request["postman-token"] = 'daf213da-e231-a074-02dc-795a149a3bb2'
request.body = "grant_type=password&username=yevhen%40gmail.com&password=qwerty"
response = http.request(request)
puts response.read_body
经过大量的努力,我已经重写了方法 configure(WebSecurity web) 的 class WebSecurityConfigurerAdapter 因为授权服务器自行配置,我还没有找到其他解决方案。您还需要 permitAll "/oauth/token" Http.Options 方法。我的方法:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/oauth/token");
}
之后我们需要添加 cors 过滤器来将 Http 状态设置为 OK。我们现在可以拦截 Http.Options 方法。
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@WebFilter("/*")
public class CorsFilter implements Filter {
public CorsFilter() {
}
@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", "x-requested-with, authorization");
response.setHeader("Access-Control-Max-Age", "3600");
if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) req).getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig config) throws ServletException {
}
}
我找到了一种方法来修复 Spring Security 5 和 Spring Security OAuth 2.3.5 上的 401 错误,而无需关闭令牌端点上所有 OPTIONS
请求的安全性。
我意识到您可以通过 AuthorizationServerSecurityConfigurer
向令牌端点添加安全过滤器。我尝试添加 CorsFilter
并且成功了。这种方法的唯一问题是我无法利用 Spring MVC 的 CorsRegistry
。如果有人知道如何使用 CorsRegistry,请告诉我。
我在下面复制了我的解决方案的示例配置:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
@EnableAuthorizationServer
public static class AuthServerConfiguration extends AuthorizationServerConfigurerAdapter {
//... other config
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
//... other config
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.applyPermitDefaultValues();
// Maybe there's a way to use config from AuthorizationServerEndpointsConfigurer endpoints?
source.registerCorsConfiguration("/oauth/token", config);
CorsFilter filter = new CorsFilter(source);
security.addTokenEndpointAuthenticationFilter(filter);
}
}
您可以扩展 AuthorizationServerSecurityConfiguration
并覆盖 void configure(HttpSecurity http)
方法以实现自定义 cors
配置,同时保持其余部分不变。
这是一个例子:
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerSecurityConfiguration;
import org.springframework.web.cors.CorsConfiguration;
public class MyAuthorizationServerSecurityConfiguration extends AuthorizationServerSecurityConfiguration {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.cors(httpSecurityCorsConfigurer -> httpSecurityCorsConfigurer.configurationSource(request -> {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedMethod("POST");
configuration.addAllowedHeader("Content-Type");
return configuration;
}));
}
}
然后,您可以自己导入相关的 classes,而不是使用拉入默认配置 class 的默认注解 @EnableAuthorizationServer
:
@Import({AuthorizationServerEndpointsConfiguration.class, MyAuthorizationServerSecurityConfiguration.class})
无需更改与 OPTIONS
方法 and/or 特定 oauth 路径相关的任何安全配置。
这对我有用
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception
{
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.applyPermitDefaultValues();
// add allow-origin to the headers
config.addAllowedHeader("access-control-allow-origin");
source.registerCorsConfiguration("/oauth/token", config);
CorsFilter filter = new CorsFilter(source);
security.addTokenEndpointAuthenticationFilter(filter);
}
}
我在使用 XMLHttpRequest 发送 POST /注销请求时遇到 CORS 错误(Keycloak 和 Spring Cloud OidcClientInitiatedServerLogoutSuccessHandler),所以我改用 HTML 形式:
<form action="/logout" method="post">
<button>Logout</button>
</form>
它可以正常工作,不需要 CORS 配置。
我在查询 oauth/token 端点时遇到错误。
我已经为我的资源配置了 cors 启用/也尝试允许所有资源,但没有任何效果。
XMLHttpRequest cannot load http://localhost:8080/oauth/token. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:1111' is therefore not allowed access. The response had HTTP status code 401.
vendor.js:1837 ERROR SyntaxError: Unexpected token u in JSON at position 0
at JSON.parse (<anonymous>)
at CatchSubscriber.selector (app.js:7000)
at CatchSubscriber.error (vendor.js:36672)
at MapSubscriber.Subscriber._error (vendor.js:282)
at MapSubscriber.Subscriber.error (vendor.js:256)
at XMLHttpRequest.onError (vendor.js:25571)
at ZoneDelegate.invokeTask (polyfills.js:15307)
at Object.onInvokeTask (vendor.js:4893)
at ZoneDelegate.invokeTask (polyfills.js:15306)
at Zone.runTask (polyfills.js:15074)
defaultErrorLogger @ vendor.js:1837
ErrorHandler.handleError @ vendor.js:1897
next @ vendor.js:5531
schedulerFn @ vendor.js:4604
SafeSubscriber.__tryOrUnsub @ vendor.js:392
SafeSubscriber.next @ vendor.js:339
Subscriber._next @ vendor.js:279
Subscriber.next @ vendor.js:243
Subject.next @ vendor.js:14989
EventEmitter.emit @ vendor.js:4590
NgZone.triggerError @ vendor.js:4962
onHandleError @ vendor.js:4923
ZoneDelegate.handleError @ polyfills.js:15278
Zone.runTask @ polyfills.js:15077
ZoneTask.invoke @ polyfills.js:15369
有了 Postman,一切都完美无缺。
我的cors安全配置:
@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedHeaders("*")
.allowedMethods("*")
.allowCredentials(true);
}
}
还尝试在允许的来源中添加 http://localhost:1111
Postman 中的代码:
require 'uri'
require 'net/http'
url = URI("http://localhost:8080/oauth/token")
http = Net::HTTP.new(url.host, url.port)
request = Net::HTTP::Post.new(url)
request["content-type"] = 'application/x-www-form-urlencoded'
request["authorization"] = 'Basic Y2hhdHRpbzpzZWNyZXRzZWNyZXQ='
request["cache-control"] = 'no-cache'
request["postman-token"] = 'daf213da-e231-a074-02dc-795a149a3bb2'
request.body = "grant_type=password&username=yevhen%40gmail.com&password=qwerty"
response = http.request(request)
puts response.read_body
经过大量的努力,我已经重写了方法 configure(WebSecurity web) 的 class WebSecurityConfigurerAdapter 因为授权服务器自行配置,我还没有找到其他解决方案。您还需要 permitAll "/oauth/token" Http.Options 方法。我的方法:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/oauth/token");
}
之后我们需要添加 cors 过滤器来将 Http 状态设置为 OK。我们现在可以拦截 Http.Options 方法。
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
@WebFilter("/*")
public class CorsFilter implements Filter {
public CorsFilter() {
}
@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", "x-requested-with, authorization");
response.setHeader("Access-Control-Max-Age", "3600");
if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) req).getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
@Override
public void destroy() {
}
@Override
public void init(FilterConfig config) throws ServletException {
}
}
我找到了一种方法来修复 Spring Security 5 和 Spring Security OAuth 2.3.5 上的 401 错误,而无需关闭令牌端点上所有 OPTIONS
请求的安全性。
我意识到您可以通过 AuthorizationServerSecurityConfigurer
向令牌端点添加安全过滤器。我尝试添加 CorsFilter
并且成功了。这种方法的唯一问题是我无法利用 Spring MVC 的 CorsRegistry
。如果有人知道如何使用 CorsRegistry,请告诉我。
我在下面复制了我的解决方案的示例配置:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
@EnableAuthorizationServer
public static class AuthServerConfiguration extends AuthorizationServerConfigurerAdapter {
//... other config
@Override
public void configure(AuthorizationServerSecurityConfigurer security) {
//... other config
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.applyPermitDefaultValues();
// Maybe there's a way to use config from AuthorizationServerEndpointsConfigurer endpoints?
source.registerCorsConfiguration("/oauth/token", config);
CorsFilter filter = new CorsFilter(source);
security.addTokenEndpointAuthenticationFilter(filter);
}
}
您可以扩展 AuthorizationServerSecurityConfiguration
并覆盖 void configure(HttpSecurity http)
方法以实现自定义 cors
配置,同时保持其余部分不变。
这是一个例子:
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerSecurityConfiguration;
import org.springframework.web.cors.CorsConfiguration;
public class MyAuthorizationServerSecurityConfiguration extends AuthorizationServerSecurityConfiguration {
@Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.cors(httpSecurityCorsConfigurer -> httpSecurityCorsConfigurer.configurationSource(request -> {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedMethod("POST");
configuration.addAllowedHeader("Content-Type");
return configuration;
}));
}
}
然后,您可以自己导入相关的 classes,而不是使用拉入默认配置 class 的默认注解 @EnableAuthorizationServer
:
@Import({AuthorizationServerEndpointsConfiguration.class, MyAuthorizationServerSecurityConfiguration.class})
无需更改与 OPTIONS
方法 and/or 特定 oauth 路径相关的任何安全配置。
这对我有用
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
@Override
public void configure(AuthorizationServerSecurityConfigurer security) throws Exception
{
security.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.applyPermitDefaultValues();
// add allow-origin to the headers
config.addAllowedHeader("access-control-allow-origin");
source.registerCorsConfiguration("/oauth/token", config);
CorsFilter filter = new CorsFilter(source);
security.addTokenEndpointAuthenticationFilter(filter);
}
}
我在使用 XMLHttpRequest 发送 POST /注销请求时遇到 CORS 错误(Keycloak 和 Spring Cloud OidcClientInitiatedServerLogoutSuccessHandler),所以我改用 HTML 形式:
<form action="/logout" method="post">
<button>Logout</button>
</form>
它可以正常工作,不需要 CORS 配置。