听说你还不会使用JWT ?

1,105 阅读6分钟

前言:

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的全部过程,感谢阅读!

卑微时看风骨,发达时看心性;有钱时看格局,没钱时看斗志!