前言:
1.什么是JWT? 答:J(json) W(web) T(token) 是一种基于JSON的、用于在网络上声明某种身份的令牌(token)
2.JWT怎么实现的? jwt是由3段信息构成的,将这三段信息用.连接到一起就成了jwt字符串。
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI1IiwiaWF0IjoxNjAwOTMxNDE3LCJleHAiOjE2MDA5MzUwMTd9.bOVLeigaKqYaOj90QKWigbtqtmaHGVR-dxKXP6jgEB8JdaP6ybImztrD8EfxhnmgmxMrOUodgJG_14DA9aWiUg
Jwt通常是由三部分组成: 第一部分 头信息(header) 加密算法 HMAC SHA256等,声明类型 jwt;
header = '{"alg":"HS256","typ":"JWT"}'
将头部进行base64加密,构成了第一部分
第二部分 消息体(payload) iss: jwt签发者 sub: jwt所面向的用户 aud: 接收jwt的一方 exp: jwt的过期时间,这个过期时间必须要大于签发时间 nbf: 定义在什么时间之前,该jwt都是不可用的. iat: jwt的签发时间 jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
第三部分 签名(signature) jwt的第三部分是一个签证信息,这个签证信息由三部分组成: 1.header (base64后的) 2.payload (base64后的) 3.secret 未签名的令牌由base64url编码的头信息和消息体拼接而成(使用"."分隔),签名则通过私有的key计算而成:
key = 'secretkey'
unsignedToken = encodeBase64(header) + '.' + encodeBase64(payload)
signature = HMAC-SHA256(key, unsignedToken)
总体来说jwt是由三部分组成:jwt头+有效载荷+签名 最后在未签名的令牌尾部拼接上base64url编码的签名(同样使用"."分隔)就是JWT了:
token = encodeBase64(header) + '.' + encodeBase64(payload) + '.' + encodeBase64(signature)
以上是对jwt简单描述,如进一步学习可进入jwt官网
3.JWT的特点 ① 三部分组成,每一部分都进行字符串的转化 ② 解密的时候没有使用数据库,仅仅使用的是secret进行解密 ③ JWT的secret千万不能泄密!
springBoot集成Jwt代码实现过程
1.导入jwt依赖
<!-- JWT依赖 -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
2.application.yml配置
config:
jwt:
# 加密密钥
secret: science123456
# token有效时长 单位毫秒
expire: 3600
# header 名称
header: token
3.jwt配置类
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.Date;
/**
* JWT的token,区分大小写
*/
@ConfigurationProperties(prefix = "config.jwt")
@Component
public class JwtConfig {
private String secret;
private long expire;
private String header;
/**
* 生成token
* @param subject
* @return
*/
public String createToken (String subject){
Date nowDate = new Date();
Date expireDate = new Date(nowDate.getTime() + expire * 1000);//token过期时间 expire = 3600毫秒 3600*1000=1小时
return Jwts.builder()
.setHeaderParam("typ", "JWT")
.setSubject(subject)
.setIssuedAt(nowDate)
.setExpiration(expireDate)
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/**
* 获取token中注册信息
* @param token
* @return
*/
public Claims getTokenClaim (String token) {
try {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
}catch (Exception e){
// e.printStackTrace();
return null;
}
}
/**
* 验证token是否过期失效
* @param expirationTime
* @return
*/
public boolean isTokenExpired (Date expirationTime) {
return expirationTime.before(new Date());
}
/**
* 获取token失效时间
* @param token
* @return
*/
public Date getExpirationDateFromToken(String token) {
return getTokenClaim(token).getExpiration();
}
/**
* 获取用户名从token中
*/
public String getUsernameFromToken(String token) {
return getTokenClaim(token).getSubject();
}
/**
* 获取jwt发布时间
*/
public Date getIssuedAtDateFromToken(String token) {
return getTokenClaim(token).getIssuedAt();
}
// --------------------- getter & setter ---------------------
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public long getExpire() {
return expire;
}
public void setExpire(long expire) {
this.expire = expire;
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
}
4.配置全局拦截
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.SignatureException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 配置拦截器
*/
@Component
public class TokenInterceptor extends HandlerInterceptorAdapter {
@Resource
private JwtConfig jwtConfig ;
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) throws SignatureException {
/** 地址过滤 登录不进行拦截,直接放行,如register也需放行,可在下继续添加放行判断*/
String uri = request.getRequestURI() ;
if (uri.contains("/login")){
return true ;
}
/** Token 验证 */
String token = request.getHeader(jwtConfig.getHeader());
if(StringUtils.isEmpty(token)){
token = request.getParameter(jwtConfig.getHeader());
}
if(StringUtils.isEmpty(token)){
throw new SignatureException(jwtConfig.getHeader()+ "不能为空");
}
Claims claims = null;
try{
claims = jwtConfig.getTokenClaim(token);
if(claims == null || jwtConfig.isTokenExpired(claims.getExpiration())){
throw new SignatureException(jwtConfig.getHeader() + "失效,请重新登录。");
}
}catch (Exception e){
throw new SignatureException(jwtConfig.getHeader() + "失效,请重新登录。");
}
/** 设置 identityId 用户身份ID */
request.setAttribute("identityId", claims.getSubject());
return true;
}
}
注册拦截器到SpringMvc
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
/**
* 注册拦截器到SpringMvc
*/
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Resource
private TokenInterceptor tokenInterceptor ;
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor).addPathPatterns("/**");
}
}
5.统一异常处理类
import com.king.science.dto.Result;
import com.king.science.enums.ResponseResultEnum;
import io.jsonwebtoken.SignatureException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* 编写统一异常处理类
*/
@RestControllerAdvice
public class PermissionHandler {
@ExceptionHandler(value = { SignatureException.class })
@ResponseBody
public Result authorizationException(SignatureException e){
return Result.failed(ResponseResultEnum.EX_TOKEN_ERROR_CODE);
}
}
6.统一返回Result工具类
import com.king.science.enums.ResponseResultEnum;
import com.king.science.util.CommonConstant;
/**
* 统一返回类型
* Created by x on 2020/09/24.
*/
public class Result extends BizResult {
public static Result toResult(int code, String msg, Object object){
Result result = new Result();
result.setCode(code);
result.setMsg(msg);
result.setData(object);
return result;
}
public static Result success(){
Result result = new Result();
result.setCode(CommonConstant.CODE_SUCCESS);
result.setMsg("success");
return result;
}
public static Result success(Object object){
Result result = new Result();
result.setCode(CommonConstant.CODE_SUCCESS);
result.setMsg("success");
result.setData(object);
return result;
}
public static Result success(String msg){
Result result = new Result();
result.setCode(CommonConstant.CODE_SUCCESS);
result.setMsg(msg);
return result;
}
public static Result success(Object object,String successMsg){
Result result = new Result();
result.setCode(CommonConstant.CODE_SUCCESS);
result.setMsg(successMsg);
result.setData(object);
return result;
}
public static Result success(ResponseResultEnum responseResultEnum){
Result result = new Result();
result.setCode(responseResultEnum.getCode());
result.setMsg(responseResultEnum.getMessage());
return result;
}
public static Result success(ResponseResultEnum responseResultEnum,Object data){
Result result = new Result();
result.setCode(responseResultEnum.getCode());
result.setMsg(responseResultEnum.getMessage());
result.setData(data);
return result;
}
public static Result failed(){
Result result = new Result();
result.setCode(CommonConstant.CODE_BIZ_ERROR);
result.setMsg("fail");
return result;
}
public static Result failed(String errorMsg){
Result result = new Result();
result.setCode(CommonConstant.CODE_BIZ_ERROR);
result.setMsg(errorMsg);
return result;
}
public static Result failed(Object object){
Result result = new Result();
result.setCode(CommonConstant.CODE_BIZ_ERROR);
result.setMsg("fail");
result.setData(object);
return result;
}
public static Result failed(Object object,String errorMsg){
Result result = new Result();
result.setCode(CommonConstant.CODE_BIZ_ERROR);
result.setMsg(errorMsg);
result.setData(object);
return result;
}
public static Result failed(ResponseResultEnum responseResultEnum){
Result result = new Result();
result.setCode(responseResultEnum.getCode());
result.setMsg(responseResultEnum.getMessage());
return result;
}
public static Result failed(ResponseResultEnum responseResultEnum,Object data){
Result result = new Result();
result.setCode(responseResultEnum.getCode());
result.setMsg(responseResultEnum.getMessage());
result.setData(data);
return result;
}
public static Result failed(ResponseResultEnum responseResultEnum,String message){
Result result = new Result();
result.setCode(responseResultEnum.getCode());
result.setMsg(message);
return result;
}
public static Result databaseUpdate(Integer updateResult){
if(updateResult!=null && updateResult>0) return Result.success();
throw new RuntimeException("databaseUpdate failed");
}
public static Result databaseUpdate(Integer updateResult,Object successData){
if(updateResult!=null && updateResult>0) return Result.success(successData);
throw new RuntimeException("databaseUpdate failed");
}
public static boolean isSuccess(BizResult bizResult){
return bizResult!=null && bizResult.getCode()==0;
}
public static boolean isFailed(BizResult bizResult){
return bizResult==null || bizResult.getCode()!=0;
}
public static void failedThrow(BizResult bizResult) throws Exception{
if(isFailed(bizResult)) throw new Exception(bizResult.getMsg()==null?"fail":bizResult.getMsg());
}
public static void failedThrow(boolean result) throws Exception{
if(!result) throw new Exception("fail");
}
public static void failedThrowRuntime(BizResult bizResult){
if(isFailed(bizResult)) throw new RuntimeException(bizResult.getMsg()==null?"fail":bizResult.getMsg());
}
public static <T> T nullFailedThrow(T object,String failedMsg){
if(object==null) throw new RuntimeException(failedMsg);
return object;
}
public Result msg(String msg){
this.setMsg(msg);
return this;
}
public Result okMsg(String msg){
if(Result.isSuccess(this)){
this.setMsg(msg);
}
return this;
}
public static BizResult toResult(boolean ok){
return ok?success():failed();
}
public static BizResult toResult(boolean ok,String faildMsg){
return ok?success():failed(faildMsg);
}
}
BizResult工具类
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;
@Setter
@Getter
@Accessors(chain = true)
public class BizResult {
private int code;
private String msg;
private Object data;
public BizResult() {
}
public BizResult(int code, String msg) {
this.code = code;
this.msg = msg;
}
public BizResult(int code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public boolean ok(){
return this.getCode()==0;
}
public boolean error(){
return this.getCode()!=0;
}
public BizResult okMsg(String msg){
if(this.ok()){
this.setMsg(msg);
}
return this;
}
}
7.公共常量工具类
/**
* ${DESCRIPTION} 通用Constant
*
* @author x
* @create 2020-06-17 14:41
*/
public class CommonConstant {
/************** 接口返回状态start ****************/
public final static int CODE_SUCCESS = 0;
public final static int CODE_BIZ_ERROR = 1;
public final static int CODE_SYS_ERROR = 2;
public final static int CODE_RPC_ERROR = 3;
public final static int CODE_ZERO_ERROR = 4;
public final static int CODE_CONFIRM = 9;
/************** 接口返回状态end ****************/
/******************** 用户token异常 ************************/
public static final Integer EX_TOKEN_ERROR_CODE = 40101;
public static final Integer EX_USER_INVALID_CODE = 40102;
/******************** 客户端token异常 **********************/
public static final Integer EX_CLIENT_INVALID_CODE = 40131;
public static final Integer EX_CLIENT_FORBIDDEN_CODE = 40331;
public static final Integer EX_OTHER_CODE = 500;
}
8.异常枚举类
public enum ResponseResultEnum {
RESPONSE_RESULT_SUCCEED(0,"success"),
RESPONSE_RESULT_FAILURE(1,"failure"),
EX_TOKEN_ERROR_CODE(40101,"Invalid Token");
ResponseResultEnum(Integer code, String message) {
this.code=code;
this.message=message;
}
private Integer code;
private String message;
public Integer getCode(){
return this.code;
}
public String getMessage(){
return this.message;
}
}
9.token控制层,进行测试验证
import com.alibaba.fastjson.JSONObject;
import com.king.science.config.JwtConfig;
import com.king.science.dto.Result;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
@RestController
public class TokenController {
@Resource
private JwtConfig jwtConfig ;
/**
* 用户登录接口
* @param userName
* @param passWord
* @return
*/
@PostMapping("/login")
public Result login (@RequestParam("userName") String userName,
@RequestParam("passWord") String passWord){
JSONObject json = new JSONObject();
System.out.println("用户名:"+userName+";密码:"+passWord);
/** 验证userName,passWord和数据库中是否一致,如不一致,直接return Result.errer(); 【这里省略该步骤】*/
// 这里模拟通过用户名和密码,从数据库查询userId
// 这里把userId转为String类型,实际开发中如果subject需要存userId,则可以JwtConfig的createToken方法的参数设置为Long类型
String userId = 5 + "";
String token = jwtConfig.createToken(userId) ;
if (!StringUtils.isEmpty(token)) {
json.put("token",token) ;
}
return Result.success(json) ;
}
/**
* 需要 Token 验证的接口
*/
@PostMapping("/info")
public Result info (){
return Result.success("info , Welcome to sys") ;
}
/**
* 根据请求头的token获取userId
* @param request
* @return
*/
@GetMapping("/getUserInfo")
public Result getUserInfo(HttpServletRequest request){
String usernameFromToken = jwtConfig.getUsernameFromToken(request.getHeader("token"));
return Result.success(usernameFromToken) ;
}
/*
为什么项目重启后,带着之前的token还可以访问到需要info等需要token验证的接口?
答案:只要不过期,会一直存在,类似于redis
*/
测试拦截是否生效,token是否进行验证
1.调用第一个接口,登录获取token
2.携带正确token调用接口
3.使用错误token调用接口
4.根据token查询用户UserId
以上就是实现springboot整合jwt的全部过程,感谢阅读!