Java實現(xiàn)JWT登錄認(rèn)證的示例代碼
什么是JWT?
JWT(Json Web Token),簡單來說就是:web領(lǐng)域中基于json格式的令牌。是最常用的令牌規(guī)范。
- 第一部分:Header(頭),指定了令牌的簽名算法、令牌類型。
- 第二部分:Payload(有效載荷),使用Base64來編碼的,不是加密算法,能夠解碼。因此,該部分不適合存放用戶的私密信息(如:密碼)。
- 第三部分:Signature(簽名),將第一部分和第二部分通過密鑰加密得到。

解析Token可以根據(jù)第三部分解密得到前兩部分的信息,再比對前端傳來的用戶信息,完成校驗。
Token驗證失敗的情況?
token過期
密鑰不正確
篡改了頭部、載荷等
為什么需要令牌?
- 承載了一定的數(shù)據(jù)信息,減少數(shù)據(jù)庫訪問次數(shù)
- 具有一定的防偽功能
如何實現(xiàn)?
該部分代碼不需要硬記,理解后直接使用即可。
添加依賴:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
JwtUtils.java(生成、解析Token的工具類)
public class JwtUtil {
/**
* 生成Token
* @param secretKey
* @param ttlMillis
* @param claims
* @return
*/
public static String createToken(String secretKey, long ttlMillis, Map<String, Object> claims) {
// 指定簽名的時候使用的簽名算法,也就是header那部分
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 生成token的過期時間
long expMillis = System.currentTimeMillis() + ttlMillis;
Date exp = new Date(expMillis);
// 設(shè)置jwt的body
JwtBuilder builder = Jwts.builder()
// 如果有私有聲明,一定要先設(shè)置這個自己創(chuàng)建的私有的聲明,這個是給builder的claim賦值,一旦寫在標(biāo)準(zhǔn)的聲明賦值之后,就是覆蓋了那些標(biāo)準(zhǔn)的聲明的
.setClaims(claims)
// 設(shè)置簽名使用的簽名算法和簽名使用的秘鑰
.signWith(signatureAlgorithm, secretKey.getBytes(StandardCharsets.UTF_8))
// 設(shè)置過期時間
.setExpiration(exp);
return builder.compact();
}
/**
* 解析token
* @param secretKey
* @param token
* @return
*/
public static Claims parseToken(String secretKey, String token) {
// 得到DefaultJwtParser
Claims claims = Jwts.parser()
// 設(shè)置簽名的秘鑰
.setSigningKey(secretKey.getBytes(StandardCharsets.UTF_8))
// 設(shè)置需要解析的jwt
.parseClaimsJws(token).getBody();
return claims;
}
}
jwt配置:
application.yaml:
user:
jwt:
# 設(shè)置jwt簽名加密時使用的秘鑰
user-secret-key: xxx
# 設(shè)置jwt過期時間
user-ttl: 7200000
# 設(shè)置前端傳遞過來的令牌名稱
user-token-name: token
JwtProperties:
@Component
@ConfigurationProperties(prefix = "user.jwt")
@Data
public class JwtProperties {
private String userSecretKey;
private long userTtl;
private String userTokenName;
}
JwtClaimsConstant(管理常量):
public class JwtClaimsConstant {
public static final String USER_ID = "userId";
}
jwt攔截器:
@Component
@Slf4j
public class JwtInterceptor implements HandlerInterceptor {
@Resource
private JwtProperties jwtProperties;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1. 獲取用戶信息
String token = request.getHeader(jwtProperties.getUserTokenName());
//2. 判斷用戶信息是否有效,存入ThreadLocal
try {
Claims claims = JwtUtil.parseToken(jwtProperties.getUserSecretKey(), token);
String userInfo = claims.get(JwtClaimsConstant.USER_ID).toString();
if (StrUtil.isNotBlank(userInfo)){
UserContext.setUser(Long.valueOf(userInfo));
}
//3. 放行
return true;
}catch (Exception e){
//4. 不通過
response.setStatus(401);
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
//清除用戶信息
UserContext.removeUser();
}
}
WebMvc配置(添加jwt攔截器)
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private JwtInterceptor jwtInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/user/**")
.excludePathPatterns("/user/user/login");
}
}
UserContext(存儲用戶登錄信息,方便其他業(yè)務(wù)需求獲取)
public class UserContext {
private static final ThreadLocal<Long> threadLocal = new ThreadLocal<>();
/**
* 獲取用戶信息
* @return
*/
public static Long getUser() {
return threadLocal.get();
}
/**
* 設(shè)置用戶信息
* @param userId
*/
public static void setUser(Long userId){
threadLocal.set(userId);
}
/**
* 移除用戶信息
*/
public static void removeUser(){
threadLocal.remove();
}
}
登錄業(yè)務(wù)邏輯:
controller層:
@RestController
@RequestMapping("/user/user")
@Slf4j
public class UserController {
@Resource
private UserService userService;
@PostMapping("/login")
public UserLoginVO login(@RequestBody UserLoginDTO userLoginDTO){
log.info("用戶登錄:{}",userLoginDTO);
return userService.login(userLoginDTO);
}
}
service層:
public interface UserService {
UserLoginVO login(UserLoginDTO userLoginDTO);
}
@Service
@Slf4j
public class UserServiceImpl implements UserService {
@Resource
private UserMapper userMapper;
@Resource
private JwtProperties jwtProperties;
@Override
public UserLoginVO login(UserLoginDTO userLoginDTO) {
String username = userLoginDTO.getUsername();
String password = userLoginDTO.getPassword();
//1. 校驗用戶名和密碼
UserLogin userLogin = userMapper.getUserByName(username);
if (userLogin == null){
throw new BaseException("用戶名或密碼錯誤");
}
password = DigestUtils.md5DigestAsHex(password.getBytes());
if (!password.equals(userLogin.getPassword())){
throw new BaseException("用戶名或密碼錯誤");
}
//2. 生成token
HashMap<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.USER_ID, userLogin.getId());
String token = JwtUtil.createToken(
jwtProperties.getUserSecretKey(),
jwtProperties.getUserTtl(),
claims);
//3. 封裝vo
UserLoginVO loginVO = UserLoginVO.builder()
.token(token)
.userId(userLogin.getId())
.username(userLogin.getUsername())
.build();
return loginVO;
}
}
mapper層:
@Mapper
public interface UserMapper {
UserLogin getUserByName(String username);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.sky.user.mapper.UserMapper">
<select id="getUserByName" resultType="com.sky.user.pojo.entity.UserLogin">
select *
from user_login where username = #{username} and is_delete = 0;
</select>
</mapper>
其他關(guān)聯(lián)代碼:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserLoginDTO {
/**
* 用戶名
*/
private String username;
/**
* 密碼
*/
private String password;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserLoginVO {
private String token;
private Long userId;
private String username;
}
測試:
登錄接口:

其他接口:


到此這篇關(guān)于Java實現(xiàn)JWT登錄認(rèn)證的示例代碼的文章就介紹到這了,更多相關(guān)Java JWT登錄認(rèn)證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
springboot+dynamicDataSource動態(tài)添加切換數(shù)據(jù)源方式
這篇文章主要介紹了springboot+dynamicDataSource動態(tài)添加切換數(shù)據(jù)源方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01
你肯定能看懂的Java IO相關(guān)知識總結(jié)
群里有大佬說想讓我寫一篇NIO,一直也沒寫,但是和同事聊天也說對Java的IO不是很清晰,因此今天就寫下Java的io,先打個基礎(chǔ),下次寫NIO,需要的朋友可以參考下2021-05-05

