How to enable CORS for Error Response in Spring MVC? - spring

I'm working on application where I use Spring MVC for the Back-end and Angular5 for the Front-end. I have been stuck with implementation of Auth2 security layer including Cross-Origin Resource Sharing. My CORS filter implementation looks like this:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
#WebFilter("/*")
public class WebSecurityCorsFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse res = (HttpServletResponse) response;
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
res.setHeader("Access-Control-Max-Age", "3600");
res.setHeader("Access-Control-Allow-Headers", "Authorization, Content-Type, Accept, x-requested-with, Cache-Control");
if ("OPTIONS".equalsIgnoreCase(((HttpServletRequest) request).getMethod())) {
res.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(request, res);
}
}
#Override
public void destroy() {
}
}
I works almost properly, I'm able to obtain access_token and use it to get protected data from ResourcesServer:
{"access_token":"4fcef1f8-4306-4047-9d4d-1c3cf74ecc44","token_type":"bearer","refresh_token":"397016eb-dfb0-4944-a2e0-50c3bd07c250","expires_in":29,"scope":"read
write trust"}
Browser console screenshot
The problem starts when I try to handle the request using expired token. In such case I'm not able to catch the correct ErrorResponeCode by Angular. Instead of 401 i Angular HttpClient got "Unknown Error" with status:0.
It looks like the problem is with CORS policy where the ErrorResponse doesn't include neccessery headers like Access-Control-Allow-Origin (...)
Failed to load http://localhost:8030/api/xxxx: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:8070' is therefore not allowed
access. The response had HTTP status code 401.
ErrorResponse Headers - Screenshot
I have searched for how to enable CORS for ErorrResponse (InvalidTokenException etc.) in Spring MVC . I tried with various approach: accessDeniedHandler and setExceptionTranslator but without success. I really made effort to find the solution myself but I'm a beginner in Spring. I am not sure if this is possible at all.
ANGULAR (UPDATE)
#hrdkisback, it's rather not angular issue, anyway this my code :
#Injectable()
export class HttpInterceptorService implements HttpInterceptor {
addToken(req: HttpRequest<any>, oauthService: AuthenticationService): HttpRequest<any> {
if(oauthService.isTokenExist()){
return req.clone({ setHeaders: { Authorization: 'Bearer ' + oauthService.getAccessToken() }})
}
return req;
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpSentEvent | HttpHeaderResponse | HttpProgressEvent | HttpResponse<any> | HttpUserEvent<any>> {
let oauthService = this.inj.get(AuthenticationService);
return next.handle(this.addToken(req,oauthService))
.do((event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// process successful responses here
}
}, (error: any) => {
if (error instanceof HttpErrorResponse) {
// Error
console.log(error);
}
});
}
}

Issue solved after I added my CORS filter on ResourcesServer configuration level like this:
The correct configuration that works for me!
#Configuration
#EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(new WebSecurityCorsFilter(), CsrfFilter.class)
...
}
....
}
In my previous configuration I added the filter in the same way but on the top level of MVC Security Configuration and it was the root couse of my issue:
The previous configuration that caused my issue
#Configuration
#EnableWebSecurity
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(new WebSecurityCorsFilter(), CsrfFilter.class)
...
}
....
}

I faced the same problem..I was trying Basic Auth with Angular 5.
The problem is that you don't add the CORS header on error response.
Here is what I did
#Component
public class AuthEntryPoint extends BasicAuthenticationEntryPoint {
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx)
throws IOException, ServletException {
response.addHeader("WWW-Authenticate", "Basic realm=" +getRealmName());
response.addHeader("Access-Control-Allow-Origin", "http://localhost:4200");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter writer = response.getWriter();
writer.println("HTTP Status 401 - " + authEx.getMessage());
}
}
That would do the trick!

Related

How to configure Spring-Security (Spring 6) for not having Filters executed on unsecured routes?

somewhat related to this other stackoverflow topic which doesn't give a proper solution nor is applicable to Spring 6 (Spring Boot 3).
I came up with a basic spring-boot app to make my case.
There is a controller with two end-points, where one must be secured and the other accessible.
#RestController
public class TestController {
#GetMapping("/secured-api")
public String securedApi() {
return "secured";
}
#GetMapping("/public/open-api")
public String openApi() {
return "open";
}
}
Security context as follow, imagine that MyFilter is doing something fancy, e.g: validating a JWT token and firing an exception if the token is invalid / expired.
#Configuration
public class ComponentSecurityContext {
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.addFilterAt(new MyFilter(), BasicAuthenticationFilter.class)
.authorizeHttpRequests(customizer -> customizer
.requestMatchers(new AntPathRequestMatcher("/public/**"))
.permitAll()
.anyRequest()
.authenticated())
.build();
}
public static class MyFilter extends OncePerRequestFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
System.out.println("Filter is called for uri: " + request.getRequestURI());
// performs some authentication
filterChain.doFilter(request, response);
}
}
}
Executing the following two curls on the server
curl http://localhost:9003/public/open-api
curl http://localhost:9003/secured-api
is triggering MyFilter
Filter is called for uri: /public/open-api
Filter is called for uri: /secured-api
I would expect MyFilter to be called only for secured end-points, I don't care if an expired token is used to access an unprotected end-point.
Any advise on how to properly wire spring-security to achieve just that?
Working solution where the filter is scoped by the securityMatcher:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.securityMatcher(new NegatedRequestMatcher(new AntPathRequestMatcher("/public/**")))
.addFilterAt(new MyFilter(), BasicAuthenticationFilter.class)
.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated())
.build();
}

How to add CORS to outh2/resource server in Spring Boot 2.x?

I have an oauth server and a resource server that I have created with JWT.
I also created an angular front end with 2 buttons:
The first button calls the auth server and gets the JWT token and adds it to the input box.
The second button calls the rest server with the JWT token as a bearer Authorisation http header.
Calling the 2 services from PostMan works perfectly but I cannot get the CORS setup correctly configured for the back end services.
Both buttons are giving me the below error:
Access to XMLHttpRequest at 'http://localhost:8085/oauth/token' from origin 'http://localhost:4200' has been blocked by CORS policy: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.
I added all 3 of these projects to my public github repo.
I have tried to add CORS with several ways:
The config on the resource rest service is smaller so I will outline that here
I tried adding the default .cors() on the HttpSecurity as well as setting it manually in the corsConfigurationSource() method.
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(final HttpSecurity http) throws Exception {
http.csrf().disable()
.cors()
.and().authorizeRequests().anyRequest().authenticated()
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
//I tried manually configured the cors as well
/*http.csrf().disable()
.cors().configurationSource(corsConfigurationSource())
.and().authorizeRequests().anyRequest().authenticated();
.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);*/
}
/* #Bean
CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
configuration.setAllowCredentials(true);
//the below three lines will add the relevant CORS response headers
configuration.addAllowedOrigin("*");
configuration.addAllowedHeader("*");
configuration.addAllowedMethod("*");
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
*/
}
I also tried adding a servlet filter
#Component #Order(Ordered.HIGHEST_PRECEDENCE) public class
SimpleCorsFilter implements Filter {
#Override
public void init(final FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws
IOException, ServletException {
final HttpServletResponse response = (HttpServletResponse) servletResponse;
final HttpServletRequest request = (HttpServletRequest) servletRequest;
response.addHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "content-type, x-requested-with, authorisation");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
filterChain.doFilter(servletRequest, servletResponse);
}
}
#Override
public void destroy() {
} }
Just can't get it to work. Can anyone please give me some guidelines here?
Silly mistake on my end as in both my SimpleCorsFilter.java files I specified that authorisation header tags are allowed but it is not authorisation with an S but with a Z.
Changing both the files in my config server
response.setHeader("Access-Control-Allow-Headers", "content-type,
x-requested-with, Authorization");
Extends your class with withWebMvcConfigurer rather than WebSecurityConfigurerAdapter. The override the following method:
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**").allowedOrigins("*").allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE").allowedHeaders("*");
}
It should add the origin. You can play with '*' and make many combination. I have given you idean now it is your turn to play with this API.

Spring 5 Cors and Csrf integration for angular js frontend http 401 [duplicate]

This question already has answers here:
CORS issue - No 'Access-Control-Allow-Origin' header is present on the requested resource
(8 answers)
Closed 3 years ago.
I am trying to execute requests from angular js frontend to spring boot middle ware (spring boot 2.1.4) . The setup used to work as expected before I migrated the app to spring boot.
Post spring boot migration all the filter and security config from web XML has been configured in the form of annotated classes.
Now my requests from UI are getting rejected by spring boot with http 401 error with cors policy (Allowed-Origin)
My current project setup looks like this
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private CustomAuthenticationProvider customAuthenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(customAuthenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.httpBasic().and().authorizeRequests().antMatchers("/**").hasAnyRole("ROLE_USER").anyRequest()
.authenticated().and().csrf().csrfTokenRepository(csrfTokenRepository());
}
private CsrfTokenRepository csrfTokenRepository() {
CustomDomainCookieCsrfTokenRepository repository = new CustomDomainCookieCsrfTokenRepository();
repository.setCookieHttpOnly(false);
return repository;
}
}
#WebFilter("/*")
public class ForceCORSFilter extends OncePerRequestFilter {
protected final Logger log = Logger.getLogger(this.getClass());
private CacheService cacheService;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
List<String> originList = getCacheService().getValidOriginUrI();
String clientOrigin = request.getHeader("origin");
if (clientOrigin == null) {
// process the request even if origin is null
processValidRequest(request, response, filterChain, clientOrigin);
}
if (clientOrigin != null) {
// Origin should be validated if not null
if (originList.contains(clientOrigin)) {
processValidRequest(request, response, filterChain, clientOrigin);
} else {
log.info("####################### ORIGIN IS INVALID #######################" + clientOrigin);
filterChain.doFilter(request, response);
}
}
} catch (Exception e) {
response.getWriter()
.write("An error has occured while processing the request. Please retry with proper request.");
log.info("An error has occured in the request " + e.getMessage());
}
}
private void processValidRequest(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain,
String clientOriginAllowed) throws IOException, ServletException {
response.addHeader("Access-Control-Allow-Origin", clientOriginAllowed);
response.addHeader("Access-Control-Allow-Credentials", "true");
if (request.getHeader("Access-Control-Request-Method") != null && "OPTIONS".equals(request.getMethod())) {
response.addHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, HEAD");
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, X-XSRF-TOKEN");
} else {
filterChain.doFilter(request, response);
}
}
public CacheService getCacheService() {
return cacheService;
}
public void setCacheService(CacheService cacheService) {
this.cacheService = cacheService;
}
}
Can someone point out what is wrong here. Why I am still getting
http 401 "No 'Access-Control-Allow-Origin' header is present on the
requested resource" errors.
One issue might be precedence -- your filter isn't run at the right order. You can use #Order(Ordered.HIGHEST_PRECEDENCE) so it is run before the Spring Security filters.
Having said that, Spring has first-class support for CORS already, so there is no need to tediously define a filter at all. See the documentation and an example.

Keycloak spring adapter - check that the authToken is active with every http request

Problem I want to solve:
For every call made to the service I want to check that the token is active, if it isn't active I want to redirect the user to the login page.
Current setup: Grails 3.2.9 , Keycloak 3.4.3
Ideas so far:
This article looked promising: https://www.linkedin.com/pulse/json-web-token-jwt-spring-security-real-world-example-boris-trivic
In my security config I added a token filter
#Bean
public TokenAuthenticationFilter authenticationTokenFilter() throws Exception {
return new TokenAuthenticationFilter();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
super.configure http
http
.addFilterBefore(authenticationTokenFilter(), BasicAuthenticationFilter.class)
.logout()
.logoutSuccessUrl("/sso/login") // Override Keycloak's default '/'
.and()
.authorizeRequests()
.antMatchers("/assets/*").permitAll()
.anyRequest().hasAnyAuthority("ROLE_ADMIN")
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
}
My TokenAuthenticationFilter just prints out the request headers at the moment :
public class TokenAuthenticationFilter extends OncePerRequestFilter {
private String getToken( HttpServletRequest request ) {
Enumeration headerEnumeration = request.getHeaderNames();
while (headerEnumeration.hasMoreElements()) {
println "${ headerEnumeration.nextElement()}"
}
return null;
}
#Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String authToken = getToken( request );
}
}
Which returns:
host
user-agent
accept
accept-language
accept-encoding
cookie
connection
upgrade-insecure-requests
cache-control
The code/logic I want to implement in the filter is something like:
KeycloakAuthenticationToken token = SecurityContextHolder.context?.authentication
RefreshableKeycloakSecurityContext context = token.getCredentials()
if(!context.isActive()){
// send the user to the login page
}
However I'm lost as to how to get there.
Any help greatly appreciated
As far as I understand, your question is about "how to check the token is active?" and not "how to redirect the user to login page?".
As I see you added the tag "spring-boot" and "keycloak" maybe you could use "Keycloak Spring Boot Adapter". Assuming you use the version 3.4 of Keycloak (v4.0 still in beta version), you can found some documentation here.
If you can't (or don't want to) use Spring Boot Adapter, here is the part of the KeycloakSecurityContextRequestFilter source code that could be interesting for your case:
KeycloakSecurityContext keycloakSecurityContext = getKeycloakPrincipal();
if (keycloakSecurityContext instanceof RefreshableKeycloakSecurityContext) {
RefreshableKeycloakSecurityContext refreshableSecurityContext = (RefreshableKeycloakSecurityContext) keycloakSecurityContext;
if (refreshableSecurityContext.isActive()) {
...
} else {
...
}
}
and here is the (Java) source code of the getKeycloakPrincipal method:
private KeycloakSecurityContext getKeycloakPrincipal() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
Object principal = authentication.getPrincipal();
if (principal instanceof KeycloakPrincipal) {
return KeycloakPrincipal.class.cast(principal).getKeycloakSecurityContext();
}
}
return null;
}
And if you want to understand how the Authentication is set in the SecurityContextHolder, please read this piece of (Java) code from KeycloakAuthenticationProcessingFilter:
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
if (authResult instanceof KeycloakAuthenticationToken && ((KeycloakAuthenticationToken) authResult).isInteractive()) {
super.successfulAuthentication(request, response, chain, authResult);
return;
}
...
SecurityContextHolder.getContext().setAuthentication(authResult);
...
try {
chain.doFilter(request, response);
} finally {
SecurityContextHolder.clearContext();
}
}
As an alternative you could also check this github repository of dynamind:
https://github.com/dynamind/grails3-spring-security-keycloak-minimal
Hoping that can help.
Best regards,
Jocker.

Invalid CORS request in Spring

I am trying to enable certain IPs to access a particular method.
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/updateDetail")
.allowedOrigins("127.0.0.1", "10.96.33.45")
.allowedMethods("GET", "POST");
}
But when I am trying to call the same method I am getting invalid CORS request. Can anyone help me with this?
"Invalid CORS request" is returned by org.springframework.web.cors.DefaultCorsProcessor when
Spring is configured to use CORS and
browser sends "Origin" header with the request and it does not match with your server domain/port/scheme and
response does not have "Access-Control-Allow-Origin" header and
the request is not a preflight request.
If you don't need CORS, call cors().disable() in your implementation of WebSecurityConfigurerAdapter#configure(HttpSecurity http) (there might be other ways to do this, for example if you use Spring Boot).
Or you could add "Access-Control-Allow-Origin" header to your reponses using e.g. org.springframework.web.cors.CorsConfiguration or addCorsMappings (like you did, but maybe you should add more methods or url or IP doesn't match?).
This class is what are you looking for:
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCorsFilter implements Filter {
public SimpleCorsFilter() {
}
#Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
HttpServletRequest request = (HttpServletRequest) req;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT");
response.setHeader("Access-Control-Max-Age", "12000");
response.setHeader("Access-Control-Allow-Headers", "Access-Control-Allow-Headers, Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With");
response.setHeader("Access-Control-Expose-Headers", "*");
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
}
#Override
public void init(FilterConfig filterConfig) {
}
#Override
public void destroy() {
}
}
This filter will resolve all your cors issues
I was debugging problem like this for few days. The actual problem was that I had typo in my service uri name /data vs. /daba etc. This cause SpringBoot to fail to retrieve CORS configuration (even when I had /** mapping) so CORS-preflight got status 403, which caused browser not to make the actual request at all - if browser would have fired the request, Spring would have returned "Resource not found" and then I would have noticed the typo much faster.
if you started your application at localhost, the browser formulates the origin as null, so application will not get the origin localhost:8080 or 127.0.0.1, it will get null
I think changing 127.0.0.1 with null will fix your problem
#Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/updateDetail")
.allowedOrigins("null", "10.96.33.45")
.allowedMethods("GET", "POST");
}

Resources