In my spring boot application i used springdoc openapi.
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.11</version>
</dependency>
All the api in my app is secure by token, so i added OAuth2 authentication to my config like below.
#Bean
public OpenAPI openAPI() {
final String authUrl = "https://test/auth/realms/test/protocol/openid-connect";
return new OpenAPI()
.components(new Components()
.addSecuritySchemes("spring_oauth", new SecurityScheme()
.type(SecurityScheme.Type.OAUTH2)
.description("Oauth2 flow")
.flows(new OAuthFlows()
.clientCredentials(new OAuthFlow()
.authorizationUrl(authUrl + "/auth")
.refreshUrl(authUrl + "/token")
.tokenUrl(authUrl + "/token")
.scopes(new Scopes())
))))
.security(Collections.singletonList(new SecurityRequirement().addList("spring_oauth")))
.info(new Info()
.title("Test Service API")
.description("Documentation Test Service API v1.0")
.version("v1.0"));
}
I have the CorsFilter config
#Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Collections.singletonList("*"));
config.addAllowedHeader("*");
config.addAllowedMethod("GET");
config.addAllowedMethod("POST");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("PATCH");
if (CollectionUtils.isNotEmpty(config.getAllowedOrigins())) {
source.registerCorsConfiguration("/api/**", config);
source.registerCorsConfiguration("/health", config);
source.registerCorsConfiguration("/info", config);
source.registerCorsConfiguration("/prometheus", config);
source.registerCorsConfiguration("/swagger-ui/**", config);
source.registerCorsConfiguration("/v3/api-docs/**", config);
}
return new CorsFilter(source);
}
When i tried to authorize, i get an error Auth ErrorTypeError: Failed to fetch
In the console i have the below error, even though i added * in the AllowedOrigins
Access to fetch at
'https://test/auth/realms/test/protocol/openid-connect/token' from
origin 'http://localhost:9019' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource. If an opaque response serves your needs, set the request's
mode to 'no-cors' to fetch the resource with CORS disabled.
My guess is that the confidential client obfuscated on your screenshot has no Allowed Origins configured with http://localhost:9019 in Keycloak.
If the failing request is https://test/auth/realms/test/protocol/openid-connect/token, then it's a request to the token endpoint of the test realm on a Keycloak (19 or before) instance. It's the request for the REST client to get an access-token (using the client-credentials flow you configured), before querying your resource-server (your spring-security config on the resource-server has not been evaluated yet).
Go to Keycloak administration console, browse to the test realm, open clients tab, select your confidential client and check allowed origins in its settings.
Related
I'm my application I'm using OKTA.
My application sends request to OKTA to URL: http://localhost:8080/oauth2/authorization/okta (Call #1)
OKTA then calls its URL: https://dev-1234567.okta.com/oauth2/v1/authorize (Call #2)
I want to disable CORS check for both my application & for dev-1234567.okta.com as well.
With below configs I'm able to disable CORS check for my application urls but not any other.
I tried had coding dev-1234567.okta.com in addAllowedOrigin as well but same issue.
What am I missing?
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http.cors().configurationSource(corsConfigurationSource()).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests()
.antMatchers("/app/**")
.hasAnyAuthority("ROLE_ADMIN").anyRequest()
.authenticated()
//........
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.addAllowedOrigin("*");
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("GET");
configuration.addAllowedMethod("PUT");
configuration.addAllowedMethod("POST");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Okta is not calling your application with CORS, but your app calls Okta with CORS. So usually you'd need to enable CORS/Redirect in both of your Okta tenants for your http://localhost:8080 under Security->API->Trusted Origin
I have a backend server made in Java with Spring Boot, Security and Web and a client made with Angular.
Currently I am trying to make a simple request under localhost:8080/resource.
The controller for this address is shown bellow:
#RestController
public class IndexController {
#CrossOrigin
#RequestMapping("/resource")
public Map<String, Object> home() {
Map<String, Object> model = new HashMap<String, Object>();
model.put("id", UUID.randomUUID().toString());
model.put("content", "Hello World");
return model;
}
}
And the Angular client (the part that performs the request) is this:
import { Component } from "#angular/core";
import { HttpClient } from "#angular/common/http";
#Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"]
})
export class AppComponent {
public title = "Security Client";
public greeting = {};
constructor(private http: HttpClient) {
http.get("http://localhost:8080/resource").subscribe(data => this.greeting = data);
}
}
The problem by using just what was shown is that I get a CORS error.
Whether removing Spring Security from my pom.xml or adding this configuration:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/resource").permitAll();
}
}
Solves the problem.
What I wanna know is why I am getting an CORS error instead of a 401 Unauthorized when accessing an address that demands user authentication.
According to the spring boot documentation:
For security reasons, browsers prohibit AJAX calls to resources
outside the current origin. For example, you could have your bank
account in one tab and evil.com in another. Scripts from evil.com
should not be able to make AJAX requests to your bank API with your
credentials — for example withdrawing money from your account!
Cross-Origin Resource Sharing (CORS) is a W3C specification
implemented by most browsers that lets you specify what kind of
cross-domain requests are authorized, rather than using less secure
and less powerful workarounds based on IFRAME or JSONP.
You're getting this error because you need to add a filter in your security configuration. In your configure, add:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.authorizeRequests().antMatchers("/resource").permitAll();
}
In the same file, you should add:
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH",
"DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("authorization", "content-type",
"x-auth-token"));
configuration.setExposedHeaders(Arrays.asList("x-auth-token"));
UrlBasedCorsConfigurationSource source = new
UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
This works fine for me.
What I wanna know is why I am getting an CORS error instead of a 401
Unauthorized when accessing an address that demands user
authentication.
You get this error because before your actual request (POST, GET...), the browser performs a pre-flight request (OPTIONS) to validate if in fact the called server is able to handle CORS requests.
During this request, the Access-Control-Request-Method and Access-Control-Request-Header are validated and some other info are added to the header.
You receive the CORS error then because your actual request is not even done if CORS validation failed on the OPTIONS request.
You can check a flowchart of how CORS validation works in here
An interesting point is that you will only get a HTTP error status like 401 during the pre-flight request when the server is not authorized to answer the OPTIONS request.
I've been on a witch hunt of trying to figure out why cross-origin requests fail only in Firefox. Turns out Firefox does not send SSL/TLS credentials on cross-origin XHRs, which apparently is defined by the W3 CORS Spec. I was able to mostly resolve this by adding a withCredentials: true to my XHR instances in my client code (it works for GET, PUT, POST, DELETE, etc.).
However, Firefox still refuses to add credentials to pre-flight OPTIONS requests despite me explicitly telling it to in the XHR. Unless there is somehow a way to do this in client code, I am left with configuring my server to allow un-authenticated OPTIONS requests (which in a 2-way SSL configuration seems insane to me). Are there security risks here?
Our server is a Spring Boot project that uses Spring Security, and is configured for 2-way SSL using X509 security certificates. I will provide all relative code to give you an idea of how the application is configured.
Main class:
#EnableAutoConfiguration
#SpringBootApplication
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class OurApp extends WebSecurityConfigurerAdapter {
#Autowired
OurUserDetailsService ourUserDetailsService;
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext ctx = SpringApplication.run(OurApp.class, args);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
// X509Configurer<HttpSecurity> x509Configurer = http.authorizeRequests().anyRequest().authenticated().and().x509();
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll().
.anyRequest().authenticated()
.and()
.x509().subjectPrincipalRegex("(.*)").authenticationUserDetailsService(ourUserDetailsService)
.and()
.cors()
.and()
.csrf().disable();
}
}
Configuration class that has CORS setup:
#Configuration
public class OurConfig {
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://client-origin"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"));
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "Access-Control-Allow-Origin",
"Access-Control-Allow-Headers", "X-Requested-With", "requestId", "Correlation-Id"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Bean
public IgnoredRequestCustomizer optionsIgnoredRequestsCustomizer() {
return configurer -> {
List<RequestMatcher> matchers = new ArrayList<>();
matchers.add(new AntPathRequestMatcher("/**", "OPTIONS"));
configurer.requestMatchers(new OrRequestMatcher(matchers));
};
}
}
This WORKS on all GET requests, and when I manually perform the requests in browser for the other verbs -- except for OPTIONS. The preflight request continues to fail during the TLS handshake and is aborted by Firefox. I'm confused, is it even possible to send an un-authenticated (credential-less) request over (two-way SSL) HTTPS?
How do I configure spring security or spring boot to allow unauthenticated OPTIONS requests with a two-way SSL (over HTTPS) enabled configuration?
The preflight request should exclude credentials, see CORS specification:
7.1.5 Cross-Origin Request with Preflight
To protect resources against cross-origin requests that could not originate from certain user agents before this specification existed a preflight request is made to ensure that the resource is aware of this specification. The result of this request is stored in a preflight result cache.
The steps below describe what user agents must do for a cross-origin request with preflight. This is a request to a non same-origin URL that first needs to be authorized using either a preflight result cache entry or a preflight request.
[...]
Exclude user credentials.
To handle a preflight request without credentials (SSL client certificate), you have to change the SSL configuration of your server.
For example, see Spring Boot Reference Guide:
server.ssl.client-auth= # Whether client authentication is wanted ("want") or needed ("need"). Requires a trust store.
I'm trying to create an application that has front-end and back-end assets separated. For the sake of example, let's say that front-end side will eventually be hosted on gh-pages, while back-end is gonna be deployed on Heroku.
I want to use OAuth2.0 protocol for authenticating my clients with GitHub being my main provider.
As a 'Prove of Concept', I wanted to create some dummy app that takes advantage of this kind of authentication. Below is the code:
Front-end (Angular2 application) - launched on localhost:8080
// template
<h1>
{{title}}
</h1>
<button type="button" (click)="getServerResponse()">getServerResponse()</button>
<h1>{{response}}</h1>
// component
export class AppComponent {
title = 'app works!';
response = 'server response';
constructor(private http: Http) {}
getServerResponse() {
this.http.get('http://localhost:9000/hello')
.subscribe(res => this.response = JSON.stringify(res.json()));
}
}
Back-end (Java + Spring application) - launched on localhost:9000
// Application.java
#SpringBootApplication
#EnableOAuth2Sso
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
// HelloController.java
#Controller
public class HelloController {
#RequestMapping("/hello")
public String hello() {
return "Hello!";
}
}
// FilterConfig.java
#Configuration
public class FilterConfig {
#Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(1);
return bean;
}
}
// resources/config/application.yml
security:
oauth2:
client:
clientId: xxx
clientSecret: yyy
accessTokenUri: https://github.com/login/oauth/access_token
userAuthorizationUri: https://github.com/login/oauth/authorize
clientAuthenticationScheme: form
scope: repo, user, read:org
resource:
userInfoUri: https://api.github.com/user
filter-order: 5
server:
port: 9000
I've tried registering both localhost:8080 and localhost:9000 as OAuth Application on GitHub, but regardless of that whenever I try to click on getServerResponse() button, I get the same result:
I wanted to ask whether it is even possible to have assets separated in such a manner? And if so, where do I make mistake?
Thank you!
The CORS message you’re seeing is because your code is sending a cross-origin request to https://github.com/login/oauth/authorize but the response from github doesn’t include the Access-Control-Allow-Origin response header.
So whatever changes you make to the CORS configuration in your Spring code won’t matter—it won’t make any difference because the behavior that would need to change is on the github side and you can’t change that.
You probably either want to do the oauth request from your backend rather than your frontend code as you’re doing now, or else set up a CORS proxy using https://github.com/Rob--W/cors-anywhere/ or such, or else set up something like https://github.com/prose/gatekeeper:
Because of some security-related limitations, Github prevents you from implementing the OAuth Web Application Flow on a client-side only application.
This is a real bummer. So we built Gatekeeper, which is the missing piece you need in order to make it work.
Gatekeeper works well with Github.js, which helps you access the Github API from the browser.
If you are using Spring-Boot you can do this in your spring configuration:
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:8080", "http://127.0.0.1:8080");
}
};
}
Startup Appplication:
#SpringBootApplication
#EnableZuulProxy
public class ZuulServer {
public static void main(String[] args) {
new SpringApplicationBuilder(ZuulServer.class).web(true).run(args);
}
}
My YAML file is like this:
server:
port:8080
spring:
application:
name: zuul
eureka:
client:
enabled: true
serviceUrl:
defaultZone: http://localhost:8761/eureka/
zuul:
proxy:
route:
springapp: /springapp
I have a microservice application (on port 8081) called springapp and has some rest services. Below is my client UI app:
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script type="text/javascript" src="js/libs/jquery/jquery.min.js" ></script>
</head>
<body>
<script type="text/javascript">
$.ajax({
url: 'http://localhost:8080/zuul/springapp/departments',
type: 'GET'
}).done(function (data) {
consoe.log(data);
document.write(data);
});
</script>
</body>
</html>
But I get a
XMLHttpRequest cannot load http://localhost:8080/zuul/springapp/departments. No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:8383' is therefore not allowed access.
This UI HTML5 app is on http://localhost:8383/SimpleAPp/index.html. CORS, CORS, CORS... Please help. BTW the http://localhost:8080/zuul/springapp/departments returns a json list as supposed to when on the browser address bar. The spring.io blog here says that there is no need for a filter because the zuulproxy takes care of that but I don't know why it is not working for me.
Adding this piece of code to your class annotated with #EnableZuulProxy should do the trick.
#Bean
public CorsFilter corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
I had a similar problem, with Angular Web app consuming RESTful services implemented by Spring Boot with Zuul and Spring Security.
None of the above solutions worked. I realized that the problem was NOT in Zuul, but in Spring Security.
As the official documentation (CORS with Spring Security) states, when using Spring Security, CORS must be configured prior to Spring Security.
Finally, I was able to integrate Grinish Nepal's (see prior answers) solution into a solution that works.
Without further ado, here is the code that enables CORS with Spring Security and Zuul:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//irrelevant for this problem
#Autowired
private MyBasicAuthenticationEntryPoint authenticationEntryPoint;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
//configure CORS -- uses a Bean by the name of corsConfigurationSource (see method below)
//CORS must be configured prior to Spring Security
.cors().and()
//configuring security - irrelevant for this problem
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint);
//irrelevant for this problem
http.addFilterAfter(new CustomFilter(),
BasicAuthenticationFilter.class);
}
//The CORS filter bean - Configures allowed CORS any (source) to any
//(api route and method) endpoint
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin(CorsConfiguration.ALL);
config.addAllowedHeaders(Collections.singletonList(CorsConfiguration.ALL));
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
source.registerCorsConfiguration("/**", config);
return source;
}
//configuring BA usernames and passwords - irrelevant for this problem
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
...
}
}
When your application runs on http://localhost:8383 then you can only make AJAX-calls to http://localhost:8383. Zuul doesn't and cannot change that.
What Zuul can do is mapping requests for e.g. http://localhost:8383/zuul/ to http://localhost:8080/zuul/. But your browser would have to call http://localhost:8383/zuul/springapp/departments and you have to configure that mapping.
Just adding the following to the configuration worked for me
zuul:
ignoredHeaders: Access-Control-Allow-Credentials, Access-Control-Allow-Origin
I had the same issue, and i have fixed by adding CorsFilter bean
#Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
And adding zuul's properties this code
zuul:
sensitiveHeaders:
ignored-headers: Access-Control-Allow-Credentials, Access-Control-Allow-Origin
You can find more detail about the issue here
That's just the browser telling you that you breached its common origin policy (see Wikipedia entry and a huge amount of material on the internet, none of which is really relevant to the tags you added). You can either teach the browser that it is OK to load resources from a different address by servicing the CORS pre-flight checks (e.g. in a Filter) or load the HTML through the proxy (hint: the latter is much easier and less error prone).
For those still having issue even when #Bean CorsFilter is added, check if the controller is also annotated with #CrossOrigin, this duplication of CORS, at controller level and at Zuul proxy is probably causing the issue.