Validate JWT token before request - spring-boot

I'm writing an API that must verify if JWS has a valid signature and it's not expired - my project has a few routes and only a single route is protected.
I have create a filter:
#Component
class JWTFilter : OncePerRequestFilter() {
override fun doFilterInternal(req: HttpServletRequest, resp: HttpServletResponse, chain: FilterChain) {
// code to validate JWT
chain.doFilter(req, resp)
}
}
And the SecConfig:
#Configuration
#EnableWebSecurity
class SecConfig(val authRequestFilter: AuthRequestFilter) : WebSecurityConfigurerAdapter() {
override fun configure(http: HttpSecurity) {
http
.authorizeRequests()
.antMatchers("/cash")
.authenticated()
.anyRequest()
.permitAll()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
http.addFilterBefore(authRequestFilter, <WhatShouldIPutHere.class>)
}
}
I have created the SecConfig class to centralize all configuration and my API doesn't have any kind of login, I just receive the JWS Token and I need to ensure that is valid.
What is the correct implementation to be used in <WhatShouldIPutHere.class>?
edit:
It works but what's the point of use UsernamePasswordAuthenticationFilter if I dont have Username/Pw authentication in my application?
http.addFilterBefore(authRequestFilter, UsernamePasswordAuthenticationFilter::class.java)
edit 2:
Why is it validating every request? I just want to validate /cash

It works but what's the point of use UsernamePasswordAuthenticationFilter if I dont have Username/Pw authentication in my application?
It tells spring where in the filter chain you want to place your JWT filter. See filter ordering. More specifically, using addFilterBefore like that will put your filter just before the UsernamePasswordAuthenticationFilter.
Why is it validating every request? I just want to validate /cash
Because you have not configured any specific paths in this line:
http.addFilterBefore(authRequestFilter, UsernamePasswordAuthenticationFilter::class.java)
Use http.antMatchers("/cash").addFilterBefore...

You must use:
org.springframework.web.filter.OncePerRequestFilter

Related

Spring Security OAuth - how to disable login page?

I want to secure my application with Spring Security, using OAuth 2. However, I don't want the server to redirect incoming unauthorized requests, but instead to respond with HTTP 401. Is it possible?
Example: this code redirects requests to a default login page.
application.properties
spring.security.oauth2.client.registration.google.client-id=...
spring.security.oauth2.client.registration.google.client-secret=...
AuthConfig.java
#Configuration
public class AuthConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/secured/**").authenticated()
.anyRequest().permitAll()
.and()
.oauth2Login();
// https://stackoverflow.com/questions/31714585/spring-security-disable-login-page-redirect
// deos not work
// .and()
// .formLogin().successHandler((request, response, authentication) -> {});
}
}
You need to create new authentication entry point and set it in configuration.
#Configuration
public class AuthConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling()
.authenticationEntryPoint(new AuthenticationEntryPoint())
.and()
.authorizeRequests()
.antMatchers("/secured/**").authenticated()
.anyRequest().permitAll()
.and()
.oauth2Login();
}
}
public class AuthenticationEntryPoint extends LoginUrlAuthenticationEntryPoint {
public AuthenticationEntryPoint() {
super("");
}
#Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
response.sendError(401, "Unauthorized");
}
}
You need to set oauth2Login.loginPage in your HttpSecurity config and create a controller mapping to return whatever you want. Here's a simple example.
So in your security config
http
.authorizeRequests()
.antMatchers("/noauth").permitAll()
.oauth2Login()
.loginPage("/noauth")
In a controller
#GetMapping("/noauth")
public ResponseEntity<?> noAuth() {
Map<String, String> body = new HashMap<>();
body.put("message", "unauthorized");
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(body);
}
You can pass a map or pojo to the body method.
I would like to expand on Petr's answer by explaining that apparently for the time being first of all, the default login page is shown when there are more than one OAuth2 configured providers. I would expect that Spring Boot would have a smart trick to bypass this page easily and choose the right provider automatically, basing e.g. on the existence of the provider's client ID in the original request. I found out the hard way that this is not the case. So the way to do this is.. this not very apparent trick of providing a custom handler for failures - that will REDIRECT the user to the correct OAuth2 endpoint for each provider, based on the original HTTP request URL. I tried this and it works and I spent a whole day trying all manners of other solutions - my original scenario was to pass additional parameters to OAuth2 scheme in order to be able to get them back on successful authentication - they used to do this appending Base64 encoded information to the "state" URL request parameter, but Spring Security does not allow this at the moment. So the only alternative was to call a Spring Security-protected URL with those parameters already there, so when the successful authentication happens, this URL is accessed again automatically with those parameters intact.
Related: Multiple Login endpoints Spring Security OAuth2

Reactive-Spring-Security-5.1.3.RELEASE, multiple authorizations

We have some endpoints, that are secured and before to access them we're verifying that the jws is correctly. In order to do that, we've defined a SecurityContext that actually persist the Auth pojo and to manipulate it downstream into the controller. The SecurityWebFilterChain config looks like that:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.csrf().disable()
.formLogin().disable()
.logout().disable()
.httpBasic().disable()
.securityContextRepository(securityContext)
.authorizeExchange()
.anyExchange().authenticated()
.and()
.build();
}
The calls were internally made, and we just verified the jws token.
Right now some external clients need to integrate with us, and we need to verify a jwe token. The thing is, that somehow we need to tell spring-security to validate for the existent endpoints the jws and for the new one the jwe.
I tried by specifying multiple security matchers but it failed :( . Do you have any other suggestions ?
You can expose more than one bean. I recommend specifying an order:
#Bean
#Order(1)
public SecurityWebFilterChain first(ServerHttpSecurity http) {
http
.securityMatcher(...)
...
return http.build();
}
#Bean
#Order(2)
public SecurityWebFilterChain second(ServerHttpSecurity http) {
http
.securityMatcher(...)
...
return http.build();
}
As a side note, Spring Security does ship with support for verifying JWS tokens reactively, and you might be able to remove some boilerplate by using it.

Spring Security with filters permitAll not working

I've got this security config:
#Override
public void configure(HttpSecurity http) throws Exception {
http
.addFilterBefore(
new JwtLoginFilter("/login", authenticationManager()),
UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(
new JwtAuthenticationFilter(),
UsernamePasswordAuthenticationFilter.class);
http.csrf().disable()
.authorizeRequests().antMatchers("/", "/register").permitAll()
.and()
.authorizeRequests().anyRequest().authenticated();
}
The two filters are doing authentication work: loginFilter checks credentials in the post body and then add cookie to the response. The authenticationFilter checks the auth cookie.
However, permitAll does not let the root route and "/register" route pass (aka. still going through the authenticationFilter, which I thought permitAll would let these routes pass the filters)
What's wrong?
permitAll() does not ignore filters. It simply grants access regardless of whether or not an Authentication is present in a request's security context after all filters have been processed.
You should check your filters and any AuthenticationProvider implementations that they use to to ensure that they are not breaking the execution flow of Spring Security by throwing unchecked/uncaught exceptions or expressly sending a response on a failed authentication.

Spring Security unexpected behavior for REST endpoints authentication?

The scenario we are looking for is as follows:
client connects with REST to a REST login url
Spring microservice (using Spring Security) should return 200 OK and a login token
the client keeps the token
the client calls other REST endpoints using the same token.
However, I see that the client is getting 302 and a Location header, together with the token. So it does authenticate, but with un-desired HTTP response status code and header.
The Spring Security configuration looks like this:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.disable() // Refactor login form
// See https://jira.springsource.org/browse/SPR-11496
.headers()
.addHeaderWriter(new XFrameOptionsHeaderWriter(XFrameOptionsHeaderWriter.XFrameOptionsMode.SAMEORIGIN))
.and()
.formLogin()
.loginPage("/signin")
.permitAll()
.and()
.logout()
.logoutUrl("/signout")
.permitAll()
.and()
.authorizeRequests()
.antMatchers("/", "/home").permitAll()
.anyRequest().authenticated();
...
}
I tried adding interceptors and filters but can't see where 302 and Location being set and added in Spring side.
However, the Location header does show in the response headers received at the client side (together with the rest of the Spring Security headers LINK):
Server=Apache-Coyote/1.1
X-Content-Type-Options=nosniff
X-XSS-Protection=1; mode=block
Cache-Control=no-cache, no-store, max-age=0, must-revalidate
Pragma=no-cache
Expires=0
X-Frame-Options=DENY, SAMEORIGIN
Set-Cookie=JSESSIONID=D1C1F1CE1FF4E1B3DDF6FA302D48A905; Path=/; HttpOnly
Location=http://ec2-35-166-130-246.us-west-2.compute.amazonaws.com:8108/ <---- ouch
Content-Length=0
Date=Thu, 22 Dec 2016 20:15:20 GMT
Any suggestion how to make it work as expected ("200 OK", no Location header and the token)?
NOTE: using Spring Boot, Spring Security, no UI, just client code calling REST endpoints.
If you need a rest api, you must not use http.formLogin(). It generates form based login as described here.
Instead you can have this configuration
httpSecurity
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.logout()
.disable()
.addFilterBefore(authTokenFilter, UsernamePasswordAuthenticationFilter.class);
Create a class, AuthTokenFilter which extends Spring UsernamePasswordAuthenticationFilter and override doFilter method, which checks for an authentication token in every request and sets SecurityContextHolder accordingly.
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse resp = (HttpServletResponse) response;
resp.setHeader("Access-Control-Allow-Origin", "*");
resp.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
resp.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, " + tokenHeader);
HttpServletRequest httpRequest = (HttpServletRequest) request;
String authToken = httpRequest.getHeader(tokenHeader);
String username = this.tokenUtils.getUsernameFromToken(authToken); // Create some token utility class to manage tokens
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(-------------);
// Create an authnetication as above and set SecurityContextHolder
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
Then create an AuthenticationController, mapped with /login url, which checks credentials, and returns token.
/*
* Perform the authentication. This will call Spring UserDetailsService's loadUserByUsername implicitly
* BadCredentialsException is thrown if username and password mismatch
*/
Authentication authentication = this.authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(),
authenticationRequest.getPassword()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetailsImp userDetails = (UserDetailsImp) authentication.getPrincipal();
// Generate token using some Token Utils class methods, using this principal
To understand loadUserByUsername , UserDetailsService and UserDetails, please refer Spring security docs
}
For better understanding, please thoroughly read above link and subsequent chapters.
It's a 302 response telling the browser to redirect to your login page. What do you expect to happen? 302 response must have a Location header.
http.formLogin()
is designed for form-based login. So the 302 status and Location header in the response is expected if you attempt to access a protected resource without being authenticated.
Based on your requirement/scenario,
client connects with REST to a REST login url
have you considered using HTTP Basic for authentication?
http.httpBasic()
Using HTTP Basic, you can populate the Authorization header with the username/password and the BasicAuthenticationFilter will take care of authenticating the credentials and populating the SecurityContext accordingly.
I have a working example of this using Angular on the client-side and Spring Boot-Spring Security on back-end.
If you look at security-service.js, you will see a factory named securityService which provides a login() function. This function calls the /principal endpoint with the Authorization header populated with the username/password as per HTTP Basic format, for example:
Authorization : Basic base64Encoded(username:passsword)
The BasicAuthenticationFilter will process this request by extracting the credentials and ultimately authenticating the user and populating the SecurityContext with the authenticated principal. After authentication is successful, the request will proceed to the destined endpoint /principal which is mapped to SecurityController.currentPrincipal which simply returns a json representation of the authenticated principal.
For your remaining requirements:
Spring microservice (using Spring Security) should return 200 OK and a login token
the client keeps the token
the client calls other REST endpoints using the same token.
You can generate a security/login token and return that instead of the user info. However, I would highly recommend looking at Spring Security OAuth if you have a number of REST endpoints deployed across different Microservices that need to be protected via a security token. Building out your own STS (Security Token Service) can become very involved and complicated so not recommended.
You can implement your custom AuthenticationSuccessHandler and override method "onAuthenticationSuccess" to change the response status as per your need.
Example:
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
ObjectMapper mapper = new ObjectMapper();
Map<String, String> tokenMap = new HashMap<String, String>();
tokenMap.put("token", accessToken.getToken());
tokenMap.put("refreshToken", refreshToken.getToken());
response.setStatus(HttpStatus.OK.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
mapper.writeValue(response.getWriter(), tokenMap);
}
You need to override the default logout success handler to avoid redirect. In spring boot2 you can do as below:
....logout().logoutSuccessHandler((httpServletRequest,httpServletResponse,authentication)->{
//do nothing not to redirect
})
For more details: Please check this.
You can use headers().defaultsDisabled() and then chain that method to add the specific headers you want.

Restrict authentication method for endpoints with Spring Security

I want to secure a REST API. The rules are simple.
The user must call /api/authenticate to get a token
The user can use a token (received from /api/authenticate) to access the API /api/**
The endpoint /api/authenticate only accepts HTTP Basic authentication (no token authentication)
The endpoints /api/** (excluding /api/authenticate) only accepts token authentication (no Basic Authentication)
All remaining endpoints are public and doesn't require authentication.
I actually use this:
#Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Autowired
private TokenAuthenticationProvider tokenAuthenticationProvider;
#Override
protected void configure(final HttpSecurity httpSecurity) throws Exception {
httpSecurity.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.headers().disable();
httpSecurity.setSharedObject(TokenAuthenticationProvider.class, this.tokenAuthenticationProvider);
httpSecurity.antMatcher("/api/authenticate").httpBasic();
httpSecurity.antMatcher("/api/**").apply(new TokenAuthenticationConfigurer());
httpSecurity.authorizeRequests()
.antMatchers("/api/**").authenticated()
.anyRequest().permitAll();
}
}
Actually, if I send a request with a token to /api/authenticate my configuration accepts the request. I think this happens because /api/authenticate is part of /api/**. So I need to exclude this path for token authentication.
How can I do that?
EDIT 1
If I use the .and() fluent style, the result is exactly the same.
#Override
protected void configure(final HttpSecurity httpSecurity) throws Exception {
httpSecurity.setSharedObject(TokenAuthenticationProvider.class, this.tokenAuthenticationProvider);
httpSecurity
.headers().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.antMatcher("/api/authenticate").httpBasic()
.and()
.antMatcher("/api/**").apply(new TokenAuthenticationConfigurer())
.and()
.authorizeRequests().antMatchers("/api/**").authenticated().anyRequest().permitAll();
}
EDIT 2
As I understand the SecurityBuilder (HttpSecurity), every call of antMatcher(...) in the configure(...) method overwrites the previous call. In the debug logs I can see, that Spring Security always tries to match the request path against /api/** but never agains /api/authenticate. If I switch the order, I can't access the API anymore, just /api/authenticate, because Spring Security now always tries to match agains /api/authenticate.
So the question is: How can I register multiple rules:
/api/authenticate -> HttpBasicConfigurer (.http())
/api/** -> TokenAuthenticationConfigurer (my token authentication configured, .apply(...))
Maybe it is because you always override the configuration of the parent and you do not use the and() method:
The Java Configuration equivalent of closing an XML tag is expressed using the and() method which allows us to continue configuring the parent. If you read the code it also makes sense. I want to configure authorized requests and configure form login and configure HTTP Basic authentication.
http://docs.spring.io/spring-security/site/docs/current/reference/html/jc.html#jc-httpsecurity

Resources