Spring SSO with facebook filter and JWT - spring

here is my problem.
I got a first authentication with mail and JWT with Spring boot 1.5.3.
=> works perfectly
Then i made a SSO filter to allow facebook tokens
The thing is, on first authentication it's ok. My server get the Token, then check with fb that says ok then it says ok to my client.
After that if i don't encode my token with my JWT token enhancer, my server says that it is not able to decode it as JSON.
Just that i know, i would normally not have to encode myself as it should be done automatically after my chain filter if i say ok ??
This code works but i've done the jwt myself, is that possible i've missed something ????
public class MyOAuth2ClientAuthenticationProcessingFilter extends OAuth2ClientAuthenticationProcessingFilter {
public MyOAuth2ClientAuthenticationProcessingFilter(String defaultFilterProcessesUrl) {
super(defaultFilterProcessesUrl);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
log.info("[attemptAuthentication facebook]");
Authentication result = null;
try {
String token = request.getHeader("oauth_token");
oauth2ClientContext.setAccessToken(new DefaultOAuth2AccessToken(token));
result = super.attemptAuthentication(request, response);
if(result.isAuthenticated()) {
FacebookService facebookService = new BasicFacebookService(token);
User fbUser = facebookService.getUser();
if(fbUser == null) {
throw new IllegalArgumentException(" fb user cannot be null");
}
if(!userService.isLoginExists(fbUser.getId())) {
CreateSocialUserModel model = new CreateSocialUserModel(
token,
DateUtil.getNow(),
"facebook");
userService.createSocialUser(model, fbUser);
}
//--- Create custom JWT token from facebook token
UserInfoTokenServices tokenService = new UserInfoTokenServices(
"https://graph.facebook.com/me",
facebookProperties.getAppId());
OAuth2AccessToken enhancedToken = jwtTokenEnhancer.enhance(oauth2ClientContext.getAccessToken(),
tokenService.loadAuthentication(oauth2ClientContext.getAccessToken().getValue()));
TokenResponse tokenResponse = new TokenResponse(enhancedToken.getValue(),
enhancedToken.getTokenType(),
enhancedToken.getRefreshToken() != null ? enhancedToken.getRefreshToken().getValue() : "");
ObjectMapper mapper = new ObjectMapper();
String jsonTokenEnhancedJack = mapper.writeValueAsString(tokenResponse);
response.addHeader("Content-Type", "application/json");
response.getWriter().flush();
response.getWriter().print(jsonTokenEnhancedJack);
}
return result;
} catch (Exception e) {
log.info("error");
log.error("error", e);
e.printStackTrace();
} finally {
return result;
}
}
}
Thank you in advance

As asked by Son Goku just putting some code to help him
First you have to put the filter like this
#Override
protected void configure(HttpSecurity http) throws Exception {
http.antMatcher("/**")
.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class)
.authorizeRequests()
.anyRequest().permitAll()
.and().csrf().disable();
}
private Filter ssoFilter() {
OAuth2ClientAuthenticationProcessingFilter facebookFilter = new MyOAuth2ClientAuthenticationProcessingFilter("/user/social");
OAuth2RestTemplate facebookTemplate = new OAuth2RestTemplate(Oauth2facebook(), oauth2ClientContext);
facebookFilter.setRestTemplate(facebookTemplate);
facebookFilter.setTokenServices(new UserInfoTokenServices(
"https://graph.facebook.com/me",
facebookProperties.getAppId()
));
return facebookFilter;
}
After that you can use the OAuth2ClientAuthenticationProcessingFilter as in first question.
Also, in first question i customized this method a lot, it works but i was surprised to not find this as easy as these library use to be.
Hope it helps you, i have struggled a bit too on this.
Maybe now, spring boot handle this much more easily.

Related

Spring security 403 with disabled csrf

Using spring security, I've looked at similar questions but they say to try disable cors & csrf.
I am using it on the browser so I will need csrf. But just testing briefly doesn't change the outcome.
On login I get an access token and refresh token.
Using this token gives me a 403 forbidden response code.
My configuration is the following:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.cors().and().csrf().disable();
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.authorizeRequests().antMatchers("/login").permitAll();
http.authorizeRequests().antMatchers(GET, "/**").hasAnyAuthority("STUDENT");
http.authorizeRequests().anyRequest().authenticated();
http.addFilter(new CustomAuthenticationFilter(authenticationManagerBean()));
http.addFilterBefore(new CustomAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class);
}
I think maybe its something to do with this filter but if I change forbidden.value to something else the result is still forbidden.value
public class CustomAuthorizationFilter extends OncePerRequestFilter { // INTERCEPTS EVERY REQUEST
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if(request.getServletPath().equals("/login")){ filterChain.doFilter(request,response); } // DO NOTHING IF LOGGING IN
else{
String authorizationHeader = request.getHeader(AUTHORIZATION);
if(authorizationHeader != null && authorizationHeader.startsWith("Bearer ")){
try {
String token = authorizationHeader.substring("Bearer ".length()); // TAKES TOKEN STRING AND REMOVES BEARER
// THIS NEEDS MAKING SECURE AND ENCRYPTED vvvvvvv
Algorithm algorithm = Algorithm.HMAC256("secret".getBytes()); // <<<<<<<<<<<<<<<<<<<<<<<
JWTVerifier verifier = JWT.require(algorithm).build(); // USING AUTH0
DecodedJWT decodedJWT = verifier.verify(token);
String email = decodedJWT.getSubject(); // GETS EMAIL
String[] roles = decodedJWT.getClaim("roles").asArray(String.class); // GETS ROLES
Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
stream(roles).forEach(role -> { authorities.add(new SimpleGrantedAuthority(role)); }); // CONVERTS ALL USERS ROLE INTO AN AUTHORITY
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, null); // PASSWORD IS NULL AT THIS POINT
SecurityContextHolder.getContext().setAuthentication(authToken); // INSERTS TOKEN INTO CONTEXT // THIS SHOWS AUTHENTICATED FALSE, DETIALS FALSE AND GRANTED AUTHORITIES EMPTY
filterChain.doFilter(request, response); // GETS TO THIS LINE HERE
}
catch (Exception e){
response.setHeader("error" , e.getMessage() );
response.setStatus(FORBIDDEN.value());
Map<String, String> error = new HashMap<>();
error.put("error_message", e.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), error); // THEN SKIPS RIGHT TO THIS LINE HERE EVEN IF BREAKPOINTING BEFORE
}
}
else{ filterChain.doFilter(request, response); }
}
}
}
debugging shows it hits filterChain.doFilter(request, response) then jumps straight to the exception catch objectMapper line
The user submitting is also of the Student role.
this line
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, null);
is missing authorities:
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(email, null, authorities);
Hope that my answer can help,
you can drop a breakpoint to the line change the response status, and then check who and why it returns 403, it can finally help you get the solution
Drop a breakpoint on the line set the 403 status, to see how this happen from the stackframes.
Guess that it returns 403 without much other information, but it must need to set the status to the response, right? So drop a breakpoint to the setStatus method, I don't know where it should locate, in tomcat lib, spring lib, or servlet lib. Check the HttpResponse, they're several implementation, set the breakpoints for those setStatus/setCode methods. (Next you can see it acutally happens at HttpResponseWrapper::setStatus)
Analyze the stackframes to see what's going on there
please check https://stackoverflow.com/a/73577697/4033979

How can I refresh tokens in Spring security

This line:
Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
Throws an error like this when my jwt token expires:
JWT expired at 2020-05-13T07:50:39Z. Current time:
2020-05-16T21:29:41Z.
More specifically, it is this function that throws the "ExpiredJwtException" exception :
How do I go about handling these exceptions? Should I catch them and send back to the client an error message and force them to re-login?
How can I implement a refresh tokens feature? I'm using Spring and mysql in the backend and vuejs in the front end.
I generate the initial token like this:
#Override
public JSONObject login(AuthenticationRequest authreq) {
JSONObject json = new JSONObject();
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authreq.getUsername(), authreq.getPassword()));
UserDetailsImpl userDetails = (UserDetailsImpl) authentication.getPrincipal();
List<String> roles = userDetails.getAuthorities().stream().map(item -> item.getAuthority())
.collect(Collectors.toList());
if (userDetails != null) {
final String jwt = jwtTokenUtil.generateToken(userDetails);
JwtResponse jwtres = new JwtResponse(jwt, userDetails.getId(), userDetails.getUsername(),
userDetails.getEmail(), roles, jwtTokenUtil.extractExpiration(jwt).toString());
return json.put("jwtresponse", jwtres);
}
} catch (BadCredentialsException ex) {
json.put("status", "badcredentials");
} catch (LockedException ex) {
json.put("status", "LockedException");
} catch (DisabledException ex) {
json.put("status", "DisabledException");
}
return json;
}
And then in the JwtUtil class:
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + EXPIRESIN))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
For more info, here is my doFilterInternal function that filters every request:
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException, ExpiredJwtException, MalformedJwtException {
try {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userService.loadUserByUsername(username);
boolean correct = jwtUtil.validateToken(jwt, userDetails);
if (correct) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken
.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
} catch (ExpiredJwtException ex) {
resolver.resolveException(request, response, null, ex);
}
}
There are 2 main approaches to deal with such situations:
Manage access and refresh tokens
In this case, the flow is the following one:
User logins into the application (including username and password)
Your backend application returns any required credentials information and:
2.1 Access JWT token with an expired time usually "low" (15, 30 minutes, etc).
2.2 Refresh JWT token with an expired time greater than access one.
From now, your frontend application will use access token in the Authorization header for every request.
When backend returns 401, the frontend application will try to use refresh token (using an specific endpoint) to get new credentials, without forcing the user to login again.
Refresh token flow
(This is only an example, usually only the refresh token is sent)
If there is no problem, then the user will be able to continue using the application. If backend returns a new 401 => frontend should redirect to login page.
Manage only one Jwt token
In this case, the flow is similar to the previous one and you can create your own endpoint to deal with such situations: /auth/token/extend (for example), including the expired Jwt as parameter of the request.
Now it's up to you manage:
How much time an expired Jwt token will be "valid" to extend it?
The new endpoint will have a similar behaviour of refresh one in the previous section, I mean, will return a new Jwt token or 401 so, from the point of view of frontend the flow will be the same.
One important thing, independently of the approach you want to follow, the "new endpoint" should be excluded from the required Spring authenticated endpoints, because you will manage the security by yourself:
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
..
#Override
protected void configure(HttpSecurity http) throws Exception {
http.
..
.authorizeRequests()
// List of services do not require authentication
.antMatchers(Rest Operator, "MyEndpointToRefreshOrExtendToken").permitAll()
// Any other request must be authenticated
.anyRequest().authenticated()
..
}
}
You can call the API for getting the refresh token as below
POST https://yourdomain.com/oauth/token
Header
"Authorization": "Basic [base64encode(clientId:clientSecret)]"
Parameters
"grant_type": "refresh_token"
"refresh_token": "[yourRefreshToken]"
Please be noticed that, the
base64encode is the method to encrypt the client authorization. You can use online at https://www.base64encode.org/
the refresh_token is the String value of the grant_type
yourRefreshToken is the refresh token received with JWT access token
The result can be seen as
{
"token_type":"bearer",
"access_token":"eyJ0eXAiOiJK.iLCJpYXQiO.Dww7TC9xu_2s",
"expires_in":20,
"refresh_token":"7fd15938c823cf58e78019bea2af142f9449696a"
}
Good luck.

Webflux JWT Authorization not working fine

I am following a tutorial about JWT in a spring reactive context (webflux).
The token generation is working fine, however the authorization is not working when I use the Authorization with bearer
Here is what I have done:
#EnableWebFluxSecurity
#EnableReactiveMethodSecurity
public class WebSecurityConfig{
#Autowired private JWTReactiveAuthenticationManager authenticationManager;
#Autowired private SecurityContextRepository securityContext;
#Bean public SecurityWebFilterChain configure(ServerHttpSecurity http){
return http.exceptionHandling()
.authenticationEntryPoint((swe , e) -> {
return Mono.fromRunnable(()->{
System.out.println( "authenticationEntryPoint user trying to access unauthorized api end points : "+
swe.getRequest().getRemoteAddress()+
" in "+swe.getRequest().getPath());
swe.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
});
}).accessDeniedHandler((swe, e) -> {
return Mono.fromRunnable(()->{
System.out.println( "accessDeniedHandler user trying to access unauthorized api end points : "+
swe.getPrincipal().block().getName()+
" in "+swe.getRequest().getPath());
swe.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
});
})
.and()
.csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.authenticationManager(authenticationManager)
.securityContextRepository(securityContext)
.authorizeExchange()
.pathMatchers(HttpMethod.OPTIONS).permitAll()
.pathMatchers("/auth/login").permitAll()
.anyExchange().authenticated()
.and()
.build();
}
As you can see, I want to simply deny all not authorized requests other than login or options based ones.
The login is working fine and I'm getting a token.
But trying to logout (a tweak that I implemented my self to make it state-full since I m only learning) is not working.
Here is my logout controller:
#RestController
#RequestMapping(AuthController.AUTH)
public class AuthController {
static final String AUTH = "/auth";
#Autowired
private AuthenticationService authService;
#PostMapping("/login")
public Mono<ResponseEntity<?>> login(#RequestBody AuthRequestParam arp) {
String username = arp.getUsername();
String password = arp.getPassword();
return authService.authenticate(username, password);
}
#PostMapping("/logout")
public Mono<ResponseEntity<?>> logout(#RequestBody LogoutRequestParam lrp) {
String token = lrp.getToken();
return authService.logout(token);
}
}
The logout request is as below:
As stated in images above, I believe that I m doing fine, however I m getting the error log message:
authenticationEntryPoint user trying to access unauthorized api end points : /127.0.0.1:45776 in /auth/logout
Here is my security context content:
/**
* we use this class to handle the bearer token extraction
* and pass it to the JWTReactiveAuthentication manager so in the end
* we produce
*
* simply said we extract the authorization we authenticate and
* depending on our implementation we produce a security context
*/
#Component
public class SecurityContextRepository implements ServerSecurityContextRepository {
#Autowired
private JWTReactiveAuthenticationManager authenticationManager;
#Override
public Mono<SecurityContext> load(ServerWebExchange swe) {
ServerHttpRequest request = swe.getRequest();
String authorizationHeaderContent = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if( authorizationHeaderContent !=null && !authorizationHeaderContent.isEmpty() && authorizationHeaderContent.startsWith("Bearer ")){
String token = authorizationHeaderContent.substring(7);
Authentication authentication = new UsernamePasswordAuthenticationToken(token, token);
return this.authenticationManager.authenticate(authentication).map((auth) -> {
return new SecurityContextImpl(auth);
});
}
return Mono.empty();
}
#Override
public Mono<Void> save(ServerWebExchange arg0, SecurityContext arg1) {
throw new UnsupportedOperationException("Not supported yet.");
}
}
I'm unable to see or find any issue or error that I have made. Where is the mistake?
There's a difference in writing
//Wrong
Jwts.builder()
.setSubject(username)
.setClaims(claims)
and
//Correct
Jwts.builder()
.setClaims(claims)
.setSubject(username)
Indeed, look at setSubject method in the DefaultJwtBuilder class :
#Override
public JwtBuilder setSubject(String sub) {
if (Strings.hasText(sub)) {
ensureClaims().setSubject(sub);
} else {
if (this.claims != null) {
claims.setSubject(sub);
}
}
return this;
}
When setSubject(username) is called first, ensureClaims() creates a DefaultClaims without yours and if you call setClaims(claims) the precedent subject is lost ! This JWT builder is bogus.
Otherwise, you're importing the wrong Role class in JWTReactiveAuthenticationManager, you have to replace :
import org.springframework.context.support.BeanDefinitionDsl.Role;
by
import com.bridjitlearning.www.jwt.tutorial.domain.Role;
Last and not least, validateToken() will return always false because of the check(token). put call is coming too late, you have to be aware of that. Either you remove this check or you move the put execution before calling the check method.
I'am not sure about what you want to do with resignTokenMemory, so i'll let you fix it by your own:
public Boolean validateToken(String token) {
return !isTokenExpired(token) && resignTokenMemory.check(token);
}
Another thing, your token is valid only 28,8 second, for testing raison i recommend you to expiraiton * 1000.

How can I test my JwtAuthentication class using JUnit & Mockito? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
I can test my JwtTokenAuthenticationFilter class. How can I write the test cases of this class using Mockito & JUnit? I can Only test this class.
I don't understand how I can mock the class.
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {
private final JwtConfig jwtConfig;
public JwtTokenAuthenticationFilter(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
System.out.println("Code is reachable");
// 1. get the authentication header. Tokens are supposed to be passed in the authentication header
String header = request.getHeader(jwtConfig.getHeader());
// 2. validate the header and check the prefix
if (header == null || !header.startsWith(jwtConfig.getPrefix())) {
chain.doFilter(request, response);
return;// If not valid, go to the next filter.
}
// If there is no token provided and hence the user won't be authenticated.
// It's Ok. Maybe the user accessing a public path or asking for a token.
// All secured paths that needs a token are already defined and secured in config class.
// And If user tried to access without access token, then he won't be authenticated and an exception will be thrown.
// 3. Get the token
String token = header.replace("Bearer","");
try { // exceptions might be thrown in creating the claims if for example the token is expired
// 4. Validate the token
Claims claims = Jwts.parser()
.setSigningKey(jwtConfig.getSecret().getBytes())
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
if (username != null) {
#SuppressWarnings("unchecked")
List<String> authorities = (List<String>) claims.get(ApplicationConstant.tokenAuthorities);
List<GrantedAuthority> grantAuthorities = new ArrayList<GrantedAuthority>();
// 5. Create auth object
// UsernamePasswordAuthenticationToken:A built-in object, used by spring to represent the current authenticated / being authenticated user.
// It needs a list of authorities, which has type of GrantedAuthority interface, where SimpleGrantedAuthority is an implementation of that interface
for (String authName : authorities) {
grantAuthorities.add(new SimpleGrantedAuthority(authName));
}
UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
username, null, grantAuthorities);
// 6. Authenticate the user
// Now, user is authenticated
SecurityContextHolder.getContext().setAuthentication(auth);
}
} catch (Exception e) {
// In case of failure. Make sure it's clear; so guarantee user won't be authenticated
SecurityContextHolder.clearContext();
}
// go to the next filter in the filter chain
chain.doFilter(request, response);
}
}
Spring gives you some mocks:
MockHttpServletRequest: mocked implementation of HttpServletRequest
MockHttpServletResponse: mocked implementation of HttpServletResponse
MockFilterChain: mocked implementation of FilterChain
MockFilterConfig: mocked implementation if FilterConfig
See the org.springframework.mock.web package for other mocks.
And here's some code that can help you to get started:
#RunWith(SpringRunner.class)
public class JwtTokenAuthenticationFilterTest {
#Before
public void before() {
SecurityContextHolder.clearContext();
}
#After
public void after() {
SecurityContextHolder.clearContext();
}
#Test
#SneakyThrows
public void doFilterInternal_shouldPopulateSecurityContext_whenTokenIsValid() {
String token = issueTokenForUser("john.doe");
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo");
request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + token);
MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = new MockFilterChain();
FilterConfig filterConfig = new MockFilterConfig();
JwtTokenAuthenticationFilter filter = new JwtTokenAuthenticationFilter();
filter.init(filterConfig);
filter.doFilter(request, response, filterChain);
filter.destroy();
assertThat(SecurityContextHolder.getContext().getAuthentication())
.satisfies(authentication -> {
assertThat(authentication).isNotNull();
assertThat(authentication.getName()).isEqualTo("john.doe");
});
}
private String issueTokenForUser(String username) {
return "xxxxx.yyyyy.zzzzz"; // Implement as per your needs
}
}
The above code uses AssertJ for assertions.

How to design a good JWT authentication filter

I am new to JWT. There isn't much information available in the web, since I came here as a last resort. I already developed a spring boot application using spring security using spring session. Now instead of spring session we are moving to JWT. I found few links and now I can able to authenticate a user and generate token. Now the difficult part is, I want to create a filter which will be authenticate every request to the server,
How will the filter validate the token? (Just validating the signature is enough?)
If someone else stolen the token and make rest call, how will I verify that.
How will I by-pass the login request in the filter? Since it doesn't have authorization header.
Here is a filter that can do what you need :
public class JWTFilter extends GenericFilterBean {
private static final Logger LOGGER = LoggerFactory.getLogger(JWTFilter.class);
private final TokenProvider tokenProvider;
public JWTFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException,
ServletException {
try {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String jwt = this.resolveToken(httpServletRequest);
if (StringUtils.hasText(jwt)) {
if (this.tokenProvider.validateToken(jwt)) {
Authentication authentication = this.tokenProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(servletRequest, servletResponse);
this.resetAuthenticationAfterRequest();
} catch (ExpiredJwtException eje) {
LOGGER.info("Security exception for user {} - {}", eje.getClaims().getSubject(), eje.getMessage());
((HttpServletResponse) servletResponse).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
LOGGER.debug("Exception " + eje.getMessage(), eje);
}
}
private void resetAuthenticationAfterRequest() {
SecurityContextHolder.getContext().setAuthentication(null);
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(SecurityConfiguration.AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
String jwt = bearerToken.substring(7, bearerToken.length());
return jwt;
}
return null;
}
}
And the inclusion of the filter in the filter chain :
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
public final static String AUTHORIZATION_HEADER = "Authorization";
#Autowired
private TokenProvider tokenProvider;
#Autowired
private AuthenticationProvider authenticationProvider;
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(this.authenticationProvider);
}
#Override
protected void configure(HttpSecurity http) throws Exception {
JWTFilter customFilter = new JWTFilter(this.tokenProvider);
http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
// #formatter:off
http.authorizeRequests().antMatchers("/css/**").permitAll()
.antMatchers("/images/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/authenticate").permitAll()
.anyRequest().fullyAuthenticated()
.and().formLogin().loginPage("/login").failureUrl("/login?error").permitAll()
.and().logout().permitAll();
// #formatter:on
http.csrf().disable();
}
}
The TokenProvider class :
public class TokenProvider {
private static final Logger LOGGER = LoggerFactory.getLogger(TokenProvider.class);
private static final String AUTHORITIES_KEY = "auth";
#Value("${spring.security.authentication.jwt.validity}")
private long tokenValidityInMilliSeconds;
#Value("${spring.security.authentication.jwt.secret}")
private String secretKey;
public String createToken(Authentication authentication) {
String authorities = authentication.getAuthorities().stream().map(authority -> authority.getAuthority()).collect(Collectors.joining(","));
ZonedDateTime now = ZonedDateTime.now();
ZonedDateTime expirationDateTime = now.plus(this.tokenValidityInMilliSeconds, ChronoUnit.MILLIS);
Date issueDate = Date.from(now.toInstant());
Date expirationDate = Date.from(expirationDateTime.toInstant());
return Jwts.builder().setSubject(authentication.getName()).claim(AUTHORITIES_KEY, authorities)
.signWith(SignatureAlgorithm.HS512, this.secretKey).setIssuedAt(issueDate).setExpiration(expirationDate).compact();
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(token).getBody();
Collection<? extends GrantedAuthority> authorities = Arrays.asList(claims.get(AUTHORITIES_KEY).toString().split(",")).stream()
.map(authority -> new SimpleGrantedAuthority(authority)).collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, "", authorities);
}
public boolean validateToken(String authToken) {
try {
Jwts.parser().setSigningKey(this.secretKey).parseClaimsJws(authToken);
return true;
} catch (SignatureException e) {
LOGGER.info("Invalid JWT signature: " + e.getMessage());
LOGGER.debug("Exception " + e.getMessage(), e);
return false;
}
}
}
Now to answer your questions :
Done in this filter
Protect your HTTP request, use HTTPS
Just permit all on the /login URI (/authenticate in my code)
I will focus in the general tips on JWT, without regarding code implemementation (see other answers)
How will the filter validate the token? (Just validating the signature is enough?)
RFC7519 specifies how to validate a JWT (see 7.2. Validating a JWT), basically a syntactic validation and signature verification.
If JWT is being used in an authentication flow, we can look at the validation proposed by OpenID connect specification 3.1.3.4 ID Token Validation. Summarizing:
iss contains the issuer identifier (and aud contains client_id if using oauth)
current time between iat and exp
Validate the signature of the token using the secret key
sub identifies a valid user
If someone else stolen the token and make rest call, how will I verify that.
Possesion of a JWT is the proof of authentication. An attacker who stoles a token can impersonate the user. So keep tokens secure
Encrypt communication channel using TLS
Use a secure storage for your tokens. If using a web front-end consider to add extra security measures to protect localStorage/cookies against XSS or CSRF attacks
set short expiration time on authentication tokens and require credentials if token is expired
How will I by-pass the login request in the filter? Since it doesn't have authorization header.
The login form does not require a JWT token because you are going to validate the user credential. Keep the form out of the scope of the filter. Issue the JWT after successful authentication and apply the authentication filter to the rest of services
Then the filter should intercept all requests except the login form, and check:
if user authenticated? If not throw 401-Unauthorized
if user authorized to requested resource? If not throw 403-Forbidden
Access allowed. Put user data in the context of request( e.g. using a ThreadLocal)
Take a look at this project it is very good implemented and has the needed documentation.
1. It the above project this is the only thing you need to validate the token and it is enough. Where token is the value of the Bearer into the request header.
try {
final Claims claims = Jwts.parser().setSigningKey("secretkey")
.parseClaimsJws(token).getBody();
request.setAttribute("claims", claims);
}
catch (final SignatureException e) {
throw new ServletException("Invalid token.");
}
2. Stealing the token is not so easy but in my experience you can protect yourself by creating a Spring session manually for every successfull log in. Also mapping the session unique ID and the Bearer value(the token) into a Map (creating a Bean for example with API scope).
#Component
public class SessionMapBean {
private Map<String, String> jwtSessionMap;
private Map<String, Boolean> sessionsForInvalidation;
public SessionMapBean() {
this.jwtSessionMap = new HashMap<String, String>();
this.sessionsForInvalidation = new HashMap<String, Boolean>();
}
public Map<String, String> getJwtSessionMap() {
return jwtSessionMap;
}
public void setJwtSessionMap(Map<String, String> jwtSessionMap) {
this.jwtSessionMap = jwtSessionMap;
}
public Map<String, Boolean> getSessionsForInvalidation() {
return sessionsForInvalidation;
}
public void setSessionsForInvalidation(Map<String, Boolean> sessionsForInvalidation) {
this.sessionsForInvalidation = sessionsForInvalidation;
}
}
This SessionMapBean will be available for all sessions. Now on every request you will not only verify the token but also you will check if he mathces the session (checking the request session id does matches the one stored into the SessionMapBean). Of course session ID can be also stolen so you need to secure the communication. Most common ways of stealing the session ID is Session Sniffing (or the Men in the middle) and Cross-site script attack. I will not go in more details about them you can read how to protect yourself from that kind of attacks.
3. You can see it into the project I linked. Most simply the filter will validated all /api/* and you will login into a /user/login for example.

Resources