SpringBoot實(shí)現(xiàn)JWT認(rèn)證的完整方案
引言
JWT(JSON Web Token)是一種用于在網(wǎng)絡(luò)應(yīng)用間安全傳遞信息的開放標(biāo)準(zhǔn),特別適合分布式系統(tǒng)的身份驗(yàn)證。下面我將詳細(xì)介紹如何在SpringBoot項(xiàng)目中實(shí)現(xiàn)JWT認(rèn)證。
一、JWT 基礎(chǔ)概念
JWT由三部分組成,用.連接:
- Header(頭部):包含令牌類型和使用的加密算法
- Payload(載荷):包含用戶信息和聲明
- Signature(簽名):使用密鑰對前兩部分進(jìn)行簽名,確保信息不被篡改
二、項(xiàng)目準(zhǔn)備
1. 添加依賴
在pom.xml中添加JWT相關(guān)依賴:
<!-- JWT依賴 -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.2</version>
</dependency>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok(可選,用于簡化代碼) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
2. 配置文件
在application.yml中添加JWT相關(guān)配置:
spring:
application:
name: springboot-jwt-demo
jwt:
# 密鑰
secret: your-secret-key-change-in-production
# 過期時(shí)間(毫秒)
expire-time: 86400000
# 請求頭中Token的名稱
header: Authorization
# Token前綴
token-prefix: Bearer
三、核心代碼實(shí)現(xiàn)
1. JWT工具類
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Calendar;
import java.util.Map;
@Component
public class JWTUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expire-time}")
private long expireTime;
/**
* 生成Token
* @param claims 要存儲在Token中的信息
* @return 生成的Token字符串
*/
public String generateToken(Map<String, String> claims) {
// 設(shè)置過期時(shí)間
Calendar instance = Calendar.getInstance();
instance.add(Calendar.MILLISECOND, (int) expireTime);
// 創(chuàng)建JWT Builder
JWTCreator.Builder builder = JWT.create();
// 添加載荷
claims.forEach(builder::withClaim);
// 設(shè)置簽名和過期時(shí)間,生成Token
return builder.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(secret));
}
/**
* 驗(yàn)證Token
* @param token 要驗(yàn)證的Token
* @return 驗(yàn)證后的DecodedJWT對象
* @throws JWTVerificationException 驗(yàn)證失敗時(shí)拋出異常
*/
public DecodedJWT verifyToken(String token) {
return JWT.require(Algorithm.HMAC256(secret))
.build()
.verify(token);
}
/**
* 解析Token
* @param token 要解析的Token
* @return 解析后的DecodedJWT對象
*/
public DecodedJWT parseToken(String token) {
return JWT.decode(token);
}
/**
* 從Token中獲取指定的Claim
* @param token Token字符串
* @param claimName Claim名稱
* @return Claim值
*/
public String getClaim(String token, String claimName) {
DecodedJWT decodedJWT = parseToken(token);
return decodedJWT.getClaim(claimName).asString();
}
}
2. JWT攔截器
import com.auth0.jwt.exceptions.JWTVerificationException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
@Component
@Slf4j
public class JWTInterceptor implements HandlerInterceptor {
@Autowired
private JWTUtil jwtUtil;
@Value("${jwt.header}")
private String jwtHeader;
@Value("${jwt.token-prefix}")
private String tokenPrefix;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String token = request.getHeader(jwtHeader);
// 檢查請求頭中是否包含Token
if (token == null || "".equals(token.trim())) {
response401(response, "Token不存在");
return false;
}
// 移除Token前綴
if (token.startsWith(tokenPrefix)) {
token = token.replace(tokenPrefix, "").trim();
} else {
response401(response, "Token格式錯(cuò)誤");
return false;
}
try {
// 驗(yàn)證Token
jwtUtil.verifyToken(token);
log.info("Token驗(yàn)證成功");
return true;
} catch (JWTVerificationException e) {
log.error("Token驗(yàn)證失敗: {}", e.getMessage());
response401(response, "Token無效或已過期");
return false;
}
}
/**
* 返回401錯(cuò)誤響應(yīng)
*/
private void response401(HttpServletResponse response, String message) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
try (PrintWriter out = response.getWriter()) {
out.append("{\"code\": 401, \"message\": \"").append(message).append("\"}");
} catch (Exception e) {
log.error("響應(yīng)錯(cuò)誤: {}", e.getMessage());
}
}
}
3. 攔截器配置
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Autowired
private JWTInterceptor jwtInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 添加攔截器,攔截需要認(rèn)證的路徑
registry.addInterceptor(jwtInterceptor)
// 需要攔截的路徑
.addPathPatterns("/api/**")
// 排除不需要攔截的路徑
.excludePathPatterns("/api/login", "/api/register", "/error");
}
}
4. 實(shí)體類
import lombok.Data;
@Data
public class LoginRequest {
private String username;
private String password;
}
@Data
public class LoginResponse {
private String token;
private String username;
private String message;
}
5. 用戶服務(wù)接口
public interface UserService {
boolean login(String username, String password);
boolean register(String username, String password);
}
6. 用戶服務(wù)實(shí)現(xiàn)
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
@Service
public class UserServiceImpl implements UserService {
// 模擬數(shù)據(jù)庫,實(shí)際項(xiàng)目中應(yīng)使用數(shù)據(jù)庫存儲
private final Map<String, String> userMap = new HashMap<>();
public UserServiceImpl() {
// 初始化測試用戶
userMap.put("admin", "123456");
}
@Override
public boolean login(String username, String password) {
return userMap.containsKey(username) && userMap.get(username).equals(password);
}
@Override
public boolean register(String username, String password) {
if (userMap.containsKey(username)) {
return false; // 用戶名已存在
}
userMap.put(username, password);
return true;
}
}
7. 登錄控制器
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("/api")
public class AuthController {
@Autowired
private UserService userService;
@Autowired
private JWTUtil jwtUtil;
@Value("${jwt.token-prefix}")
private String tokenPrefix;
/**
* 用戶登錄
*/
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
if (userService.login(request.getUsername(), request.getPassword())) {
// 構(gòu)建Token中的載荷信息
Map<String, String> claims = new HashMap<>();
claims.put("username", request.getUsername());
// 生成Token
String token = jwtUtil.generateToken(claims);
// 構(gòu)建響應(yīng)
LoginResponse response = new LoginResponse();
response.setToken(tokenPrefix + " " + token);
response.setUsername(request.getUsername());
response.setMessage("登錄成功");
return ResponseEntity.ok(response);
}
return ResponseEntity.badRequest().body("用戶名或密碼錯(cuò)誤");
}
/**
* 用戶注冊
*/
@PostMapping("/register")
public ResponseEntity<?> register(@RequestBody LoginRequest request) {
if (userService.register(request.getUsername(), request.getPassword())) {
return ResponseEntity.ok("注冊成功");
}
return ResponseEntity.badRequest().body("用戶名已存在");
}
/**
* 測試接口,需要驗(yàn)證Token
*/
@GetMapping("/test")
public ResponseEntity<?> test() {
return ResponseEntity.ok("訪問成功,Token有效");
}
}
四、安全性考慮
- 密鑰安全:密鑰必須保密,生產(chǎn)環(huán)境應(yīng)使用強(qiáng)密鑰
- Token過期時(shí)間:設(shè)置合理的過期時(shí)間,避免永久有效
- HTTPS傳輸:在生產(chǎn)環(huán)境中使用HTTPS傳輸Token
- 防XSS攻擊:Token存儲在localStorage時(shí)可能被XSS攻擊竊取
- Token刷新機(jī)制:實(shí)現(xiàn)Token刷新機(jī)制,避免頻繁登錄
- 黑名單機(jī)制:對于已注銷的Token,添加到黑名單中
五、優(yōu)化建議
- 使用Redis存儲Token:可以更靈活地管理Token生命周期
- 實(shí)現(xiàn)Refresh Token:提供長期的Refresh Token用于獲取新的Access Token
- 增加角色權(quán)限:在Token中添加用戶角色信息,實(shí)現(xiàn)更細(xì)粒度的權(quán)限控制
- 使用非對稱加密:生產(chǎn)環(huán)境可考慮使用RSA等非對稱加密算法
- 添加IP綁定:在Token中綁定用戶IP,增加安全性
- 添加日志記錄:記錄Token的生成、驗(yàn)證和使用情況
六、測試方法
- 啟動(dòng)SpringBoot應(yīng)用
- 使用Postman或其他工具測試登錄接口:
POST http://localhost:8080/api/login - 在請求體中添加用戶名和密碼
- 成功后將返回的Token添加到請求頭
- 訪問需要認(rèn)證的接口:
GET http://localhost:8080/api/test
這個(gè)實(shí)現(xiàn)提供了SpringBoot中JWT認(rèn)證的完整流程,包括Token生成、驗(yàn)證和攔截器實(shí)現(xiàn)。您可以根據(jù)實(shí)際需求進(jìn)行調(diào)整和擴(kuò)展。
以上就是SpringBoot實(shí)現(xiàn)JWT認(rèn)證的完整方案的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot JWT認(rèn)證的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot 打包 failed with MalformedInput
本文主要介紹了SpringBoot項(xiàng)目在打包時(shí)常見的錯(cuò)誤,通常是由于資源文件的編碼問題導(dǎo)致的,下面就一起來介紹一下該問題的詳細(xì)步驟,感興趣的可以了解一下2025-07-07
詳解Spring框架之基于Restful風(fēng)格實(shí)現(xiàn)的SpringMVC
這篇文章主要介紹了詳解Spring框架之基于Restful風(fēng)格實(shí)現(xiàn)的SpringMVC,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
如何通過RabbitMq實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)詳解
工作中經(jīng)常會(huì)有定時(shí)任務(wù)的需求,常見的做法可以使用Timer、Quartz、Hangfire等組件,這次想嘗試下新的思路,使用RabbitMQ死信隊(duì)列的機(jī)制來實(shí)現(xiàn)定時(shí)任務(wù),下面這篇文章主要給大家介紹了關(guān)于如何通過RabbitMq實(shí)現(xiàn)動(dòng)態(tài)定時(shí)任務(wù)的相關(guān)資料,需要的朋友可以參考下2022-01-01

