Spring Authorization Server 0.3.1 CORS issue - spring

i created an authorization server using spring-auth-server 0.3.1, and implemented the Authorization code workflow, my issue is that when my front end -springdoc- reaches the last step i get a 401 and this is what's logged into browser console :
Access to fetch at 'http://authorization-server:8080/oauth2/token' from origin 'http://client:8081' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
i'm using spring boot 2.6.12 and here is my CORS configuration for authorization server (also copy pasted it to the client in case ):
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfiguration {
private final Set<String> allowedOrigins;
#Autowired
public WebSecurityConfiguration(
#Value("${spring.security.cors.allowed-origins:*}") List<String> allowedOrigins) {
this.allowedOrigins = new LinkedHashSet<>(allowedOrigins);
}
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.cors().configurationSource(corsConfigurationSource())
.and()
.csrf().disable() // without session cookies we do not need this anymore
.authorizeRequests().anyRequest().permitAll();
return http.build();
}
private CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
boolean useAllowedOriginPatterns = allowedOrigins.isEmpty() || allowedOrigins.contains("*");
if (useAllowedOriginPatterns) {
configuration.setAllowedOriginPatterns(Collections.singletonList(CorsConfiguration.ALL));
} else {
configuration.setAllowedOrigins(new ArrayList<>(allowedOrigins));
}
configuration.setAllowedMethods(Collections.singletonList(CorsConfiguration.ALL));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Collections.singletonList(CorsConfiguration.ALL));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
and here are my security filter chain for the Auth server :
#Order(1)
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
return http.formLogin(Customizer.withDefaults()).build();
}
#Bean
#Order(2)
public SecurityFilterChain standardSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults());
return http.build();
}
Any idea on what i'm missing ?

If your backend and your app are not running on the same address your browser does normally not allow you to call your backend. This is intended to be a security feature.
To allow your browser to call your api add the Access-Control-**** headers to your backend response (when answering from Spring).
please add the below line in your header
Access-Control-Allow-Origin: *
here is an tutorial also, please visit here on spring.io

i solved it by dropping the corsconfiguration from filter chain bean and creating a filter instead.
'''
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCORSFilter implements Filter {
private final Set<String> allowedOrigins;
#Autowired
public SimpleCORSFilter(#Value("${spring.security.cors.allowed-origins:*}") Set<String> allowedOrigins) {
this.allowedOrigins = allowedOrigins;
}
#Override
public void init(FilterConfig fc) throws ServletException {
}
#Override
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) resp;
HttpServletRequest request = (HttpServletRequest) req;
String origin = request.getHeader("referer");
if(origin != null ){
Optional<String> first = allowedOrigins.stream().filter(origin::startsWith).findFirst();
first.ifPresent(s -> response.setHeader("Access-Control-Allow-Origin", s));
}
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization, Content-Type, Authorization, credential, X-XSRF-TOKEN");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, resp);
}
}
#Override
public void destroy() {
}
}
'''

Related

Adding parameters to header does not work while implementing SpringBoot Security JWT for REST API

I'm trying to implement authentication and authorization using JWT token in SpringBoot REST API.
In my JWTAuthentication class
#Override
protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res,
FilterChain chain, Authentication auth) throws IOException, ServletException {
String token = Jwts.builder().setSubject(((User) auth.getPrincipal()).getUsername())
.claim("roles", ((User) auth.getPrincipal()).getAuthorities())
.setExpiration(new Date(System.currentTimeMillis() + SecurityConstants.EXPIRATION_TIME))
.signWith(SignatureAlgorithm.HS512, SecurityConstants.SECRET.getBytes()).compact();
res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
chain.doFilter(req, res);
System.out.println("Token:"+token);
}
When I test my code by sending by posting the following message to 127.0.0.1:8080/login URL, I see that authentication is successful.
{"username":"admin", "password":"admin"}
And then Spring calls my JWT Authorization class
#Override
protected void doFilterInternal(
HttpServletRequest req, HttpServletResponse res, FilterChain chain)
throws IOException, ServletException {
String header = req.getHeader(SecurityConstants.HEADER_STRING);
if (header == null || !header.startsWith(SecurityConstants.TOKEN_PREFIX)) {
if (header == null) {
System.out.println("header null");
} else if (!header.startsWith(SecurityConstants.TOKEN_PREFIX)) {
System.out.println("token prefix missing in header");
}
chain.doFilter(req, res);
return;
}
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
It prints the message: "token prefix missing in header"
Although I add the TOKEN_PREFIX in the successfulAuthentication method, it can not find it in the header in doFilterInternal method.
By the way, my security config is like this:
#EnableWebSecurity(debug = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired private UserDetailsService userDetailsService;
#Autowired private BCryptPasswordEncoder bCryptPasswordEncoder;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf()
.disable()
.authorizeRequests()
.antMatchers("/admin/**")
.hasRole("ADMIN")
.anyRequest()
.authenticated()
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
// this disables session creation on Spring Security
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers(
"/v2/api-docs",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**");
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
I checked the SpringBoot books but could not find a book that describes the inner details of the security framework. Since I did not understand how the framework works, I could not solve the problems by just looking at the blogs. Is there a book that you can suggest describing the details of SpringBoot Security?
Thanks
You set your token after you successfully authenticated the user to the header of
the Http response:
res.addHeader(SecurityConstants.HEADER_STRING, SecurityConstants.TOKEN_PREFIX + token);
The internal JWT filter (from what I understand in your question is called after yours), looks in the Http headers of the request
String header = req.getHeader(SecurityConstants.HEADER_STRING);
and there they are not present.
In general, the second filter should not be active after you authenticated a user and should just return the JWT token to the client. Any subsequent call of the client should then include the JWT token in the Authorization header using Bearer: YourJWTToken for calling e.g. protected APIs.

Spring Boot : CORS Issue

I am using Spring Boot version 2.0.2Release.
Below is my security configuration
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true)
#ComponentScan("com.mk")
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationProvider myAuthenticationProvider;
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.cors().configurationSource(corsConfigurationSource())
.and()
.csrf().disable()
.anonymous().and()
.authorizeRequests()
.antMatchers(HttpMethod.GET,"/index.html").permitAll()
.antMatchers(HttpMethod.POST,"/login").permitAll()
.antMatchers(HttpMethod.GET,"*").authenticated()
.and().httpBasic();
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
I am unable to invoke any API (including login which is permitAll) because of CORS issue.
On Browser I am getting (It works with Postman, since CORS check is not made there)
Failed to load http://localhost:8080/myurl: 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:4200' is therefore not allowed
access. The response had HTTP status code 403.
Although Spring security provides a way to configure CORS in http configurer, there's a much cleaner approach to add CORS filter to the application-
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class MyCORSFilter implements Filter {
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With, remember-me");
chain.doFilter(req, res);
}
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void destroy() {
}
}
Ordering the filter with highest precedence makes sure that MyCORSFilter implementation of javax.servlet.Filter is the first one in the chain. Hope this helps
Checkout this guide from Spring:
https://spring.io/guides/gs/rest-service-cors/
There are few ways to add CORS support in Spring Boot.
Using global configuration:
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:9000");
}
};
}
And using #CrossOrigin annotation:
#CrossOrigin(origins = "http://localhost:9000")
#GetMapping("/greeting")
public Greeting greeting(#RequestParam(required=false, defaultValue="World") String name) {
System.out.println("==== in greeting ====");
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
There is no need of adding any additional Filters or WebMvcConfigurer. The main problem is 'Access-Control-Allow-Origin' does not present in the header because corsConfigurationSource does not add the necessary configuration to get the relevant CORS response headers. Hence, the below missing configurations have to be added when we configure CorsConfigurationSource
configuration.addAllowedOrigin("*");
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*");
We have to configure cors CorsConfigurationSource as below
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.cors().configurationSource(corsConfigurationSource())
.and()
.....
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
configuration.setAllowCredentials(true);
//the below three lines will add the relevant CORS response headers
configuration.addAllowedOrigin("*");
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
If anyone facing CORS issue with Spring Boot 2.4.0 plus versions when having the following combination then refer to answer
CorsConfigurationSource#setAllowedOrigins value as *
and
CorsConfigurationSource#setAllowCredentials value as true
Ok, so I realized that it was deprecated. If you look on baeldung it has how to do it the new way since they updated webmvcconfigurer:
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*").allowedOrigins(frontDomain);
}
}

I am getting null header value in spring security REST implementation

I am using jersey , spring boot and spring security to create rest web service.
Which will be consumed by angular 2 client.
Client is sending authorization header in request , But on server i am not receiving any header value. I am using jersey for web service resource also using spring security authentication and authorization.
Kindly help.
public class SecurityConfiguration extends WebSecurityConfigurerAdapter{
#Autowired
private CustomUserDetailsService userDetailService;
public SecurityConfiguration(CustomUserDetailsService userDetailService) {
this.userDetailService = userDetailService;
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/assets/**")
.and().ignoring().antMatchers("/app/**")
.and().ignoring().antMatchers("/opas/Payment/**") ;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.cors();
http.authorizeRequests()
.antMatchers(HttpMethod.POST, SIGN_UP_URL).permitAll()
.antMatchers(HttpMethod.POST, "/login").permitAll()
.and().authorizeRequests().antMatchers("/opas/common/**").permitAll()
.and().authorizeRequests().antMatchers("/opas/register/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new CORSFilter(), ChannelProcessingFilter.class)
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
.addFilter(new JWTAuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService);
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(ImmutableList.of("*"));
configuration.setAllowedMethods(ImmutableList.of("HEAD","GET", "POST", "PUT", "DELETE", "PATCH","OPTIONS"));
// setAllowCredentials(true) is important, otherwise:
// The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
configuration.setAllowCredentials(true);
// setAllowedHeaders is important! Without it, OPTIONS preflight request
// will fail with 403 Invalid CORS request
configuration.setAllowedHeaders(ImmutableList.of("Authorization", "Cache-Control", "Content-Type","X-Requested-With"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
I am getting null header value in following code
public class JWTAuthorizationFilter extends BasicAuthenticationFilter {
public JWTAuthorizationFilter(AuthenticationManager authManager) {
super(authManager);
}
#Override
protected void doFilterInternal(HttpServletRequest req,
HttpServletResponse res,
FilterChain chain) throws IOException, ServletException {
String header = req.getHeader(HEADER_STRING);
if (header == null || !header.startsWith(TOKEN_PREFIX)) {
chain.doFilter(req, res);
return;
}
try {
UsernamePasswordAuthenticationToken authentication = getAuthentication(req);
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(req, res);
}
catch (ExpiredJwtException eje) {
// TODO: handle exception
ResponseMessage responseMessage = new ResponseMessage();
responseMessage.setStatusCode(DomainConstants.FORBIDDEN_ERROR);
responseMessage.setMessage(DomainConstants.SESSION_EXPIRED);
Gson gson = new Gson();
res.getWriter().write(gson.toJson(responseMessage));
}
}
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request)throws ExpiredJwtException {
String token = request.getHeader(HEADER_STRING);
if (token != null) {
// parse the token.
String user = Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
.getBody()
.getSubject();
if (user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}}
in latest versions of spring, if your header value equal to null, you get NullPointerException in spring security. maybe for your case you need to remove it with HttpServletResponseWrapper like this post

Spring Boot OAuth2Client - handling facebook login

Good day,
I have a spring boot app which is run at: 8080. Basic its function - handle "login/facebook" GET request and do a proper login there. It works well, when request is sent from the same domain (e.g. from http://localhost:8080/help page).
It is implemented in a way:
#Configuration
#EnableOAuth2Client
public class SclLoginSecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private OAuth2ClientContext oauth2ClientContext;
#Bean
public FilterRegistrationBean oauth2ClientFilterRegistration(
OAuth2ClientContextFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(filter);
registration.setOrder(-100);
return registration;
}
#Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
.antMatcher("/**")
.authorizeRequests()
.antMatchers("/", "/login/**", "/help").permitAll()
.anyRequest().authenticated().and()
.exceptionHandling().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/")).and()
.logout().logoutSuccessUrl("/").and()
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
#Bean
#ConfigurationProperties("facebook")
public ClientResources facebook() {
return new ClientResources();
}
private Filter ssoFilter() {
CompositeFilter filter = new CompositeFilter();
List<Filter> filters = new ArrayList<>();
filters.add(ssoFilter(facebook(), "/login/facebook"));
//add more authorization servers here
filter.setFilters(filters);
return filter;
}
private Filter ssoFilter(ClientResources client, String path) {
OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(path);
OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
filter.setRestTemplate(template);
filter.setTokenServices(new UserInfoTokenServices(
client.getResource().getUserInfoUri(), client.getClient().getClientId()));
return filter;
}
class ClientResources {
#NestedConfigurationProperty
private AuthorizationCodeResourceDetails client = new AuthorizationCodeResourceDetails();
#NestedConfigurationProperty
private ResourceServerProperties resource = new ResourceServerProperties();
public AuthorizationCodeResourceDetails getClient() {
return client;
}
public ResourceServerProperties getResource() {
return resource;
}
}
}
Cors filter exists and implemented in a way:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class CORSFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
chain.doFilter(req, res);
}
public void init(FilterConfig filterConfig) {}
public void destroy() {}
}
Application properties related to facebook:
facebook.client.client-id=...
facebook.client.client-secret=...
facebook.client.access-token-uri=https://graph.facebook.com/oauth/access_token
facebook.client.user-authorization-uri=https://www.facebook.com/dialog/oauth
facebook.client.token-name=oauth_token
facebook.client.authentication-scheme=query
facebook.client.client-authentication-scheme=form
facebook.resource.user-info-uri=https://graph.facebook.com/me
On the other side - I'm developing presentation layer (react + axious app) which is hosted at: 8000, where I had an intention to call GET to "http://localhost:8080/login/facebook" and be redirected to login page of facebook, but that is never happened. Instead I'm getting in browser:
XMLHttpRequest cannot load https://www.facebook.com/dialog/oauth?client_id=...&redirect_uri=http://localhost:8080/login/facebook&response_type=code&state=335Pc0. Redirect from 'https://www.facebook.com/dialog/oauth?client_id=...&redirect_uri=http://localhost:8080/login/facebook&response_type=code&state=335Pc0' to 'https://www.facebook.com/login.php?skip_api_login=1&api_key=..._&display=page&locale=en_US&logger_id=13caa792-a9a9-4187-bdb3-732702703d31' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'null' is therefore not allowed access.
At the same time, logs from spring boot side:
[nio-8080-exec-4] o.s.s.web.DefaultRedirectStrategy : Redirecting to 'https://www.facebook.com/dialog/oauth?client_id=...&redirect_uri=http://localhost:8080/login/facebook&response_type=code&state=335Pc0'
Can someone advise on how to enable this usecase?
Really appreciate attention and answer,
Vitaliy
The solution was complex:
1. make a 8080 to be as authorization server (Server).
2. host 8000 application within spring boot mvc (Client), enable authentication with Server
Very similar solution to described here: how to Secure Spring Boot RESTful service with OAuth2 and Social login

Spring Boot Security CORS

I have a problem with CORS filter on spring security URL's.
It doesn't set Access-Control-Allow-Origin and other exposed header on URL's belonging to spring sec (login/logout) or filtered by Spring Security.
Here are the configurations.
CORS:
#Configuration
#EnableWebMvc
public class MyWebMvcConfig extends WebMvcConfigurerAdapter {
********some irrelevant configs************
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/*").allowedOrigins("*").allowedMethods("GET", "POST", "OPTIONS", "PUT")
.allowedHeaders("Content-Type", "X-Requested-With", "accept", "Origin", "Access-Control-Request-Method",
"Access-Control-Request-Headers")
.exposedHeaders("Access-Control-Allow-Origin", "Access-Control-Allow-Credentials")
.allowCredentials(true).maxAge(3600);
}
}
Security:
#Configuration
#EnableWebSecurity
public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
.formLogin()
.successHandler(ajaxSuccessHandler)
.failureHandler(ajaxFailureHandler)
.loginProcessingUrl("/authentication")
.passwordParameter("password")
.usernameParameter("username")
.and()
.logout()
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.and()
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/authentication").permitAll()
.antMatchers("/oauth/token").permitAll()
.antMatchers("/admin/*").access("hasRole('ROLE_ADMIN')")
.antMatchers("/user/*").access("hasRole('ROLE_USER')");
}
}
So, if I make a request to the url's which are not listened by security - CORS headers are set. Spring security URL's - not set.
Spring boot 1.4.1
Option 1 (Use WebMvcConfigurer bean):
The CORS configuration that you started with is not the proper way to do it with Spring Boot. You need to register a WebMvcConfigurer bean. Reference here.
Example Spring Boot CORS configuration:
#Configuration
#Profile("dev")
public class DevConfig {
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:4200");
}
};
}
}
This will provide the CORS configuration for a basic (no security starter) Spring Boot application. Note that CORS support exists independent of Spring Security.
Once you introduce Spring Security, you need to register CORS with your security configuration. Spring Security is smart enough to pick up your existing CORS configuration.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
....
Option 2 (Use CorsConfigurationSource bean):
The first option I described is really from the perspective of adding Spring Security to an existing application. If you are adding Spring Security from the get-go, the way that is outlined in the Spring Security Docs involves adding a CorsConfigurationSource bean.
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// by default uses a Bean by the name of corsConfigurationSource
.cors().and()
...
}
#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;
}
}
Instead of using the CorsRegistry you can write your own CorsFilter and add it to your security configuration.
Custom CorsFilter class:
public class CorsFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpServletRequest request= (HttpServletRequest) servletRequest;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS");
response.setHeader("Access-Control-Allow-Headers", "*");
response.setHeader("Access-Control-Allow-Credentials", true);
response.setHeader("Access-Control-Max-Age", 180);
filterChain.doFilter(servletRequest, servletResponse);
}
#Override
public void destroy() {
}
}
Security config class:
#Configuration
#EnableWebSecurity
public class OAuth2SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Bean
CorsFilter corsFilter() {
CorsFilter filter = new CorsFilter();
return filter;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(corsFilter(), SessionManagementFilter.class) //adds your custom CorsFilter
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).and()
.formLogin()
.successHandler(ajaxSuccessHandler)
.failureHandler(ajaxFailureHandler)
.loginProcessingUrl("/authentication")
.passwordParameter("password")
.usernameParameter("username")
.and()
.logout()
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.and()
.csrf().disable()
.anonymous().disable()
.authorizeRequests()
.antMatchers("/authentication").permitAll()
.antMatchers("/oauth/token").permitAll()
.antMatchers("/admin/*").access("hasRole('ROLE_ADMIN')")
.antMatchers("/user/*").access("hasRole('ROLE_USER')");
}
}
This is quite clean and doesn't require any extra configurations. Pass asterisks where you want all option to be valid (like I did in setAllowedHeaders).
#EnableWebSecurity
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.cors().configurationSource(request -> {
var cors = new CorsConfiguration();
cors.setAllowedOrigins(List.of("http://localhost:4200", "http://127.0.0.1:80", "http://example.com"));
cors.setAllowedMethods(List.of("GET","POST", "PUT", "DELETE", "OPTIONS"));
cors.setAllowedHeaders(List.of("*"));
return cors;
}).and()...
}
}
I have a React based web client, and my backend REST API is running Spring Boot Ver 1.5.2
I wanted to quickly enable CORS on all controller route requests from my client running on localhost:8080. Inside my security configuration, I simply added a #Bean of type FilterRegistrationBean and got it working easily.
Here is the code:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class AuthConfiguration extends WebSecurityConfigurerAdapter {
....
....
#Bean
public FilterRegistrationBean corsFilter() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin(corsAllowedOrigin); // #Value: http://localhost:8080
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // **permit OPTIONS call to all**
....
}
....
....
}
You can refer Spring Boot docs here
I just had a similar issue, I was trying to execute a request from my frontend in React executing on http://localhost:3000, to my backend in SpringBoot executing at http://localhost:8080. I had two errors:
Access Control Allow Origin
I solved this very easily by adding this to my RestController:
#CrossOrigin(origins = ["http://localhost:3000"])
After fixing this, I started getting this error:
The value of the 'Access-Control-Allow-Credentials' header in the response is '' which must be 'true'
Access-Control-Allow-Credentials
This one can be worked around in two ways:
Adding allowCredentials = "true" to the CrossOrigin configuration:
#CrossOrigin(origins = ["http://localhost:3000"], allowCredentials = "true")
Changing the credential options of the fetch in the frontend request. Basically, you'll need to perform the fetch call like this:
fetch('http://localhost:8080/your/api', { credentials: 'same-origin' })
Hope this helps =)
Currently the OPTIONS requests are blocked by default if security is enabled.
Just add an additional bean and preflight requests will be handled correctly:
#Bean
public IgnoredRequestCustomizer optionsIgnoredRequestsCustomizer() {
return configurer -> {
List<RequestMatcher> matchers = new ArrayList<>();
matchers.add(new AntPathRequestMatcher("/**", "OPTIONS"));
configurer.requestMatchers(new OrRequestMatcher(matchers));
};
}
Please note that depending on your application this may open it for potential exploits.
Opened issue for a better solution: https://github.com/spring-projects/spring-security/issues/4448
If you need it for quick local development just add this annotation on your controller. (offcourse change origins as required)
#CrossOrigin(origins = "http://localhost:4200", maxAge = 3600)
You could also achieve this with an interceptor.
Use the exception to ensure you are ending the lifecycle of the request:
#ResponseStatus (
value = HttpStatus.NO_CONTENT
)
public class CorsException extends RuntimeException
{
}
Then, in your interceptor, set headers for all OPTIONS requests and throw the exception:
public class CorsMiddleware extends HandlerInterceptorAdapter
{
#Override
public boolean preHandle (
HttpServletRequest request,
HttpServletResponse response,
Object handler
) throws Exception
{
if (request.getMethod().equals("OPTIONS")) {
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Credentials", "true");
response.addHeader("Access-Control-Allow-Methods","GET, POST, PUT, OPTIONS, DELETE");
response.addHeader("Access-Control-Allow-Headers", "DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,Authorization,If-Modified-Since,Cache-Control,Content-Type");
response.addHeader("Access-Control-Max-Age", "3600");
response.addHeader("charset", "utf-8");
throw new CorsException();
}
return super.preHandle(request, response, handler);
}
}
Lastly, apply the interceptor to all routes:
#Configuration
public class MiddlewareConfig extends WebMvcConfigurerAdapter
{
#Override
public void addInterceptors (InterceptorRegistry registry)
{
registry.addInterceptor(new CorsMiddleware())
.addPathPatterns("/**");
}
}
If anyone struggles with the same problem in 2020. here's what did the work for me. This app is for learning purposes so I have enabled everything
CorsFilter class:
public class CorsFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
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-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, Content-Length, X-Requested-With");
chain.doFilter(req, res);
}
#Override
public void destroy() {
}
}
and then again setup of headers in class extending WebSecurityConfigurerAdapter:
#Configuration
#EnableWebSecurity
public class SpringSecurityConfigurationBasicAuth extends WebSecurityConfigurerAdapter {
#Bean
CorsFilter corsFilter() {
CorsFilter filter = new CorsFilter();
return filter;
}
protected void configure(HttpSecurity http) throws Exception {
System.out.println("Im configuring it");
(
(HttpSecurity)
(
(HttpSecurity)
(
(ExpressionUrlAuthorizationConfigurer.AuthorizedUrl)
http
.headers().addHeaderWriter(
new StaticHeadersWriter("Access-Control-Allow-Origin", "*")).and()
.addFilterBefore(corsFilter(), SessionManagementFilter.class)
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS,"/**").permitAll()
.anyRequest()
).authenticated().and()
).formLogin().and()
).httpBasic();
}
}
I tried with below config and it worked!
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable().cors().configurationSource(configurationSource()).and()
.requiresChannel()
.anyRequest()
.requiresSecure();
}
private CorsConfigurationSource configurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.setAllowCredentials(true);
config.addAllowedHeader("X-Requested-With");
config.addAllowedHeader("Content-Type");
config.addAllowedMethod(HttpMethod.POST);
source.registerCorsConfiguration("/**", config);
return source;
}
}

Resources