说明
本文代码基于 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@152f6e2System.out.println(encodedKey);//[B@152f6e2System.out.println(Base64.encodeBase64URLSafeString(encodedKey));//7786df7fc3a34e26a61c034d5ec8245dSecretKey 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;@Repositorypublic 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@Servicepublic class TRbtUserServiceImpl extends ServiceImpl<TRbtUserRepository, TRbtUserEntity> implements ITRbtUserService {/*** 获取用户信息* @param param 获取用户信息请求参数* @return TRbtUserEntity*/@Overridepublic 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 {@Resourceprivate 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")@Slf4jpublic class TRbtUserController {/*** Http Request*/@Autowiredprivate HttpServletRequest request;//@Resource@Autowiredprivate 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();//获取 TokenString 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/getTokenBody 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*/@Slf4jpublic 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 = "/*")@Slf4jpublic class OauthFilter implements Filter {/*** 调用接口前* @param filterConfig* @throws ServletException*/@Overridepublic void init(FilterConfig filterConfig) throws ServletException {System.out.println("init");}/*** 调用接口后* @param servletRequest* @param servletResponse* @param filterChain* @throws IOException* @throws ServletException*/@Overridepublic 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() 则返回 nullResponseVO<TRbtUserEntity> vo = checkUrl(httpServletRequest);if (vo.getCode().equals(ResponseCode.OK.value())) {if (vo.getData() != null) {//生成新的tokentry {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();}@Overridepublic 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(修改)
@Overridepublic 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,versionfrom t_rbt_testwhere 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/QueryBody 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}