您的当前位置:首页正文

【JWT】SpringBoot+微信小程序根据指定参数生成Token、更新Token、判断Token是否已经过期、封装wx.request请求更新Token并判断Token是否过期

2024-11-28 来源:个人技术集锦

1、生成Token、更新Token、判断Token是否过期工具类

package com.ckm.ball.utils;
import java.util.Base64;
import java.util.Date;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;

public class JwtUtilChat {

    private static final String SECRET_KEY = "secret"; // 秘钥
    private static final long VALID_TIME = 30 * 60 * 1000; // 有效时间30分钟

    /**
     * 生成Token
     * @param openId 用户唯一标识
     * @return Token
     */
    public static String generateToken(String openId) {
        Date now = new Date();
        Date expireTime = new Date(now.getTime() + VALID_TIME);
        byte[] secretBytes = Base64.getEncoder().encode(SECRET_KEY.getBytes());
        JwtBuilder builder = Jwts.builder()
                .setId(openId)
                .setIssuedAt(now)
                .setExpiration(expireTime)
                .setSubject("jwt")
                .signWith(SignatureAlgorithm.HS256, secretBytes);
        return builder.compact();
    }

    /**
     * 更新Token
     * @param token 原始Token
     * @return 新的Token
     * @throws SignatureException 无效Token异常
     */
    public static String refreshToken(String token) throws SignatureException {
        Claims claims = Jwts.parser()
                .setSigningKey(Base64.getEncoder().encode(SECRET_KEY.getBytes()))
                .parseClaimsJws(token)
                .getBody();
        Date now = new Date();
        Date expireTime = new Date(now.getTime() + VALID_TIME);
        JwtBuilder builder = Jwts.builder()
                .setId(claims.getId())
                .setIssuedAt(now)
                .setExpiration(expireTime)
                .setSubject("jwt")
                .signWith(SignatureAlgorithm.HS256, Base64.getEncoder().encode(SECRET_KEY.getBytes()));
        return builder.compact();
    }

    /**
     * 验证Token是否过期 如果返回false则代表token没有过期,如果返回true则代表token已经过期
     * @param token 要验证的Token
     * @return 是否过期
     * @throws SignatureException 无效Token异常
     */
    public static boolean isTokenExpired(String token) throws SignatureException {
        Claims claims = Jwts.parser()
                .setSigningKey(Base64.getEncoder().encode(SECRET_KEY.getBytes()))
                .parseClaimsJws(token)
                .getBody();
        return claims.getExpiration().before(new Date());
    }
    
    /**
    * 根据Token获取openId
    * @param token 要解析的Token
    * @return openId
    * @throws SignatureException 无效Token异常
    */
    public static String getOpenIdFromToken(String token) throws SignatureException {
        Claims claims = Jwts.parser()
                .setSigningKey(Base64.getEncoder().encode(SECRET_KEY.getBytes()))
                .parseClaimsJws(token)
                .getBody();
        return claims.getId();
    }
    
}

2、微信小程序登录生成Token

微信小程序点击登录按钮调用该方法

login() {
  let that = this
  //调用wx登录接口获取openId
  wx.login({
    success: (res) => {
      //调用getUserCode获取openId
      getUserCode(res.code).then((data) => {
        //生成Token
        userLogin(data.openid).then((result) => {
          that.setData({
            userInfo: result
          })
          //保存到缓存中,后续做判断
          wx.setStorageSync('userInfo', result)
          wx.showLoading()
          setTimeout(function () {
            wx.hideLoading()
            wx.navigateBack({
              delta: 0,
            })
          }, 500)
        })
      })
    },
  })
},

java后端代码

  • getUserCode方法为获取用户的唯一标识openId
  • userLogin方法用于用户授权登录并获取Token
@ApiOperation(value = "获取用户openId",tags = "用户接口")
@GetMapping(value="/getUserCode")
public JSONObject getUserCode(@RequestParam("code") String code){
    System.out.println(code);
    JSONObject jsonObject = new JSONObject();
    try{
        if(code == null || code.equals("")){
            jsonObject.put("errcode","10001");
            jsonObject.put("errmsg","数据传输错误,code为空");
            return jsonObject;
        }
        Map<String, String> data = new HashMap<String, String>();
        data.put("appid", "wx4ab3xxxxxxxx52b");
        data.put("secret", "33ae24be19xxxxxxxxxx77b990");
        data.put("js_code", code);
        data.put("grant_type", "authorization_code");
        String response = HttpRequest.get("https://api.weixin.qq.com/sns/jscode2session").form(data).body();
        jsonObject = JSON.parseObject(response);
        jsonObject.remove("session_key"); //删除session_key 避免泄露用户信息
    }catch (Exception ex){
        jsonObject.put("errcode","10004");
        jsonObject.put("errmsg","获取失败,发生未知错误");
    }
    System.out.println(jsonObject);
    return jsonObject;
}

@GetMapping("/userLogin")
@ApiOperation(value = "用户登录",tags = "用户接口")
public BallUserVo userLogin(@RequestParam("openId") String openId){
    return ballUserService.userLogin(openId);
}

userLogin实现类方法

@Override
public BallUserVo userLogin(String openId) {
    //根据openId生成Token
    String token = JwtUtilChat.generateToken(openId);
    BallUser ballUser = this.getOne(new LambdaQueryWrapper<BallUser>().eq(BallUser::getOpenId, openId));
    //没有记录,首次授权登录
    if (ObjectUtils.isEmpty(ballUser)){
        BallUser newBallUser = new BallUser();
        newBallUser.setCreateTime(new Date());
        newBallUser.setAvatarUrl(avatarUrl);
        newBallUser.setNickName("微信用户"+RandomStringGenerator.generateRandomString());
        newBallUser.setOpenId(openId);
        this.save(newBallUser);

        BallUserVo ballUserVo = new BallUserVo();
        //token赋值
        ballUserVo.setToken(token);
        BeanUtils.copyProperties(newBallUser,ballUserVo);
        return ballUserVo;
    }
    BallUserVo ballUserVo = new BallUserVo();
    //token赋值
    ballUserVo.setToken(token);
    BeanUtils.copyProperties(ballUser,ballUserVo);
    return ballUserVo;
}

3、微信小程序每次发起请求更新Token

封装了wx的request请求,每次发起请求的时候都走一遍更新Token的接口/user/updateTokenTime,如果接口返回offline则代表已经过期,则跳转到登录页面,否则就是没有过期,则更新缓存userInfo中的token。

微信小程序js代码

//经过token验证
function request(options) {
  let userInfo = wx.getStorageSync('userInfo');
  if (userInfo == null || userInfo == undefined || userInfo == '') {
    const userInfoNologin = {
      avatarUrl: "/images/nologin.png",
      nickName: "授权登录"
    };
    wx.setStorageSync('userInfo', userInfoNologin);
    console.log("请登录!");
    wx.navigateTo({
      url: '/pages/login/login',
    });
    return Promise.reject("用户未登录");
  } else {
    const API_ROOT = "http://192.168.1.2:8088";
    return new Promise((resolve, reject) => {
      refreshToken(API_ROOT, userInfo)
        .then(() => {
          wx.request({
            ...options,
            url: API_ROOT + options.url,
            success: function (res) {
              if (res.statusCode === 200) {
                resolve(res.data);
              } else {
                reject(res.errMsg);
              }
            },
            fail: function (err) {
              reject(err.errMsg);
            },
          });
        })
        .catch(error => {
          reject(error);
        });
    });
  }
}

// 更新 Token
function refreshToken(url, userInfo) {
  return new Promise((resolve, reject) => {
    wx.request({
      url: url + "/user/updateTokenTime",
      method: 'GET',
      data: { 'token': userInfo.token },
      success: res => {
        if (res.data == "offline") {
          const userInfoNologin = {
            avatarUrl: "/images/nologin.png",
            nickName: "授权登录"
          };
          
          wx.setStorageSync('userInfo', userInfoNologin);
          wx.navigateTo({
            url: '/pages/login/login',
          });
          
          reject("用户已离线");
        } else {
          userInfo.token = res.data;
          wx.setStorageSync('userInfo', userInfo);
          resolve();
        }
      },
      fail: error => {
        console.log(error);
        reject(error);
      }
    });
  });
}

export default request;

java后端代码

  • updateTokenTime方法用于更新Token并判断原来的Token是否已经过期
 @GetMapping("/updateTokenTime")
 @ApiOperation(value = "更新token",tags = "用户接口")
 public String updateTokenTime(@RequestParam("token") String token){
     return ballUserService.updateTokenTime(token);
 }

updateTokenTime实现类方法

@Override
public String updateTokenTime(String token) {
    // 判断token是否为空
    if (StringUtils.isEmpty(token)) {
        // Token为空,则返回未认证错误
        return "offline";
    }
    try {
        //判断token是否过期,返回false则代表没有过期,反正true则代表过期
        boolean flag = JwtUtilChat.isTokenExpired(token);
        if (!flag){
            // 更新token
            return JwtUtilChat.refreshToken(token);
        }else{
            return "offline";
        }
    }catch (Exception e){
        //异常代表token已经过期
        return "offline";
    }
}

如果你想有一些接口不要经过这个request封装,就比如用户没有登录是可以进入到首页等页面的,开放一些允许用户没有登录状态可以访问的接口。我们可以再封装一个request,专门开放接口不经过Token验证。

//不经过token验证
function onlineRequest(options) {
    const API_ROOT = "http://192.168.1.2:8088";
    return new Promise((resolve, reject) => {
      wx.request({
        ...options,
        url: API_ROOT + options.url, // 在请求的 URL 前加上 API_ROOT
        success: function (res) {
          if (res.statusCode === 200) {
            resolve(res.data);
          } else {
            reject(res.errMsg);
          }
        },
        fail: function (err) {
          reject(err.errMsg);
        },
      });
    });
}
export default onlineRequest;

API怎么编写?

import request from "../../utils/request"; //经过token的封装
import onlineRequest from "../../utils/onlineRequest"; //不经过token的封装

//用户没有登录,但是可以访问这个接口
export function getBallCourtInfoByPoint(lat,lng) {
  return onlineRequest({
    url: '/court/getBallCourtInfoByPoint',
    method: 'get',
    data:{
      lat,
      lng
    }
  });
}

//用户必须登录才能访问这个接口
export function getBallCourtInfoByPointById(id) {
  return request({
    url: '/court/getBallCourtInfoByPointById',
    method: 'get',
    data:{id}
  });
}

接口怎么调用?

import {
  getBallCourtInfoByPoint,
  getBallCourtInfoByPointById
} from "../../../api/home/home.js";

getBallCourtInfoByPoint(res){
  let that = this
  //该方法不经过Token验证
  getBallCourtInfoByPoint(res.latitude, res.longitude).then((res) => {
    res.forEach(element => {
      element['id'] = Number(element.id)
      element['markerId'] = Number(element.id)
      element['title'] = element.ballCourtName
      element['width'] = 45
      element['height'] = 45
      element['latitude'] = Number(element.lat)
      element['longitude'] = Number(element.lng)
      element['lat'] = Number(element.lat)
      element['lng'] = Number(element.lng)
      element['iconPath'] = "/images/ball.png"
    });
    that.setData({
      markers: res
    })
  });
},

onMarkerTap(e) {
  let that = this
  console.log(e);
  //该方法经过Token验证
  getBallCourtInfoByPointById(e.detail.markerId).then((res) => {
    that.setData({
      showPopup: true,
      ballCourtInfo: res
    })
  })
}
显示全文