java如何利用JWT和注解,自定义参数的方式优雅实现小程序用户Id管理

在我们的开发项目中,经常需要用到用户ID,比如在小程序商城系统中,我们将商品加入购物车,这时前端就需要发送请求,携带上用户的ID。基本上很多种请求操作都需要携带用户ID,如果每个请求都需要我们往data中添加id的话,那样需要写很多重复代码,并且代码也不美观;所以我们可以利用JWT跟注解的方式来实现;

一、编写token管理器

1.1、导入jwt包

在maven中加入该依赖

        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.4.1</version>
        </dependency>

1.2、创建JwtHelper,用于创建和验证token

package com.maomao.demo.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTCreationException;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.*;

public class JwtHelper {
    //秘钥
    public static final String SECRET = "hdriverbird-token";
    //签名是由谁生成的
    public static final String ISSUSER = "hdriverbird";
    //签名的主题
    public static final String SUBJECT = "token test";
    //观众
    public static final String AUDIENCE = "miniapp";

    public String createToken(String userId) {
        try {
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            Map<String, Object> m = new HashMap<>();
            m.put("alg", "HS256");
            m.put("typ", "JWT");
            //签名时间
            Date nowDate = new Date();

            //过期时间Date对象
            Date expire = getAfterDate(nowDate, 0, 0, 0, 1, 0, 0);
            String token = JWT.create().
                    //设置头部
                            withHeader(m)
                    // 设置 载荷 Payload
                    .withClaim("userId", userId)
                    .withIssuer(ISSUSER)
                    .withSubject(SUBJECT)
                    .withAudience(AUDIENCE)
                    //签名时间
                    .withIssuedAt(nowDate)
                    //过期时间
                    .withExpiresAt(expire)
                    .sign(algorithm);
            return token;
        }catch (JWTCreationException exception){
            exception.printStackTrace();
        }
        return null;
    }

    /**
     * 验证token
     * @return
     */
    public String verifyToken(String token){
        try {
            Algorithm algorithm = Algorithm.HMAC256(SECRET);
            JWTVerifier build = JWT.require(algorithm)
                    .withIssuer(ISSUSER)
                    .build();
            DecodedJWT verify = build.verify(token);
            //获取声明信息
            Map<String, Claim> claims = verify.getClaims();
            Claim claim = claims.get("userId");
            //转为字符串
            return claim.asString();

        }catch (JWTCreationException e){
            e.printStackTrace();
        }
        return "";
    }


    //获取某个时间点的日期对象
    public Date getAfterDate(Date date, int year, int month, int day, int hour, int minute, int second) {
        if (date == null) {
            date = new Date();
        }

        Calendar cal = new GregorianCalendar();

        cal.setTime(date);
        if (year != 0) {
            cal.add(Calendar.YEAR, year);
        }
        if (month != 0) {
            cal.add(Calendar.MONTH, month);
        }
        if (day != 0) {
            cal.add(Calendar.DATE, day);
        }
        if (hour != 0) {
            cal.add(Calendar.HOUR_OF_DAY, hour);
        }
        if (minute != 0) {
            cal.add(Calendar.MINUTE, minute);
        }
        if (second != 0) {
            cal.add(Calendar.SECOND, second);
        }
        return cal.getTime();
    }
}

1.3、创建token管理工具

package com.maomao.demo.utils;
import org.springframework.stereotype.Component;

/**
 * 维护用户token
 */
//加入spring类管理
@Component
public class UserTokenManager {
	public static String generateToken(String id) {
        JwtHelper jwtHelper = new JwtHelper();
        return jwtHelper.createToken(id);
    }
    public static String getUserId(String token) {
    	JwtHelper jwtHelper = new JwtHelper();
    	String userId = jwtHelper.verifyToken(token);
    	if(userId==null||userId.length()==0){
    		return null;
    	}
        return userId;
    }
}

二、编写controller,提供api接口

2.1、创建用户实体类

package com.maomao.demo.entity;

public class User {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

2.2、编写登陆方法,并传递token

因为是demo,所以就简单定义了账号和密码,就不跟数据库交互啦

package com.maomao.demo.controller;

import com.maomao.demo.annotation.LoginUser;
import com.maomao.demo.entity.User;
import com.maomao.demo.utils.UserTokenManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
@RequestMapping("/token")
public class TokenController {
    @Autowired
    UserTokenManager tokenManager;

    /**
     * 登录
     */
    @RequestMapping("login")
    public Object login(@RequestBody User user){
        Map<String,Object> map=new HashMap<>();
        //账号
        String myUsername="xiaoming";
        //密码
        String myPassword="123";
        //判断账号密码是否正确
        if (user.getUsername().equals(myUsername)&&user.getPassword().equals(myPassword)){
            map.put("code",0);
            //创建token
            String token = tokenManager.generateToken(user.getUsername());
            map.put("token",token);
            return map ;
        }

        map.put("code",-1);
        return map;
    }

}

2.3、在小程序中调用登陆接口,进行登陆操作

xml文件中,放入两个按钮,一个用来登陆,一个用来后续步骤中测试结果使用的

	<button  bindtap="login">登陆</button>
	<button bindtap="submit">测试</button>

js:

const app = getApp()
Page({
  data: {

  },
  onLoad: function () {

  },
  //登陆
  login() {
    wx.request({
      url: 'http://localhost:8888/token/login',
      data: {
        username: "xiaoming",
        password: "123"
      },
      method: "POST",
      header: {
        'Content-Type': "application/json"
      },
      success: function (res) {
        console.log(res)
        if (res.data.code == 0) {
		//登陆成功后,将我们的token存入本地存储中
          wx.setStorageSync('token', res.data.token)
        }
        if (res.data.code == -1) {
          wx.showToast({
            title: '账号或密码不正确!',
            icon: "none"
          })
        }
      }
    })
  }
})

2.4、在小程序中我们可以使用Promise来构建我们的请求

2.4.1、创建一个request.js的工具类
2.4.2、在我们每次请求时,在请求头部中加入我们的token
/**
 * 封装微信请求方法
 */
function request(url,data={},method="GET"){
  return new Promise(function(resolve,reject){
    wx.request({
      url: url,
      method:method,
      data:data,
      header:{
        'Content-Type':'application/json',
        'myToken':wx.getStorageSync('token')
      },
      success:function(res){
        if(res.data.code==401){
          wx.showToast({
            title: '没有权限',
          })
          return ;
        }
        resolve(res)
      },
      fail:function(err){
        reject(err)
      }
    })
  })
}
module.exports={
  request:request
}

三、自定义方法参数构造器和参数注解

3.1、创建一个注解类

package com.maomao.demo.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
//声明为参数注解
@Target(ElementType.PARAMETER)
//声明为在class和jvm中都有效
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}

3.2、创建自定义方法参数解析器

3.2.1、创建类并实现 HandlerMethodArgumentResolver接口
3.2.2、实现该接口后,需要重写它的两个方法supportsParameter和resolveArgument

supportsParameter:用于判断是否支持该参数,如果支持则返回true,这时的resolveArgument方法就会被调用
resolveArgument:参数解析器,最后需要返回该方法参数的值

3.2.3、我们需要在第一个函数中,判断方法中函数的类型以及它是否有使用我们自定义的注解
    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        //判断方法参数是否是字符串类型或其子类&&判断方法参数是否有使用@LoginUser注解
        return methodParameter.getParameterType().isAssignableFrom(String.class)&&methodParameter.hasParameterAnnotation(LoginUser.class);
    }
3.2.4、我们需要参数解析方法中获取请求头中的token,并对其进行有效性判断,最后返回值
    @Override
    public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
      //从请求头中获取携带的token
        String myToken = nativeWebRequest.getHeader("myToken");
        //判断值是否有效
        if (myToken == null || myToken.isEmpty()) {
            return null;
        }
        return UserTokenManager.getUserId(myToken);
    }

3.3、编写完解析器后,我们需要在springmvc中加入该解析器

3.3.1、因为项目使用的是springboot,所以可以直接实现WebMvcConfigurer该接口,并将解析器加入即可
3.3.2、实现代码
package com.maomao.demo.config;

import com.maomao.demo.annotation.support.UserLoginHandleMethodArgumentResolver;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;
//注解为配置类,这样springboot启动时就会加载
@Configuration
public class loginWebMvcConfiguration implements WebMvcConfigurer {
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new UserLoginHandleMethodArgumentResolver());
    }
}

四、测试

4.1、我们创建一个测试submit接口,来验证我们的username是否有被赋值

package com.maomao.demo.controller;

import com.maomao.demo.annotation.LoginUser;
import com.maomao.demo.entity.User;
import com.maomao.demo.utils.UserTokenManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

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

@RestController
@RequestMapping("/token")
public class TokenController {
    @Autowired
    UserTokenManager tokenManager;

    /**
     * 登录
     */
    @RequestMapping("login")
    public Object login(@RequestBody User user){
        Map<String,Object> map=new HashMap<>();
        //账号
        String myUsername="xiaoming";
        //密码
        String myPassword="123";
        //判断账号密码是否正确
        if (user.getUsername().equals(myUsername)&&user.getPassword().equals(myPassword)){
            map.put("code",0);
            //创建token
            String token = tokenManager.generateToken(user.getUsername());
            map.put("token",token);
            return map ;
        }

        map.put("code",-1);
        return map;
    }


    /**
     * 提交订单
     */
    @RequestMapping("submit")
    public Object submit(@LoginUser String username){
        Map<String,Object> map=new HashMap<>();
        System.out.println("username:"+username);
        if (username==null||username.length()==0){
            map.put("code",401);
            return map;
        }
        map.put("code",0);
        return map;
    }
}

4.2、在小程序中创建submit方法调用接口进行测试

const app = getApp()
var utils = require('../utils/request')
Page({
  data: {

  },
  onLoad: function () {

  },
  //登陆
  login() {
    wx.request({
      url: 'http://localhost:8888/token/login',
      data: {
        username: "xiaoming",
        password: "123"
      },
      method: "POST",
      header: {
        'Content-Type': "application/json"
      },
      success: function (res) {
        console.log(res)
        if (res.data.code == 0) {

          wx.setStorageSync('token', res.data.token)
        }
        if (res.data.code == -1) {
          wx.showToast({
            title: '账号或密码不正确!',
            icon: "none"
          })
        }
      }
    })
  },
  //提交订单
  submit() {
    utils.request('http://localhost:8888/token/submit', {}, "POST")
    .then(function (res) {
      console.log(res)
    })
  }
})

4.3、测试结果

请求信息:
在这里插入图片描述

后端服务器:
在这里插入图片描述

  • 8
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

@猪大肠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值