Integrating JWT Authentication in Spring Boot
This article explains the fundamentals of JSON Web Tokens (JWT), their structure, main use cases, and provides a step‑by‑step guide for integrating JWT authentication into a Spring Boot application, including dependency setup, custom annotations, token generation, interceptor implementation, and configuration details.
What is JWT
JSON Web Token (JWT) is an open standard (RFC 7519) for securely transmitting claims between parties as a JSON object. The token is compact, self‑contained and can be signed using HMAC or RSA algorithms, making the information trustworthy.
JWT Request Flow
The typical flow is:
User sends a POST request with credentials.
The server creates a JWT using a private key.
The server returns the JWT to the browser.
The browser includes the JWT in the Authorization header of subsequent requests.
The server validates the JWT.
Upon successful validation, the server returns the requested resource.
Main Application Scenarios
Identity Authentication: After a user logs in, the JWT is attached to every request to verify identity and access rights. Its small size makes it ideal for Single Sign‑On (SSO) across different domains.
Information Exchange: JWTs can securely encode data between services because the payload is signed, preventing tampering.
Advantages
Compact – can be sent via URL, POST parameters, or HTTP headers.
Self‑contained – carries all required information, reducing database lookups.
Language‑agnostic – any platform that can handle JSON can use JWT.
No server‑side session storage – suitable for distributed micro‑services.
JWT Structure
A JWT consists of three Base64‑URL encoded parts separated by dots:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
Header
The header contains the token type and signing algorithm.
{
"alg": "HS256",
"typ": "JWT"
}Typical fields: alg: signing algorithm (e.g., HS256). typ: token type (JWT).
Payload
The payload carries the actual claims. Claims are divided into three categories:
Registered claims (standard, optional): iss, sub, aud, exp, nbf, iat, jti.
Public claims (custom, non‑sensitive).
Private claims (application‑specific, should avoid sensitive data because they are only Base64‑encoded).
Signature
The signature is created by signing the Base64‑encoded header and payload with the algorithm specified in the header and a secret key stored on the server.
Integrating JWT with Spring Boot
1. Add JWT Dependency
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>2. Define Custom Annotations
Two annotations are used to control authentication:
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface PassToken {
boolean required() default true;
}
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface UserLoginToken {
boolean required() default true;
}The @Target meta‑annotation specifies where the annotation can be applied (methods, types, etc.), and @Retention(RUNTIME) makes it available at runtime for reflection.
3. Create a Simple User Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
private String id;
private String username;
private String password;
}4. Token Generation Method
public String getToken(User user) {
String token = "";
token = JWT.create()
.withAudience(user.getId())
.sign(Algorithm.HMAC256(user.getPassword()));
return token;
}5. Authentication Interceptor
public class AuthenticationInterceptor implements HandlerInterceptor {
@Autowired
UserService userService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = request.getHeader("token");
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod hm = (HandlerMethod) handler;
Method method = hm.getMethod();
// Skip if @PassToken is present
if (method.isAnnotationPresent(PassToken.class)) {
PassToken pt = method.getAnnotation(PassToken.class);
if (pt.required()) {
return true;
}
}
// Verify if @UserLoginToken is present
if (method.isAnnotationPresent(UserLoginToken.class)) {
UserLoginToken ult = method.getAnnotation(UserLoginToken.class);
if (ult.required()) {
if (token == null) {
throw new RuntimeException("No token, please log in again");
}
String userId;
try {
userId = JWT.decode(token).getAudience().get(0);
} catch (JWTDecodeException e) {
throw new RuntimeException("401");
}
User user = userService.findUserById(userId);
if (user == null) {
throw new RuntimeException("User not found, please log in again");
}
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(user.getPassword())).build();
try {
verifier.verify(token);
} catch (JWTVerificationException e) {
throw new RuntimeException("401");
}
return true;
}
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {}
}6. Register the Interceptor
In Spring Boot 2.x the old WebMvcConfigurerAdapter is deprecated. Use WebMvcConfigurer directly:
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor())
.addPathPatterns("/**");
}
@Bean
public AuthenticationInterceptor authenticationInterceptor() {
return new AuthenticationInterceptor();
}
}7. Apply Annotations to Controllers
@RestController
@RequestMapping("api")
public class UserApi {
@Autowired
UserService userService;
@Autowired
TokenService tokenService;
@PostMapping("/login")
public Object login(@RequestBody User user) {
JSONObject json = new JSONObject();
User dbUser = userService.findByUsername(user);
if (dbUser == null) {
json.put("message", "Login failed, user does not exist");
return json;
}
if (!dbUser.getPassword().equals(user.getPassword())) {
json.put("message", "Login failed, wrong password");
return json;
}
String token = tokenService.getToken(dbUser);
json.put("token", token);
json.put("user", dbUser);
return json;
}
@UserLoginToken
@GetMapping("/getMessage")
public String getMessage() {
return "You have passed verification";
}
}The login endpoint does not require authentication, while /getMessage is protected by @UserLoginToken. Clients must include the JWT in the request header token to access the protected endpoint.
8. Testing with Postman
1. Call /api/login with valid credentials to obtain a token. 2. Call /api/getMessage without the token – the server returns an error. 3. Add the token to the request header token and call the endpoint again – the request succeeds.
9. Additional Notes
WebMvcConfigurerAdapteris deprecated in Spring Boot 2.0 / Spring 5.0; implement WebMvcConfigurer directly.
The secret key used for signing should be stored securely on the server and never exposed to the client.
Never place sensitive information in the JWT payload because it is only Base64‑encoded.
For the full source code, see the GitHub repository: https://github.com/JinBinPeng/springboot-jwt .
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Top Architect
Top Architect focuses on sharing practical architecture knowledge, covering enterprise, system, website, large‑scale distributed, and high‑availability architectures, plus architecture adjustments using internet technologies. We welcome idea‑driven, sharing‑oriented architects to exchange and learn together.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
