5.基于 JWT(Json web token) 验证权限

2019年10月12日 17:38 · 阅读(651) ·

说明

本文代码基于 4.Spring Boot 过滤器 Filter 使用 项目代码进行修改

JWT(Json web token) 加解密的类 JWTUtil 使用了 JWT(Json Web Token) 这篇文章的类

参考

JWT(Json Web Token)

https://jwt.io/

JSON Web Token 入门教程

为什么要在密码里加点“盐”

IntelliJ IDEA 2018.3 创建 Maven 多模块(Module)项目+负载均衡

4.Spring Boot 过滤器 Filter 使用

【项目实践】一文带你搞定页面权限、按钮权限以及数据权限

【项目实践】一文带你搞定Session和JWT的登录认证方式

目标

1.使用用户名,密码获取 Token
2.使用 Token 验证,验证成功后才能使用查询功能

开发环境

名称 版本
操作系统 Windows 10 X64
JDK JDK1.8(jdk-8u151-windows-x64)
IntelliJ IDEA IntelliJ IDEA 2018.3
Maven Maven 3.6.0

测试数据表

用户表-t_rbt_user

  1. -- ----------------------------
  2. -- Table structure for t_rbt_user
  3. -- ----------------------------
  4. DROP TABLE IF EXISTS `t_rbt_user`;
  5. CREATE TABLE `t_rbt_user` (
  6. `id` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '主键',
  7. `loginName` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '用户名',
  8. `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '密码',
  9. `withSalt` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '密码加盐',
  10. `gender` varchar(10) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '性别',
  11. `phone` varchar(10) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '手机号',
  12. `city` varchar(10) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '城市',
  13. `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '地址',
  14. `create_by` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '创建人',
  15. `create_time` timestamp(0) NULL DEFAULT NULL COMMENT '创建时间',
  16. `update_by` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '修改人',
  17. `update_time` timestamp(0) NULL DEFAULT NULL COMMENT '修改时间',
  18. `is_delete` int(1) NULL DEFAULT NULL COMMENT '是否删除',
  19. `biz_time` timestamp(0) NULL DEFAULT NULL COMMENT '系统时间戳',
  20. PRIMARY KEY (`id`) USING BTREE
  21. ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
  22. SET FOREIGN_KEY_CHECKS = 1;

测试表-t_rbt_test

测试表-t_rbt_test

Json web token (JWT) 公共类-JWTUtil

原理

参考阮一峰的 JSON Web Token 入门教程, 我就不抄了

不过我发现一个网址 https://jwt.io/

里面的这个页面很好的表现了这个原理

pom.xml(test-invoice-cloud)

  1. <properties>
  2. <jjwt.version>0.8.0</jjwt.version>
  3. </properties>
  4. <dependencies>
  5. <!--jwt start-->
  6. <dependency>
  7. <groupId>io.jsonwebtoken</groupId>
  8. <artifactId>jjwt</artifactId>
  9. <version>${jjwt.version}</version>
  10. </dependency>
  11. <!--jwt end-->
  12. </dependencies>

pom.xml(test-invoice-common)

  1. <!--Base64-->
  2. <dependency>
  3. <groupId>org.apache.tomcat.embed</groupId>
  4. <artifactId>tomcat-embed-core</artifactId>
  5. </dependency>

JWTUtil

test-invoice-common 项目添加类 JWTUtil

  1. package com.test.invoice.util;
  2. import io.jsonwebtoken.Claims;
  3. import io.jsonwebtoken.JwtBuilder;
  4. import io.jsonwebtoken.Jwts;
  5. import io.jsonwebtoken.SignatureAlgorithm;
  6. import org.apache.tomcat.util.codec.binary.Base64;
  7. import javax.crypto.SecretKey;
  8. import javax.crypto.spec.SecretKeySpec;
  9. import java.util.Date;
  10. import java.util.HashMap;
  11. import java.util.Map;
  12. /**
  13. * Json web token (JWT)
  14. * https://blog.csdn.net/qq_37636695/article/details/79265711
  15. *
  16. * @author:
  17. * @version:
  18. * @date: 2019-10-10 19:16
  19. */
  20. public class JWTUtil {
  21. /**
  22. * 创建 Json web token (JWT)
  23. * @param id Json web token (JWT) 的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性 token,从而回避重放攻击
  24. * @param subject Json web token (JWT) 的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志
  25. * @param ttlMillis 过期的时间长度,单位毫秒
  26. * @return Json web token (JWT)
  27. * @throws Exception
  28. */
  29. public static String createJWT(String id, String subject, long ttlMillis) throws Exception {
  30. SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
  31. long nowMillis = System.currentTimeMillis();//生成JWT的时间
  32. Date now = new Date(nowMillis);
  33. Map<String,Object> claims = new HashMap<String,Object>();//创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
  34. claims.put("uid", "DSSFAWDWADAS...");
  35. claims.put("user_name", "admin");
  36. claims.put("nick_name","DASDA121");
  37. SecretKey key = generalKey();//生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
  38. //下面就是在为payload添加各种标准声明和私有声明了
  39. JwtBuilder builder = Jwts.builder() //这里其实就是new一个JwtBuilder,设置jwt的body
  40. .setClaims(claims) //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
  41. .setId(id) //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
  42. .setIssuedAt(now) //iat: jwt的签发时间
  43. .setSubject(subject) //sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
  44. .signWith(signatureAlgorithm, key);//设置签名使用的签名算法和签名使用的秘钥
  45. if (ttlMillis >= 0) {
  46. long expMillis = nowMillis + ttlMillis;
  47. Date exp = new Date(expMillis);
  48. builder.setExpiration(exp); //设置过期时间
  49. }
  50. return builder.compact(); //就开始压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt
  51. //打印了一哈哈确实是下面的这个样子
  52. //eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJEU1NGQVdEV0FEQVMuLi4iLCJzdWIiOiIiLCJ1c2VyX25hbWUiOiJhZG1pbiIsIm5pY2tfbmFtZSI6IkRBU0RBMTIxIiwiZXhwIjoxNTE3ODI4MDE4LCJpYXQiOjE1MTc4Mjc5NTgsImp0aSI6Imp3dCJ9.xjIvBbdPbEMBMurmwW6IzBkS3MPwicbqQa2Y5hjHSyo
  53. }
  54. /**
  55. * 解密Json web token (JWT)
  56. * @param Json web token (JWT)
  57. * @return
  58. * @throws Exception
  59. */
  60. public static Claims parseJWT(String jwt) throws Exception{
  61. SecretKey key = generalKey(); //签名秘钥,和生成的签名的秘钥一模一样
  62. Claims claims = Jwts.parser() //得到DefaultJwtParser
  63. .setSigningKey(key) //设置签名的秘钥
  64. .parseClaimsJws(jwt) //设置需要解析的jwt
  65. .getBody();
  66. return claims;
  67. }
  68. /**
  69. * 由字符串生成加密 key
  70. * @return
  71. */
  72. public static SecretKey generalKey(){
  73. String stringKey = "7786df7fc3a34e26a61c034d5ec8245d";//自定义,本地配置文件中加密的密文
  74. byte[] encodedKey = Base64.decodeBase64(stringKey); //本地的密文解码 [B@152f6e2
  75. System.out.println(encodedKey);//[B@152f6e2
  76. System.out.println(Base64.encodeBase64URLSafeString(encodedKey));//7786df7fc3a34e26a61c034d5ec8245d
  77. SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");// 根据给定的字节数组使用AES加密算法构造一个密钥,使用 encodedKey中的始于且包含 0 到前 leng 个字节这是当然是所有。(后面的文章中马上回推出讲解Java加密和解密的一些算法)
  78. return key;
  79. }
  80. }

基于用户名密码获取 Token

test-invoice-common

SuperEntity

基础实体类-SuperEntity

TRbtUserEntity

  1. package com.test.invoice.model;
  2. import com.baomidou.mybatisplus.annotation.TableField;
  3. import com.baomidou.mybatisplus.annotation.TableName;
  4. import com.test.invoice.model.base.SuperEntity;
  5. import io.swagger.annotations.ApiModelProperty;
  6. import lombok.Data;
  7. import lombok.EqualsAndHashCode;
  8. import lombok.ToString;
  9. import lombok.experimental.Accessors;
  10. /**
  11. * t_rbt_user 表对应实体类-TRbtUserEntity
  12. *
  13. * @author:
  14. * @version:
  15. * @date:
  16. */
  17. @Data
  18. @EqualsAndHashCode(callSuper = true)
  19. @Accessors(chain = true)
  20. @ToString
  21. @TableName("t_rbt_user")
  22. public class TRbtUserEntity extends SuperEntity {
  23. @ApiModelProperty("用户名")
  24. @TableField(value="loginName")
  25. private String loginName;
  26. @ApiModelProperty("密码")
  27. @TableField(value="password")
  28. private String password;
  29. @ApiModelProperty("密码加盐")
  30. @TableField(value="withSalt")
  31. private String withSalt;
  32. @ApiModelProperty("性别")
  33. @TableField(value="gender")
  34. private String gender;
  35. @ApiModelProperty("手机号")
  36. @TableField(value="phone")
  37. private String phone;
  38. @ApiModelProperty("城市")
  39. @TableField(value="city")
  40. private String city;
  41. @ApiModelProperty("地址")
  42. @TableField(value="address")
  43. private String address;
  44. }

LoginRequestVO

  1. package com.test.invoice.vo;
  2. import com.baomidou.mybatisplus.annotation.TableField;
  3. import io.swagger.annotations.ApiModelProperty;
  4. import lombok.Data;
  5. import lombok.experimental.Accessors;
  6. /**
  7. * 获取用户信息请求参数
  8. *
  9. * @author: v_hwhao
  10. * @version:
  11. * @date: 2019-10-11 17:50
  12. */
  13. @Data
  14. @Accessors(chain = true)
  15. public class LoginRequestVO {
  16. @ApiModelProperty("用户名")
  17. private String loginName;
  18. @ApiModelProperty("密码")
  19. private String password;
  20. }

test-invoice-contract

TRbtUserConsumer

  1. package com.test.invoice.service.consumer;
  2. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  3. import com.test.invoice.data.TRbtTestData;
  4. import com.test.invoice.data.TRbtTestDataParam;
  5. import com.test.invoice.model.TRbtUserEntity;
  6. import com.test.invoice.service.hystrix.NotBreakerConfiguration;
  7. import com.test.invoice.vo.LoginRequestVO;
  8. import com.test.invoice.vo.ResponseVO;
  9. import org.springframework.cloud.openfeign.FeignClient;
  10. import org.springframework.web.bind.annotation.*;
  11. /**
  12. * Feign-api 接口定义——TRbtUserConsumer
  13. *
  14. * @author:
  15. * @version:
  16. * @date:
  17. */
  18. //@FeignClient(value = "service-producer")
  19. //modify by v_hwhao 20190927 - 不进入熔断,直接抛出异常
  20. @FeignClient(value = "service-producer",configuration = NotBreakerConfiguration.class)
  21. public interface TRbtUserConsumer {
  22. /**
  23. * 获取用户信息
  24. * @param param 获取用户信息请求参数
  25. * @return TRbtUserEntity
  26. */
  27. @PostMapping("/Get")
  28. ResponseVO<TRbtUserEntity> Get(LoginRequestVO param);
  29. }

ITRbtUserService

  1. package com.test.invoice.service.producer;
  2. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  3. import com.test.invoice.data.TRbtTestData;
  4. import com.test.invoice.data.TRbtTestDataParam;
  5. import com.test.invoice.model.TRbtUserEntity;
  6. import com.test.invoice.vo.LoginRequestVO;
  7. import com.test.invoice.vo.ResponseVO;
  8. import org.springframework.web.bind.annotation.RequestBody;
  9. import org.springframework.web.bind.annotation.RequestParam;
  10. /**
  11. * 接口定义-ITRbtUserService
  12. */
  13. public interface ITRbtUserService {
  14. /**
  15. * 获取用户信息
  16. * @param param 获取用户信息请求参数
  17. * @return TRbtUserEntity
  18. */
  19. ResponseVO<TRbtUserEntity> Get(LoginRequestVO param);
  20. }

test-invoice-service

TRbtUserRepository

  1. package com.test.invoice.mapper;
  2. import com.baomidou.mybatisplus.core.mapper.BaseMapper;
  3. import com.test.invoice.model.TRbtUserEntity;
  4. import org.springframework.stereotype.Repository;
  5. @Repository
  6. public interface TRbtUserRepository extends BaseMapper<TRbtUserEntity> {
  7. }

TRbtUserServiceImpl

  1. package com.test.invoice.service.impl;
  2. import cn.hutool.crypto.digest.DigestAlgorithm;
  3. import cn.hutool.crypto.digest.Digester;
  4. import com.baomidou.mybatisplus.core.conditions.Wrapper;
  5. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  6. import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
  7. import com.baomidou.mybatisplus.core.metadata.IPage;
  8. import com.baomidou.mybatisplus.core.toolkit.Wrappers;
  9. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  10. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  11. import com.test.invoice.data.TRbtTestData;
  12. import com.test.invoice.data.TRbtTestDataParam;
  13. import com.test.invoice.enums.ResponseCode;
  14. import com.test.invoice.mapper.TRbtTestRepository;
  15. import com.test.invoice.mapper.TRbtUserRepository;
  16. import com.test.invoice.model.TRbtTestEntity;
  17. import com.test.invoice.model.TRbtUserEntity;
  18. import com.test.invoice.service.producer.ITRbtTestService;
  19. import com.test.invoice.service.producer.ITRbtUserService;
  20. import com.test.invoice.vo.LoginRequestVO;
  21. import com.test.invoice.vo.ResponseVO;
  22. import lombok.extern.slf4j.Slf4j;
  23. import org.apache.commons.lang3.StringUtils;
  24. import org.springframework.stereotype.Service;
  25. import org.springframework.web.bind.annotation.RequestBody;
  26. import org.springframework.web.bind.annotation.RequestParam;
  27. import java.util.List;
  28. /**
  29. * TRbtUserServiceImpl
  30. *
  31. * @author:
  32. * @version:
  33. * @date:
  34. */
  35. @Slf4j
  36. @Service
  37. public class TRbtUserServiceImpl extends ServiceImpl<TRbtUserRepository, TRbtUserEntity> implements ITRbtUserService {
  38. /**
  39. * 获取用户信息
  40. * @param param 获取用户信息请求参数
  41. * @return TRbtUserEntity
  42. */
  43. @Override
  44. public ResponseVO<TRbtUserEntity> Get(LoginRequestVO param){
  45. ResponseVO<TRbtUserEntity> responseVO = new ResponseVO<>();
  46. Wrapper<TRbtUserEntity> whereWrapper = new QueryWrapper<>();
  47. ((QueryWrapper<TRbtUserEntity>) whereWrapper).eq("loginName",param.getLoginName());
  48. TRbtUserEntity entity = this.baseMapper.selectOne(whereWrapper);
  49. //账户名不存在
  50. if(entity == null){
  51. responseVO.setCode(ResponseCode.ACCOUNT_PASSWORD_ERROR.value());
  52. responseVO.setMsg(ResponseCode.ACCOUNT_PASSWORD_ERROR.getDescription());
  53. return responseVO;
  54. }
  55. Digester sha512 = new Digester(DigestAlgorithm.SHA512);
  56. String loginPwd = sha512.digestHex(param.getPassword()+entity.getWithSalt());
  57. if(!entity.getPassword().equals(loginPwd)){
  58. responseVO.setCode(ResponseCode.ACCOUNT_PASSWORD_ERROR.value());
  59. responseVO.setMsg(ResponseCode.ACCOUNT_PASSWORD_ERROR.getDescription());
  60. }
  61. else{
  62. responseVO.setData(entity);
  63. responseVO.setCode(ResponseCode.OK.value());
  64. responseVO.setMsg(ResponseCode.OK.getDescription());
  65. }
  66. return responseVO;
  67. }
  68. }

TRbtUserController

  1. package com.test.invoice.contorller;
  2. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  3. import com.test.invoice.data.TRbtTestData;
  4. import com.test.invoice.data.TRbtTestDataParam;
  5. import com.test.invoice.model.TRbtUserEntity;
  6. import com.test.invoice.service.producer.ITRbtTestService;
  7. import com.test.invoice.service.producer.ITRbtUserService;
  8. import com.test.invoice.vo.LoginRequestVO;
  9. import com.test.invoice.vo.ResponseVO;
  10. import io.swagger.annotations.Api;
  11. import io.swagger.annotations.ApiOperation;
  12. import org.springframework.web.bind.annotation.*;
  13. import javax.annotation.Resource;
  14. /**
  15. *
  16. * 生产者:业务微服务-测试框架系统控制类
  17. *
  18. * @author:
  19. * @version:
  20. * @date: 2019-08-12 09:53
  21. */
  22. @RestController
  23. @Api(description = "测试框架系统控制类")
  24. public class TRbtUserController {
  25. @Resource
  26. private ITRbtUserService service;
  27. /**
  28. * 获取用户信息
  29. * @param param 获取用户信息请求参数
  30. * @return TRbtUserEntity
  31. */
  32. @PostMapping("/Get")
  33. public ResponseVO<TRbtUserEntity> Get(@RequestBody LoginRequestVO param){
  34. return service.Get(param);
  35. }
  36. }

test-invoice-web

TRbtUserController

  1. package com.test.invoice.controller;
  2. import com.alibaba.fastjson.JSON;
  3. import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
  4. import com.google.gson.Gson;
  5. import com.google.gson.GsonBuilder;
  6. import com.test.invoice.data.TRbtTestData;
  7. import com.test.invoice.data.TRbtTestDataParam;
  8. import com.test.invoice.enums.ResponseCode;
  9. import com.test.invoice.model.TRbtTestEntity;
  10. import com.test.invoice.model.TRbtUserEntity;
  11. import com.test.invoice.service.consumer.TRbtTestConsumer;
  12. import com.test.invoice.service.consumer.TRbtUserConsumer;
  13. import com.test.invoice.util.ConvertHelper;
  14. import com.test.invoice.util.DateUtils;
  15. import com.test.invoice.util.JWTUtil;
  16. import com.test.invoice.util.MapperUtils;
  17. import com.test.invoice.vo.LoginRequestVO;
  18. import com.test.invoice.vo.ResponseVO;
  19. import io.swagger.annotations.Api;
  20. import io.swagger.annotations.ApiOperation;
  21. import lombok.extern.slf4j.Slf4j;
  22. import org.apache.commons.lang3.StringUtils;
  23. import org.springframework.beans.factory.annotation.Autowired;
  24. import org.springframework.web.bind.annotation.*;
  25. import javax.servlet.http.HttpServletRequest;
  26. import java.time.LocalDate;
  27. /**
  28. * 消费者,通过 Feign-api 调用
  29. *
  30. * @author:
  31. * @version:
  32. * @date: 2019-08-12 14:49
  33. */
  34. @RestController
  35. @Api(description = "测试框架系统控制类")
  36. @RequestMapping("/Inv/Api")
  37. @Slf4j
  38. public class TRbtUserController {
  39. /**
  40. * Http Request
  41. */
  42. @Autowired
  43. private HttpServletRequest request;
  44. //@Resource
  45. @Autowired
  46. private TRbtUserConsumer consumer;
  47. /**
  48. * 获取 Token
  49. * @param param 获取用户信息请求参数
  50. * @return Token
  51. */
  52. @PostMapping("/getToken")
  53. public ResponseVO<String> GetToken(@RequestBody LoginRequestVO param){
  54. ResponseVO<String> responseVO = new ResponseVO<>();
  55. try{
  56. //校验参数
  57. if(param == null){
  58. log.info("参数为空");
  59. responseVO.setCode(ResponseCode.PARAM_INVALID.value());
  60. responseVO.setMsg("参数为空");
  61. return responseVO;
  62. }
  63. if(StringUtils.isBlank(param.getLoginName())){
  64. log.info("账号为空");
  65. responseVO.setCode(ResponseCode.PARAM_INVALID.value());
  66. responseVO.setMsg("账号为空");
  67. return responseVO;
  68. }
  69. if(StringUtils.isBlank(param.getPassword())){
  70. log.info("密码为空");
  71. responseVO.setCode(ResponseCode.PARAM_INVALID.value());
  72. responseVO.setMsg("密码为空");
  73. return responseVO;
  74. }
  75. //获取用户信息实体
  76. ResponseVO<TRbtUserEntity> entity = consumer.Get(param);
  77. //获取用户信息失败
  78. if(entity.getCode() != ResponseCode.OK.value()){
  79. log.info(entity.getMsg());
  80. responseVO.setCode(entity.getCode());
  81. responseVO.setMsg(entity.getMsg());
  82. return responseVO;
  83. }
  84. TRbtUserEntity userInfo = entity.getData();
  85. //获取 Token
  86. String token = JWTUtil.createJWT(userInfo.getId(), JSON.toJSONString(userInfo), 1000000);
  87. responseVO.setData(token);
  88. responseVO.setCode(ResponseCode.OK.value());
  89. responseVO.setMsg("获取 token 成功");
  90. }catch (Exception ex){
  91. log.error(ex.getMessage());
  92. responseVO.setCode(ResponseCode.SYSTEM_EXCEPTION.value());
  93. responseVO.setMsg("获取 token 出现异常");
  94. }
  95. return responseVO;
  96. }
  97. }

测试

1.t_rbt_user 添加用户数据

这里的密码是 luomaPassword

  1. Digester sha512 = new Digester(DigestAlgorithm.SHA512);
  2. String loginPwd = sha512.digestHex("luomaPassword" + "+luomaWithSalt");
id loginName password withSalt
1 luoma 40587b974b67cca8c7c80f654d74d45edd39aaa89c60e4ee50f3f02ca7e606f4f88c61bac4a914cbed6a9beb9d8fcfcbbf0848b4cfb6968519ea4d487efe171d +luomaWithSalt

2.Postman 访问接口

  1. URL: http://localhost:8080/Inv/Api/getToken
  2. Body raw: {"loginName":"luoma","password":"luomaPassword"}

得到结果

  1. {
  2. "code": 2000,
  3. "msg": "获取 token 成功",
  4. "data": "eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJEU1NGQVdEV0FEQVMuLi4iLCJzdWIiOiJ7XCJhZGRyZXNzXCI6XCLngavmmJ9cIixcImJpelRpbWVcIjoxNTcwNzk4MDEwMDAwLFwiY2l0eVwiOlwi5rex5ZyzXCIsXCJjcmVhdGVCeVwiOlwibHVvbWFcIixcImNyZWF0ZVRpbWVcIjoxNTcwNzk3OTk5MDAwLFwiZ2VuZGVyXCI6XCLnlLdcIixcImlkXCI6XCIxXCIsXCJpc0RlbGV0ZVwiOjAsXCJsb2dpbk5hbWVcIjpcImx1b21hXCIsXCJwYXNzd29yZFwiOlwiNDA1ODdiOTc0YjY3Y2NhOGM3YzgwZjY1NGQ3NGQ0NWVkZDM5YWFhODljNjBlNGVlNTBmM2YwMmNhN2U2MDZmNGY4OGM2MWJhYzRhOTE0Y2JlZDZhOWJlYjlkOGZjZmNiYmYwODQ4YjRjZmI2OTY4NTE5ZWE0ZDQ4N2VmZTE3MWRcIixcInBob25lXCI6XCI4ODg4ODg4OFwiLFwidXBkYXRlQnlcIjpcImx1b21hXCIsXCJ1cGRhdGVUaW1lXCI6MTU3MDc5ODAwNzAwMCxcIndpdGhTYWx0XCI6XCIrbHVvbWFXaXRoU2FsdFwifSIsInVzZXJfbmFtZSI6ImFkbWluIiwibmlja19uYW1lIjoiREFTREExMjEiLCJleHAiOjE1NzA4NzMwMTksImlhdCI6MTU3MDg3MjAxOSwianRpIjoiMSJ9.q0sgkHgPwMyC0p3fwoJYlTiFACkZx39P3NAiTI_7Yws"
  5. }

基于 Token 进行验证

基础知识:4.Spring Boot 过滤器 Filter 使用

test-invoice-commmon

LocalProvider

  1. package com.test.invoice.handler;
  2. import com.test.invoice.model.TRbtUserEntity;
  3. import lombok.extern.slf4j.Slf4j;
  4. import javax.servlet.ServletContext;
  5. import javax.servlet.http.HttpServletRequest;
  6. import javax.servlet.http.HttpServletResponse;
  7. /**
  8. * 设置获取用户登入态信息
  9. *
  10. * @author:
  11. * @version:
  12. * @date: 2019-10-11 19:13
  13. */
  14. @Slf4j
  15. public class LocalProvider {
  16. private static ContextProvider contextProvider;
  17. private static final ThreadLocal<TRbtUserEntity> USER_INFO = new ThreadLocal<>();
  18. public static void init(HttpServletRequest request, HttpServletResponse response, TRbtUserEntity userInfo) {
  19. contextProvider = new ContextProvider(request, response);
  20. USER_INFO.set(userInfo);
  21. }
  22. public static TRbtUserEntity getUser() {
  23. return USER_INFO.get();
  24. }
  25. public static void destroy(){
  26. USER_INFO.remove();
  27. }
  28. private static boolean isNull(){
  29. return USER_INFO.get() == null;
  30. }
  31. public static HttpServletRequest getRequest() {
  32. return contextProvider.getRequest();
  33. }
  34. public static HttpServletResponse getResponse() {
  35. return contextProvider.getResponse();
  36. }
  37. public static ServletContext getServletContext() {
  38. return getRequest().getServletContext();
  39. }
  40. }

test-invoice-web

OauthFilter(修改)

  1. package com.test.invoice.filter;
  2. import com.alibaba.fastjson.JSON;
  3. import com.test.invoice.enums.ResponseCode;
  4. import com.test.invoice.handler.LocalProvider;
  5. import com.test.invoice.model.TRbtUserEntity;
  6. import com.test.invoice.util.JWTUtil;
  7. import com.test.invoice.vo.ResponseVO;
  8. import io.jsonwebtoken.ExpiredJwtException;
  9. import io.jsonwebtoken.MalformedJwtException;
  10. import io.jsonwebtoken.SignatureException;
  11. import io.jsonwebtoken.UnsupportedJwtException;
  12. import lombok.extern.slf4j.Slf4j;
  13. import org.apache.commons.lang3.StringUtils;
  14. import org.springframework.core.annotation.Order;
  15. import javax.servlet.*;
  16. import javax.servlet.annotation.WebFilter;
  17. import javax.servlet.http.HttpServletRequest;
  18. import javax.servlet.http.HttpServletResponse;
  19. import java.io.IOException;
  20. import java.util.regex.Pattern;
  21. import static com.xiaoleilu.hutool.lang.Console.log;
  22. /**
  23. * 过滤器
  24. *
  25. * @author:
  26. * @version:
  27. * @date: 2019-09-26 19:47
  28. */
  29. @Order(1)//越小越先执行
  30. @WebFilter(filterName = "oauthFilter", urlPatterns = "/*")
  31. @Slf4j
  32. public class OauthFilter implements Filter {
  33. /**
  34. * 调用接口前
  35. * @param filterConfig
  36. * @throws ServletException
  37. */
  38. @Override
  39. public void init(FilterConfig filterConfig) throws ServletException {
  40. System.out.println("init");
  41. }
  42. /**
  43. * 调用接口后
  44. * @param servletRequest
  45. * @param servletResponse
  46. * @param filterChain
  47. * @throws IOException
  48. * @throws ServletException
  49. */
  50. @Override
  51. public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
  52. System.out.println("doFilter");
  53. HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
  54. HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
  55. //从 token 中获取用户信息,如果是不需要验证的接口(如 getToken),vo.getData() 则返回 null
  56. ResponseVO<TRbtUserEntity> vo = checkUrl(httpServletRequest);
  57. if (vo.getCode().equals(ResponseCode.OK.value())) {
  58. if (vo.getData() != null) {
  59. //生成新的token
  60. try {
  61. httpServletResponse.setHeader("Content-token", JWTUtil.createJWT(vo.getData().getId(), vo.getData().toString(), 1000));
  62. } catch (Exception e) {
  63. e.printStackTrace();
  64. }
  65. TRbtUserEntity info = vo.getData();
  66. LocalProvider.init(httpServletRequest, (HttpServletResponse) servletResponse, info);
  67. }
  68. filterChain.doFilter(servletRequest, servletResponse);
  69. } else {
  70. httpServletResponse.getWriter().write(JSON.toJSONString(vo));
  71. }
  72. LocalProvider.destroy();
  73. }
  74. @Override
  75. public void destroy() {
  76. System.out.println("destroy");
  77. }
  78. /**
  79. * 请求方法为OPTIONS的无需校验有效性
  80. * OPTIONS请求方法的主要用途有两个:
  81. * 1、获取服务器支持的HTTP请求方法。
  82. * 2、用来检查服务器的性能。例如:AJAX进行跨域请求时的预检,
  83. * 需要向另外一个域名的资源发送一个HTTP OPTIONS请求头,用以判断实际发送的请求是否安全。
  84. */
  85. private ResponseVO<TRbtUserEntity> checkUrl(HttpServletRequest httpServletRequest) {
  86. String uri = httpServletRequest.getRequestURI();
  87. log.info("【监听请求】" + uri);
  88. TRbtUserEntity info = null;
  89. String userInfo="";
  90. try {
  91. //匹配对应的 url,没有匹配到需要校验 token,如 getToken 则不用校验
  92. if (!isIgnoredTokenUri(uri)) {
  93. String token = httpServletRequest.getHeader("Content-token");
  94. log.info("【过滤器获取TOKEN】" + token);
  95. //Token缺失
  96. if (StringUtils.isEmpty(token)) {
  97. return new ResponseVO<>(ResponseCode.TOKEN_MISSING);
  98. }
  99. //解析Token失效
  100. userInfo = JWTUtil.parseJWT(token).getSubject();
  101. //解析Token过期
  102. info = JSON.parseObject(userInfo, TRbtUserEntity.class);
  103. return new ResponseVO<>(ResponseCode.OK, info);
  104. }
  105. }catch (ExpiredJwtException e) {
  106. log.warn("==============================>>>>>> Token已过期: {} " + e);
  107. return new ResponseVO<>(ResponseCode.TOKEN_EXPIRATION);
  108. // throw new TokenException("Token已过期");
  109. } catch (UnsupportedJwtException e) {
  110. log.warn("==============================>>>>>> Token格式错误: {} " + e);
  111. return new ResponseVO<>(ResponseCode.TOKEN_FORMAT_ERROR);
  112. // throw new TokenException("Token格式错误");
  113. } catch (MalformedJwtException e) {
  114. log.warn("==============================>>>>>> Token没有被正确构造: {} " + e);
  115. return new ResponseVO<>(ResponseCode.TOKEN_MALFORMED);
  116. // throw new TokenException("Token没有被正确构造");
  117. } catch (SignatureException e) {
  118. log.warn("==============================>>>>>> 签名失败: {} " + e);
  119. return new ResponseVO<>(ResponseCode.TOKEN_SIGNATURE_ERROR);
  120. // throw new TokenException("签名失败");
  121. } catch (IllegalArgumentException e) {
  122. log.warn("==============================>>>>>> 非法参数异常: {} " + e);
  123. return new ResponseVO<>(ResponseCode.ILLEGAL_ARGUMENT);
  124. // throw new TokenException("非法参数异常");
  125. } catch (Exception e) {
  126. // e.printStackTrace();
  127. return new ResponseVO<>(ResponseCode.TOKEN_INVALID);
  128. }
  129. return new ResponseVO<>(ResponseCode.OK, info);
  130. }
  131. /**
  132. * 匹配对应的 url
  133. * @param uri uri
  134. * @return 匹配到返回 true
  135. */
  136. private boolean isIgnoredTokenUri(String uri) {
  137. String pattern = "(/Inv/Api/getToken)|(/webapp/tax/api/inv/api/*.*)|(/webapp/inv/pc/v1/authorize/*.*)|(/webapp/tax/api/webjars/*)|(/webapp/inv/api/v1/test/*.*)|(/webapp/inv/pc/api/v1/authorize/*.*)|(/mweb/*.*)|(/webapp/inv/api/v1/authorize/getLoginUrl)|(/webapp/inv/api/v1/authorize/getTokenByCode)|(/webapp/inv/api/v1/wx/callbackData/*.*)|(/webapp/inv/api/v1/wx/callbackOrder/*.*)|(/webapp/tax/api/swagger.*)|(/favicon.ico)|(/.*.js)|(/.*.css)|(/.*.png)|(/.*.map)|(/.*.html)|(/webapp/.*.txt)|(.*download.*)|(/.*.svg)|(/.*.jpg)|(/webapp/tax/api/v2.*)";
  138. // String pattern = "(/inv/pc/v1/authorize/*.*)|(/webjars/*)|(/inv/api/v1/test/*.*)|(/inv/pc/api/v1/authorize/*.*)|(/mweb/*.*)|(/inv/api/v1/authorize/getLoginUrl)|(/inv/api/v1/authorize/getTokenByCode)|(/inv/api/v1/wx/callbackData/*.*)|(/inv/api/v1/wx/callbackOrder/*.*)|(/swagger.*)|(/favicon.ico)|(/.*.js)|(/.*.css)|(/.*.png)|(/.*.map)|(/.*.html)|(/.*.txt)|(.*download.*)|(/.*.svg)|(/.*.jpg)|(/v2.*)";
  139. // String pattern = "(/enta/api/swagger.*)|(/favicon.ico)|(enta/api/webjars/*)|(/.*.js)|(/.*.css)|(/.*.png)|(/.*.map)|(/.*.html)|(/.*.txt)|(.*download.*)|(/.*.svg)|(/.*.jpg)|(/enta/api/v2.*)";
  140. return Pattern.matches(pattern, uri);
  141. }
  142. }

修改 Web Query 方法进行测试

这里只列出修改部分的代码,Query 的代码可以参考 Query

test-invoice-service

TRbtTestServiceImpl(修改)

  1. @Override
  2. public ResponseVO<Page<TRbtTestData>> Query(@RequestBody TRbtTestDataParam param){
  3. //设置参数中拼接的值,防止 sql 注入
  4. if(!StringUtils.isBlank(param.getName())){
  5. param.setName("%"+param.getName()+"%");
  6. }
  7. Page<TRbtTestData> page = new Page<>();
  8. page.setCurrent(param.getPage());
  9. page.setSize(param.getPageSize());
  10. //return new ResponseVO<>(ResponseCode.OK, baseMapper.selectPageVO(page, param));
  11. IPage<TRbtTestData> ipageData = baseMapper.selectPageVO(page, param);
  12. //IPage<TRbtTestData> 转换为 Page<TRbtTestData>
  13. Page<TRbtTestData> pageData = new Page<>();
  14. pageData.setRecords(ipageData.getRecords());
  15. pageData.setCurrent(ipageData.getCurrent());
  16. pageData.setSize(ipageData.getSize());
  17. pageData.setTotal(ipageData.getTotal());
  18. return new ResponseVO<>(ResponseCode.OK, pageData);
  19. }

TRbtTestMapper.xml(修改)

Token 中获取用户名作为 create_by 的条件

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="com.test.invoice.mapper.TRbtTestRepository">
  4. <select id="selectPageVO" resultType="com.test.invoice.data.TRbtTestData"
  5. parameterType="com.test.invoice.data.TRbtTestDataParam">
  6. select id,name,version
  7. from t_rbt_test
  8. where create_by = #{param.createBy}
  9. <if test="param.name != null and param.name != ''">
  10. and name like #{param.name}
  11. </if>
  12. order by name
  13. </select>
  14. </mapper>

test-invoice-web

TRbtTestController(修改)

  1. @PostMapping("/Test/Query")
  2. @ApiOperation(value = "系统框架测试-获取分页数据", httpMethod = "POST", response = ResponseVO.class, notes = "系统框架测试-获取分页数据")
  3. public ResponseVO Query(@RequestBody TRbtTestDataParam param){
  4. ResponseVO<Page<TRbtTestData>> responseVO = new ResponseVO<>();
  5. try{
  6. //从 token 中获取用户名
  7. String token = request.getHeader("Content-token");
  8. String userInfoStr = JWTUtil.parseJWT(token).getSubject();
  9. TRbtUserEntity userInfo = JSON.parseObject(userInfoStr, TRbtUserEntity.class);
  10. param.setCreateBy(userInfo.getLoginName());
  11. responseVO = testConsumer.Query(param);
  12. }catch (Exception ex){
  13. log.error(ex.getMessage());
  14. responseVO.setCode(ResponseCode.SYSTEM_EXCEPTION.value());
  15. responseVO.setMsg("获取分页数据异常,"+ex.getMessage());
  16. }
  17. return responseVO;
  18. }

使用 Postman 测试

把上一步测试获取到的 Token 放到这次请求的 Header 中,名称为 Content-token

Header 部分

属性
Content-Type application/json
Content-token eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJEU1NGQVdEV0FEQVMuLi4iLCJzdWIiOiJ7XCJhZGRyZXNzXCI6XCLngavmmJ9cIixcImJpelRpbWVcIjoxNTcwNzk4MDEwMDAwLFwiY2l0eVwiOlwi5rex5ZyzXCIsXCJjcmVhdGVCeVwiOlwibHVvbWFcIixcImNyZWF0ZVRpbWVcIjoxNTcwNzk3OTk5MDAwLFwiZ2VuZGVyXCI6XCLnlLdcIixcImlkXCI6XCIxXCIsXCJpc0RlbGV0ZVwiOjAsXCJsb2dpbk5hbWVcIjpcImx1b21hXCIsXCJwYXNzd29yZFwiOlwiNDA1ODdiOTc0YjY3Y2NhOGM3YzgwZjY1NGQ3NGQ0NWVkZDM5YWFhODljNjBlNGVlNTBmM2YwMmNhN2U2MDZmNGY4OGM2MWJhYzRhOTE0Y2JlZDZhOWJlYjlkOGZjZmNiYmYwODQ4YjRjZmI2OTY4NTE5ZWE0ZDQ4N2VmZTE3MWRcIixcInBob25lXCI6XCI4ODg4ODg4OFwiLFwidXBkYXRlQnlcIjpcImx1b21hXCIsXCJ1cGRhdGVUaW1lXCI6MTU3MDc5ODAwNzAwMCxcIndpdGhTYWx0XCI6XCIrbHVvbWFXaXRoU2FsdFwifSIsInVzZXJfbmFtZSI6ImFkbWluIiwibmlja19uYW1lIjoiREFTREExMjEiLCJleHAiOjE1NzA4NzMwMTksImlhdCI6MTU3MDg3MjAxOSwianRpIjoiMSJ9.q0sgkHgPwMyC0p3fwoJYlTiFACkZx39P3NAiTI_7Yws
  1. URL: http://localhost:8080/Inv/Api/Test/Query
  2. Body raw: {"page":1,"pageSize":3,"name":"哥"}

返回值

  1. {
  2. "code": 2000,
  3. "msg": "Success",
  4. "data": {
  5. "records": [
  6. {
  7. "id": "0ab4a8f4ab6e50404b12584691586131",
  8. "name": "三哥",
  9. "version": "1.0.6"
  10. },
  11. {
  12. "id": "09436c41966040a991d0275d5265f7ea",
  13. "name": "三哥",
  14. "version": "1.0.6"
  15. },
  16. {
  17. "id": "09e167f4121cc86002ebb583eb031c7a",
  18. "name": "三哥",
  19. "version": "1.0.6"
  20. }
  21. ],
  22. "total": 45,
  23. "size": 3,
  24. "current": 1,
  25. "searchCount": true,
  26. "pages": 15
  27. },
  28. "timestamp": 1570872550819
  29. }

修改 Content-token 的值为 1

返回 {"code":4004,"msg":"token??????","timestamp":1570882695985}


相关附件 5.基于 JWT(Json web token) 验证权限-10 | 大小:0.57M | 下载