Token前后端分离验证demo

4 篇文章 0 订阅
1 篇文章 0 订阅

1、Token介绍(摘取自官方网站)

       官方网站:JSON Web Token Introduction - jwt.io

        1)token可以做权限的验证和信息的交换

         2)token主要有三部分组成

                分别是Header、Payload、Signature。

                编码之后token呈xxxxx.yyyyy.zzzzz结构分别对应Header、Payload、Signature三部分。

          3)token示例

JWT.io Debugger

   2、开始准备demo

          目标:

                1、使用postman工具模拟发送请求登录

                2、登录验证成功后返回token

                3、postman模拟前端工程师将token写入请求的header中 

                4、访问测试方法进行验签

        1)准备一个JWTUtil用来封装JWT常用的方法

package com.example.jwtdemo.utils;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;

import java.util.Calendar;
import java.util.Map;

public class JWTUtils {

    private static final String SIGNATURE = "!pcfhaHJI#$%";

    /**
     * 生成 token header.payload.signature
     */
    public static String getToken(Map<String, String> map, Integer expireTime) {

        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.MINUTE, expireTime);

        JWTCreator.Builder builder = JWT.create();

        // payload
        map.forEach((k, v)->{
            builder.withClaim(k, v);
        });

        String token = builder.withExpiresAt(instance.getTime()) // 指定令牌过期的时间.
                .sign(Algorithm.HMAC256(SIGNATURE));// 签名

        return token;
    }

    /**
     *  验证token合法性
     * @param token 传入token
     */
    public static DecodedJWT verify(String token){

        // 验签对象
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SIGNATURE)).build();

        // 验证token
        DecodedJWT verify = jwtVerifier.verify(token);

        return verify;
    }


}

         2)创建一个测试数据库 

        用来存放我们的数据,模拟真实的登录验证。

         SQL语句

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '唯一主键',
  `user_name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户昵称',
  `login_account` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '登录账号',
  `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '登录密码',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of t_user
-- ----------------------------
INSERT INTO `t_user` VALUES (1, '杰克', 'jack', '123123');

SET FOREIGN_KEY_CHECKS = 1;

        3)创建mapper和映射文件.xml

        由于测试较为简单,所以不进行mybatis的逆向工程

              a、首先在yaml文件中进行数据库链接等配置

server:
  port: 8888

spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/jwt_test?characterEncoding=utf-8
    username: root
    password: 123456

mybatis:
  type-aliases-package: com.example.jwtdemo.entities
  mapper-locations: classpath:/mybatis/mapper/*.xml

                b、在entities中创建PO对象和VO对象

package com.example.jwtdemo.entities.po;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class UserPO {

    // 用户id
    private long id;

    // 用户姓名
    private String userName;

    // 用户登录账号
    private String loginAccount;

    // 用户密码
    private String password;

}
package com.example.jwtdemo.entities.vo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class LoginUserVO {

    // 用户登录的账号
    private String loginAccount;

    // 用户登录的密码
    private String password;

}

                c、创建mapper接口

                注意一定要有注解@Mapper 

package com.example.jwtdemo.mapper;

import com.example.jwtdemo.entities.po.UserPO;
import com.example.jwtdemo.entities.vo.LoginUserVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface UserPOMapper {

    UserPO login(@Param("loginUserVO") LoginUserVO loginUserVO);

}

                d、创建xml映射文件

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.jwtdemo.mapper.UserPOMapper">

    <resultMap id="UserMap" type="com.example.jwtdemo.entities.po.UserPO">
        <id property="id" column="id" jdbcType="BIGINT"/>
        <result property="userName" column="user_name" jdbcType="VARCHAR"/>
        <result property="loginAccount" column="login_account" jdbcType="VARCHAR"/>
        <result property="password" column="password" jdbcType="VARCHAR"/>
    </resultMap>
    
    <select id="login" parameterType="loginUserVO" resultMap="UserMap">
        select * from t_user
            where login_account = #{loginUserVO.loginAccount} and password = #{loginUserVO.password}
    </select>
</mapper>

3、业务层代码

        1)service接口

package com.example.jwtdemo.service;

import com.example.jwtdemo.entities.vo.LoginUserVO;

public interface TestJWTService {


    // 用户登录
    String login(LoginUserVO loginUserVO);

}

        2)实现类

package com.example.jwtdemo.service.impl;

import com.example.jwtdemo.entities.po.UserPO;
import com.example.jwtdemo.entities.vo.LoginUserVO;
import com.example.jwtdemo.mapper.UserPOMapper;
import com.example.jwtdemo.service.TestJWTService;
import com.example.jwtdemo.utils.JWTUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.HashMap;

@Service
public class TestJWTServiceImpl implements TestJWTService {

    @Autowired
    private UserPOMapper userPOMapper;

    /**
     * 用户登录
     * @param loginUserVO 表单传递的用户信息
     * @return 冲数据库中查到的用户信息
     */
    @Override
    public String login(LoginUserVO loginUserVO) {

        // 查询数据库
        UserPO User = userPOMapper.login(loginUserVO);

        // 判断是否为空
        if (User != null){
            // 若返回数据不为空
            // 创建HashMao
            HashMap<String, String> map = new HashMap<>();

            // 放入数据
            map.put("id", User.getId() + "");
            map.put("userName", User.getUserName());
            map.put("loginAcconut", User.getLoginAccount());

            return JWTUtils.getToken(map, 2);
        }

        throw new RuntimeException("数据库中未查到此用户");
    }
}

        3)控制层代码

                方法 testInterceptors 用来测试登录成功之后再次访问后header是否带有token。

 具体测试方法在拦截器中(下方)。

@RestController
public class TestJWTController {

    @Autowired
    private TestJWTService testJWTService;

    // 测试拦截器
    @GetMapping(value = "/test/interceptors")
    public CommonResult<String> testInterceptors(){

        return new CommonResult<>(200, "测试成功");
    }

    // 测试登录
    @PostMapping(value = "/test/user/login")
    public CommonResult<String> login(LoginUserVO loginUserVO){

        try {
            // 成功则直接返回
            String token = testJWTService.login(loginUserVO);
            return new CommonResult<>(200, "登录成功", token);

        } catch (Exception exception) {
            // 不成功则返回异常
            exception.printStackTrace();
            return new CommonResult<>(444, exception.getMessage());
        }
    }
}

4、WebMvcConfig

        1)创建拦截器

           前后端分离项目一般将token放在header中,后端通过拦截器在请求达到之前拿到header中的token并进行验签,验签成功则放行,失败则拦截。

package com.example.jwtdemo.interceptors;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.example.jwtdemo.controller.TestJWTController;
import com.example.jwtdemo.entities.CommonResult;
import com.example.jwtdemo.utils.JWTUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author : pcf
 * @date : 12:15 2021/10/23
 */
@Slf4j
public class TestTokenInterceptor implements HandlerInterceptor {

    // 请求之前
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        // 拿到header中的token
        String token = request.getHeader("token");

        // 创建返回消息体
        CommonResult<String> result = null;

        // 对token进行验签
        try {
            // 验签成功则放行
            JWTUtils.verify(token);
            return true;
        } catch (TokenExpiredException exception) {
            // token过期
            exception.printStackTrace();
            result = new CommonResult<>(400, "token过期");
        } catch (SignatureVerificationException exception) {
            // 签名错误
            exception.printStackTrace();
            result = new CommonResult<>(400, "签名错误");
        } catch (AlgorithmMismatchException exception) {
            // 加密算法不匹配
            exception.printStackTrace();
            result = new CommonResult<>(400, "加密算法不匹配");
        } catch (Exception exception) {
            // 无效token
            exception.printStackTrace();
            result = new CommonResult<>(400, "无效token");
        }

        // 将返回消息体转化为json
        String json = new ObjectMapper().writeValueAsString(result);

        // 设置编码类型
        response.setContentType("application/json;charset=UTF-8");

        // 放入响应中
        response.getWriter().println(json);
        return false;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}

        2)在WebConfig中注册拦截器

            为了测试简单,我们仅仅测试   http://localhost:8888/test/interceptors 这一个请求来达到我们测试token的目的。

package com.example.jwtdemo.config;

import com.example.jwtdemo.interceptors.TestTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author : pcf
 * @date : 11:04 2021/10/23
 */
@Configuration
public class WebConfig implements WebMvcConfigurer {

    // 添加拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new TestTokenInterceptor()).addPathPatterns("/test/interceptors");
    }
}

至此我们的demo搭建完毕下面进行测试。

5、测试

        我们使用postman作为测试工具进行测试

        1)测试登录

         发送post请求,模拟表单登录!

        登录成功后后端返回token

        2)测试登录之后请求的header中是否带有token

        将token写入header 

                验签成功!

         

        idea后台打印

        3)修改token

        随便修改token中的字母或数字。 (模拟解码后修改数据并再次编码)

  

                控制台报错,token验签失败!

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值