Springboot项目整合JWT

一、JWT简介

  1. JWT: JSON Web Token(JSON Web令牌)

JWT是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的。

  1. JWT认证流程:

Springboot项目整合JWT_第1张图片
  1. JWT优点:

  • 简洁(Compact):可以通过URL,POST参数或者在HTTP header发送,数据量小,传输速度也很快;

  • 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库;

  • Token是以JSON加密的形式保存在客户端,所以JWT是跨语言的,原则上任何web形式都支持。

  • 不需要在服务端保存会话信息,特别适用于分布式微服务。

  1. JWT结构:

JWT令牌token,是一个String字符串,由3部分组成,中间用点隔开

Springboot项目整合JWT_第2张图片

令牌组成:标头(Header).有效载荷(Payload).签名(Signature)

token格式:head.payload.singurater 如:

eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxIiwic3ViIjoie1wiZWRpdGVkXCI6MTY3OTIyODMwNjAwMCxcImVkaXRvclwiOlwiXCIsXCJoZWFkUG9ydHJhaXRcIjpcImh0dHBzOi8vemhpaHVpcGFpYmFuLTEzMTcwNTExMjkuY29zLmFwLW5hbmppbmcubXlxY2xvdWQuY29tL2hlYWRQb3J0cmFpdC8yNTM1MTY3OTIyOTU0MjI0MC5qcGdcIixcImlkXCI6MjIsXCJqb2JOdW1iZXJcIjpcIjIwMjA0MjI4MDEzXCIsXCJtYWlsYm94XCI6XCIyMzIyNjU2NDMwQHFxLmNvbVwiLFwibmFtZVwiOlwi6ams5pmT5aSpXCIsXCJwYXNzd29yZFwiOlwiNjNlZTQ1MTkzOWVkNTgwZWYzYzRiNmYwMTA5ZDFmZDBcIixcInBob25lXCI6XCIxODc3MjYzODE2MFwiLFwicm9sZWlkXCI6MTAsXCJzZXhcIjpcIjBcIixcInVzZXJuYW1lXCI6XCIxODc3MjYzODE2MFwifSIsImlzcyI6IkZhbmppbmd4dWFuIiwiaWF0IjoxNjc5MjMwOTAyLCJleHAiOjE2NzkyMzA5NjJ9.j0WFGkEACHpTnvEc9EiTEJCQKJ20oIptfrL_kMPz4kU

二、Springboot项目整合JWT验证+拦截器

(参考项目:intelligent_scheduling)

  1. 在pom文件中添加JWT的依赖

 

    com.auth0
    java-jwt
    3.2.0


    io.jsonwebtoken
    jjwt
    0.7.0
  1. 在model层创建JWT的实体类CheckResult

package cn.com.fanjingxuan.model;

import io.jsonwebtoken.Claims;

/**
  * @className: CheckResult.java
  * @methodName: CheckResult
  * @effect: jwt验证信息实体封装类
  * @author: JingxuanFan
  * @date: 2023/3/19 15:16
  **/
public class CheckResult {

    //
    private int errCode;
    //
    private boolean success;
    //
    private Claims claims;

    public int getErrCode(){ return errCode;}
    public void setErrCode(int errCode){ this.errCode = errCode;}
    public boolean isSuccess(){ return success;}
    public void setSuccess(boolean success){ this.success = success;}
    public Claims getClaims(){ return claims;}
    public void setClaims(Claims claims){ this.claims = claims;}
}
  1. 在constant层中创建JWT的系统级静态变量SystemConstant

package cn.com.fanjingxuan.constant;

/**
 *系统级静态变量
 * @author JingxuanFan
 * @date: 2023/3/19 15:13
 */
public class SystemConstant {

    /**
     *token
     */
    public static final int JWT_ERRCODE_NULL = 4000; //Token不存在
    public static final int JWT_ERRCODE_EXPIRE = 4001; //Token过期
    public static final int JWT_ERRCODE_FAIL = 4002; //验证不通过
    /**
     * JWT
     */
    public static final String JWT_SECERT = "8677df7fc3a34e26a61c034d5ec8245d"; //密匙
    public static final long JWT_TTL = 30 * 60 * 1000; //token有效时间为半小时
}
  1. 在util层创建JWT的工具包JwtUtil

package cn.com.fanjingxuan.util;

import cn.com.fanjingxuan.constant.SystemConstant;
import cn.com.fanjingxuan.model.CheckResult;
import com.alibaba.fastjson.JSON;
import io.jsonwebtoken.*;
import org.bouncycastle.util.encoders.Base64;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;

/**
  * @className: JwtUtil.java
  * @methodName: JwtUtil
  * @effect: jwt加密和解密的工具类
  * @author: JingxuanFan
  * @date: 2023/3/19 14:52
  **/
public class JwtUtil {
    /**
     * 签发JWT
     * @param id
     * @param subject 可以是JSON数据 尽可能少
     * @param ttlMillis
     * @return
     */
    public static String createJWT(String id, String subject, long ttlMillis) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long nowMillis = System.currentTimeMillis();
        Date now = new Date(nowMillis);
        SecretKey secretKey = generalKey();
        JwtBuilder builder = Jwts.builder()
                .setId(id)
                .setSubject(subject)   // 主题
                .setIssuer("Java1234")     // 签发者
                .setIssuedAt(now)      // 签发时间
                .signWith(signatureAlgorithm, secretKey); // 签名算法以及密匙
        if (ttlMillis >= 0) {
            long expMillis = nowMillis + ttlMillis;
            Date expDate = new Date(expMillis);
            builder.setExpiration(expDate); // 过期时间
        }
        return builder.compact();
    }

    /**
     * 验证JWT
     * @param jwtStr
     * @return
     */
    public static CheckResult validateJWT(String jwtStr) {
        CheckResult checkResult = new CheckResult();
        Claims claims = null;
        try {
            claims = parseJWT(jwtStr);
            checkResult.setSuccess(true);
            checkResult.setClaims(claims);
        } catch (ExpiredJwtException e) {
            checkResult.setErrCode(SystemConstant.JWT_ERRCODE_EXPIRE);
            checkResult.setSuccess(false);
        } catch (SignatureException e) {
            checkResult.setErrCode(SystemConstant.JWT_ERRCODE_FAIL);
            checkResult.setSuccess(false);
        } catch (Exception e) {
            checkResult.setErrCode(SystemConstant.JWT_ERRCODE_FAIL);
            checkResult.setSuccess(false);
        }
        return checkResult;
    }

    /**
     * 生成加密Key
     * @return
     */
    public static SecretKey generalKey() {
        byte[] encodedKey = Base64.decode(SystemConstant.JWT_SECERT);
        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
        return key;
    }

    /**
     * 解析JWT字符串
     * @param jwt
     * @return
     * @throws Exception
     */
    public static Claims parseJWT(String jwt) throws Exception {
        SecretKey secretKey = generalKey();
        return Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();
    }

    //进行测试的方法
    public static void main(String[] args) throws InterruptedException {
        //小明有效时间 1小时
        User user = new User();
        user.setId(1001l);
        user.setName("123123");
        user.setPassword("111111");
        user.setEdited(new Date());
        String userStr = JSON.toJSONString(user);
        String sc = createJWT("1","20,"+userStr, SystemConstant.JWT_TTL);
        Thread.sleep(3000);
        System.out.println(validateJWT(sc).getClaims().getSubject());
    }

}
5.在model层中定义页面响应的实体类R

package cn.com.fanjingxuan.model;

import java.util.HashMap;
import java.util.Map;

/**
  * @className: R.java
  * @methodName:  R
  * @effect: 页面响应的实体类
  * @author: JingxuanFan
  * @date: 2023/3/19 17:36
  **/
public class R extends HashMap {

    private static final long serialVersionUID = 1L;

    public R() {
        put("code", 0);
    }

    public static R error() {
        return error(500, "未知异常,请联系管理员");
    }

    public static R error(String msg) {
        return error(500, msg);
    }

    public static R error(int code, String msg) {
        R r = new R();
        r.put("code", code);
        r.put("msg", msg);
        return r;
    }

    public static R ok(String msg) {
        R r = new R();
        r.put("msg", msg);
        return r;
    }

    public static R ok(Map map) {
        R r = new R();
        r.putAll(map);
        return r;
    }

    public static R ok() {
        return new R();
    }

    public R put(String key, Object value) {
        super.put(key, value);
        return this;
    }
}
6.在controller层定义JWT登入方法

 /**
      * @className: AdministratorController.java
      * @methodName: login
      * @effect:  定义带token的登入方法
      * @author: JingxuanFan
      * @date: 2023/3/19 16:46
      **/
    @RequestMapping("login")
    @ResponseBody
    public Map login(Administrator administrator, HttpServletRequest request) throws Exception {
        Map result = new HashMap<>();
        //校验用户名和密码
        Administrator admin = administratorService.login(administrator);
        if (admin != null){
            if (admin.getPassword().equals(administrator.getPassword())){
                //将用户信息存储到Session  //登入成功
                HttpSession session = request. getSession();
                session. setAttribute("userInfo", admin);  //1.用于拦截器的判断  2.界面显示用户信息
                result. put("Id", admin.getId());
                //把token返回给客户端-->客户端保存至localStorage-->客户端每次请求附带localStorage参数
                    //SystemConstant.JWT_TTL:token有效时间
                String JWT = JwtUtil.createJWT("1", JSON.toJSONString(admin), SystemConstant.JWT_TTL);
                log.info(JWT);
                result.put("JWT",JWT);
                return result;
            }
            //登录失败 ,密码错误
            result. put("success", false);
            result. put("msg", "密码错误!");
            return result;
        }
        //登录失败,用户名错误或不存在
        result. put("success", false);
        result. put("msg", "用户名错误或不存在!");
        return result;
    }
7.在controller层定义刷新用户token的方法

(与5在同一个类里面)


 /**
      * @className: AdministratorController.java
      * @methodName: refreshToken
      * @effect: 刷新用户token
      * @author: JingxuanFan
      * @date: 2023/3/19 16:38
      **/
    @GetMapping(value = "/refreshToken")
    public Map refreshToken(HttpServletRequest request){
        Map result = new HashMap<>();
        //旧的token令牌
        log.info(request.getHeader("token"));
        Claims claims = JwtUtil.validateJWT(request.getParameter("token")).getClaims();
        String subject = claims.getSubject();
        System.out.println(subject);//20,{"edited":1650100778678,"id":1001,"name":"123123","password":"111111"}
        String JWT = JwtUtil.createJWT(claims.getId(),claims.getSubject(), SystemConstant.JWT_TTL);
        log.info("新token"+JWT);
        result.put("JWT",JWT);
        return result;
    }
8.搭载拦截器权限认证

(在interceptor(拦截器)包中创建自定义拦截器SysInterceptor.java)


package cn.com.fanjingxuan.interceptor;

import cn.com.fanjingxuan.constant.SystemConstant;
import cn.com.fanjingxuan.model.CheckResult;
import cn.com.fanjingxuan.model.R;
import cn.com.fanjingxuan.util.JwtUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
  * @className: SysInterceptor.java
  * @methodName: SysInterceptor
  * @effect: 拦截器 用户权限校验
  * @author: JingxuanFan
  * @date: 2023/3/19 17:25
  **/
public class SysInterceptor implements HandlerInterceptor {

    //日志对象
    private final static Logger logger= LoggerFactory.getLogger(SysInterceptor.class);

    //拦截的核心方法
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        logger.info("执行了拦截的核心方法");
        //获取页面的请求地址
        String contextPath = request.getRequestURI();
        logger.info("路径"+contextPath);
        //判断请求对象中有没有请求方法
        if (handler instanceof HandlerMethod){
            //从请求中取到token令牌
            String authHeader = request.getHeader("token");
            logger.info(authHeader);
            //先判断token令牌是否为空,
            if (StringUtils.isEmpty(authHeader)) {
                logger.info("验证失败,签名验证不存在");
                //调用自定义方法print
                print(response, R.error(SystemConstant.JWT_ERRCODE_NULL,"签名验证不存在"));
                return false;
            }else{
                //验证JWT的签名,返回CheckResult对象,将JWT对象(令牌token)进行解密
                CheckResult checkResult = JwtUtil.validateJWT(authHeader);
                if (checkResult.isSuccess()) {
                    logger.info("签名验证通过");
                    return true;
                } else {
                    switch (checkResult.getErrCode()) {
                        // 签名验证不通过
                        case SystemConstant.JWT_ERRCODE_FAIL:
                            logger.info("签名验证不通过");
                            //调用自定义方法print
                            print(response,R.error(checkResult.getErrCode(),"签名验证不通过"));
                            break;
                        // 签名过期,返回过期提示码
                        case SystemConstant.JWT_ERRCODE_EXPIRE:
                            logger.info("签名过期");
                            //调用自定义方法print
                            print(response,R.error(checkResult.getErrCode(),"签名过期"));
                            break;
                        default:
                            break;
                    }
                    return false;
                }
            }
        }else{
            return true;
        }
    }

    //拦截的执行POST请求的方法
    @Override
    public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception {
        logger.info("拦截的执行POST请求的方法");
    }

    /**
     * 该方法也是需要当前对应的Interceptor的preHandle方法的返回值为true时才会执行。该方法将在整个请求完成之后,也就是DispatcherServlet渲染了视图执行,
     * 这个方法的主要作用是用于清理资源的,当然这个方法也只能在当前这个Interceptor的preHandle方法的返回值为true时才会执行。
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        logger.info("执行了afterCompletion方法");
    }

    //自定义响应方法
    //将请求处理结果响应回浏览器
    public void print(HttpServletResponse response, Object message){
        try {
            //设置响应头的响应状态
            response.setStatus(HttpStatus.OK.value());
            //设置响应头的响应内容,并转成UTF8
            response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
            //设置头部信息
            response.setHeader("Cache-Control", "no-cache, must-revalidate");
            //字符输出流
            PrintWriter writer = response.getWriter();
            //将message写入到浏览器当中
            writer.write(message.toString());
            //刷新流
            writer.flush();
            //关闭流
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
  1. 在config(配置)包下面创建WebAppConfigurer.java,进行拦截配置--调用链

@Configuration 注解可以控制是否开启JWT拦截验证


package cn.com.fanjingxuan.config;

import cn.com.fanjingxuan.interceptor.SysInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
  * @className: WebAppConfigurer.java
  * @methodName:  WebAppConfigurer
  * @effect: 拦截配置--调用链
  * @author: JingxuanFan
  * @date: 2023/3/19 17:57
  **/
// 使用 @Configuration 注解的类就可以被 Spring 识别为配置类,并处理该类上的相关功能注解
@Configuration 
public class WebAppConfigurer extends WebMvcConfigurerAdapter {

    /**
     * 配置不需要拦截和需要拦截的请求
     * @param registry
     */
    //@Override 注解是用来指定方法重写的,只能修饰方法并且只能用于方法重写,不能修饰其它的元素。它可以强制一个子类必须重写父类方法或者实现接口的方法。
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加不拦截的方法(登入,注册)
        String[] patterns = new String[] {"/*/login","/administrator/saveT","/employee/saveT"};
        registry.addInterceptor(new SysInterceptor())
                .addPathPatterns("/**")  //先拦截所有方法
                .excludePathPatterns(patterns);  //在拦截的方法中剔除掉 patterns 中的方法(不拦截)
    }
}

三、ApiPost测试

  1. 登入成功生成的token令牌

Springboot项目整合JWT_第3张图片
  1. 不带token令牌执行被拦截的命令

Springboot项目整合JWT_第4张图片
  1. 带token令牌执行被拦截的命令(同一命令)可以查询

Springboot项目整合JWT_第5张图片

四、第二种生成JWT令牌的方法

1.在pom中加入相关依赖

        
        
            com.auth0
            java-jwt
            4.0.0
        
        
            com.10duke.client.jwt
            jjwt
            1.0.0
        

2.在utils中加入JWT工具类JwtUtil

package cn.com.fjxuan.utils;

import cn.com.fjxuan.model.User;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.JWTVerifier;
import java.util.Calendar;
import java.util.Map;

/**
 * @className: JwtUtil.java
 * @methodName: JwtUtil
 * @effect: 生成token的工具类
 * @author: JingxuanFan
 * @date: 2023/9/20 21:58
**/
public class JwtUtil {

    /**
     * @methodName testJwtCreate
     * @effect: 生成token
     */
    public static String testJwtCreate(User user) {
        //创建了一个 Calendar 类的实例,表示当前的日期和时间。
        Calendar instance = Calendar.getInstance();
        //设置过期时间:将当前的日期和时间增加了6个月
        instance.add(Calendar.MONTH, 6);
        String token = JWT.create()
                //头可以不指定一般用默认值
                .withClaim("userId", user.getId())
                .withClaim("username", user.getUsername())//payLoad
                .withClaim("pwd", user.getPwd())
                .withExpiresAt(instance.getTime()) //指定令牌的过期时间
                //使用HMAC256算法和密钥"qweqwrpf1"对JWT进行签名。密钥用于验证JWT的完整性。
                .sign(Algorithm.HMAC256("qweqwrpf1")); // Singnature
        //Algorithm.ECDSA256()使用哪种加密算法  括号中写自己指定的密钥

        return token;
    }


    /**
     * @methodName testRequire
     * @effect: 解析token
     * @return
     */
    public static Map testRequire(String jwt) {
        //require  解密传入加密的签名
        //创建验证对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("qweqwrpf1")).build();
        //使用验证对象中的验证方法  传入生成的JWT串  会返回一个解码的JWT
        DecodedJWT decodedJWT = jwtVerifier.verify(jwt);
        //返回加密对象
        return decodedJWT.getClaims();
    }
}

3.调用工具类生成JWT令牌

你可能感兴趣的:(技术,spring,boot,后端)