-
Notifications
You must be signed in to change notification settings - Fork 10
/
JwtAuthenticationFilter.java
100 lines (87 loc) · 4.17 KB
/
JwtAuthenticationFilter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
package com.behl.overseer.filter;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import com.behl.overseer.dto.ExceptionResponseDto;
import com.behl.overseer.utility.ApiEndpointSecurityInspector;
import com.behl.overseer.utility.JwtUtility;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
/**
* JwtAuthenticationFilter is a custom filter registered with the spring
* security filter chain and works in conjunction with the security
* configuration, as defined in {@link com.behl.overseer.configuration.SecurityConfiguration}.
*
* It is responsible for verifying the authenticity of incoming HTTP requests to
* secured API endpoints by examining JWT token in the request header, verifying
* it's signature, expiration.
* If authentication is successful, the filter populates the security context with
* the user's unique identifier which can be referenced by the application later.
*
* This filter is only executed when a secure API endpoint in invoked, and is skipped
* if the incoming request is destined to a non-secured public API endpoint.
*
* @see com.behl.overseer.configuration.SecurityConfiguration
* @see com.behl.overseer.utility.ApiEndpointSecurityInspector
* @see com.behl.overseer.utility.JwtUtility
*/
@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final ObjectMapper objectMapper;
private final JwtUtility jwtUtility;
private final ApiEndpointSecurityInspector apiEndpointSecurityInspector;
private static final String AUTHORIZATION_HEADER = "Authorization";
private static final String BEARER_PREFIX = "Bearer ";
private static final String MISSING_TOKEN_ERROR_MESSAGE = "Authentication failure: Token missing, invalid or expired";
@Override
@SneakyThrows
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
final var unsecuredApiBeingInvoked = apiEndpointSecurityInspector.isUnsecureRequest(request);
if (Boolean.FALSE.equals(unsecuredApiBeingInvoked)) {
final var authorizationHeader = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.isNotEmpty(authorizationHeader) && authorizationHeader.startsWith(BEARER_PREFIX) ) {
final var token = authorizationHeader.replace(BEARER_PREFIX, StringUtils.EMPTY);
final var userId = jwtUtility.getUserId(token);
final var authentication = new UsernamePasswordAuthenticationToken(userId, null, null);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
setAuthErrorDetails(response);
return;
}
}
filterChain.doFilter(request, response);
}
/**
* Sets the authentication error details in the HTTP response.
*
* @param response instance of HttpServletResponse to which error response will be set.
*/
@SneakyThrows
private void setAuthErrorDetails(HttpServletResponse response) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
final var errorResponse = prepareErrorResponseBody();
response.getWriter().write(errorResponse);
}
/**
* Returns a JSON representation of the invalid token error response body.
*/
@SneakyThrows
private String prepareErrorResponseBody() {
final var exceptionResponse = new ExceptionResponseDto<String>();
exceptionResponse.setStatus(HttpStatus.UNAUTHORIZED.toString());
exceptionResponse.setDescription(MISSING_TOKEN_ERROR_MESSAGE);
return objectMapper.writeValueAsString(exceptionResponse);
}
}