Spring Security+ReactJS - Preflight Request - java

I am working on a project with FE as react and BE as Springboot. I am trying to add FE to the application. After registration, I have tried to login to the application. After successful login, we get JWT Token. For that we need to send username, password and grant type in body and Basic authentication details in header. The is
var postData = {
username: a,
password: b,
grant_type:'c'
};
let axiosConfig = {
headers: {
'Content-Type': 'application/json;charset=UTF-8',
"Access-Control-Allow-Origin": "*",
"Accept": "application/json" ,
"Authorization":"Basic" + " " +base64.encode("U" + ":" + "p")
}
};
axios.post('http://localhost:9003/login/token', postData,axiosConfig)
.then((res) => {
console.log("RESPONSE RECEIVED: ", res);
})
.catch((err) => {
console.log("AXIOS ERROR: ", err);
})
When I run this program, I got the error,
Access to XMLHttpRequest at 'http://localhost:9003/login/token' from origin 'http://localhost:3000' 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.
And My Spring boot Code is
#Override
#CrossOrigin(origins = "*", allowedHeaders = "*") public void
configure(HttpSecurity http) throws Exception {
http.cors().and().exceptionHandling() .authenticationEntryPoint( (request,
response, authException) ->
response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and().authorizeRequests().antMatchers("/*").authenticated().and().httpBasic();
http.exceptionHandling().authenticationEntryPoint(new
CustomAuthenticationEntryPoint());
}
#Override
#CrossOrigin(origins = "*",allowedHeaders="*")
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.pathMapping("/oauth/token", "/login/token").tokenStore(tokenStore())
.tokenEnhancer(jwtAccessTokenConverter()).authenticationManager(authenticationManager)
.userDetailsService(userDetailsService);
Anybody know how to solve this?

As per MDN docs, the pre-flight response headers for a credentialed request should include a specific set of Access-Control-Allow-Origin and not a wild-card * .The cors config for the service can be setup by extending the WebSecurityConfigurerAdapter.
We faced a similar challenge with our spring-boot project and the following configuration helped overcome the cors failure
#EnableWebSecurity
public class DefaultAuthConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors(cors -> {
CorsConfigurationSource cs = resources -> {
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(List.of("http://localhost:3000","http://localhost:3001"));
corsConfiguration.setAllowedMethods(List.of("POST", "GET", "PUT", "DELETE", "OPTIONS"));
corsConfiguration.setAllowedHeaders(List.of("Authorization",
"Content-Type",
"X-Requested-With",
"Accept",
"X-XSRF-TOKEN"));
corsConfiguration.setAllowCredentials(true);
return corsConfiguration;
};
cors.configurationSource(cs);
});
}
}

This class below to config CORS policy it worked for me.And i think your poblem is #CrossOrigin should be located in controller class.
#Configuration
public class AppConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**");
}
}
If you want more config follow this link https://spring.io/guides/gs/rest-service-cors/

Related

CORS policy: Cannot parse Access-Control-Allow-Headers response header field in preflight response

I have a CORS issue with my angular & spring boot app. I don't understand what it is.
CORS policy error
My angular code :
login(user : User)
{
return this.http.post<User>(this.apiURL+'/login', user , {observe:'response'});
}
and to the server side, i allow cross origin like that
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods",
"GET,HEAD,OPTIONS,POST,PUT,DELETE");
response.addHeader("Access-Control-Allow-Headers", "Access-Control-Allow-Headers," +
"Origin, Accept, X - Requested - With, Content - Type, Access - Control - Request - Method," +
"Access - Control - Request - Headers, Authorization");
response.addHeader("Access-Control-Expose-Headers", "Authorization, Access-ControlAllow-Origin,Access-Control-Allow-Credentials ");
if (request.getMethod().equals("OPTIONS")) {
response.setStatus(HttpServletResponse.SC_OK);
return;
}
In your Spring application you should have a security configuration file, you can call it SecurityConfig.java.
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors()
.configurationSource(corsConfigurationSource())
// other config here
}
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration.applyPermitDefaultValues());
return source;
}
}
Try this and see if it works.
In your Spring Controller you should also have this annotation before your controller class #CrossOrigin(origins = "http://localhost:4200")
Check if 4200 is the port you use for your Angular app, if not change it with your port.
#CrossOrigin(origins = "http://localhost:4201")
#RestController
public class YourController {
....

Getting error: 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed

I tried to make a connection between my Angular frontend and a REST Endpoint in Java / Spring (which I didn't developed and don't know so well). By GET, all works. By POST, I receive the message in the terminal
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.
and, in the Network tab from the dev instruments, an error 403 on OPTIONS method
Request Method: OPTIONS
Status Code: 403
Remote Address: xx.xx.xx.xx:xxxx
Referrer Policy: strict-origin-when-cross-origin
So, I found this case after several searching in internet and the cause is CORS settings: usually, in this scenario, a OPTIONS call is sent before a POST; but, due to CORS, an OPTIONS call is not allowed. So, I tried to set this row on my controller
#CrossOrigin(origins = "*", methods = {RequestMethod.OPTIONS, RequestMethod.GET, RequestMethod.POST, RequestMethod.PUT, RequestMethod.DELETE})
This time the error changed in
Multiple CORS header 'Access-Control-Allow-Origin' not allowed
But the code I added is the only similar to #CrossOrigin, I dind't found others similar.
So, in according to the post CORS issue - No 'Access-Control-Allow-Origin' header is present on the requested resource, I tried the following solution:
#Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
}
}
and
#Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
// http.csrf().disable();
http.cors();
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(ImmutableList.of("*"));
configuration.setAllowedMethods(ImmutableList.of("HEAD",
"GET", "POST", "PUT", "DELETE", "PATCH"));
// 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"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
But this time the error I see in the console became
has been blocked by CORS policy: The 'Access-Control-Allow-Origin' header contains multiple values '*, *', but only one is allowed.
So, this is the last point I reached. How can I solve this last error about multiple values? Each time I work on this, I do a step ahead and the error changes but it is still there.
Just add this to your WebSecurityConfigurerAdapter
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/*#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
}*/ not needed
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// by default uses a Bean by the name of corsConfigurationSource
.cors(withDefaults())
...
}
#Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://localhost:5000"));// if your front end running on localhost:5000
configuration.setAllowedMethods(Arrays.asList("GET","POST"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
Make sure you don't have any other filter or annotation for cors except the code above
Spring CORS section in Spring Security documentation.
If you are not using Spring Security:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedOrigins("*","http://localhost:5000");// list all domains
}
};
}
}

Getting a Post 403 Forbidden with Spring Boot (VueJS and Axios Frontend)

I've been having an issue with CORS and I have tried everything I could find on Stack Overflow and basically anything that I found on Google and have had no luck.
So I have user authentication on my backend and I have a login page on my frontend. I hooked up the login page with Axios so I could make a post request and tried to login but I kept getting errors like "Preflight request" so I fixed that then I started getting the "Post 403 Forbidden" error.
It appeared like this:
POST http://localhost:8080/api/v1/login/ 403 (Forbidden)
Even trying to login using Postman doesn't work so something is clearly wrong. Will be posting class files below
On my backend, I have a class called WebSecurityConfig which deals with all the CORS stuff:
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private UserDetailsServiceImpl userDetailsService;
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("GET", "POST", "HEAD", "PUT", "DELETE", "OPTIONS");
}
};
}
#Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("*"); // TODO: lock down before deploying
config.addAllowedHeader("*");
config.addExposedHeader(HttpHeaders.AUTHORIZATION);
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().disable();
http
.cors()
.and()
.csrf().disable().authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/h2/**").permitAll()
.antMatchers(HttpMethod.POST, "/api/v1/login").permitAll()
.anyRequest().authenticated()
.and()
// We filter the api/login requests
.addFilterBefore(new JWTLoginFilter("/api/v1/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class);
// And filter other requests to check the presence of JWT in header
//.addFilterBefore(new JWTAuthenticationFilter(),
// UsernamePasswordAuthenticationFilter.class);
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// Create a default account
auth.userDetailsService(userDetailsService);
// auth.inMemoryAuthentication()
// .withUser("admin")
// .password("password")
// .roles("ADMIN");
}
}
On our frontend which is written in VueJS and using Axios to make the call
<script>
import { mapActions } from 'vuex';
import { required, username, minLength } from 'vuelidate/lib/validators';
export default {
data() {
return {
form: {
username: '',
password: ''
},
e1: true,
response: ''
}
},
validations: {
form: {
username: {
required
},
password: {
required
}
}
},
methods: {
...mapActions({
setToken: 'setToken',
setUser: 'setUser'
}),
login() {
this.response = '';
let req = {
"username": this.form.username,
"password": this.form.password
};
this.$http.post('/api/v1/login/', req)
.then(response => {
if (response.status === 200) {
this.setToken(response.data.token);
this.setUser(response.data.user);
this.$router.push('/dashboard');
} else {
this.response = response.data.error.message;
}
}, error => {
console.log(error);
this.response = 'Unable to connect to server.';
});
}
}
}
</script>
So when I debugged via Chrome's tools (Network), I noticed that the OPTIONS request goes through as shown below:
Here is a picture of the POST error:
Here is another class which handles the OPTIONS request (JWTLoginFilter as referenced in the WebSecurityConfig):
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
public JWTLoginFilter(String url, AuthenticationManager authManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authManager);
}
#Override
public Authentication attemptAuthentication(
HttpServletRequest req, HttpServletResponse res)
throws AuthenticationException, IOException, ServletException {
AccountCredentials creds = new ObjectMapper()
.readValue(req.getInputStream(), AccountCredentials.class);
if (CorsUtils.isPreFlightRequest(req)) {
res.setStatus(HttpServletResponse.SC_OK);
return null;
}
return getAuthenticationManager().authenticate(
new UsernamePasswordAuthenticationToken(
creds.getUsername(),
creds.getPassword(),
Collections.emptyList()
)
);
}
#Override
protected void successfulAuthentication(
HttpServletRequest req,
HttpServletResponse res, FilterChain chain,
Authentication auth) throws IOException, ServletException {
TokenAuthenticationService
.addAuthentication(res, auth.getName());
}
}
When you configure Axios, you can simply specify the header once and for all:
import axios from "axios";
const CSRF_TOKEN = document.cookie.match(new RegExp(`XSRF-TOKEN=([^;]+)`))[1];
const instance = axios.create({
headers: { "X-XSRF-TOKEN": CSRF_TOKEN }
});
export const AXIOS = instance;
Then (here I assume you use SpringBoot 2.0.0, while it should work also in SpringBoot 1.4.x onward) in your Spring Boot application you should add the following security configs.
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// CSRF Token
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// you can chain other configs here
}
}
In this way, Spring will return the token as a cookie in the response (I assume you do a GET first) and you will read it in the AXIOS configuration file.
You should not disable CSRF as per Spring Security documentation except, few special cases. This code will put the CSRF header to VUE. I used vue-resource.
//This token is from Thymeleaf JS generation.
var csrftoken = [[${_csrf.token}]];
console.log('csrf - ' + csrftoken) ;
Vue.http.headers.common['X-CSRF-TOKEN'] = csrftoken;
Hope this helps.
Axios will, by default, handle the X-XSRF-TOKEN correctly.
So the only action is to configure the server, like JeanValjean explained:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
// CSRF Token
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// you can chain other configs here
}
}
Axios will automatically send the correct token in the request headers, so there's no need to change the front-end.
I had the same kind of problem where a GET request was working, and yet a POST request was replied with status 403.
I found that for my case, it was because of the CSRF protection enabled by default.
A quick way to make sure of this case is to disable CSRF:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// …
#Override
protected void configure(HttpSecurity http) throws Exception {
// …
http.csrf().disable();
// …
}
// …
}
More information on Spring-Security website.
Mind that disabling CSRF isn't always the correct answer as it is there for security purpose.

Basic authentification with spring and angular2 (How to set XSRF-TOKEN and JSESSIONID)

So I have a spring server in which I have implemented the following configuration:
#Configuration
#EnableWebSecurity
#Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static String REALM="Authentication";
#Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("cris").password("123").roles("ADMIN");
auth.inMemoryAuthentication().withUser("felix").password("felix123").roles("USER");
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
httpBasic().and()
.authorizeRequests()
.antMatchers("/user", "/vehicles", "/signin").permitAll()
.anyRequest().authenticated().and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
}
I have the following interface
#RequestMapping("logs")
public interface LogController {
#RequestMapping(value = "", method = RequestMethod.GET)
ResponseEntity getLogs();
}
And it's implementation:
#CrossOrigin(origins = "*", exposedHeaders = {"x-auth-token", "x-requested-with"}, allowedHeaders="*", allowCredentials = "true")
#RestController( )
public class LogControllerImpl implements LogController {
#Autowired
LogService logService;
//Get all logs
public ResponseEntity getLogs() {
List<LogEntityDTO> allLogs = logService.getAllLogs();
if (allLogs == null)
return ResponseEntity.notFound().build();
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("authenticated", "you");
return ResponseEntity.ok(allLogs);
}
In angular2 I make a request as following:
sendAuthentification(credentials: string): Observable {
var headers = new Headers();
headers.append('Authorization', 'Basic ' + btoa('cris:123'));
headers.append('Content-Type', 'application/x-www-form-urlencoded');
headers.append('withCredentials', 'true');
return this.http.get(this.requestService.getPath() + "/logs", {headers});
}
The problem is that when I make the request from Angular2 the response has the following headers (pragma, content-type, cache control, expires ) :
But in reality the response headers from the server are the following:
The expected behaiviour would be the for the JSESSIONID and XSRF-TOKEN to be saved automatically in browser as cookies, but it does not do that.
And the problem is that with angular2 I can not access the Get-Cookie header to try to manually save the cookies.
NOTE: If I try to make the request from the browser directly (without the angular2 app) the browser stores automatically the JSESSIONID and XSRF-TOKEN as cookies.
So is this problem an angular2 problem or a spring server configuration problem? And how can get the JSESSIONID and XSRF-TOKEN from angular2?

Spring boot & security - cors

According to the spring boot documentation I added bean
#Bean
WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("http://localhost:3000");
}
};
}
to enable global access from localhost:3000 , which is my frontend app.
I also use spring security, so if user enter localhost:8080/something he is redirected to login page ( if not logged ) . The problem is that this global cors configuration doesn't work.
I have simple controller which returns
List<String>
On the other hand I have angular service, which is responsible for making a get request to the server. It looks like this :
this.http.get("http://localhost:8080/words", {
headers: new Headers({
'Authorization': 'Basic ' + btoa('login:password')
})
}).map((res:Response) => res.json())
.subscribe(
data => { this.words = data},
err => console.error('Error : ' + err),
() => console.log('done')
);
and as a result I can see in google chrome console :
XMLHttpRequest cannot load http://localhost:8080/words. Response for preflight is invalid (redirect)
How can I fix this ?
This is because your front end application makes an OPTIONS HTTP before actual data transfer happens. Try adding this configuration to your spring project:
#Configuration
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Value("${angular}")
private String angularOrigin;
#Bean
public WebMvcConfigurer corsConfigurer(){
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedOrigins(angularOrigin)
.allowedHeaders("Authorization", "Cache-Control", "Content-Type", "Accept", "X-Requested-With", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Origin")
.exposedHeaders("Access-Control-Expose-Headers", "Authorization", "Cache-Control", "Content-Type", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Origin")
.allowedMethods("PUT","GET","POST","DELETE","OPTIONS");
}
};
}
}

Categories

Resources