Preflight request fails on Chrome, Safary, Opera - spring-security

I´m struggling with CORS requests on an Springsecurity backen project + Angulajs frontend.
CORS requests are working fine on IE (also with curl, wget and python requests) but miserably fail on Chrome and Safary because of Preflight bad request.
I know that those Browsers are blocking CORS POSTs, making the request empty as soon as the reach the backend, in fact I don’t see any data when I log out the request from backend. I tried every possible combination of:
Frontend side:
1) $http(method: POST)
2) $http.post(
3) Adding flags: Access-Control-Allow-Origin, Access-Control-Expose, etc.
4) Adding all possible header combination: ‘Content–Type’:’application/
Browser side:
1) Start chrome with flag: --disable-web-security
2) Installing Chrome extension CORS
Backend side:
1) Spring Security Disable csfr
2) Spring Security Permit all
3) Spring Security HttpMethod.OPTION
4) Set up a CORS Filter that accept all origins: “*”
5) Activated CORS framework for spring extending WebMvcConfigurerAdapter class.
Nothing, NHOTING worked for me!
I discussed this issue in another post: CORS POST request fails on Chrome, Safari and Firefox
I´m still unable to perform CORS requests, this is now I major issue and I suspect the problem is in LoginFilter:
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
private TokenAuthenticationService tokenAuthenticationService;
public JWTLoginFilter(String url, AuthenticationManager
authenticationManager) {
super(new AntPathRequestMatcher(url));
setAuthenticationManager(authenticationManager);
tokenAuthenticationService = new TokenAuthenticationService();
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse)
throws AuthenticationException, IOException, ServletException {
try {
ServletInputStream inputStream = httpServletRequest.getInputStream();
httpServletRequest.getCharacterEncoding();
AccountCredentials credentials = null;
ObjectMapper mapper = new ObjectMapper();
credentials = mapper.readValue(inputStream, AccountCredentials.class);
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(credentials.getUsername(), credentials.getPassword());
return getAuthenticationManager().authenticate(token);
} catch (JsonMappingException e) {
e.printStackTrace();
throw e;
} catch (JsonParseException e) {
e.printStackTrace();
throw e;
} catch (AuthenticationException e) {
e.printStackTrace();
throw e;
} catch (IOException e) {
e.printStackTrace();
throw e;
}
}
#Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication)
throws IOException, ServletException {
AccountCredentials cred = (AccountCredentials) authentication.getPrincipal();
tokenAuthenticationService.addAuthentication(response, cred);
}
}
EDIT
the exact error on Google Chrome is:
:8000/#!/login:1 XMLHttpRequest cannot load http://localhost:8080/myApp/login. Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8000' is therefore not allowed access. The response had HTTP status code 403.

So I found that it does not has ANYTHING to do with the request headers, but the problems are the response headers.
To make the preflight passing through, all response headers have to be mapped, as example:
response.addHeader("Access-Control-Expose-Headers", "xsrf-token, Authorization, Barer, Token");

The preflight request is sent AUTOMATICALLY with verb option by browser itself BEFORE the real request is sent.
You must configure your server to send response with some headers when this preflight request is sent. With spring security you can use :
#Provider
#Component
public class CrossDomainContainerResponseFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext containerRequestContext,
ContainerResponseContext containerResponseContext) throws IOException {
containerResponseContext.getHeaders().add("Access-Control-Allow-Origin", "YOUR FRONTEND URI");
containerResponseContext.getHeaders().add("Access-Control-Allow-Headers",
"Access-Control-Allow-Origin");
containerResponseContext.getHeaders().add("Access-Control-Allow-Credentials", "true");
containerResponseContext.getHeaders().add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD");
}
}
If you are using XML config, you can also use the <cors /> tag.
The --disable-web-security never worked for me on Chrome. But it worked on Vivaldi Browser.

Related

How to customise exception handling in Spring Authorization Server for token endpoint

I am trying to implements additional checks for a user which is exchanging code for tokens using "/oauth2/token" endpoint in Spring Authorization Server. And for this I need to provide custom error message, error code and provide specific http status(other than 400 or 500).
I see that the code exchange starts in OAuth2TokenEndpointFilter but it has a strict exception hanling like
private void sendErrorResponse(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException { ... }
and it can not be overridden as well as can not be set
private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse;
So I can extend from OAuth2AuthenticationException but it does not suite as I can not control the status and the response body.
Ok, I should read doc more carefully.
I still have to extend from AuthenticationException but I also have full controll over failure so adding custom body/code/status
#Bean
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception {
OAuth2AuthorizationServerConfigurer authorizationServerConfigurer =
new OAuth2AuthorizationServerConfigurer();
http.apply(authorizationServerConfigurer);
authorizationServerConfigurer
.tokenEndpoint(tokenEndpoint ->
((OAuth2TokenEndpointConfigurer)tokenEndpoint).errorResponseHandler(errorResponseHandler)// instance of AuthenticationFailureHandler
);
return http.build();
}

How to append the same parameter for multiple identical login pages if authentication fails?

I have the same login form on different endpoints: /welcome,
/login
If I set failureUrl(/welcome?error) it will redirect the /login to /welcome?error as well if authentication fails instead of /login?error
I think I need to implement a failureHandler, but how can I extract the actual login endpoint from HttpServletRequest?
I would like to something like that:
response.sendRedirect(/[actual_login_endpoint]?error);
I solved it. I had to send a hidden attribute with the login forms. I don't think there is an easier solution.
#Override
public void onAuthenticationFailure(
final HttpServletRequest request,
final HttpServletResponse response,
final AuthenticationException exception) throws IOException, ServletException {
if (request.getParameter("loginForm").equals("welcome")) {
response.sendRedirect("/welcome?error");
} else if (request.getParameter("loginForm").equals("login")) {
response.sendRedirect("/login?error");
}
}

Spring Security OAuth2: CORS preflight channel did not succeed

I was receiving this error while making a call to '/oauth/token' when I was making an HTTP call to the server instance running on my own system. I fixed this by creating a filter like this:
#Component
public class SimpleCORSFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Authorization");
HttpServletRequest httpServletRequest = (HttpServletRequest) req;
if (httpServletRequest.getMethod() != "OPTIONS") {
chain.doFilter(req, res);
} else {
// In case of HTTP OPTIONS method, just return the response
}
}
I have added it as a filter in WebConfigurer:
private void initCORSFilter(ServletContext servletContext, EnumSet<DispatcherType> disps) {
log.debug("Registering CORS Filter");
FilterRegistration.Dynamic corsFilter = servletContext.addFilter("corsFilter", new SimpleCORSFilter());
Map<String, String> parameters = new HashMap<>();
corsFilter.setInitParameters(parameters);
corsFilter.addMappingForUrlPatterns(disps, true, "/*");
corsFilter.setAsyncSupported(true);
}
I am getting this error in FireFox:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://182.176.221.94:9091/ams/oauth/token. (Reason: CORS preflight channel did not succeed).
In short I was making sure the the preflight OPTIONS call always gets a response to proceed ahead. This was working on my own system, but now that the server instance is deployed on a Linux server, I am again getting this issue. And I am getting this only on calling 'oauth/token', everything other call is working fine.
What can I possibly do to get rid of this. Any help?
Your filter does not need to be annotated with #Component and it should be mapped with adequate url pattern in WebConfigurer class like other filters used in JHipster.
Also your filter should not break the filter chain as it does for OPTIONS. It's not consistent to allow OPTIONS method in header and then to not return the headers if you are processing an OPTIONS request.
The problem was that I was using != comparison for if (httpServletRequest.getMethod() != "OPTIONS". I changed it to if (!httpServletRequest.getMethod().equalsIgnoreCase("OPTIONS")) instead and it worked. This could have something to do with the fact that I tested on local machine while running the codebase but created a WAR file out of it and deployed on the server where it didn't work. I am not sure what exactly is the reason, but this fixed the issue.

Overriding AjaxAwareAuthenticationSuccessHandler.onAuthenticationSuccess Gotchas

I wrote a custom success handler for our application because the client was to redirect to specific pages based on roles. In doing this, I was originally calling super.onAuthenticationSuccess but since it already does redirects, when I attempted to redirect again, obviously, there were state exceptions.
So what I'm doing now is checking roles and redirecting accordingly and falling back on super.onAuthenticationSuccess...
#Override
public void onAuthenticationSuccess(final HttpServletRequest request, final HttpServletResponse response,
final Authentication authentication) throws ServletException, IOException {
if (goToSomePage) {
response.sendRedirect(request.contextPath + '/admin/index')
}else{
super.onAuthenticationSuccess(request, response, authentication)
}
}
I'm wondering if I'm going to run into issues later because something in the parent onAuthenticationSuccess method is required that I'm not doing.

Glassfish JSF 2.0 charset problem

I'm working on a project developed with JSF 2.0 (Mojarra 2.0.3) front end and deployed on Glassfish v.3.0.1 server. Application must accept ISO-8859-2 charset and write data to MySql database.
To problem is that data is not in right charset.
The request Http header has attribute value:
content-type: application/x-www-form-urlencoded; charset=UTF-8
The problem is not with response, since data can be displayed correctly when read from database. Also, MySql connection URL should be correct because it is set for latin2 collaction. I even tried with creating custom filter, but without any result.
Any ideas how can I accomplish to accept correct charset?
Thanks in advance.
You can always force ISO-8859-2 encoding by creating a Filter and defining it in your web.xml. At a bare minimum, the Filter should have:
public class CustomFilter implements Filter {
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
if ((request instanceof HttpServletRequest)
&& (response instanceof HttpServletResponse)) {
request.setCharacterEncoding("ISO-8859-2");
response.setContentType("text/html; charset=ISO-8859-2")
}
chain.doFilter(request, response);
} catch (Exception e) {
// Do your logging here
}
}
}

Resources