JWT应用样例
一、简述
JWT由三部分组成:Header、Payload(负载)、Signature(签名)。
- Header部分记录使用的加密算法和Token类型。
- Payload部分可以放一些用户信息。如账号、姓名等非敏感信息。
- 签名是将Header部分和Payload部分做加密签名,使用Header中指定的加密算法。
二、JWT工具类
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
/**
* JWT生成、解析、校验工具类
*
* @author neo
* @since 2025-02-27
*/
public class JWTUtil {
// 加密密钥。在程序启动后初始化
private String secret = "";
private JWTUtil(){
}
public static JWTUtil instance(){
return JWTUtilBuilder.instance;
}
/**
* 设置加密密钥
*
* @param secret 密钥
* @author neo
* @since 2025/3/25
*/
public void setSecret(String secret){
this.secret = secret;
}
/**
* 创建Token
*
* @param parameterMap 参数表
* @return 生成的Token
* @author neo
* @since 2025/3/25
*/
public String createToken(Map<String, String> parameterMap){
Calendar calendar = Calendar.getInstance();
// 设置三天后过期
calendar.add(Calendar.DATE, 3);
JWTCreator.Builder builder = JWT.create();
// 设置header
Map<String, Object> headerMap = new HashMap<>();
headerMap.put("alg", "HS256");
headerMap.put("typ", "JWT");
builder.withHeader(headerMap);
// 填充负载
for (Map.Entry<String, String> entry : parameterMap.entrySet()) {
builder.withClaim(entry.getKey(), entry.getValue());
}
// 创建token
String token = builder.withExpiresAt(calendar.getTime()).sign(Algorithm.HMAC256(secret));
return token;
}
/**
* 校验Token是否有效
*
* @param token 令牌
* @return 校验结果
*/
public DecodedJWT verifyJWT(String token){
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(secret)).build();
return verifier.verify(token);
}
/**
* 解析Token获取负载中的参数
*
* @param token 令牌
* @return 参数
*/
public Map<String, String> decodeToken(String token){
DecodedJWT decodedJWT = verifyJWT(token);
// 提取参数
Map<String, String> parameterMap = new HashMap<>();
Map<String, Claim> claimMap = decodedJWT.getClaims();
claimMap.forEach((k, v)->{
parameterMap.put(k, v.asString());
});
return parameterMap;
}
private static final class JWTUtilBuilder {
private static JWTUtil instance = new JWTUtil();
}
}
JWTUtil 工具类实现了JWT的生成,校验、参数解析功能。使用的密钥secret可以在程序启动时从配置文件中读取,然后通过setSecret方法保存到内存。
三、提供登录接口
@RestController
@RequestMapping("/v1/login")
@Api(tags = "用户登录服务")
public class LoginController extends BaseController {
private static final Logger LOGGER = LogManager.getLogger(LoginController.class);
@Resource
private LoginService loginService;
/**
* 用户登录
*
* @param loginUser 用户信息
* @return 生成的token
*/
@PostMapping("/login-in")
@ApiOperation(value = "用户登录")
public ResultModel<String> login(@RequestBody UserLogin loginUser){
return loginService.login(loginUser);
}
}
前端在用户登录时调用后台的登录接口,传入用户名,密码等信息(封装在UserLogin中)。后端服务调用JWTUtil的接口生成Token信息并返回给前端。前端可以将Token信息保存在本地存储或者session中。
用户登录后,前端后续调用其它业务接口时将Token放到http的请求头中传递到后台,后台拦截器提取Token数据做权限认证。
四、通过拦截器拦截http请求并认证
import com.elon.base.util.JWTUtil;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 拦截rest接口请求,做用户合法性认证.
*
* @author neo
* @since 2025-02-28
*/
public class JWTInterceptor implements HandlerInterceptor {
private static final Logger LOGGER = LogManager.getLogger(JWTInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 提起请求头中的jwt信息
String jwtToken = request.getHeader("jwt-token");
try {
JWTUtil.instance().verifyJWT(jwtToken);
response.setHeader("Access-Control-Allow-Origin", "*");
return true;
} catch (Exception e){
LOGGER.error("Verify token fail.", e);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().println("Token is valid");
// 设置为没有权限,前端拿到响应码需跳转到登录页面
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return false;
}
}
}
认证不通过的请求返回401。
五、添加拦截器及拦截规则
import com.neo.lesson.manage.JWTInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
/**
* 注册JWT Token拦截器
*
* @author neo
* @since 2025-02-28
*/
@Configuration
public class JWTConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry){
registry.addInterceptor(new JWTInterceptor())
.addPathPatterns("/v1/lesson/**", "/v1/student/**")
.excludePathPatterns("/v1/login/**");
}
}
仅有用户登录的接口不拦截,其它业务接口均需要拦截。
六、扩展考虑
-
JWT在生成时设置了过期时间,但在实际项目中可能会根据业务需要设置不同的过期时间。例如:如果用户登录后30分钟没有做任何操作,那么系统将自动登出。这种情况可以考虑将token数据放到redis中存储,利用redis的失效机制。
-
系统不仅需要认证用户身份合法性,还需要根据用户的不同角色对接口做鉴权。此时需要在拦截http请求后获取用户的权限信息,结合当前请求的接口地址做校验。