说明
本文代码基于 4.Spring Boot 过滤器 Filter 使用 项目代码进行修改
JWT(Json web token) 加解密的类 JWTUtil
使用了 JWT(Json Web Token) 这篇文章的类
参考
目标
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
-- ----------------------------
-- Table structure for t_rbt_user
-- ----------------------------
DROP TABLE IF EXISTS `t_rbt_user`;
CREATE TABLE `t_rbt_user` (
`id` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL COMMENT '主键',
`loginName` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '用户名',
`password` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '密码',
`withSalt` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '密码加盐',
`gender` varchar(10) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '性别',
`phone` varchar(10) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '手机号',
`city` varchar(10) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '城市',
`address` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '地址',
`create_by` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '创建人',
`create_time` timestamp(0) NULL DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL COMMENT '修改人',
`update_time` timestamp(0) NULL DEFAULT NULL COMMENT '修改时间',
`is_delete` int(1) NULL DEFAULT NULL COMMENT '是否删除',
`biz_time` timestamp(0) NULL DEFAULT NULL COMMENT '系统时间戳',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
测试表-t_rbt_test
Json web token (JWT) 公共类-JWTUtil
原理
参考阮一峰的 JSON Web Token 入门教程, 我就不抄了
不过我发现一个网址 https://jwt.io/
里面的这个页面很好的表现了这个原理
pom.xml(test-invoice-cloud)
<properties>
<jjwt.version>0.8.0</jjwt.version>
</properties>
<dependencies>
<!--jwt start-->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jjwt.version}</version>
</dependency>
<!--jwt end-->
</dependencies>
pom.xml(test-invoice-common)
<!--Base64-->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
</dependency>
JWTUtil
test-invoice-common 项目添加类 JWTUtil
package com.test.invoice.util;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.apache.tomcat.util.codec.binary.Base64;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* Json web token (JWT)
* https://blog.csdn.net/qq_37636695/article/details/79265711
*
* @author:
* @version:
* @date: 2019-10-10 19:16
*/
public class JWTUtil {
/**
* 创建 Json web token (JWT)
* @param id Json web token (JWT) 的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性 token,从而回避重放攻击
* @param subject Json web token (JWT) 的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志
* @param ttlMillis 过期的时间长度,单位毫秒
* @return Json web token (JWT)
* @throws Exception
*/
public static String createJWT(String id, String subject, long ttlMillis) throws Exception {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; //指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。
long nowMillis = System.currentTimeMillis();//生成JWT的时间
Date now = new Date(nowMillis);
Map<String,Object> claims = new HashMap<String,Object>();//创建payload的私有声明(根据特定的业务需要添加,如果要拿这个做验证,一般是需要和jwt的接收方提前沟通好验证方式的)
claims.put("uid", "DSSFAWDWADAS...");
claims.put("user_name", "admin");
claims.put("nick_name","DASDA121");
SecretKey key = generalKey();//生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
//下面就是在为payload添加各种标准声明和私有声明了
JwtBuilder builder = Jwts.builder() //这里其实就是new一个JwtBuilder,设置jwt的body
.setClaims(claims) //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的
.setId(id) //设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。
.setIssuedAt(now) //iat: jwt的签发时间
.setSubject(subject) //sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。
.signWith(signatureAlgorithm, key);//设置签名使用的签名算法和签名使用的秘钥
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp); //设置过期时间
}
return builder.compact(); //就开始压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt
//打印了一哈哈确实是下面的这个样子
//eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJEU1NGQVdEV0FEQVMuLi4iLCJzdWIiOiIiLCJ1c2VyX25hbWUiOiJhZG1pbiIsIm5pY2tfbmFtZSI6IkRBU0RBMTIxIiwiZXhwIjoxNTE3ODI4MDE4LCJpYXQiOjE1MTc4Mjc5NTgsImp0aSI6Imp3dCJ9.xjIvBbdPbEMBMurmwW6IzBkS3MPwicbqQa2Y5hjHSyo
}
/**
* 解密Json web token (JWT)
* @param Json web token (JWT)
* @return
* @throws Exception
*/
public static Claims parseJWT(String jwt) throws Exception{
SecretKey key = generalKey(); //签名秘钥,和生成的签名的秘钥一模一样
Claims claims = Jwts.parser() //得到DefaultJwtParser
.setSigningKey(key) //设置签名的秘钥
.parseClaimsJws(jwt) //设置需要解析的jwt
.getBody();
return claims;
}
/**
* 由字符串生成加密 key
* @return
*/
public static SecretKey generalKey(){
String stringKey = "7786df7fc3a34e26a61c034d5ec8245d";//自定义,本地配置文件中加密的密文
byte[] encodedKey = Base64.decodeBase64(stringKey); //本地的密文解码 [B@152f6e2
System.out.println(encodedKey);//[B@152f6e2
System.out.println(Base64.encodeBase64URLSafeString(encodedKey));//7786df7fc3a34e26a61c034d5ec8245d
SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");// 根据给定的字节数组使用AES加密算法构造一个密钥,使用 encodedKey中的始于且包含 0 到前 leng 个字节这是当然是所有。(后面的文章中马上回推出讲解Java加密和解密的一些算法)
return key;
}
}
基于用户名密码获取 Token
test-invoice-common
SuperEntity
TRbtUserEntity
package com.test.invoice.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.test.invoice.model.base.SuperEntity;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.Accessors;
/**
* t_rbt_user 表对应实体类-TRbtUserEntity
*
* @author:
* @version:
* @date:
*/
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@ToString
@TableName("t_rbt_user")
public class TRbtUserEntity extends SuperEntity {
@ApiModelProperty("用户名")
@TableField(value="loginName")
private String loginName;
@ApiModelProperty("密码")
@TableField(value="password")
private String password;
@ApiModelProperty("密码加盐")
@TableField(value="withSalt")
private String withSalt;
@ApiModelProperty("性别")
@TableField(value="gender")
private String gender;
@ApiModelProperty("手机号")
@TableField(value="phone")
private String phone;
@ApiModelProperty("城市")
@TableField(value="city")
private String city;
@ApiModelProperty("地址")
@TableField(value="address")
private String address;
}
LoginRequestVO
package com.test.invoice.vo;
import com.baomidou.mybatisplus.annotation.TableField;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 获取用户信息请求参数
*
* @author: v_hwhao
* @version:
* @date: 2019-10-11 17:50
*/
@Data
@Accessors(chain = true)
public class LoginRequestVO {
@ApiModelProperty("用户名")
private String loginName;
@ApiModelProperty("密码")
private String password;
}
test-invoice-contract
TRbtUserConsumer
package com.test.invoice.service.consumer;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.test.invoice.data.TRbtTestData;
import com.test.invoice.data.TRbtTestDataParam;
import com.test.invoice.model.TRbtUserEntity;
import com.test.invoice.service.hystrix.NotBreakerConfiguration;
import com.test.invoice.vo.LoginRequestVO;
import com.test.invoice.vo.ResponseVO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
/**
* Feign-api 接口定义——TRbtUserConsumer
*
* @author:
* @version:
* @date:
*/
//@FeignClient(value = "service-producer")
//modify by v_hwhao 20190927 - 不进入熔断,直接抛出异常
@FeignClient(value = "service-producer",configuration = NotBreakerConfiguration.class)
public interface TRbtUserConsumer {
/**
* 获取用户信息
* @param param 获取用户信息请求参数
* @return TRbtUserEntity
*/
@PostMapping("/Get")
ResponseVO<TRbtUserEntity> Get(LoginRequestVO param);
}
ITRbtUserService
package com.test.invoice.service.producer;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.test.invoice.data.TRbtTestData;
import com.test.invoice.data.TRbtTestDataParam;
import com.test.invoice.model.TRbtUserEntity;
import com.test.invoice.vo.LoginRequestVO;
import com.test.invoice.vo.ResponseVO;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
/**
* 接口定义-ITRbtUserService
*/
public interface ITRbtUserService {
/**
* 获取用户信息
* @param param 获取用户信息请求参数
* @return TRbtUserEntity
*/
ResponseVO<TRbtUserEntity> Get(LoginRequestVO param);
}
test-invoice-service
TRbtUserRepository
package com.test.invoice.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.test.invoice.model.TRbtUserEntity;
import org.springframework.stereotype.Repository;
@Repository
public interface TRbtUserRepository extends BaseMapper<TRbtUserEntity> {
}
TRbtUserServiceImpl
package com.test.invoice.service.impl;
import cn.hutool.crypto.digest.DigestAlgorithm;
import cn.hutool.crypto.digest.Digester;
import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.test.invoice.data.TRbtTestData;
import com.test.invoice.data.TRbtTestDataParam;
import com.test.invoice.enums.ResponseCode;
import com.test.invoice.mapper.TRbtTestRepository;
import com.test.invoice.mapper.TRbtUserRepository;
import com.test.invoice.model.TRbtTestEntity;
import com.test.invoice.model.TRbtUserEntity;
import com.test.invoice.service.producer.ITRbtTestService;
import com.test.invoice.service.producer.ITRbtUserService;
import com.test.invoice.vo.LoginRequestVO;
import com.test.invoice.vo.ResponseVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
/**
* TRbtUserServiceImpl
*
* @author:
* @version:
* @date:
*/
@Slf4j
@Service
public class TRbtUserServiceImpl extends ServiceImpl<TRbtUserRepository, TRbtUserEntity> implements ITRbtUserService {
/**
* 获取用户信息
* @param param 获取用户信息请求参数
* @return TRbtUserEntity
*/
@Override
public ResponseVO<TRbtUserEntity> Get(LoginRequestVO param){
ResponseVO<TRbtUserEntity> responseVO = new ResponseVO<>();
Wrapper<TRbtUserEntity> whereWrapper = new QueryWrapper<>();
((QueryWrapper<TRbtUserEntity>) whereWrapper).eq("loginName",param.getLoginName());
TRbtUserEntity entity = this.baseMapper.selectOne(whereWrapper);
//账户名不存在
if(entity == null){
responseVO.setCode(ResponseCode.ACCOUNT_PASSWORD_ERROR.value());
responseVO.setMsg(ResponseCode.ACCOUNT_PASSWORD_ERROR.getDescription());
return responseVO;
}
Digester sha512 = new Digester(DigestAlgorithm.SHA512);
String loginPwd = sha512.digestHex(param.getPassword()+entity.getWithSalt());
if(!entity.getPassword().equals(loginPwd)){
responseVO.setCode(ResponseCode.ACCOUNT_PASSWORD_ERROR.value());
responseVO.setMsg(ResponseCode.ACCOUNT_PASSWORD_ERROR.getDescription());
}
else{
responseVO.setData(entity);
responseVO.setCode(ResponseCode.OK.value());
responseVO.setMsg(ResponseCode.OK.getDescription());
}
return responseVO;
}
}
TRbtUserController
package com.test.invoice.contorller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.test.invoice.data.TRbtTestData;
import com.test.invoice.data.TRbtTestDataParam;
import com.test.invoice.model.TRbtUserEntity;
import com.test.invoice.service.producer.ITRbtTestService;
import com.test.invoice.service.producer.ITRbtUserService;
import com.test.invoice.vo.LoginRequestVO;
import com.test.invoice.vo.ResponseVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
/**
*
* 生产者:业务微服务-测试框架系统控制类
*
* @author:
* @version:
* @date: 2019-08-12 09:53
*/
@RestController
@Api(description = "测试框架系统控制类")
public class TRbtUserController {
@Resource
private ITRbtUserService service;
/**
* 获取用户信息
* @param param 获取用户信息请求参数
* @return TRbtUserEntity
*/
@PostMapping("/Get")
public ResponseVO<TRbtUserEntity> Get(@RequestBody LoginRequestVO param){
return service.Get(param);
}
}
test-invoice-web
TRbtUserController
package com.test.invoice.controller;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.test.invoice.data.TRbtTestData;
import com.test.invoice.data.TRbtTestDataParam;
import com.test.invoice.enums.ResponseCode;
import com.test.invoice.model.TRbtTestEntity;
import com.test.invoice.model.TRbtUserEntity;
import com.test.invoice.service.consumer.TRbtTestConsumer;
import com.test.invoice.service.consumer.TRbtUserConsumer;
import com.test.invoice.util.ConvertHelper;
import com.test.invoice.util.DateUtils;
import com.test.invoice.util.JWTUtil;
import com.test.invoice.util.MapperUtils;
import com.test.invoice.vo.LoginRequestVO;
import com.test.invoice.vo.ResponseVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDate;
/**
* 消费者,通过 Feign-api 调用
*
* @author:
* @version:
* @date: 2019-08-12 14:49
*/
@RestController
@Api(description = "测试框架系统控制类")
@RequestMapping("/Inv/Api")
@Slf4j
public class TRbtUserController {
/**
* Http Request
*/
@Autowired
private HttpServletRequest request;
//@Resource
@Autowired
private TRbtUserConsumer consumer;
/**
* 获取 Token
* @param param 获取用户信息请求参数
* @return Token
*/
@PostMapping("/getToken")
public ResponseVO<String> GetToken(@RequestBody LoginRequestVO param){
ResponseVO<String> responseVO = new ResponseVO<>();
try{
//校验参数
if(param == null){
log.info("参数为空");
responseVO.setCode(ResponseCode.PARAM_INVALID.value());
responseVO.setMsg("参数为空");
return responseVO;
}
if(StringUtils.isBlank(param.getLoginName())){
log.info("账号为空");
responseVO.setCode(ResponseCode.PARAM_INVALID.value());
responseVO.setMsg("账号为空");
return responseVO;
}
if(StringUtils.isBlank(param.getPassword())){
log.info("密码为空");
responseVO.setCode(ResponseCode.PARAM_INVALID.value());
responseVO.setMsg("密码为空");
return responseVO;
}
//获取用户信息实体
ResponseVO<TRbtUserEntity> entity = consumer.Get(param);
//获取用户信息失败
if(entity.getCode() != ResponseCode.OK.value()){
log.info(entity.getMsg());
responseVO.setCode(entity.getCode());
responseVO.setMsg(entity.getMsg());
return responseVO;
}
TRbtUserEntity userInfo = entity.getData();
//获取 Token
String token = JWTUtil.createJWT(userInfo.getId(), JSON.toJSONString(userInfo), 1000000);
responseVO.setData(token);
responseVO.setCode(ResponseCode.OK.value());
responseVO.setMsg("获取 token 成功");
}catch (Exception ex){
log.error(ex.getMessage());
responseVO.setCode(ResponseCode.SYSTEM_EXCEPTION.value());
responseVO.setMsg("获取 token 出现异常");
}
return responseVO;
}
}
测试
1.t_rbt_user 添加用户数据
这里的密码是 luomaPassword
Digester sha512 = new Digester(DigestAlgorithm.SHA512);
String loginPwd = sha512.digestHex("luomaPassword" + "+luomaWithSalt");
id | loginName | password | withSalt | … |
---|---|---|---|---|
1 | luoma | 40587b974b67cca8c7c80f654d74d45edd39aaa89c60e4ee50f3f02ca7e606f4f88c61bac4a914cbed6a9beb9d8fcfcbbf0848b4cfb6968519ea4d487efe171d | +luomaWithSalt | … |
2.Postman 访问接口
URL: http://localhost:8080/Inv/Api/getToken
Body raw: {"loginName":"luoma","password":"luomaPassword"}
得到结果
{
"code": 2000,
"msg": "获取 token 成功",
"data": "eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJEU1NGQVdEV0FEQVMuLi4iLCJzdWIiOiJ7XCJhZGRyZXNzXCI6XCLngavmmJ9cIixcImJpelRpbWVcIjoxNTcwNzk4MDEwMDAwLFwiY2l0eVwiOlwi5rex5ZyzXCIsXCJjcmVhdGVCeVwiOlwibHVvbWFcIixcImNyZWF0ZVRpbWVcIjoxNTcwNzk3OTk5MDAwLFwiZ2VuZGVyXCI6XCLnlLdcIixcImlkXCI6XCIxXCIsXCJpc0RlbGV0ZVwiOjAsXCJsb2dpbk5hbWVcIjpcImx1b21hXCIsXCJwYXNzd29yZFwiOlwiNDA1ODdiOTc0YjY3Y2NhOGM3YzgwZjY1NGQ3NGQ0NWVkZDM5YWFhODljNjBlNGVlNTBmM2YwMmNhN2U2MDZmNGY4OGM2MWJhYzRhOTE0Y2JlZDZhOWJlYjlkOGZjZmNiYmYwODQ4YjRjZmI2OTY4NTE5ZWE0ZDQ4N2VmZTE3MWRcIixcInBob25lXCI6XCI4ODg4ODg4OFwiLFwidXBkYXRlQnlcIjpcImx1b21hXCIsXCJ1cGRhdGVUaW1lXCI6MTU3MDc5ODAwNzAwMCxcIndpdGhTYWx0XCI6XCIrbHVvbWFXaXRoU2FsdFwifSIsInVzZXJfbmFtZSI6ImFkbWluIiwibmlja19uYW1lIjoiREFTREExMjEiLCJleHAiOjE1NzA4NzMwMTksImlhdCI6MTU3MDg3MjAxOSwianRpIjoiMSJ9.q0sgkHgPwMyC0p3fwoJYlTiFACkZx39P3NAiTI_7Yws"
}
基于 Token 进行验证
基础知识:4.Spring Boot 过滤器 Filter 使用
test-invoice-commmon
LocalProvider
package com.test.invoice.handler;
import com.test.invoice.model.TRbtUserEntity;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 设置获取用户登入态信息
*
* @author:
* @version:
* @date: 2019-10-11 19:13
*/
@Slf4j
public class LocalProvider {
private static ContextProvider contextProvider;
private static final ThreadLocal<TRbtUserEntity> USER_INFO = new ThreadLocal<>();
public static void init(HttpServletRequest request, HttpServletResponse response, TRbtUserEntity userInfo) {
contextProvider = new ContextProvider(request, response);
USER_INFO.set(userInfo);
}
public static TRbtUserEntity getUser() {
return USER_INFO.get();
}
public static void destroy(){
USER_INFO.remove();
}
private static boolean isNull(){
return USER_INFO.get() == null;
}
public static HttpServletRequest getRequest() {
return contextProvider.getRequest();
}
public static HttpServletResponse getResponse() {
return contextProvider.getResponse();
}
public static ServletContext getServletContext() {
return getRequest().getServletContext();
}
}
test-invoice-web
OauthFilter(修改)
package com.test.invoice.filter;
import com.alibaba.fastjson.JSON;
import com.test.invoice.enums.ResponseCode;
import com.test.invoice.handler.LocalProvider;
import com.test.invoice.model.TRbtUserEntity;
import com.test.invoice.util.JWTUtil;
import com.test.invoice.vo.ResponseVO;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureException;
import io.jsonwebtoken.UnsupportedJwtException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.annotation.Order;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.regex.Pattern;
import static com.xiaoleilu.hutool.lang.Console.log;
/**
* 过滤器
*
* @author:
* @version:
* @date: 2019-09-26 19:47
*/
@Order(1)//越小越先执行
@WebFilter(filterName = "oauthFilter", urlPatterns = "/*")
@Slf4j
public class OauthFilter implements Filter {
/**
* 调用接口前
* @param filterConfig
* @throws ServletException
*/
@Override
public void init(FilterConfig filterConfig) throws ServletException {
System.out.println("init");
}
/**
* 调用接口后
* @param servletRequest
* @param servletResponse
* @param filterChain
* @throws IOException
* @throws ServletException
*/
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
System.out.println("doFilter");
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
//从 token 中获取用户信息,如果是不需要验证的接口(如 getToken),vo.getData() 则返回 null
ResponseVO<TRbtUserEntity> vo = checkUrl(httpServletRequest);
if (vo.getCode().equals(ResponseCode.OK.value())) {
if (vo.getData() != null) {
//生成新的token
try {
httpServletResponse.setHeader("Content-token", JWTUtil.createJWT(vo.getData().getId(), vo.getData().toString(), 1000));
} catch (Exception e) {
e.printStackTrace();
}
TRbtUserEntity info = vo.getData();
LocalProvider.init(httpServletRequest, (HttpServletResponse) servletResponse, info);
}
filterChain.doFilter(servletRequest, servletResponse);
} else {
httpServletResponse.getWriter().write(JSON.toJSONString(vo));
}
LocalProvider.destroy();
}
@Override
public void destroy() {
System.out.println("destroy");
}
/**
* 请求方法为OPTIONS的无需校验有效性
* OPTIONS请求方法的主要用途有两个:
* 1、获取服务器支持的HTTP请求方法。
* 2、用来检查服务器的性能。例如:AJAX进行跨域请求时的预检,
* 需要向另外一个域名的资源发送一个HTTP OPTIONS请求头,用以判断实际发送的请求是否安全。
*/
private ResponseVO<TRbtUserEntity> checkUrl(HttpServletRequest httpServletRequest) {
String uri = httpServletRequest.getRequestURI();
log.info("【监听请求】" + uri);
TRbtUserEntity info = null;
String userInfo="";
try {
//匹配对应的 url,没有匹配到需要校验 token,如 getToken 则不用校验
if (!isIgnoredTokenUri(uri)) {
String token = httpServletRequest.getHeader("Content-token");
log.info("【过滤器获取TOKEN】" + token);
//Token缺失
if (StringUtils.isEmpty(token)) {
return new ResponseVO<>(ResponseCode.TOKEN_MISSING);
}
//解析Token失效
userInfo = JWTUtil.parseJWT(token).getSubject();
//解析Token过期
info = JSON.parseObject(userInfo, TRbtUserEntity.class);
return new ResponseVO<>(ResponseCode.OK, info);
}
}catch (ExpiredJwtException e) {
log.warn("==============================>>>>>> Token已过期: {} " + e);
return new ResponseVO<>(ResponseCode.TOKEN_EXPIRATION);
// throw new TokenException("Token已过期");
} catch (UnsupportedJwtException e) {
log.warn("==============================>>>>>> Token格式错误: {} " + e);
return new ResponseVO<>(ResponseCode.TOKEN_FORMAT_ERROR);
// throw new TokenException("Token格式错误");
} catch (MalformedJwtException e) {
log.warn("==============================>>>>>> Token没有被正确构造: {} " + e);
return new ResponseVO<>(ResponseCode.TOKEN_MALFORMED);
// throw new TokenException("Token没有被正确构造");
} catch (SignatureException e) {
log.warn("==============================>>>>>> 签名失败: {} " + e);
return new ResponseVO<>(ResponseCode.TOKEN_SIGNATURE_ERROR);
// throw new TokenException("签名失败");
} catch (IllegalArgumentException e) {
log.warn("==============================>>>>>> 非法参数异常: {} " + e);
return new ResponseVO<>(ResponseCode.ILLEGAL_ARGUMENT);
// throw new TokenException("非法参数异常");
} catch (Exception e) {
// e.printStackTrace();
return new ResponseVO<>(ResponseCode.TOKEN_INVALID);
}
return new ResponseVO<>(ResponseCode.OK, info);
}
/**
* 匹配对应的 url
* @param uri uri
* @return 匹配到返回 true
*/
private boolean isIgnoredTokenUri(String uri) {
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.*)";
// 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.*)";
// String pattern = "(/enta/api/swagger.*)|(/favicon.ico)|(enta/api/webjars/*)|(/.*.js)|(/.*.css)|(/.*.png)|(/.*.map)|(/.*.html)|(/.*.txt)|(.*download.*)|(/.*.svg)|(/.*.jpg)|(/enta/api/v2.*)";
return Pattern.matches(pattern, uri);
}
}
修改 Web Query 方法进行测试
这里只列出修改部分的代码,Query 的代码可以参考 Query
test-invoice-service
TRbtTestServiceImpl(修改)
@Override
public ResponseVO<Page<TRbtTestData>> Query(@RequestBody TRbtTestDataParam param){
//设置参数中拼接的值,防止 sql 注入
if(!StringUtils.isBlank(param.getName())){
param.setName("%"+param.getName()+"%");
}
Page<TRbtTestData> page = new Page<>();
page.setCurrent(param.getPage());
page.setSize(param.getPageSize());
//return new ResponseVO<>(ResponseCode.OK, baseMapper.selectPageVO(page, param));
IPage<TRbtTestData> ipageData = baseMapper.selectPageVO(page, param);
//IPage<TRbtTestData> 转换为 Page<TRbtTestData>
Page<TRbtTestData> pageData = new Page<>();
pageData.setRecords(ipageData.getRecords());
pageData.setCurrent(ipageData.getCurrent());
pageData.setSize(ipageData.getSize());
pageData.setTotal(ipageData.getTotal());
return new ResponseVO<>(ResponseCode.OK, pageData);
}
TRbtTestMapper.xml(修改)
Token 中获取用户名作为 create_by
的条件
<?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.test.invoice.mapper.TRbtTestRepository">
<select id="selectPageVO" resultType="com.test.invoice.data.TRbtTestData"
parameterType="com.test.invoice.data.TRbtTestDataParam">
select id,name,version
from t_rbt_test
where create_by = #{param.createBy}
<if test="param.name != null and param.name != ''">
and name like #{param.name}
</if>
order by name
</select>
</mapper>
test-invoice-web
TRbtTestController(修改)
@PostMapping("/Test/Query")
@ApiOperation(value = "系统框架测试-获取分页数据", httpMethod = "POST", response = ResponseVO.class, notes = "系统框架测试-获取分页数据")
public ResponseVO Query(@RequestBody TRbtTestDataParam param){
ResponseVO<Page<TRbtTestData>> responseVO = new ResponseVO<>();
try{
//从 token 中获取用户名
String token = request.getHeader("Content-token");
String userInfoStr = JWTUtil.parseJWT(token).getSubject();
TRbtUserEntity userInfo = JSON.parseObject(userInfoStr, TRbtUserEntity.class);
param.setCreateBy(userInfo.getLoginName());
responseVO = testConsumer.Query(param);
}catch (Exception ex){
log.error(ex.getMessage());
responseVO.setCode(ResponseCode.SYSTEM_EXCEPTION.value());
responseVO.setMsg("获取分页数据异常,"+ex.getMessage());
}
return responseVO;
}
使用 Postman 测试
把上一步测试获取到的 Token 放到这次请求的 Header
中,名称为 Content-token
Header 部分
属性 | 值 |
---|---|
Content-Type | application/json |
Content-token | eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOiJEU1NGQVdEV0FEQVMuLi4iLCJzdWIiOiJ7XCJhZGRyZXNzXCI6XCLngavmmJ9cIixcImJpelRpbWVcIjoxNTcwNzk4MDEwMDAwLFwiY2l0eVwiOlwi5rex5ZyzXCIsXCJjcmVhdGVCeVwiOlwibHVvbWFcIixcImNyZWF0ZVRpbWVcIjoxNTcwNzk3OTk5MDAwLFwiZ2VuZGVyXCI6XCLnlLdcIixcImlkXCI6XCIxXCIsXCJpc0RlbGV0ZVwiOjAsXCJsb2dpbk5hbWVcIjpcImx1b21hXCIsXCJwYXNzd29yZFwiOlwiNDA1ODdiOTc0YjY3Y2NhOGM3YzgwZjY1NGQ3NGQ0NWVkZDM5YWFhODljNjBlNGVlNTBmM2YwMmNhN2U2MDZmNGY4OGM2MWJhYzRhOTE0Y2JlZDZhOWJlYjlkOGZjZmNiYmYwODQ4YjRjZmI2OTY4NTE5ZWE0ZDQ4N2VmZTE3MWRcIixcInBob25lXCI6XCI4ODg4ODg4OFwiLFwidXBkYXRlQnlcIjpcImx1b21hXCIsXCJ1cGRhdGVUaW1lXCI6MTU3MDc5ODAwNzAwMCxcIndpdGhTYWx0XCI6XCIrbHVvbWFXaXRoU2FsdFwifSIsInVzZXJfbmFtZSI6ImFkbWluIiwibmlja19uYW1lIjoiREFTREExMjEiLCJleHAiOjE1NzA4NzMwMTksImlhdCI6MTU3MDg3MjAxOSwianRpIjoiMSJ9.q0sgkHgPwMyC0p3fwoJYlTiFACkZx39P3NAiTI_7Yws |
URL: http://localhost:8080/Inv/Api/Test/Query
Body raw: {"page":1,"pageSize":3,"name":"哥"}
返回值
{
"code": 2000,
"msg": "Success",
"data": {
"records": [
{
"id": "0ab4a8f4ab6e50404b12584691586131",
"name": "三哥",
"version": "1.0.6"
},
{
"id": "09436c41966040a991d0275d5265f7ea",
"name": "三哥",
"version": "1.0.6"
},
{
"id": "09e167f4121cc86002ebb583eb031c7a",
"name": "三哥",
"version": "1.0.6"
}
],
"total": 45,
"size": 3,
"current": 1,
"searchCount": true,
"pages": 15
},
"timestamp": 1570872550819
}
修改 Content-token
的值为 1
返回 {"code":4004,"msg":"token??????","timestamp":1570882695985}