"addCorsMapping" blocking Swagger UI - spring

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/");
}

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 erro Cors

I have a problem with the spring Cors.
I get this error on chome:
Access to XMLHttpRequest at 'http://localhost:8080/api/informationWS' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
My file WebSecurityConfigurerAdapter
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private LoginService loginService;
#Bean
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(loginService)
.passwordEncoder(this.passwordEncoderAutentication());
}
#Bean
public PasswordEncoder passwordEncoderAutentication() {
String idForEncode = "bcrypt";
Map<String, PasswordEncoder> encoders = new HashMap<>();
encoders.put(idForEncode, new BCryptPasswordEncoder());
encoders.put("pbkdf2", new Pbkdf2PasswordEncoder());
encoders.put("scrypt", new SCryptPasswordEncoder());
PasswordEncoder passwordEncoder = new DelegatingPasswordEncoder(idForEncode, encoders);
return passwordEncoder;
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.cors();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
}
}
My file ResourceServerConfigurerAdapter
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/api/informationWS").permitAll()
.antMatchers(HttpMethod.POST, "/api/work").authenticated()
.anyRequest().denyAll();
}
}
I tried to work with Cors in the two ways below, but neither of them worked, generating the same error
My file cors
#Configuration
#EnableWebMvc
public class Cors implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:4200");
}
}
My file Cors2
#Configuration
public class Cors {
#Bean
public FilterRegistrationBean<CorsFilter> corsFilterFilterRegistrationBean(){
List<String> host = Arrays.asList("http://localhost:4200");
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(host);
corsConfiguration.setAllowedHeaders(Arrays.asList("*"));
corsConfiguration.setAllowedMethods(Arrays.asList("*"));
corsConfiguration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", corsConfiguration);
CorsFilter corsFilter = new CorsFilter(source);
FilterRegistrationBean<CorsFilter> filter = new FilterRegistrationBean<>(corsFilter);
filter.setOrder(Ordered.HIGHEST_PRECEDENCE);
return filter;
}
}
What you could try/check:
check if the application code is executed - maybe server stops execution for some reason, and so your spring code cannot add a header.
maybe there is preflight request and server does not allow it (so again server stopped execution and your backend code could not send the header)
maybe you yourself stop script somewhere before the header is added, like System.exit(0);
maybe there is redirect to code which does not add header, for example some exception
try running the request from Postman - you should not get the error and maybe you will see something surprising.
does this .antMatchers(HttpMethod.GET, "/api/informationWS") really match the request? Maybe there is a way to add wildcard just for testing and see if it works? Are you sending GET request?
More details, technologies different but concept same: https://dariuscoder.com/2021/09/16/how-to-debug-cors/

Spring Security pre authentication filter gets called every time

I have a Spring Boot app where I have custom pre authentication filter. I want to ignore security for health URL but I am not able to do it. Below is my configuration.
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Order(1000)
public class UserSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken> userDetailsService;
#Autowired
private IUserIdentityService iUserIdentityService;
#Value("${spring.profiles.active}")
private String profileType;
#Autowired
#Qualifier("publicEndpoints")
private Map<String, String> publicEndpoints;
#Autowired
private GenericDataService genericDataService;
#Bean(name = "preAuthProvider")
PreAuthenticatedAuthenticationProvider preauthAuthProvider() {
PreAuthenticatedAuthenticationProvider provider = new PreAuthenticatedAuthenticationProvider();
provider.setPreAuthenticatedUserDetailsService(userDetailsService);
return provider;
}
#Bean
AppPreAuthenticatedProcessingFilter appPreAuthenticatedProcessingFilter() throws Exception {
appPreAuthenticatedProcessingFilter filter = new appPreAuthenticatedProcessingFilter(iUserIdentityService, genericDataService);
filter.setAuthenticationManager(super.authenticationManagerBean());
filter.setContinueFilterChainOnUnsuccessfulAuthentication(false);
filter.setCheckForPrincipalChanges(true);
return filter;
}
/**
* Uses JEE pre-authentication filter, that assumes that the user has been
* pre-authenticated into the container.
*/
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/health/e2e").permitAll()
.and()
.addFilter(appPreAuthenticatedProcessingFilter())
.authorizeRequests()
.anyRequest().authenticated()
.and()
.authenticationProvider(preauthAuthProvider())
.csrf()
.csrfTokenRepository(this.csrfTokenRepository())
.and()
.httpBasic().disable();
// Disabling the CSRF implementation, if "csrf.disabled" property set to "true"
// in System Properties.
if (!StringUtils.isEmpty(profileType) && profileType.equals("local")) {
http.csrf().disable();
}
}
/**
* Method to ignore web security for urls
*/
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("*/api-docs", "/configuration/ui", "/swagger-resources/**", "/configuration/**", "/swagger-ui.html", "/webjars/**", "/health/e2e", "*/health/e2e", "**/health/e2e");
}
/**
* Method to to return CsrfTokenRepository
*/
private CsrfTokenRepository csrfTokenRepository() {
CookieCsrfTokenRepository tokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
tokenRepository.setCookiePath("/");
return tokenRepository;
}
}
Custom authentication filter looks like
#Slf4j
public class AppPreAuthenticatedProcessingFilter extends AbstractPreAuthenticatedProcessingFilter {
private IUserIdentityService iUserIdentityService;
private GenericDataService genericDataService;
public AppPreAuthenticatedProcessingFilter(IUserIdentityService iUserIdentityService, GenericDataService genericDataService) {
this.iUserIdentityService = iUserIdentityService;
this.genericDataService = genericDataService;
}
#Override
protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) {
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
return iUserIdentityService.getUserName();
}
#Override
protected Object getPreAuthenticatedCredentials(HttpServletRequest request) {
return AppConst.DEFAULT_CREDENTIAL;
}
}
I am not sure why /health/e2e is secured?
P.S. I tried removing #Bean from pre auth filter but in that case, filter never gets called for any request.
The problem is two fold
Your security setup contains an error
The filter is added to the regular filter bean as well.
With your current security setup the AppPreAuthenticatedProcessingFilter is added only to the /health/e2d URL. Your attempt to fix something has actually broken things instead.
Your configuration should be something along the lines of
http.authorizeRequests().anyRequest().authenticated()
.and().httpBasic()
.and().authenticationProvider(preauthAuthProvider())
.csrf().csrfTokenRepository(this.csrfTokenRepository())
.and().addFilterBefore(appPreAuthenticatedProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
// in System Properties.
if (!StringUtils.isEmpty(profileType) && profileType.equals("local")) {
http.csrf().disable();
}
Spring Boot will by default register an javax.servlet.Filter in the normal filter chain, to disable this you need to add a FilterRegistrationBean to disable this.
#Bean
public FilterRegistrationBean<AppPreAuthenticatedProcessingFilter> preAuthenticationFilterRegistrationBean(AppPreAuthenticatedProcessingFilter filter) {
FilterRegistrationBean<AppPreAuthenticatedProcessingFilter> frb = new FilterRegistrationBean<>(filter);
frb.setEnabled(false);
return frb;
}

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("/**");
}
};
}
}

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