0
votes

I don't have a Spring Security implementation in my rest service, and I'm facing CORS 401 Unauthorized issue when trying to call resources in rest.

I red about it:

https://www.baeldung.com/spring-security-cors-preflight

https://docs.spring.io/spring-security/site/docs/5.0.7.RELEASE/reference/html5/#cors

I have 2 calls to rest:

1) Login

2) Others

The login functionality is based on Shiro, and when I perform login from Postman for example and then try to call other resource it works.

My problem starts when I started implementing a client side app with react and I'm trying to call the rest from fetch javascript method. I faced CORS issue when calling the login first and I solved it by adding @CorsOrigin annotation to my controller, but after login is succeeding the second call is still failing on cors 401.

If I correctly understand, CORS can be solved by adding filter t oWebSecurityConfig but if my app is working from Postman then there is no need to perform such a change in my server side app and I want to solve it from client side.

That leaves me with the option of passing the JSESSIONID in my requests, right?! I still didn't figure out how to do it... where should I take the JSESSIONID from? I tried reading the cookies of the browser but didn't find it there... I tried taking it from Response headers of the first call to Login but the Response is coming empty!

Tried to solve it with Spring in server side but with no luck:

WebSecurityConfig:

@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {

//        http
//        .cors();
//        .headers().disable();

//      http
//        .authorizeRequests()
//        .requestMatchers(CorsUtils::isCorsRequest).permitAll()
//        .anyRequest().authenticated()
//        .and().httpBasic()
//        .and().addFilterBefore(new WebSecurityCorsFilter(), ChannelProcessingFilter.class);

        http.addFilterBefore(new CorsFilter(), ChannelProcessingFilter.class);

    }
}

WebSecurityCorsFilter:

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");
    chain.doFilter(request, res);
}

@Override
public void destroy() {
}

}

Or, CorsFilter (using on of them):

@Component
//@Order(Ordered.HIGHEST_PRECEDENCE)
public class CorsFilter implements Filter{

public CorsFilter () {
    super();
}

@Override
public final void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
    final HttpServletResponse response = (HttpServletResponse) res;
    response.setHeader("Access-Control-Allow-Origin", "http://localhost:3000");

    // without this header jquery.ajax calls returns 401 even after successful login and SSESSIONID being succesfully stored.
    response.setHeader("Access-Control-Allow-Credentials", "true");

    response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
    response.setHeader("Access-Control-Max-Age", "3600");
    response.setHeader("Access-Control-Allow-Headers", "X-Requested-With, Authorization, Origin, Content-Type, Version");
    response.setHeader("Access-Control-Expose-Headers", "X-Requested-With, Authorization, Origin, Content-Type");

    final HttpServletRequest request = (HttpServletRequest) req;
    if (!request.getMethod().equals("OPTIONS")) {
        chain.doFilter(req, res);
    } else {
        // do not continue with filter chain for options requests
    }
}

@Override
public void destroy() {

} 

@Override
public void init(FilterConfig filterConfig) throws ServletException {       
}
}
1
I tried this approach already, didn't help :~ (Updated my post with the code) - zbeedatm
Are you passing JSESSIONID in you request header? - hrdkisback
Nop, that was my main question... how to do it? where to get it from? If I inspect cookies from chrome dev tools --> Network tab I couldn't see any cookies attached to my request! I tried to get it from the Response of the login first call but response is empty with no headers... maybe I need to populate it in response manually (from server controller side)? I added the login resource code to my post - zbeedatm
maybe need to do something on my server container (tomcat) to enable JSESSIONID? - zbeedatm

1 Answers

0
votes

Thanks to this similar fetch issue: https://github.com/github/fetch/issues/386

I managed finally. So, to summarize:

1) No need for the Spring Security Config changes, except of Adding this annotation to controllers (you can add it just once to a base controller and all can inherit from it)

@CrossOrigin(origins = " * ", allowedHeaders = " * ")

2) From client side, when using fetch I had to use from both requests (Login and the second one):

credentials: 'include', //'same-origin'

(be attention that when using cors don't use the 'same-origin' value of credentials)

No need to set any cookies manually, the browser should handle it (If you need to write a Java client then it's needed... you can search for CookiesManagement java class and you'll find such implementation)

enter image description here