Spring Security CORS Blocked By Policy - spring

This should be easy, but of course since it's Spring Security, it's not.
I am attempting to access a relatively simple api running as a Spring Boot application from an Angular application. Angular makes the calls to the API just fine, but the backend blocks the request due to CORS policy:
I added the following to my Security configuration:
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://localtest.me:4200","http://localtest.me:4200"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Bean
#Profile("dev")
public SecurityFilterChain devFilterChain(HttpSecurity http) throws Exception {
// define a custom filter, irrelevant to question
// #formatter:off
http
.addFilterAfter(filter, ConcurrentSessionFilter.class)
.authorizeRequests()
.antMatchers("/path1","/path2","/logout").permitAll()
.anyRequest().authenticated()
.and()
.cors();
// #formatter:on
return http.build();
}
This STILL does not prevent the CORS policy block.
I've also tried putting various iterations of #CrossOrigin (with and without origins argument):
on the Controller class
on the endpoint method itself
Am I making a simple error causing this?
Edit: I added breakpoints to Spring's CorsFilter, and they are not being hit.
Edit: Adding screenshot by request:

try to add this at the head ( beggining of your controller)
#CrossOrigin(origins = "http://localhost:{youy_angular_application_port}")
public class YourRestController {
}

Not the proudest and most beautiful solution, but some months ago, I also needed to expose some endpoints to my frontend, so my angular application could send requests to them.
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/user").allowedOrigins("http://localhost:4200");
registry.addMapping("/post").allowedOrigins("http://localhost:4200");
registry.addMapping("/post/").allowedOrigins("http://localhost:4200");
registry.addMapping("/user/{id}").allowedOrigins("http://localhost:4200");
registry.addMapping("/post/{id}").allowedOrigins("http://localhost:4200");
registry.addMapping("/post/user").allowedOrigins("http://localhost:4200");
registry.addMapping("/post/user/").allowedOrigins("http://localhost:4200");
registry.addMapping("/post/user/{id}").allowedOrigins("http://localhost:4200");
registry.addMapping("/user/").allowedOrigins("http://localhost:4200");
}
};
}
The bean can get implemented where ever, since its a bean. In my case I implemented it in the MainApplication.java class.

Okay, here's what happened.
At end-of-day the day before yesterday, some numbskull checked in a change to application.properties changing the context-root of the application.
The application was no longer being served at http://localtest.me:8000/api , it was being servered at http://localtest.me:8000/appname/api.
Effectively, I had a 404 error as much as I had a CORS error. Chrome didn't tell me that the path didn't exist, it just kept telling me it was blocked.

Related

Spring Boot REST API disable Form Login redirect

I have been banging my head against the wall for hours trying to figure out something that I would expect to work out of the box these days.
I am building an API with Spring Boot backend and I will create a react front end.
I only have one server so I dont need to use tokens. I want the same normal server side sessions and cookies.
I managed to get the Authentication to work but for some reason it keeps redirecting after success to the default / endpoint.
I really do not want this to happen and can't figure out why this is the case. I also can't find any decent resources on the internet of people that have encountered this issue.
I have seen a few videos where I have seen people handling the login in a Rest Controller end point rather than using filters. I assume this could work but then how would I implement session management?
Here is the code so far:
#Configuration
#EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
#Autowired
private AuthUserService authUserService;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(authUserService);
}
#Bean
public CorsConfigurationSource corsConfigurationSource(){
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/api/**").hasRole("AUTH_USER")
.mvcMatchers("/**").permitAll();
http.cors();
http.addFilterAfter(new CsrfHandlerFilter(), CsrfFilter.class);
AuthenticationFilter filter = new AuthenticationFilter();
filter.setAuthenticationManager(authenticationManager());
http.addFilterAt(filter, UsernamePasswordAuthenticationFilter.class);
}
#Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
}
Authentication Filter:
public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
public AuthenticationFilter(){
super.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login", "POST"));
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
System.out.println("Custom Authentication Filter fired!");
ObjectMapper mapper = new ObjectMapper();
Login login = new Login();
try {
login = mapper.readValue(request.getInputStream(), Login.class);
} catch (StreamReadException e) {
e.printStackTrace();
} catch (DatabindException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(
login.getUsername(),
login.getPassword()
);
return this.getAuthenticationManager().authenticate(token);
}
}
Login Model class:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Login {
private String username;
private String password;
}
I want a normal server side session. I am not using JWT just because it is a JavaScript client. But all I want is for it to not redirect. Is this possible?
Any advice would be appreciated
There are a few ways to approach this, depending on your preference.
Certainly, you can stand up your own Spring MVC endpoint and set the SecurityContext yourself. Spring Security's SecurityContextPersistenceFilter will store the SecurityContext in an HttpSessionSecurityContextRepository by default, which induces the container to write a JSESSIONID session cookie that can be used on subsequent requests.
The main reason to go this route is if you want to have access to the MVC feature set when writing this endpoint.
One downside of this route is that Spring Security 6 will no longer save the security context for you when it comes to custom MVC endpoints, so you would need to be aware of that when upgrading.
HTTP Basic
That said, it doesn't seem like your requirements are so sophisticated that you can't use Spring Security's OOTB behavior.
One way to do this is with HTTP Basic. Note that for simplicity, I'll publish the SecurityFilterChain as a #Bean instead of using the now-deprecated WebSecurityConfigurerAdapter:
#Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.mvcMatchers("/api/**").hasRole("AUTH_USER")
.mvcMatchers("/**").permitAll()
)
.httpBasic(Customizer.withDefaults())
.cors(Customizer.witHDefaults())
.addFilterAfter(new CsrfHandlerFilter(), CsrfFilter.class);
return http.build();
}
This will allow you to send the username/password using the Authorization: Basic header. There's no need in this case for you to stand up anything custom. The filter chain will store the security
context in the session, and your Javascript can call endpoints using the JSESSIONID or by resending the username/password creds.
AuthenticationSuccessHandler
If for some reason you want to use form login (what your sample is customizing right now), instead of creating a custom filter, you can configure the existing form login filter with an AuthenticationSuccessHandler that does not redirect:
#Bean
SecurityFilterChain web(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.mvcMatchers("/api/**").hasRole("AUTH_USER")
.mvcMatchers("/**").permitAll()
)
.formLogin((form) -> form
.successHandler((request, response, authentication) ->
response.setStatusCode(200)
)
)
.cors(Customizer.witHDefaults())
.addFilterAfter(new CsrfHandlerFilter(), CsrfFilter.class);
return http.build();
}
Once again, the filter chain will save the subsequent UsernamePasswordAuthenticationToken to the session and issue a JSESSIONID for subsequent requests.

Resource server test fails with Auth0 issuer uri

I created a simple example, only acts Resource server to provide APIs for clients.
The complete codes can be found on Github - hantsy/spring-webmvc-auth0-sample.
I have browsed Spring security samples, it used a jwk-set-uri, in my application, I used issuer-uri instead.
security:
oauth2:
resourceserver:
jwt:
issuer-uri: <auth0 provided issuer uri>
And I followed Auth0 Spring security 5 API Guide , add audience claim validation.
I tried to add a ApplicationTests using MockMVC.
#Test
public void testGetById() throws Exception {
Post post = Post.builder().title("test").content("test content").build();
post.setId(1L);
given(this.posts.findById(anyLong())).willReturn(Optional.of(post));
this.mockMvc
.perform(
get("/posts/{id}", 1L)
.accept(MediaType.APPLICATION_JSON)
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("test"));
verify(this.posts, times(1)).findById(any(Long.class));
verifyNoMoreInteractions(this.posts);
}
And my security config is similar to this.
#Bean
SecurityFilterChain springWebFilterChain(HttpSecurity http) throws Exception {
return http
.httpBasic(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeRequests(c -> c
.antMatchers("/", "/info").permitAll()
.antMatchers(HttpMethod.GET, "/posts/**").permitAll()//.hasAuthority("SCOPE_read:posts")
.antMatchers(HttpMethod.POST, "/posts/**").hasAuthority("SCOPE_write:posts")
.antMatchers(HttpMethod.PUT, "/posts/**").hasAuthority("SCOPE_write:posts")
.antMatchers(HttpMethod.DELETE, "/posts/**").hasAuthority("SCOPE_delete:posts")
.anyRequest().authenticated()
)
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt)
.cors().and().build();
}
When running the tests.
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.NullPointerException: Cannot invoke "org.springframework.test.web.servlet.DefaultMvcResult.setHandler(Object)" because "mvcResult" is null
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
I am not sure where is wrong? I have checked the official samples, it includes a spring.factories file to enable a mockserver env, it is requried in the Spring Boot?
The issue appears to be the inclusion of cors() in the configuration.
Spring Security's CorsFilter delegates by default to HandlerMappingInterceptor, which wraps the request in a specialized HttpServletRequestWrapper. When combined with RouterFunctions, MockMvc's MVC_REQUEST_ATTRIBUTE request attribute is getting removed.
One fix, then, is to remove cors() from your configuration. Indeed, when I remove it from your sample, the tests run as expected.
Another is to not use the HandlerMappingInterceptor default. Instead, you can publish your own CorsConfigurationSource, like so:
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
In your sample app, when I added the above to your SecurityConfig, the tests ran again as expected.
It seems like there might be a way to adjust this specialized wrapper in HandlerMappingIntrospector as well so as to not accidentally remove the MVC_REQUEST_ATTRIBUTE, but the Spring Framework team will probably have more to say if any adjustments are needed. I've filed a ticket there to see if anything can be done.

Spring boot custom response header blocked by CORS

I have Spring Boot application that provides GET REST API endpoint to a list of objects. The list has a default pagination and provides custom 'Link' HTTP header with info about the next and previous pages based on the current page in the navigation for the client to utilize.
Example of Link HTTP header
link: <http://localhost:8080/api/v1/articles?page=1&size=1>; rel="next", <http://localhost:8080/api/v1/articles?page=4&size=1>; rel="last"'
When the client and web server utilizes the same origin then the header is included. However, I am unable to include the link header in the response header when the client has different origin. The application has CORS configuration, but I couldn't find anything to make it include my custom header. Only default response headers are included.
Any idea how include custom HTTP headers in the CORS response?
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Bean
public SpringDataUserDetailsService customUserDetailsService() {
return new SpringDataUserDetailsService();
}
#Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("*"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/admin").authenticated().and().formLogin();
}
}
-- EDIT 1 --
Response headers when the client is of the same origin as web server
Response headers when the client is on the other port
I found the solution to my problem. The custom headers must be exposed in CorsConfigurationSource bean.
Adding this line of code, allowed cross orgin requests to get the custom header 'Link' in response.
configuration.addExposedHeader("Link");

No 'Access-Control-Allow-Origin' header is present on the requested resource (Spring)

I know there are a lot threads on the forum about this issue but still haven't figure out a solution.
So, I have deployed two applications in a private JVM/tomcat 8.5.30 on my vps. The one is my ROOT.war and the other one is the admin.war They were accesible from http://example.com and http://example.com/admin
Before I installed a ssl certificate everything worked fine. After installing it and forcing https redirect I am facing a problem with my admin.war (now they are both accesible from https://example.com and https://example.com/admin)
My admin works with a lot of jquery (I cannot change that) and I am getting this error every time I am trying to submit something
Access to XMLHttpRequest at 'http: //example.com/admin/add' from origin 'https: //example.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
So I am trying to fix this via spring security. In my security configuration I have
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class SiteSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
//.....
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedHeaders(Arrays.asList("Access-Control-Allow-Headers"));
configuration.addExposedHeader("Access-Control-Allow-Headers");
configuration.setAllowedMethods(Arrays.asList("POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
I do this for both my root app and my admin app ( I don't know if that's correct to do it for both of them). Still doesn't work.
Any help?
thanks!!
If you see Error
'http://example.com/admin/add' from origin 'https://example.com' has been blocked
There are 2 issues
1 I guess your /add API call is not getting redirected to https. Ideally it should be https://example.com/admin/add Either you resolve this
or
2 Change setAllowedOrigins in your Admin App to http as well like this
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com", "http://example.com"));
configuration.setAllowedHeaders(Arrays.asList("Access-Control-Allow-Headers"));
configuration.addExposedHeader("Access-Control-Allow-Headers");
configuration.setAllowedMethods(Arrays.asList("POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}

What's the difference between #CrossOrigin annotation http.csrf().disable()?

This question is related to Disable Keycloak authentication for a specific url in spring-boot
I have a 3rd party dashboard which manages my front end through an iFrame. But it calls my search api directly through it's search widget. The code mentioned below does not solves the problem of CORS for this search API only and sends this error
, all the other API works smoothly.
#Override
protected void configure(#Nonnull final HttpSecurity http) throws Exception {
super.configure(http);
http.csrf().disable();
http
.cors()
.and()
.authorizeRequests()
.antMatchers("/health", "/error").permitAll()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.anyRequest().authenticated();
}
Now I added the following code on my controller and it started working:
#CrossOrigin(origins = "https://dashboard-url")
so,
Is this the correct way to do this ? Is there any pitfalls using this ?
What is the difference between these 2, what is missing from my previous approach.
I have 3 stages(dev/stage/prod) onto which I might need to add this #CrossOrigin annotation, any suggestions how to proceed. I can make use of the profiles but prod does not have specific -prod tag e.g. dev and stage has the following url dashboard-dev.com/dashboard-stage.com. But prod has dashboard.com only.
Yes, what you are doing is correct
Please check the URL
https://spring.io/blog/2015/06/08/cors-support-in-spring-framework
https://docs.spring.io/spring-security/site/docs/5.1.3.RELEASE/reference/html5/#cors
If you are using Spring Security, make sure to enable CORS at Spring Security level as well to allow it to leverage the configuration defined at Spring MVC level.
So as per your coding, you have enabled cors at security using http.cors() and as there is no corsConfigurationSource (CORS filter ) been defined it uses the MVC level defined using #CORS(***)
However, if you want the values to be dynamic you can use as below with values fetching from external files based on env
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}

Resources