Spring Boot Cross origin blocking requests - spring-boot

I have this Spring Boot application with a controller for Login endpoint. I've set these web configurations:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(securedEnabled = true, jsr250Enabled = true, prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable().authorizeRequests()
.antMatchers(HttpMethod.POST, PathConstants.USER_AUTH +"/**", PathConstants.HELIOS+"/dashboard/**").permitAll()
.antMatchers(HttpMethod.GET, "/"+PathConstants.PROCESS_DEFINITION+"/**").permitAll()
.antMatchers(HttpMethod.POST, "/"+PathConstants.PROCESS_DEFINITION+"/**").permitAll()
.antMatchers(HttpMethod.GET, "/"+PathConstants.PROCESS_INSTANCE+"/**").permitAll()
//.antMatchers(HttpMethod.POST, PathConstants.LOGIN_ACTION).permitAll()
//.anyRequest().authenticated()
.anyRequest().permitAll()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthEntryPoint).and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// custom jwt filter.
http.addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class);
}
and web mvc configurations:
#Configuration
#EnableWebMvc
public class WebMvcConfiguration implements WebMvcConfigurer {
private final long MAX_AGE = 3600;
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("*")
.allowedMethods("HEAD", "OPTIONS", "GET", "POST", "PUT", "PATCH", "DELETE")
.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")
.maxAge(MAX_AGE);
}
I've also tried to add .allowCredentials(true), but when I perform the login action it gives me cross origin error and my request is blocked.
This is my controller class:
#RestController
#RequestMapping
#CrossOrigin
public class AuthenticationControllerImpl implements AuthenticationController {
#PostMapping(PathConstants.LOGIN_ACTION)
#Override
public SysdataUser autenticate(#Valid #RequestBody LoginRequest request) {
Authentication auth = authManager
.authenticate(new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
SecurityContextHolder.getContext().setAuthentication(auth);
String token = jwtProvider.generateToken(auth);
SysdataUser user = sysdataUserService.getUserProfile(request.getUsername());
user.setToken(token);
return user;
}
What's missing in configurations?

I don't know why your config doesn't override default behavior. this is what I do and it always works for me, change #CrossOrigin in your AuthenticationControllerImpl class to #CrossOrigin(origins = "*"). let me know if it worked for you.

Related

Unable to resolve CORS errors

Assumptions
We are developing a web application with the following library.
When a request is sent from the front end to the back end, a CORS error occurs.
Frontend: Vue.js (Version: 3)
Backend: SpringBoot (version: 2.7.6)
Authentication: SpringSecurity
What we want to achieve
We would like to resolve the following CORS errors that occur when a request is sent from the front-end side to the back-end side.
Access to XMLHttpRequest at 'http://localhost:8085/users/profile/1' from origin 'http://localhost:8888' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Source code
Send request to Spring in Vue.js (Edit.vue)
onClickDelete() {
const path = 'users/profile/'
axios.delete(
process.env.VUE_APP_ROOT_API + path + this.$store.state.user_id,{
headers: {
"Authorization": "Bearer " + this.$store.state.jwt_token,
},
})
.then(response => {
})
.catch(error => {
console.log(error)
})
},
Receiving process in Spring (UsersController.java)
#RestController
#RequestMapping("/users/profile")
public class UsersController {
#DeleteMapping("/{user_id}")
#ResponseStatus(code = HttpStatus.NO_CONTENT, value = HttpStatus.NO_CONTENT)
public void profiledelete(#PathVariable("user_id") Long id) throws Exception {
}
}
SpringSecurity configuration file (WebSecurityConfig.java)
#Profile("production")
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
private final JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider;
#Value("${security.secret-key:secret}")
private String secretKey = "secret";
public WebSecurityConfig(JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider// ,
) {
this.jsonRequestAuthenticationProvider = jsonRequestAuthenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JsonRequestAuthenticationFilter jsonAuthFilter =
new JsonRequestAuthenticationFilter(userRepository);
jsonAuthFilter.setAuthenticationManager(authenticationManagerBean());
http.cors().configurationSource(request -> new CorsConfiguration().applyPermitDefaultValues());
http.addFilter(jsonAuthFilter);
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler())
.and()
.csrf().
disable()
.addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
}
What we tried
#CrossOrigin to the process (UsersController.java) that receives the process in Spring
What we did
Receive process in Spring (UsersController.java)
#RestController
#RequestMapping("/users/profile")
#CrossOrigin
public class UsersController {
#DeleteMapping("/{user_id}")
#ResponseStatus(code = HttpStatus.NO_CONTENT, value = HttpStatus.NO_CONTENT)
public void profiledelete(#PathVariable("user_id") Long id) throws Exception {
}
}
Result
The CORS error is still displayed.
Additional Information
Before SpringSecurity was installed, I think that granting #CrossOrigin on the Spring side solved the CORS error.
When the GET method is used in other requests, it succeeds without any CORS errors with the Spring side.
This seems to be an issue with your setup with spring security.
There are two primary ways to fix this error; however, I would also recommend upgrading to a newer version of spring security, because WebSecurityConfigurerAdapter has now been deprecated.
Primary method
CORS on Spring security (2.x)
#Profile("production")
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
private final JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider;
#Value("${security.secret-key:secret}")
private String secretKey = "secret";
public WebSecurityConfig(JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider// ,
) {
this.jsonRequestAuthenticationProvider = jsonRequestAuthenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JsonRequestAuthenticationFilter jsonAuthFilter =
new JsonRequestAuthenticationFilter(userRepository);
jsonAuthFilter.setAuthenticationManager(authenticationManagerBean());
http.cors().configurationSource(request -> {
var cors = new CorsConfiguration();
cors.setAllowedOrigins(List.of("*"));
cors.setAllowedMethods(List.of("GET","POST", "PUT", "DELETE", "OPTIONS"));
cors.setAllowedHeaders(List.of("*"));
return cors;
});
http.addFilter(jsonAuthFilter);
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler())
.and()
.csrf().
disable()
.addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
}
CORS disable
#Profile("production")
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserRepository userRepository;
private final JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider;
#Value("${security.secret-key:secret}")
private String secretKey = "secret";
public WebSecurityConfig(JsonRequestAuthenticationProvider jsonRequestAuthenticationProvider// ,
) {
this.jsonRequestAuthenticationProvider = jsonRequestAuthenticationProvider;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JsonRequestAuthenticationFilter jsonAuthFilter =
new JsonRequestAuthenticationFilter(userRepository);
jsonAuthFilter.setAuthenticationManager(authenticationManagerBean());
http.cors().disable();
http.addFilter(jsonAuthFilter);
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler())
.and()
.csrf().
disable()
.addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
}
CORS on Spring security (3.x)
#Configuration
public class WebConfiguration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("*");
}
}
Always go for the second method.

Spring boot application with Office 365 authentication

I'm developing an application (spring boot for backend and react for frontend) with authentication with Office 365. But I want to use my own group and permissions for Users. For instance, when a User access to /api/auth for the first time, I want to retrieve informations from microsoft graph and save it to my DB and then protect my endpoint with my own roles/permissions.
So far I managed to do this :
When i go to localhost:8080 (back) i'm redirect to Azure portal to authenticate. Then, I can access to my endpoints
I can protect my endpoints with my own roles
When i go to localhost:3000 (react app) I have a button that redirect me to the portal and give me an azure access token (thanks to MSAL.js)
So my problem is that I can't validate this azure token in my backend and send a new token from the back to the front to send request (like GET /api/users or POST /api/todos). I think my backend configuration and implementation is wrong but I didn't find a way to validate tokens and return a token for my backend...
I hope I was clear, english isn't my native language
Here is my WebSecurityConfig
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().antMatchers("/**")
.authenticated()
.and()
.csrf().csrfTokenRepository(csrfTokenRepository()).and()
.addFilterAfter(csrfHeaderFilter(), CsrfFilter.class)
.oauth2Login();
}
private Filter csrfHeaderFilter() {
return new OncePerRequestFilter() {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie == null || token != null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
};
}
#Bean
public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowUrlEncodedSlash(true);
return firewall;
}
#Override
public void configure(WebSecurity web) throws Exception {
super.configure(web);
web.httpFirewall(allowUrlEncodedSlashHttpFirewall());
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
}
And here's how I protect my endpoints :
#PreAuthorize("hasPermission(#foo, 'write')")
The conf :
#Configuration
public class AclPermissionEvaluator implements PermissionEvaluator {
#Autowired
private RoleRepository roleRepository;
#Autowired
private SecurityService securityService;
#Override
public boolean hasPermission(final Authentication authentication, final Object privilegeName, final Object privilegeType) {
DefaultOidcUser principal = (DefaultOidcUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Map<String, Object> userAttributes = principal.getAttributes();
UserInfo userInfo = securityService.getUserInfoByLogin((String) userAttributes.get("unique_name"));
if (userInfo == null || StringUtils.isBlank((CharSequence) privilegeName) || StringUtils.isBlank((CharSequence) privilegeType)) {
return false;
}
for (Permission permission : roleRepository.getByName(userInfo.getRoleName()).getPermissions()) {
if (permission.getPermission().startsWith((String) privilegeName) || permission.getPermission().equals("*")) {
return true;
}
}
return false;
}
//We don't need an implementation of this function for now
#Override
public boolean hasPermission(final Authentication authentication, final Serializable serializable, final String s, final Object o) {
return false;
}
}
Security Service :
#Service
#Transactional
public class SecurityServiceImpl implements SecurityService {
#Autowired private UserRepository userRepo;
#Autowired
private RoleRepository roleRepository;
#Override
public UserInfo getUserInfoByLogin(String username) {
User user = userRepo.getUserByUsername(username);
ModelMapper modelMapper = new ModelMapper();
return modelMapper.map(user, UserInfo.class);
}
}
RoleRepository :
#Repository
public interface RoleRepository extends JpaRepository<Role, Long> {
public Role getByName(String name);
}
And on my application.yml :
spring:
security:
oauth2:
client:
registration:
azure:
clientId: <my-clientId>
clientSecret: <my-secret>
azure:
activedirectory:
tenant-id: <my-tenant-id>
user-group:
allowed-groups: all
active-directory-groups: all
you can use similar to the below code
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
#Import(SecurityProblemSupport.class)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CorsFilter corsFilter;
private final SecurityProblemSupport problemSupport;
private final OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService;
public SecurityConfiguration(CorsFilter corsFilter, SecurityProblemSupport problemSupport, OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService) {
this.corsFilter = corsFilter;
this.problemSupport = problemSupport;
this.oidcUserService = oidcUserService;
}
#Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(HttpMethod.OPTIONS, "/**")
.antMatchers("/app/**/*.{js,html}")
.antMatchers("/i18n/**")
.antMatchers("/content/**")
.antMatchers("/swagger-ui/index.html")
.antMatchers("/test/**");
}
#Override
public void configure(HttpSecurity http) throws Exception {
// #formatter:off
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.addFilterBefore(corsFilter, CsrfFilter.class)
.exceptionHandling()
.accessDeniedHandler(problemSupport)
.and()
.headers()
.contentSecurityPolicy("default-src 'self'; frame-src 'self' data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://storage.googleapis.com; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:")
.and()
.referrerPolicy(ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
.and()
.featurePolicy("geolocation 'none'; midi 'none'; sync-xhr 'none'; microphone 'none'; camera 'none'; magnetometer 'none'; gyroscope 'none'; speaker 'none'; fullscreen 'self'; payment 'none'")
.and()
.frameOptions()
.deny()
.and()
.authorizeRequests()
.antMatchers("/api/auth-info").permitAll()
.antMatchers("/api/**").authenticated()
.antMatchers("/management/health").permitAll()
.antMatchers("/management/info").permitAll()
.antMatchers("/management/prometheus").permitAll()
.antMatchers("/management/**").hasAuthority(AuthoritiesConstants.ADMIN)
.and()
.oauth2Login()
.userInfoEndpoint()
.oidcUserService(oidcUserService);
// #formatter:on
}
/**
* Map authorities from "groups" or "roles" claim in ID Token.
*
* #return a {#link GrantedAuthoritiesMapper} that maps groups from
* the IdP to Spring Security Authorities.
*/
#Bean
public GrantedAuthoritiesMapper userAuthoritiesMapper() {
return (authorities) -> {
Set<GrantedAuthority> mappedAuthorities = new HashSet<>();
authorities.forEach(authority -> {
mappedAuthorities.addAll(authorities);
});
return mappedAuthorities;
};
}
}
you need to change the userAuthoritiesMapper method to achieve what you looking for
extra configuration
spring security config
spring:
security:
oauth2:
client:
registration:
azure:
client-id: <<CLIENT_ID>>
client-secret: <<CLIENT_SECRET>>
Azur Active directory config
azure:
activedirectory:
tenant-id: <<YOUR_TENANT_ID>>
active-directory-groups: Users
b2c:
reply-url: http://localhost:9000 # should be absolute url.
logout-success-url: http://localhost:9000
I'll give a secret whenever you stuck in any of the spring boot configurations go and find how jhipster make it
you can find more information by visiting this article posted by jhipster creator
NOTE: SecurityProblemSupport is a library implemented by zalando
finally the code posted copied from this repo

Access to XMLHttpRequest at '' () from origin '' has been blocked by CORS policy:No 'Access-Control-Allow-Origin' header is present

I am getting the below issue while logging out from openid connect.
"Access to XMLHttpRequest at '' (redirected from '') from origin
'http://localhost:8080' has been blocked by CORS policy: No
'Access-Control-Allow-Origin' header is present on the requested
resource."
and the Network call is showing "cancelled" status.
Here is code
SecurityConfig.java
#Override
protected void configure(HttpSecurity http) throws Exception {
LOG.info("in configure httpsecurity");
http.csrf().disable().cors().and()
.addFilterAfter(new OAuth2ClientContextFilter(), AbstractPreAuthenticatedProcessingFilter.class)
.addFilterAfter(myFilter(), OAuth2ClientContextFilter.class)
.httpBasic().authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint(openIdConfig.getEntrypoint()))
.and()
.authorizeRequests()
.antMatchers(openIdConfig.getEntrypoint()).permitAll()
.anyRequest().authenticated()
.and().logout()//.clearAuthentication(true)
.logoutUrl(openIdConfig.getLogoffURL()+openIdConfig.getRedirectUri()).permitAll()
.invalidateHttpSession(true)
.deleteCookies(OpenIDConstants.SESSION_TOKEN, OpenIDConstants.USERNAME,
OpenIDConstants.JSESSIONID)
.logoutSuccessHandler(logoutSuccessHandler())
.logoutSuccessUrl(openIdConfig.getRedirectUri());
;
LOG.info("in configure httpsecurity end");
// #formatter:on
}
You probably did enable CORS on security level, but not on the web level.
To enable CORS on web level, you can do it at method level, class level or for the entire application.
Method level
#CrossOrigin(origins = "http://example.com")
#GetMapping(path="/")
public String homeInit(Model model) {
return "home";
}
Class level
#CrossOrigin(origins = "*", allowedHeaders = "*")
#Controller
public class HomeController
{
#GetMapping(path="/")
public String homeInit(Model model) {
return "home";
}
}
Global
#Configuration
#EnableWebMvc
public class CorsConfiguration extends WebMvcConfigurerAdapter
{
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("GET", "POST");
}
}
or, for a Spring Boot application, the recommended way:
#Configuration
public class CorsConfiguration
{
#Bean
public WebMvcConfigurer corsConfigurer()
{
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
};
}
}

"addCorsMapping" blocking Swagger UI

I'm working on a Spring application and I have some troubles with Swagger and Spring Security.
I had to add a specific configuration to allow almost every access (CORS) and it worked well so far, but somehow it is blocking Swagger....
This is my SwaggerConfiguration.java
#Configuration
#EnableSwagger2
#SwaggerDefinition(
info = #Info(
description = "Web Service",
version = "V0.0.1",
title = "Web Service",
contact = #Contact(
name = "Me",
email = "dev#me.com",
url = "https://www.me.com/"
)
),
consumes = {"application/json"},
produces = {"application/json"},
schemes = {SwaggerDefinition.Scheme.HTTP, SwaggerDefinition.Scheme.HTTPS}
)
public class SwaggerConfiguration {
/** List of Swagger endpoints (used by {#code WebSecurityConfig}) */
static final String[] SWAGGER_ENDPOINTS = {
"/v2/api-docs",
"/swagger-resources",
"/swagger-resources/**",
"/configuration/ui",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**"
};
#Bean
public Docket swaggerSpringMvcPlugin() {
return new Docket(DocumentationType.SWAGGER_2)
.groupName("admin-api")
.select()
.paths(paths()) // and by paths
.build();
}
private Predicate<String> paths() {
return or(
regex("/admin.*"),
regex("/issuer.*"),
regex("/validator.*"),
regex("/data.*"));
}
}
And this is my WebSecurityConfig.java :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private JwtTokenDecoder jwtTokenDecoder;
#Bean
// Mandatory to be able to have % in URL
// FIXME Set it only for dev environment
public HttpFirewall allowUrlEncodedPercentHttpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowUrlEncodedPercent(true);
firewall.setAllowUrlEncodedSlash(true);
return firewall;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.httpBasic().disable()
.formLogin().disable()
.logout().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Install the JWT authentication filter
http.addFilterBefore(new JwtAuthenticationFilter(jwtTokenDecoder), BasicAuthenticationFilter.class);
// Authorize only authenticated requests
http.authorizeRequests()
.anyRequest().authenticated();
http.cors();
}
#Override
public void configure(WebSecurity web) {
// Allow access to /admin/login without authentication
web.ignoring().mvcMatchers("/admin/login", "/admin/validate", "/campaigns", "/data/**", "/issuer/**", "/validator/**");
web.ignoring().antMatchers(SwaggerConfiguration.SWAGGER_ENDPOINTS);
web.httpFirewall(allowUrlEncodedPercentHttpFirewall());
}
}
Finally, I have a WebConfig.java used to set CORS authorizations.
Here it is :
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE");
}
}
Very simple. It should authorize almost any access.
When I remove it, Swagger is available from URL localhost:8080/swagger-ui.html (but not my webservices...)
When I put it back, it is blocked, with a 403 error (forbidden)
Any idea of what I am missing ?
So the solution was to add some configuration in WebConfig
I have added this implementation
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry
.addResourceHandler("swagger-ui.html")
.addResourceLocations("classpath:/META-INF/resources/");
registry
.addResourceHandler("/webjars/**")
.addResourceLocations("classpath:/META-INF/resources/webjars/");
}

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