I am trying to send a request with an authorization header with Angular to a Spring backend.
export class TokenInterceptor implements HttpInterceptor{
constructor(public sharedService : SharedService){}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const jwtToken = this.sharedService.getJwtToken();
if(jwtToken){
req = this.addToken(req, jwtToken)
}
return next.handle(req)
}
addToken(req: HttpRequest<any>, jwtToken: any){
return req.clone({
headers: req.headers.append('Authorization', 'Bearer ' + jwtToken)
});
}
}
This is what my interceptor looks like. If I try to console.log() the authorization header before returning the next().handle , I can see the correct token inside the request. The problem is that the backend instead recieves a null Authorization header.
Inside by backend I have a doFilterInternal() method that filters any request and gets the Authentication header.
I don't think the problem is inside this filter because the request sent with Postman are handled correctly.
I have already enabled CORS on my backend
#Override
public void addCorsMappings(CorsRegistry corsRegistry){
corsRegistry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("*")
.allowedHeaders("*")
.exposedHeaders("Authorization")
.allowCredentials(true)
.maxAge(3600L);
}
I believe token is not set, because headers property is read-only.
Try to use setHeaders property of clone method argument:
addToken(req: HttpRequest<any>, jwtToken: any){
return req.clone({
setHeaders: { Authorization: 'Bearer ' + jwtToken }
});
}
After banging my head against the wall for several hours I found the solution.
When creating a class and implementing the WebMvcConfigurer (to enable CORS) this is right and it SHOULD work.
public class WebConfig implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry corsRegistry){
corsRegistry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("*")
.allowedHeaders("*")
.exposedHeaders("Authorization")
.allowCredentials(true)
.maxAge(3600L);
}
}
BUT this isn't enough, since I had to enable CORS also into the security config chain
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{
http.cors().and().csrf().disable();
return http.build();
}
by tiping http.cors()
Related
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/
I'm going through an issue with Axios and a Java API.
I'm using this in my
Application.java:
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedMethods("*");
}
};
}
It worked great until today because I needed to use a Basic Auth in the provided api my get request like this (it works great in both PostMan and Chrome) :
axios({
method: 'get',
url: 'http://localhost:8180/unv-api/api/rubriques/1527/appli',
withCredentials: true,
headers:{ Authorization:'Basic '+btoa('admin' + ':' + 'password')}
})
with axios, an ACTION method is performed and returns a 401 error + a CORS error.
Any idea? Thanks.
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.
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?
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");
}
};
}
}