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.
Related
I just upgraded my project from Spring boot 2.7.7 to 3.0.2 and I'm seeing some weird behavior.
When I login to my application Spring adds "continue" query parameter to URL. It wasn't like this in 2.7.7. Is there something which I'm missing?
I use formLogin and have implemenatation of AuthenticationSuccessHandler, though it doesn't add any parameters.
This is covered in Spring Security's migration guide.
Basically, the query parameter was added to make so that Spring Security knows whether to query the session on that request or not. It is a performance optimization.
You can change the value by configuring the RequestCache like so:
#Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName("mycustomparameter");
http
// ...
.requestCache((cache) -> cache
.requestCache(requestCache)
);
return http.build();
}
Or, if you don't want this performance optimization, you can turn it off in the same way:
#Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName(null);
http
// ...
.requestCache((cache) -> cache
.requestCache(requestCache)
);
return http.build();
}
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.
I added springdoc-openapi-ui 1.6.12 in my Spring Boot project. I configured oAuth2 with PKCE and everything works fine, when I click on the "authorize" button it redirects me to a sso connection page. Then, I can send requests via swagger ui.
The problem is that it interferes with my Angular front end authentication. The front loops on the /login rout, with a 401 error. Swagger oauth uses the same session cookie as Spring Security/Angular.
Is there a way to use the same session for swagger ui AND angular ? Or is the problem somewhere else ?
This is my configuration :
springdoc:
swagger-ui:
path: /api-docs
tagsSorter: alpha
oauth:
clientId: "XXX"
clientSecret: "XXX"
use-pkce-with-authorization-code-grant: true
oAuthFlow:
authorizationUrl: "XXX/as/authorization.oauth2"
tokenUrl: "XXX/as/token.oauth2"
scope : XXX profile groups XXX email
edit :
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().configurationSource(corsConfigurationSource())
.and().csrf().disable()
.headers()
.frameOptions().disable()
.httpStrictTransportSecurity()
.includeSubDomains(false)
.maxAgeInSeconds(60*60*24*5)
.and().and()
.authorizeRequests(a -> a
// Management endpoints
.antMatchers(
"/health" + MATCH_ALL,
"/info",
"/prometheus",
"/loggers" + MATCH_ALL,
"/metrics" + MATCH_ALL
).permitAll()
// Authentication
.antMatchers(Routes.CURRENT_USER).permitAll()
.antMatchers("/oauth2/authorization/XXX").permitAll()
.antMatchers(Routes.LOGIN).authenticated()
// Preflight requests
.antMatchers(HttpMethod.OPTIONS).permitAll()
// Applications
.antMatchers(Routes.XXX.BASE + MATCH_ALL).hasAuthority(AuthorityUtil.AUTHORITY_XXX)
)
// By setting the login page here, Spring won't ask which provider we want to use
.oauth2Login().loginPage("/oauth2/authorization/XXX")
.and()
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::opaqueToken)
;
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration config = new CorsConfiguration();
config.applyPermitDefaultValues();
config.setAllowedOriginPatterns(List.of("*"));
config.setAllowedMethods(List.of("*"));
config.setAllowedHeaders(List.of("*"));
config.setAllowCredentials(true);
config.setMaxAge(1800L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration(MATCH_ALL, config);
return source;
}
The following SecurityWebFilterChain works very fine in Spring Boot 2.7.x but not working any more in Spring Boot 3.0.0. It just show "An expected CSRF token cannot be found" when calling the REST API in Postman. Would you please to teach me how to solve it?
#Bean
public SecurityWebFilterChain securitygWebFilterChain(ServerHttpSecurity http) {
http
.cors().disable()
.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint((swe, e) ->
Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED))
).accessDeniedHandler((swe, e) ->
Mono.fromRunnable(() -> swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN))
)
.and()
.authenticationManager(authenticationManager)
.securityContextRepository(securityContextRepository)
.authorizeExchange(exchange -> exchange
.pathMatchers(HttpMethod.OPTIONS).permitAll()
.pathMatchers("/login", "/register").permitAll()
.anyExchange().authenticated()
.and()
.cors().disable()
.csrf().disable()
)
.formLogin().disable()
.httpBasic().disable()
;
return http.csrf(csrf -> csrf.disable()).build();
}
I experienced the same symptoms when migrating my webflux application to Spring Boot 3.0.0 today, which worked perfectly with 2.7.5. So I googled for "csrf-disabling no longer working" and found this and some few other posts...
However in my case, it was an annotation change of Spring security 6, that caused the problem: #EnableWebFluxSecurity contained "#Configuration" in 5.x version (I checked) - but obviously does no longer and has to be added explicitly.
Thus the complete SecurityWebFilterChain bean was not found after migrating... Now the working code looks as follows:
#EnableWebFluxSecurity
#Configuration // <- this was integrated in #EnableWebFluxSecurity with Spring Security 5.x
public class AccountWebSecurityConfig {
#Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http,
ReactiveAuthenticationManager authenticationManager,
ServerAccessDeniedHandler accessDeniedHandler,
ServerAuthenticationEntryPoint authenticationEntryPoint) {
http.csrf().disable()
.httpBasic(httpBasicSpec -> httpBasicSpec
.authenticationManager(authenticationManager)
// when moving next line to exceptionHandlingSpecs, get empty body 401 for authentication failures (e.g. Invalid Credentials)
.authenticationEntryPoint(authenticationEntryPoint)
)
.authorizeExchange()
//...
}
As your FilterChain - snippet does not show the annotations at your class, chances are, you may also missing the #Configuration
In my case now everything works as before :-)
You can try it out
https://docs.spring.io/spring-security/reference/servlet/oauth2/resource-server/jwt.html
application.yml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: https://idp.example.com/issuer
You forgot to enable resource-server in your filter-chain:
http.oauth2ResourceServer().jwt().jwtAuthenticationConverter(authenticationConverter)
You need a custom authentication converter, as done above, mostly for roles mapping.
Detailed tutorials (for servlets) here: https://github.com/ch4mpy/spring-addons/tree/master/samples/tutorials. You might then refer to samples for reactive apps.
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;
}