Understanding JWT Structure
JWT (JSON Web Token) is an open standard (RFC 7519) that defines a compact method for securely transmitting information betwean parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs are commonly used for authentication and authorization purposes in web applications.
A JWT consists of three distinct parts:
- Header: A JSON object containing metadata about the token, including the signing algorithm and token type
- Payload: A JSON object containing claims such as user information, issuer details, and expiration time
- Signature: A cryptographic signature created by encrypting the base64-encoded header and payload using the specified algorithm
Dependency Configuration
Add the JWT library dependency to you're project's build configuration:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
JWT Utility Class Implementation
Create a utility class for JWT operations with configurable signing key and expiration time:
public class JwtHelper {
private static String secret = "defaultSecret";
private static Long validity = 43200000L;
public static String createToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS256, secret)
.setExpiration(new Date(System.currentTimeMillis() + validity))
.compact();
}
public static Claims decodeToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
}
Spring Boot Integration Methods
Method 1: Bean Declaration
@Bean
public JwtHelper jwtHelper() {
return new JwtHelper();
}
Method 2: Auto Configuration
Create META-INF/spring.factories file with configuration classes:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.example.utils.JwtHelper
Login Service Implementation
@Service
public class AuthService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
public String authenticateUser(LoginRequest credentials) {
User account = findUserByPhone(credentials.getPhone());
if(PasswordUtil.validate(credentials.getPhone() + credentials.getPassword(),
account.getPassword())) {
Map<String, Object> tokenData = new HashMap<>();
tokenData.put("userId", account.getId());
tokenData.put("userPhone", account.getPhone());
String authToken = JwtHelper.createToken(tokenData);
redisTemplate.opsForValue().set(authToken, authToken, 2, TimeUnit.HOURS);
return authToken;
}
return null;
}
}
Authenticasion Interceptor
@Component
public class AuthInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
String authHeader = request.getHeader("Authorization");
try {
String storedToken = redisTemplate.opsForValue().get(authHeader);
if (storedToken == null) {
throw new AuthenticationException("Token invalid or expired");
}
Claims tokenClaims = JwtHelper.decodeToken(authHeader);
RequestContext.setClaims(tokenClaims);
return true;
} catch (Exception error) {
response.setStatus(HttpStatus.UNAUTHORIZED.value());
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
RequestContext.clear();
}
}
Interceptor Configuration
@Configuration
public class SecurityConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.excludePathPatterns("/auth/login", "/auth/register",
"/auth/sms-login", "/auth/send-code");
}
}