Recently I start implementing a token based security system with angularjs and spring mvc. The idea is the following: 1. Visit /user/authenticate to get a security token and save the token to local storage 2. For each request sent by the angularJS client, use an interceptor to inject a X-Auth-Token header to the request.
In my spring back-end I have implemented an AuthenticationTokenProcessingFilter and a CustomAuthenticationEntryPoint. The first for extracting the token from the header and check if it is valid and the second to return a 401 unauthorized status when a request is not authenticated.
Please find some details about my back end code
AuthenticationController.java
@RestController
@RequestMapping(value="user")
public class AuthenticationController {
@RequestMapping(value="authenticate", method = RequestMethod.POST)
public ResponseEntity<?> login(@RequestParam("email") String email,
@RequestParam("password") String password) {
//Check if user is valid and return token
}
}
SecurityConfig.java
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
UsersRepository usersRepo;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {...}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(
new AuthenticationTokenProcessingFilter(usersRepo),
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(this.corsFilter(), UsernamePasswordAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable().exceptionHandling()
.and()
.httpBasic()
.authenticationEntryPoint(new CustomAuthenticationEntryPoint())
.and()
.authorizeRequests()
.antMatchers(HttpMethod.POST, "/user/authenticate").permitAll()
.antMatchers("/**").authenticated()
.anyRequest().authenticated();
}
@Bean
public CORSFilter corsFilter() {
return new CORSFilter();
}
}
CORSFilter.java
public class CORSFilter 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, PUT");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With, Origin, X-Auth-Token");
response.addHeader("Access-Control-Expose-Headers", "X-Auth-Token");
chain.doFilter(req, res);
}
}
Now I am using the following angularjs code in order to query the /user/authenticate endpoint which is not behind firewall
return $http.post(baseUrl + 'user/authenticate', 'email='+username+'&password='+password,
{
headers : {
'content-type' : 'application/x-www-form-urlencoded'
}
}
);
When I use the above code everything works. However If I remove the headers parameter from the request my angularjs client sends an OPTION request (rather than a POST request - I imagine this is related to my CORS filter) and my back end sends a 401 Unauthorised response.
Could you please give me a few more details why this is happening?
Thank you in advance!
I think I had a similar (the same?) problem and I tweaked my filter chain in WebSecurityConfigurerAdapter::configure(HttpSecurity http)
a little bit to solve it.
I think what happens in your case is that the browser sends an OPTIONS request to figure out whether CORS is allowed or not by your server. The OPTIONS request is handled in your filter chain and will eventually match
.antMatchers("/**").authenticated()
Now you'll probably end up in your CustomAuthenticationEntryPoint() and return a 401.
You could add:
.antMatchers(HttpMethod.OPTIONS, "/user/authenticate/").permitAll()
However, I think you still would get problems afterwards. When the client now wants to access other resources the token has to be sent in the header. Most likely this will result in another OPTIONS request which does not contain your token and so you will eventually end up with the same problem.
So what I did was to explicitly allow all OPTIONS request without checking for authentication.
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
Now you bypass your security filter and you won't return a 401. I think that this does not affect the security of the application as long as you won't handle OPTIONS requests by yourself in a controller and allow someone to access critical data.
OPTIONS request is a so called preflight request:
To protect resources against cross-origin requests that could not originate from certain user agents before this specification existed a preflight request is made to ensure that the resource is aware of this specification.
Basically, it means that an OPTIONS
request is issued whenever the resource (backend server) didn't supply Origin
and Access-Control-*
in the response (to the client). You'll need CORS
activated as soon as your backend and client (webapp) are located under different domains e.g. backend is available under domainA
and your client is available under domainB
. Even domainA:80
and domain:8080
are treated as different domains.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With