Angular cannot GET request on Spring boot with Keycloak throws 403 - java

I created spring boot service using with Keycloak. And I implemented login end-point. When I logged in the service over Postman, I can connect to keycloak and take token. And I can use this token on other request calls succesfully. When I logged in it with angular I still the token but request returns 403 error every time in spring boot. When I use the same token in postman, there is no problem.
KeycloakSecurityConfig
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class KeycloakSecurityConfig extends KeycloakWebSecurityConfigurerAdapter {
#Value("${server.servlet.context-path}")
public String contextPath;
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure(http);
http.authorizeRequests()
.antMatchers("/v2/api-docs",
"/configuration/ui",
"/swagger-resources/**",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**",
"/auth/login").permitAll()
.anyRequest().authenticated()
.and().cors()
.and().csrf().disable();
}
#Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) {
KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
auth.authenticationProvider(keycloakAuthenticationProvider);
}
#Bean
#Override
protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl());
}
#Bean
#Override
protected KeycloakAuthenticationProcessingFilter keycloakAuthenticationProcessingFilter() throws Exception {
KeycloakAuthenticationProcessingFilter filter = new KeycloakAuthenticationProcessingFilter(this.authenticationManagerBean());
filter.setSessionAuthenticationStrategy(this.sessionAuthenticationStrategy());
filter.setAuthenticationFailureHandler(new CustomKeycloakAuthenticationFailureHandler());
return filter;
}
#Bean
public KeycloakConfigResolver keycloakConfigResolver() {
return new KeycloakSpringBootConfigResolver();
}
}
application.yaml
keycloak:
realm: <realm_name>
auth-server-url: http://localhost:8287/auth/
resource: mm-service-1
credentials:
secret: bla-bla-bla
use-resource-role-mappings: false
cors: true
bearer-only: true
enabled: true
public-client: true
JwtInteceptor
#Injectable()
export class JwtInterceptor implements HttpInterceptor {
constructor(private accountService: AccountService) {
}
intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// add account header with jwt if user is logged in and request is to the api url
const user = this.accountService.userValue;
const isLoggedIn = user && user.token;
const isApiUrl = request.url.startsWith(Constants.SERVER_BASE_PATH);
console.log('REQUEST: ', request);
if (isLoggedIn && isApiUrl) {
request = request.clone({
setHeaders: {
Authorization: `Bearer ${user.token}`,
'withCredentials': `true`,
'Access-Control-Allow-Credentials': `true`,
}
});
}
console.log('NEW REQUEST: ', request);
return next.handle(request);
}
}
Get request on Angular
getListMachine(): void {
this.machineApi.listMachine(null, null, null, null, null, 'response')
.pipe(takeUntil(this.unsubscribeAll))
.subscribe((res: any) => {
const resHeader = res.headers;
this.totalRows = resHeader.get('X-Total-Count');
this.machineInfos = res.body;
}, (err) => {
console.error('Err:', err);
});
}
Origin Config
#Configuration
public class ApiOriginCorsConfigurer {
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry
.addMapping("/**")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH", "OPTIONS")
.allowedOriginPatterns("*")
.exposedHeaders(
"Content-Type",
"Accept",
"Authorization",
"link",
"content-range",
"x-total-count",
"location",
"etag",
"Access-Control-Allow-Origin",
"Access-Control-Allow-Credentials")
.allowedHeaders("*")
.allowCredentials(true)
;
}
};
}
}
Postman request
Angular Request

Finally, I found problem. I disabled cors of the keycloak and it works. But When I want to use cors, I don't know what to do.

If i were you i will add this line logging.level.org.springframework.security=debug to application.properties and then observe the behavior of your app with CORS or without it.
Hope that will help you.

If the same token works in postman; probably angular application is the problem.
Check your "Authorization" header in request with angular

Related

How to Fix the problem of No 'Access-Control-Allow-Origin' in a SpringBoot+Vuejs app build with Gradle plugin

in this app im doing when i work in local with a h2-embeded database , the problem doesnt shows up, and only appears once i deploy to heroku and create a Postgresql database which use all the data from h2.
Literall y the app doesnt recognize the headers brought form the request , throwing as respopnse
Access to fetch at 'https://xxxxxx.herokuapp.com/bikes/all' from origin 'http://localhost:8082'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the
requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors'
to fetch the resource with CORS disabled.
The dependencies used for spring are:
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-data-rest'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
//runtimeOnly 'com.h2database:h2'
compile("org.postgresql:postgresql")
......
and in my app properties in order to connect with that database once is deployed to Heroku , i initialize this:
### ----- COMMENT THIS PART TO WORK IN LOCAL! FROM HERE... -----
spring.jackson.serialization.fail-on-empty-beans=false
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.datasource.driverClassName=org.postgresql.Driver
spring.datasource.url=${JDBC_DATABASE_URL}
spring.datasource.username=${JDBC_DATABASE_USERNAME}
spring.datasource.password=${JDBC_DATABASE_PASSWORD}
spring.datasource.testWhileIdle=true
spring.datasource.validationQuery=SELECT 1
spring.jpa.show-sql=true
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=update
Then on my application class , i do enable all referring to web security included the allowed origins which i declared as (*)
#Configuration
#EnableWebSecurity
class WebSecurityConfiguration extends GlobalAuthenticationConfigurerAdapter {
#Autowired
UserRepository userRepository;
#Override
public void init(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(inputName-> {
User user = userRepository.findByUserName(inputName);
if (user != null) {
return new org.springframework.security.core.userdetails.User(user.getUserName(), user.getUserPassword(),
AuthorityUtils.createAuthorityList("USER"));
} else {
throw new UsernameNotFoundException("Unknown user: " + inputName);
}
});
}
}
#Configuration
#EnableWebSecurity
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors();///de heroku tambien
http.authorizeRequests()
.antMatchers("/bikes/all").permitAll()
.antMatchers("/bikes/user/register").permitAll()
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/rest/**").hasAuthority("ADMIN")
.antMatchers("/**").hasAuthority("USER")
.anyRequest().fullyAuthenticated();
http.formLogin()
.usernameParameter("name")
.passwordParameter("password")
.loginPage("/bikes/login");
http.logout().logoutUrl("/api/logout");
http.csrf().disable();
http.exceptionHandling().authenticationEntryPoint((req, res, exc) -> res.sendError(HttpServletResponse.SC_UNAUTHORIZED));
http.formLogin().successHandler((req, res, auth) -> clearAuthenticationAttributes(req));
http.formLogin().failureHandler((req, res, exc) -> res.sendError(HttpServletResponse.SC_UNAUTHORIZED));
http.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
http.headers().frameOptions().sameOrigin();
}
private void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session != null) {
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("HEAD",
"GET", "POST", "PUT", "DELETE", "PATCH"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
Even as alternative isntead of (*), i specified the exact origin might be allowed , iin this case
http://localhost:8082(cause im doing the request of my front from local)
But neither works
In my front end calling the action , either put the header specifying the origing allowed (*), or localhot :8082, or even none , and still the error persists
const url=xxxxxxxxx.herokuapp.com/
fetchAllBikesJson({ commit}) {
fetch(url +"bikes/all", {
credentials: "include",
Option1
headers: {
"Access-Control-Allow-Origin": "*"
},
Option 2
headers: {
"Access-Control-Allow-Origin": "http://localhost:8082"
},
Option 2
NO HEADERS
method: "GET",
})
.then((response) => {
return response.json();
})
.then((response) => {
// console.log(response);
response;
commit("setAllBikesJson", response);
})
.catch((error) => {
// console.log("error", error);
error;
});
},
Also adding a view for the error from my wen console as well as one from my web console in the Network item
Any help about about his issue .thanks in advance!!!!
Try defining the configuration instead of bean cors configuration
#Configuration
public class CorsConfiguration implements WebMvcConfigurer {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH", "OPTIONS");
}
}
Also I noticed that you have in your code like this:
http.cors();
....
...http.etc()
Instead it should be http.cors().and() // May be this is your issue
Please check this

Request header field ack is not allowed by Access-Control-Allow-Headers in preflight response even the server already allowing it

I know this question already spreading all over the stackoverflow, but this one quite different.
I got an error when trying to hit a java API using axios, with below way,
axios
.get("http://127.0.0.1:8090/api/v1/homescreen")
.then(response => {
console.log(response);
})
.catch(err => {
console.log(err);
});
}
Axios configuration were
axios.defaults.headers.common['Content-Type'] =
'application/x-www-form-urlencoded';
axios.defaults.headers.common['Authorization'] = 'Bearer
eyJhbGciOiJIUzI1N';
axios.defaults.headers.common['Ack'] = 'MTIwNzIwMjBL==' ;
Already tried with axios.defaults.headers.common['Content-Type'] = application/json; and got the same error.
The error was
Access to XMLHttpRequest at 'http://127.0.0.1:8090/api/v1/homescreen'
from origin 'http://localhost:8080' has been blocked by CORS policy:
Request header field ack is not allowed by
Access-Control-Allow-Headers in preflight response.
Now in the server side i already configure it like this
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true)
#Order(Ordered.LOWEST_PRECEDENCE)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
public SecurityConfig() {
super();
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.cors().configurationSource(corsConfigurationSource()).and().csrf().disable()
.exceptionHandling().and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers(HttpMethod.GET, "/api/v1/cache/**").permitAll()
.antMatchers("/api/v1/**").authenticated().and().authorizeRequests()
.and().httpBasic();
}
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("Access-Control-Allow-Headers",
"Access-Control-Allow-Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers",
"Origin", "Cache-Control", "Content-Type", "Authorization", "Ack", "ack", "ackwhatever", "goddamnack"));
configuration.setAllowedMethods(Arrays.asList("DELETE", "GET", "POST", "PATCH", "PUT"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS).antMatchers("/api/v1/login/*");
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
even in the spring filter i put the header allowance
public class HttpRequestAuditFilter implements Filter {
private static final Logger LOG = LoggerFactory.getLogger("access");
private static final int MAX_PAYLOAD_LENGTH = 10000;
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if ((request instanceof HttpServletRequest)
&& !(request instanceof ContentCachingRequestWrapper)) {
request = new ContentCachingRequestWrapper((HttpServletRequest) request);
}
HttpServletResponse responseQ = (HttpServletResponse) response;
HttpServletRequest requestQ = (HttpServletRequest) request;
try {
responseQ.setHeader("Access-Control-Allow-Origin", "*");
responseQ.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
responseQ.setHeader("Access-Control-Max-Age", "3600");
responseQ.setHeader("Access-Control-Allow-Headers", "x-requested-with, authorization, content-type, ack, Ack");
responseQ.setHeader("Access-Control-Expose-Headers", "x-requested-with, authorization, content-type, ack, Ack");
if ("OPTIONS".equalsIgnoreCase(requestQ.getMethod())) {
responseQ.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(requestQ, responseQ);
}
} finally {
if (requestQ instanceof HttpServletRequest) {
performRequestAudit((HttpServletRequest) requestQ);
}
}
}
public void performRequestAudit(HttpServletRequest httpRequest) {
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(httpRequest, ContentCachingRequestWrapper.class);
String payload = "";
if (wrapper != null) {
byte[] requestBuffer = wrapper.getContentAsByteArray();
if (requestBuffer.length > 0) {
int length = Math.min(requestBuffer.length, MAX_PAYLOAD_LENGTH);
try {
payload = new String(requestBuffer,
0, length, wrapper.getCharacterEncoding());
} catch (UnsupportedEncodingException unex) {
payload = "[Unsupported-Encoding]";
}
}
}
LOG.trace("{}|{}", payload, wrapper.getHeaderNames());
}
}
When i try with curl i got the response even in the mobile app its work perfectly, only with browser that got error (the browser itself already --disable-web-security).
Any help and explanation will be apreciated.
Instead of providing the cors configuration class manually, let it be a bean and let spring take it up automatically, also remove the lowest order so that your configuration does not get overridden. Also remove the spring filter that you created to manually add the headers in the response as when configured correctly spring security will automatically add those headers in response. #EnableGlobalMethodSecurity can be used along with any #Configuration annotated classes but try like :
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
public SecurityConfig() {
super();
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.cors().and().csrf().disable()
.exceptionHandling().and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers(HttpMethod.GET, "/api/v1/cache/**").permitAll()
.antMatchers("/api/v1/**").authenticated().and().authorizeRequests()
.and().httpBasic();
}
#Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowCredentials(true);
configuration.setAllowedHeaders(Arrays.asList("Access-Control-Allow-Headers",
"Access-Control-Allow-Origin", "Access-Control-Request-Method", "Access-Control-Request-Headers",
"Origin", "Cache-Control", "Content-Type", "Authorization", "Ack", "ack", "ackwhatever", "goddamnack"));
configuration.setAllowedMethods(Arrays.asList("DELETE", "GET", "POST", "PATCH", "PUT"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
#Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring().antMatchers(HttpMethod.OPTIONS).antMatchers("/api/v1/login/*");
}
#Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
#Bean
#Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
If this doesn't work separate out the method level security configurations to another configuration class and keep the web security as a separate connfiguration.

Spring Security And Angular 6 HTTPS requests

my backend app in spring boot and secured with ssl. I used OAuth2 facebook login. Also the frontend app in Angular 7 and secured by ssl. My problem is sending requests Angular to my Spring boot App. All apps is https.
P.S. All works if i add url to webSecurity.ignoring(). and not secure my backend. i think some problem with security and HTTPS requests. THANKS FOR HELP.
BACKEND
SecurityConfig.java
#RestController
#CrossOrigin(origins = "https://192.168.1.106:4400")
#Configuration
#Order(1000)
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
UserRepo userRepo;
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers(HttpMethod.GET, "/unauth/**").permitAll()
.antMatchers(HttpMethod.POST, "/unauth/upload").permitAll()
.antMatchers(HttpMethod.POST, "/api/**").authenticated()
.antMatchers(HttpMethod.PUT, "/api/**").authenticated()
.antMatchers(HttpMethod.DELETE, "/api/**").authenticated()
.antMatchers(HttpMethod.GET, "/api/**").authenticated()
.anyRequest().permitAll()
.and().logout().logoutSuccessUrl("/").permitAll();
}
#Override
public void configure(WebSecurity webSecurity) {
webSecurity.ignoring().antMatchers(HttpMethod.GET, "/unauth/**");
webSecurity.ignoring().antMatchers(HttpMethod.POST, "/unauth/**");
}
webSecurity.ignoring().antMatchers(HttpMethod.POST, "/unauth/**");
}
SomeRestController.java
#RestController
#CrossOrigin(origins = "https://192.168.1.106:4400")
#RequestMapping ("/api")
public class ProductService {
#Autowired
private ProductRepo productRepo;
#CrossOrigin(origins = "https://192.168.1.106:4400")
#GetMapping("/products")
public List<Product> getProducts(){
return productRepo.findAll();
}
SpringBootApplication.java
#SpringBootApplication
#EnableOAuth2Sso
#CrossOrigin(origins = {"https://192.168.1.106:4400"}, allowCredentials = "false")
public class MongoTestApplication {
public static void main(String[] args) {
SpringApplication.run(MongoTestApplication.class, args);
}
}
FRONTEND
SomeComponent.html
< button (click)="makeRequest()"> MAKE REQUEST < /button >
SomeComponent.ts
val:any = {};
makeRequest(){
this.http.get("https://localhost:8443/api/products").subscribe(value => {this.val = value; console.log(this.val.key)});
}
ERROR
error in browser
Access to XMLHttpRequest at 'https://localhost:8443/api/brands' from origin 'https://192.168.1.106:4400' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
core.js.pre-build-optimizer.js:15714 ERROR n {headers: t, status: 0, statusText: "Unknown Error", url: "https://localhost:8443/api/brands", ok: false, …}
Edit your main class as below and remove all #CrossOrigin from the controllers.
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
#SpringBootApplication
#EnableOAuth2Sso
public class MongoTestApplication {
public static void main(String[] args) {
SpringApplication.run(MongoTestApplication.class, args);
}
#SuppressWarnings("deprecation")
#Bean
public WebMvcConfigurer corsConfigurer()
{
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedMethods("GET", "PUT", "POST", "DELETE", "OPTIONS");
}
};
}
}

CORS error and 403 response with JWT token

I am trying to secure my Web Application via JWT token, but when I try to make a request from my Angular app (localhost:4200) to my Spring Boot app (localhost: 8080) I get the following error:
From the message alone I can see that it is a CORS issue, the problem is that I've already enabled requests from different origin at my back-end, and here is the code for it:
UPDATE: I've added OPTIONS into allowedMethods(), but the error remains the same.
#Configuration
public class AppConfiguration {
#Autowired
private Environment env;
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:4200")
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD","OPTIONS")
.allowedHeaders("Content-Type", "Date", "Total-Count", "loginInfo")
.exposedHeaders("Content-Type", "Date", "Total-Count", "loginInfo")
.maxAge(3600);
}
};
}
Here is the code from my Angular app as well :
baseUrl = 'http://localhost:8080/api/';
constructor(private http: Http) { }
private postaviHeadere() : RequestOptions{
let headers = new Headers();
console.log("***************** Set Headers *****************");
console.log('Getting token from local storage:');
console.log(localStorage.getItem('jwt_token'))
console.log("***********************************************");
headers.append('JWT_TOKEN', localStorage.getItem('JWT_TOKEN'));
let options = new RequestOptions({headers : headers});
console.log(options);
return options;
}
getUserByToken(): any {
return this.http.get(this.baseUrl + 'user/secured', this.postaviHeadere())
}
Create a java class "CorsFilterConfig" :
#Component
public class CorsFilterConfig extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "authorization, content-type, xsrf-token");
response.addHeader("Access-Control-Expose-Headers", "xsrf-token");
if ("OPTIONS".equals(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
filterChain.doFilter(request, response);
}
}
}
Then call it to your WebSecurityConfig :
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// don't create session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
.authorizeRequests()
// Un-secure H2 Database
.antMatchers("/h2-console/**/**").permitAll()
//whitelist swagger configuration
.antMatchers(
"/swagger-resources/**",
"/api/swagger-resources/**",
"/api/**",
"/null/**",
"/v2/api-docs/**",
"/webjars/springfox-swagger-ui/**",
"/"
).permitAll()
.anyRequest().authenticated();
httpSecurity.cors();
// Custom JWT based security filter
JwtAuthorizationTokenFilter authenticationTokenFilter =
new JwtAuthorizationTokenFilter(userDetailsService(), jwtTokenUtil, tokenHeader);
httpSecurity.addFilterBefore(new CorsFilterConfig(), ChannelProcessingFilter.class);
httpSecurity
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
You have to allow the OPTIONS method too:
#Configuration
public class AppConfiguration {
#Autowired
private Environment env;
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:4200")
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS")
.allowedHeaders("Content-Type", "Date", "Total-Count", "loginInfo")
.exposedHeaders("Content-Type", "Date", "Total-Count", "loginInfo")
.maxAge(3600);
}
};
}}
I've managed to fix the issue by adding jwt_token into configuration:
#Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurerAdapter() {
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:4200")
.allowedMethods("GET", "POST", "PUT", "DELETE", "HEAD","OPTIONS")
.allowedHeaders("Content-Type", "Date", "Total-Count", "loginInfo","jwt_token")
.exposedHeaders("Content-Type", "Date", "Total-Count", "loginInfo", "jwt_token")
.maxAge(3600);
}
};
}
Thank you all for your help!

Adding any options or headers on the Angular 2 http.post sends OPTIONS

I am trying to send token information back to the server over http.post(). If I remove the options from it it sends a POST but if I add them back on it sends OPTIONS which is rejected from the server code. I tried removing the "withCredentials" as well.
export class EntityService {
public entity: EntityModel;
private options: RequestOptions;
constructor( #Inject(Http) private http: Http, #Inject(AuthenticationService) authService) {
let headers = new Headers({ 'X-Authorization': 'Bearer ' + authService.token});
this.options = new RequestOptions({ headers: headers, withCredentials: true });
}
public store(entity: EntityModel): Observable<string> {
var request;
if (!entity.uuid) {
request = this.http.post("http://localhost:8080/api/entity", JSON.stringify(entity), this.options);
}
else {
request = this.http.put("http://localhost:8080/api/entity", JSON.stringify(fact), this.options);
}
return request.map((res: Response) => res.text());
}
}
My authentication service looks like this:
import { Injectable, Inject } from '#angular/core';
import { Http, Headers, Response } from '#angular/http';
import { Observable } from 'rxjs';
import 'rxjs/add/operator/map'
//http://jasonwatmore.com/post/2016/08/16/angular-2-jwt-authentication-example-tutorial
#Injectable()
export class AuthenticationService {
public token: string;
constructor(#Inject(Http) private http: Http) {
// set token if saved in local storage
var currentUser = JSON.parse(localStorage.getItem('currentUser'));
this.token = currentUser && currentUser.token;
}
login(username: string, password: string): Observable<boolean> {;
console.log("login...");
return this.http.post('http://localhost:8080/api/auth/login', JSON.stringify({ username: username, password: password }))
.map((response: Response) => {
// login successful if there's a jwt token in the response
let token = response.json() && response.json().token;
if (token) {
// set token property
this.token = token;
// store username and jwt token in local storage to keep user logged in between page refreshes
localStorage.setItem('currentUser', JSON.stringify({ username: username, token: token }));
// return true to indicate successful login
return true;
} else {
// return false to indicate failed login
return false;
}
});
}
logout(): void {
// clear token remove user from local storage to log user out
this.token = null;
localStorage.removeItem('currentUser');
}
}
Here is my Spring configuration:
#SpringBootApplication
public class SpringBootApp extends WebMvcConfigurerAdapter {
private boolean workOffline = true;
private boolean setupSchema = false;
private IGraphService graphService;
private DbC conf;
#Autowired
public SpringBootApp(IGraphService graphService, DbC conf)
{
this.graphService = graphService;
this.conf = conf;
}
public static void main(String[] args) throws Exception {
SpringApplication.run(SpringBootApp.class, args);
}
#Bean
public Filter caseInsensitiveRequestFilter() {
return new CaseInsensitiveRequestFilter();
}
#Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://localhost:3000")
.allowedMethods("GET", "PUT", "POST", "DELETE","OPTIONS");
}
#Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("http://localhost:3000");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
bean.setOrder(0);
return bean;
}
}
I really don't know what to do since I am following what is being said in Angular2 OPTIONS method sent when asking for http.GET and this is not a preflight request. I had this issue earlier with the wrong content-type.
The OPTIONS request is made by the browser alone. Angular is not involved at all.
"and this is not a preflight request." - it definitely is.
You need to configure your server to respond properly to the OPTIONS request or ensure that the Angular application is loaded from the same server (also same port) as where you make the request to.
The actual fix was due to two reasons:
improper CORS implementation - for more please have a look here: Spring 4/5 global CORS configuration doesn't work giving `No 'Access-Control-Allow-Origin' header is present on the requested resource`
Then when I was POSTing after the login I was getting error 415 Unsupported Media Type. After following the instructions here: POST JSON fails with 415 Unsupported media type, Spring 3 mvc
I added Content-Type and Accept header on my request and it fixed the problem. It seems that Content-Type is what was actually needed.
export class EntityService {
public entity: EntityModel;
private options: RequestOptions;
constructor( #Inject(Http) private http: Http, #Inject(AuthenticationService) authService) {
let headers = new Headers({
'X-Authorization': 'Bearer ' + authService.token,
'Content-Type': 'application/json'
});
this.options = new RequestOptions({ headers: headers, withCredentials: true });
}
public store(entity: EntityModel): Observable<string> {
var request;
if (!entity.uuid) {
request = this.http.post("http://localhost:8080/api/entity", JSON.stringify(entity), this.options);
}
else {
request = this.http.put("http://localhost:8080/api/entity", JSON.stringify(fact), this.options);
}
return request.map((res: Response) => res.text());
}
}
Using http post like this
import { Http, Headers, Response, Request } from '#angular/http';
let headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('X-Authorization', this.token);
headers.append('Authorization', 'Bearer ' + jwtToken);
return this.http.post(url, data, {headers})
.map(res => { console.log(res) })
.catch(err => { console.log(err) } );
Note this example returns an Observable that you can subscribe to. Also my example

Categories