SpringBoot3.0集成Jwt實(shí)現(xiàn)token驗(yàn)證過程
一、pom.xml引入依賴
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<version>2.3.1</version>
</dependency>二、創(chuàng)建工具類JwtUtils
public class JwtUtils {
private static final HashMap<String, String> _audiences = new HashMap<>();
public static String createJWT(UserProviderDto user) {
// 指定簽名的時(shí)候使用的簽名算法,也就是header那部分
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
HashMap<String, Object> claims = new HashMap<>();
claims.put(Const.JWT_CLAIMS_USER, JSON.toJSONString(user));
// 設(shè)置jwt的body
JwtBuilder builder = Jwts.builder()
.setClaims(claims)
.setSubject(user.getUsername())
.setAudience(updateAudience(user.getUsername()))
.setIssuedAt(new Date())
// 設(shè)置簽名使用的簽名算法和簽名使用的秘鑰
.signWith(signatureAlgorithm, Const.JWT_SECRET_KEY.getBytes(StandardCharsets.UTF_8))
// 設(shè)置過期時(shí)間
.setExpiration(TimeUtils.getTodayEnd());
return builder.compact();
}
/**
* Token解密
*/
public static Claims parseJWT(String token) {
try {
// 得到DefaultJwtParser
return Jwts.parser()
// 設(shè)置簽名的秘鑰
.setSigningKey(Const.JWT_SECRET_KEY.getBytes(StandardCharsets.UTF_8))
// 設(shè)置需要解析的jwt
.parseClaimsJws(token)
.getBody();
}
catch (Exception ex) {
return null;
}
}
/**
* 更新當(dāng)前登錄賬號(hào)的最新狀態(tài)
* @param userNo 用戶名
* @return 狀態(tài)標(biāo)志
*/
public static String updateAudience(String userNo) {
if (LogwingStringUtils.isNullOrEmpty(userNo))
return "";
var audience = userNo + TimeUtils.getCurrentTimeStamp();
_audiences.put(userNo, audience);
return audience;
}
/**
* 判斷當(dāng)前token是否最新的,不是需重新登錄
* @return boolean
*/
public static boolean isNewestAudience(String userNo, String audience) {
if (LogwingStringUtils.isNullOrEmpty(userNo) || LogwingStringUtils.isNullOrEmpty(audience))
return false;
if (!_audiences.containsKey(userNo))
return false;
return _audiences.get(userNo).equals(audience);
}
/**
* 獲取token
* @param request 當(dāng)前http請(qǐng)求
* @return token
*/
public static String getToken(HttpServletRequest request) {
String token = request.getHeader("Authorization");
if (LogwingStringUtils.isNullOrEmpty(token)) {
return "";
}
token = token.replace("Bearer ", "");
if (LogwingStringUtils.isNullOrEmpty(token)) {
return "";
}
return token;
}
}三、添加全局?jǐn)r截器,校驗(yàn)token是否有效
public class JwtInterceptor implements HandlerInterceptor {
//主要功能攔截請(qǐng)求驗(yàn)證token
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String token = JwtUtils.getToken(request);
if (LogwingStringUtils.isNullOrEmpty(token)) {
throw new InvalidUserException();
}
var claims = JwtUtils.parseJWT(token);
if (claims == null) {
throw new InvalidUserException();
}
Date createTime = claims.getIssuedAt();
if (createTime == null || createTime.compareTo(TimeUtils.getTodayStart()) < 0) {
throw new InvalidUserException();
}
String audience = claims.getAudience();
if (!JwtUtils.isNewestAudience(claims.getSubject(), audience)) {
throw new InvalidUserException();
}
return true;
}
}四、將攔截器添加到WebConfig配置中
攔截請(qǐng)求進(jìn)行token校驗(yàn)
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new JwtInterceptor())
//攔截的路徑
.addPathPatterns("/**")
//不需要的攔截請(qǐng)求
.excludePathPatterns(
"/api/OAuth/login",
"/api/OAuth/logout",
"/api/getDate",
"/swagger-ui/**",
"/v3/**");
}
}五、登錄接口增加生成token
返回前端,賬號(hào)密碼校驗(yàn)完成之后生成token,將用戶基本信息存入token
public LoginOutputDto login(LoginUserInputDto input) {
Admin loginUser = adminRepository.getLoginUserByUserName(input.getUserName());
if (loginUser == null)
return new LoginOutputDto(false, "用戶名不存在,請(qǐng)重新輸入。");
var inputPassword = RSAUtils.decryptByRSA(input.getEncryptedPassword());
var encryptPassword = MD5Utils.encryptMD5(MD5Utils.encryptMD5(inputPassword) + loginUser.getSalt());
if (!encryptPassword.equals(loginUser.getPassword()))
return new LoginOutputDto(false, "密碼有誤,請(qǐng)重新輸入。");
UserProviderDto userProvider = getUserProvider(loginUser);
String token = JwtUtils.createJWT(userProvider);
LoginOutputDto output = new LoginOutputDto(true, "登錄成功。");
output.setAccessToken(token);
return output;
}
private UserProviderDto getUserProvider(Admin loginUser) {
UserProviderDto userProvider = new UserProviderDto();
userProvider.setId(loginUser.getId());
userProvider.setUsername(loginUser.getUsername());
userProvider.setStatus(loginUser.getStatus());
userProvider.setType(loginUser.getType());
return userProvider;
}
}六、定義UserProvider
提供從token解析出當(dāng)前用戶信息的接口方法
@Service
public class UserProvider implements IUserProvider {
@Autowired
private HttpServletRequest httpServletRequest;
@Override
public UserProviderDto getCurrentUser() {
String token = JwtUtils.getToken(httpServletRequest);
if (LogwingStringUtils.isNullOrEmpty(token)) {
throw new RuntimeException(Const.UN_LOGIN_TIPS);
}
Claims claims = JwtUtils.parseJWT(token);
if (claims == null) {
throw new RuntimeException(Const.UN_LOGIN_TIPS);
}
Date createTime = claims.getIssuedAt();
if (createTime == null || createTime.compareTo(TimeUtils.getTodayStart()) < 0) {
throw new RuntimeException(Const.UN_LOGIN_TIPS);
}
String user = (String) claims.get(Const.JWT_CLAIMS_USER);
return JSON.parseObject(user, UserProviderDto.class);
}
}七、定義controller接口
登錄接口
@PostMapping("/api/OAuth/login")
@ResponseBody
@Operation(summary = "登錄授權(quán)接口", description = "登錄授權(quán)接口")
public LoginOutputDto login(@Valid @RequestBody LoginUserInputDto input) throws NoSuchAlgorithmException {
return loginService.login(input);
}退出接口
退出時(shí)更新當(dāng)前賬號(hào)的Audience,用來避免退出后用舊的token仍然可以通過接口token是否有效的攔截(因?yàn)閖wt具有無狀態(tài)性,生成token之后改token只能等設(shè)置的過期時(shí)間到了之后才能自動(dòng)銷毀,否則該token在過期時(shí)間之前仍然有效),具體方法見jwtutils中的updateaudience和isnewestaudience方法
@PostMapping("/api/OAuth/logout")
@ResponseBody
@Operation(summary = "退出接口")
public ResponseOutputDto logout() {
var user = userProvider.getCurrentUser();
JwtUtils.updateAudience(user.getUsername());
return new ResponseOutputDto();
}八、自定義異常InvalidUserException
并攔截全局異常InvalidUserException,捕捉攔截器拋出的異常,返回給前端
public class InvalidUserException extends Exception {
public InvalidUserException() {
super(Const.UN_LOGIN_TIPS);
}
}@ControllerAdvice
@Slf4j
public class SystemExceptionHandler {
@ExceptionHandler(InvalidUserException.class)
@ResponseBody
public LoginOutputDto doException(InvalidUserException ex) {
return new LoginOutputDto(false, ex.getMessage());
}
@ExceptionHandler(Exception.class)
@ResponseBody
public ResponseOutputDto doException(Exception ex) {
if (ex.getMessage().contains("未登錄")) {
return new ResponseOutputDto(false, ex.getMessage());
}
log.error(ex.getMessage(), ex);
return new ResponseOutputDto(false, "程序發(fā)生錯(cuò)誤,請(qǐng)聯(lián)系客服。");
}
}總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- springboot集成JWT之雙重token的實(shí)現(xiàn)
- SpringBoot中基于JWT的單token授權(quán)和續(xù)期方案步驟詳解
- springBoot整合jwt實(shí)現(xiàn)token令牌認(rèn)證的示例代碼
- springboot中通過jwt令牌校驗(yàn)及前端token請(qǐng)求頭進(jìn)行登錄攔截實(shí)戰(zhàn)記錄
- SpringBoot整合JWT(JSON?Web?Token)生成token與驗(yàn)證的流程及示例
- springboot+shiro+jwtsession和token進(jìn)行身份驗(yàn)證和授權(quán)
- SpringBoot整合Mybatis-Plus、Jwt實(shí)現(xiàn)登錄token設(shè)置
- SpringBoot集成JWT實(shí)現(xiàn)Token登錄驗(yàn)證的示例代碼
相關(guān)文章
SpringBoot利用Junit動(dòng)態(tài)代理實(shí)現(xiàn)Mock方法
說到Spring Boot 單元測(cè)試主要有兩個(gè)主流集成分別是Mockito,Junit,這個(gè)各有特點(diǎn),在實(shí)際開發(fā)中,我想要的測(cè)試框架應(yīng)該是這個(gè)框架集成者,本文給大家介紹了SpringBoot利用Junit動(dòng)態(tài)代理實(shí)現(xiàn)Mock方法,需要的朋友可以參考下2024-04-04
java連接Mysql數(shù)據(jù)庫(kù)的工具類
這篇文章主要介紹了java連接Mysql數(shù)據(jù)庫(kù)的工具類,非常的實(shí)用,推薦給大家,需要的朋友可以參考下2015-03-03
超詳細(xì)講解Java秒殺項(xiàng)目登陸模塊的實(shí)現(xiàn)
這是一個(gè)主要使用java開發(fā)的秒殺系統(tǒng),項(xiàng)目比較大,所以本篇只實(shí)現(xiàn)了登陸模塊,代碼非常詳盡,感興趣的朋友快來看看2022-03-03
SpringBoot項(xiàng)目加載配置文件的6種方式小結(jié)
這篇文章給大家總結(jié)了六種SpringBoot項(xiàng)目加載配置文件的方式,通過@value注入,通過@ConfigurationProperties注入,通過框架自帶對(duì)象Environment實(shí)現(xiàn)屬性動(dòng)態(tài)注入,通過@PropertySource注解,yml外部文件,Java原生態(tài)方式注入這六種,需要的朋友可以參考下2023-09-09
詳解Java Fibonacci Search斐波那契搜索算法代碼實(shí)現(xiàn)
這篇文章主要介紹了詳解Java Fibonacci Search斐波那契搜索算法代碼實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-10-10
Springboot項(xiàng)目參數(shù)校驗(yàn)方式(Validator)
本文介紹了如何在Spring Boot項(xiàng)目中使用`spring-boot-starter-validation`包和注解來實(shí)現(xiàn)請(qǐng)求參數(shù)校驗(yàn),主要介紹了校驗(yàn)注解的使用方法、校驗(yàn)失敗的異常捕獲以及`@Validated`的分組功能2025-02-02

