记一次token安全认证的实践
阅读此文前请先阅读上一篇SpringBoot整合JWT实现用户认证了解JWT。
背景介绍:
因项目需求,有PC端 APP端和小程序端,但登陆接口是同一个,然而微服务也无法使用传统的session解决用户登录问题(注意这里是传统的session不是spring session),使用户信息在其他服务共享。
如此一来就想到了token安全认证,而JWT生成token可以包含用户信息,也就果断选择了JWT作为SpringCloud gateway网关的token校验工具,这样,我们便可以直接解析token获取用户信息了。
具体实现思路:
让JWT在其他所有服务可以共同使用,父工程需要引入JWT jar。避免在其他服务重复引入。
如何使用JWT生成token。
如何解析token。
如何让网关拦截用户请求校验token。
如何避免首次登录被网关拦截。
代码实现:
1.创建SpringCloud项目
SpringCloud子项目包含 eureka,gateway,auth三个工程,父工程maven依赖如下。
<dependency>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
<version>6.0</version>
</dependency>
2.Auth和gateway编写TOKEN工具类
public class Token {
private static final Logger log = LoggerFactory.getLogger(Token.class);
/**
* 1.创建一个32-byte的密匙JWT生成TOKEN
*/
private static final byte[] secret = "geiwodiangasfdjsikolkjikolkijswe".getBytes();
//生成一个token
public static String creatToken(Map<String,Object> payloadMap) throws JOSEException {
//3.先建立一个头部Header
/**
* JWSHeader参数:1.加密算法法则,2.类型,3.。。。。。。。
* 一般只需要传入加密算法法则就可以。
* 这里则采用HS256
* JWSAlgorithm类里面有所有的加密算法法则,直接调用。
*/
JWSHeader jwsHeader = new JWSHeader(JWSAlgorithm.HS256);
//建立一个载荷Payload
Payload payload = new Payload(new JSONObject(payloadMap));
//将头部和载荷结合在一起
JWSObject jwsObject = new JWSObject(jwsHeader, payload);
//建立一个密匙
JWSSigner jwsSigner = new MACSigner(secret);
//签名
jwsObject.sign(jwsSigner);
//生成token
return jwsObject.serialize();
}
/**
* 解析一个token
* @param token
* @return
* @throws ParseException
* @throws JOSEException
*/
public static Map<String,Object> valid(String token) throws ParseException, JOSEException {
//解析token
JWSObject jwsObject = JWSObject.parse(token);
//获取到载荷
Payload payload=jwsObject.getPayload();
//建立一个解锁密匙
JWSVerifier jwsVerifier = new MACVerifier(secret);
Map<String, Object> resultMap = new HashMap<>();
//判断token
if (jwsObject.verify(jwsVerifier)) {
resultMap.put("Result", 0);
//载荷的数据解析成json对象。
JSONObject jsonObject = payload.toJSONObject();
resultMap.put("data", jsonObject);
//判断token是否过期
if (jsonObject.containsKey("exp")) {
Long expTime = Long.valueOf(jsonObject.get("exp").toString());
Long nowTime = new Date().getTime();
//判断是否过期
if (nowTime > expTime) {
//已经过期
resultMap.clear();
resultMap.put("Result", 2);
}
}
}else {
resultMap.put("Result", 1);
}
return resultMap;
}
/**
* 生成token的业务逻辑 登录接口调用次业务
* @param uid
* @return
*/
public static String TokenTest(Long uid,Long deptId,String userType,int companyId) {
//获取生成token
Map<String, Object> map = new HashMap<>();
//建立载荷,这些数据根据业务,自己定义。
map.put("uid", uid);
map.put("deptId", deptId);
map.put("userType", userType);
map.put("companyId", companyId);
//生成时间
map.put("sta", new Date().getTime());
//过期时间
map.put("exp", new Date().getTime()+1000*3600*24*15);
try {
String token = Token.creatToken(map);
System.out.println("token="+token);
return token;
} catch (JOSEException e) {
System.out.println("生成token失败");
e.printStackTrace();
}
return null;
}
/**
* 处理解析的业务逻辑 gateway JWT认证过滤器解析
* @param token
*/
public static Map<String,Object> ValidToken(String token) {
Map<String, Object> userMsg = new HashMap<String, Object>();
//解析token
try {
if (token != null) {
Map<String, Object> validMap = Token.valid(token);
int i = (int) validMap.get("Result");
if (i == 0) {
log.info("token解析成功");
JSONObject jsonObject = (JSONObject) validMap.get("data");
log.info("uid是:" + jsonObject.get("uid"));
log.info("deptId是:" + jsonObject.get("deptId"));
log.info("userType是:" + jsonObject.get("userType"));
log.info("companyId是:" + jsonObject.get("companyId"));
log.info("生成时间是:"+jsonObject.get("sta"));
log.info("过期时间是:"+jsonObject.get("exp"));
userMsg.put("token",token);
userMsg.put("uid",jsonObject.get("uid"));
userMsg.put("deptId",jsonObject.get("deptId"));
userMsg.put("companyId",jsonObject.get("companyId"));
userMsg.put("userType",jsonObject.get("userType"));
return userMsg;
} else if (i == 2) {
log.info("token已经过期");
return userMsg;
}
}
} catch (ParseException e) {
e.printStackTrace();
} catch (JOSEException e) {
e.printStackTrace();
}
return userMsg;
}
public static void main(String[] ages) {
//获取token
Long uid = 1L;
Long deptId = 2L;
String userType = "3";
int companyId = 4;
String token = TokenTest(uid,deptId,userType,companyId);
//解析token
log.info(ValidToken(token).toString());
}
}
特别提示:以上工具类可以在用户登录授权接口中调用,用以生成token,示例代码如下(可以借鉴不可复制哦,请根据自己业务逻辑在合适的地方调用TOKEN工具)
@RestController
@RequestMapping("/currency")
public class CurrencyLoginController {
//密钥 (需要前端和后端保持一致)
private static final String KEY = "abcdefgabcdefg12";
//redis初始KEY值
private static final String LOGIN_USER = "login_user";
@Autowired
private RedisUtil ru;
@PostMapping("/login")
public Map<String, Object> ajaxLogin(String username, String password, Boolean rememberMe) throws Exception{
password = AESUtil.aesDecrypt(password,KEY);//双向加密规则
UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
Subject subject = SecurityUtils.getSubject();
try{
subject.login(token);
User user = ShiroUtils.getUser();
String access_token = Token.generateToken(user.getUserId(), user.getDeptId(),user.getLoginUserType(), user.getCompanyId());
UserMsg resultUser = new UserMsg();
resultUser.setCompanyId(user.getCompanyId());
resultUser.setUserType(user.getLoginUserType());
resultUser.setDeptId(user.getDeptId());
resultUser.setUid(user.getUserId());
resultUser.setToken(access_token);
ru.set(LOGIN_USER+user.getUserId(), resultUser, 3600*24*15);
return ResultMap.ok("登录成功", resultUser);//改造——》》获取用户信息保存到redis中实现用户信息在微服务中共享,生成token
}catch (AuthenticationException e){
String msg = "用户或密码错误";
if (StringUtils.isNotEmpty(e.getMessage())){
msg = e.getMessage();
}
return ResultMap.error(msg);
}
}
}
好了,此时呢,我们已经通过auth工程完成了用户登录授权,并且生成了token。那么如何在gateway网关中进行token认证呢?
3.gateway网关中编写JwtCheckGatewayFilterFactory过滤器。
此类需要继承gateway的AbstractGatewayFilterFactory。
代码实现如下:
首先gateway网关yml文件中需要代理auth路由。
spring:
cloud:
gateway:
routes:
- id: neo_route
uri: lb://YUNXI-AUTH
predicates:
- Path=/auth/**
filters:
- StripPrefix=1
- JwtCheck
自定义 JwtCheckGatewayFilterFactory 继承 AbstractGatewayFilterFactory 抽象类,代码如下:
public class JwtCheckGatewayFilterFactory extends AbstractGatewayFilterFactory<JwtCheckGatewayFilterFactory.Config> {
private static final Logger log = LoggerFactory.getLogger(JwtCheckGatewayFilterFactory .class);
//定义用户认证登录接口
private static final String CURRENCY_URL="/currency/login";
//redis初始KEY值
private static final String LOGIN_USER = "login_user";
@Autowired
private RedisUtil ru;
public JwtCheckGatewayFilterFactory() {
super(Config.class);
}
@Override
public GatewayFilter apply(Config config) {
return (exchange, chain) -> {
String jwtToken = exchange.getRequest().getHeaders().getFirst("Authorization");
log.info(exchange.getRequest().getURI().toString());
//校验jwtToken的合法性,如果当前请求url和认证url相同跳过认证,表示用户首次登录认证
if(exchange.getRequest().getURI().toString().contains(CURRENCY_URL)){
return chain.filter(exchange);
}
if(jwtToken != null){
log.info(Token.ValidToken(jwtToken).toString());
//解析TOKEN
Map<String, Object> userMsg = Token.ValidToken(jwtToken);
Long uid = (Long) userMsg.get("uid");
if(ru.hasKey(LOGIN_USER+uid)){
Object obj = ru.get(LOGIN_USER+uid);
UserMsg userModel = (UserMsg) obj;
//解析客户端传过来的TOKEN是否和缓存中的TOKEN相同,并且判断TOKEN过期时间是否大于当前时间
if(userModel.getToken().equals(jwtToken)){
return chain.filter(exchange);
}else{
ServerHttpResponse response = exchange.getResponse();
String warningStr = "不合法的请求";
DataBuffer bodyDataBuffer = response.bufferFactory().wrap(warningStr.getBytes());
return response.writeWith(Mono.just(bodyDataBuffer));
}
}else{
ServerHttpResponse response = exchange.getResponse();
String warningStr = "登录超时";
DataBuffer bodyDataBuffer = response.bufferFactory().wrap(warningStr.getBytes());
return response.writeWith(Mono.just(bodyDataBuffer));
}
}
//不合法(响应未登录的异常)
ServerHttpResponse response = exchange.getResponse();
//设置headers
HttpHeaders httpHeaders = response.getHeaders();
httpHeaders.add("Content-Type", "application/json; charset=UTF-8");
httpHeaders.add("Cache-Control", "no-store, no-cache, must-revalidate, max-age=0");
//设置body
String warningStr = "未授权的请求,请登录";
DataBuffer bodyDataBuffer = response.bufferFactory().wrap(warningStr.getBytes());
return response.writeWith(Mono.just(bodyDataBuffer));
};
}
public static class Config {
//Put the configuration properties for your filter here
}
}
编写config文件将JWT认证过滤器添加到Spring bean中。
@Configuration
public class AppConfig {
@Bean
public JwtCheckGatewayFilterFactory jwtCheckGatewayFilterFactory(){
return new JwtCheckGatewayFilterFactory();
}
}
此时我们就完成了整个token认证过程,其实简单的来说就是:
第一步:Auth工程配合用户登录生成token,并将token和用户信息存储在redis中。
第二步:在gayeway中编写JWT认证过滤器,用以校验用户请求中携带的token。
有图有真相

特别提示:我的auth工程端口是8766,登录认证接口路由是/currency/login。而此时我请求的认证接口是/main/currency/login,端口是8765,我们在文章开头就已说明,gateway网关在yml文件中配置auth代理为auth/,和这里的main是同一个道理。
如果此时我们再去请求项目中其他端口携带过期的token试试看效果:

我们登陆认证返回的token是:
eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsInN0YSI6MTU1NjcxODU2Nzc3NCwiY29tcGFueUlkIjowLCJkZXB0SWQiOjEwMCwidXNlclR5cGUiOm51bGwsImV4cCI6MTU1ODAxNDU2Nzc3NH0.6oXx4Wk-eWHSWTHyJHmoiGowKnAmBdCHIRCzsMq5XlA;
携带的其他过期的token是:
eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsInN0YSI6MTU1NjQ1NjUwNzIwMiwiY29tcGFueUlkIjowLCJkZXB0SWQiOjEwMCwidXNlclR5cGUiOm51bGwsImV4cCI6MTU1Nzc1MjUwNzIwMn0._yF2TeaR4MTmF-Re9QciMZOeRKBOQmfvi3o4hWeGSMU
再携带错误的token试试看:

登陆认证返回的token是:
eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsInN0YSI6MTU1NjcxODU2Nzc3NCwiY29tcGFueUlkIjowLCJkZXB0SWQiOjEwMCwidXNlclR5cGUiOm51bGwsImV4cCI6MTU1ODAxNDU2Nzc3NH0.6oXx4Wk-eWHSWTHyJHmoiGowKnAmBdCHIRCzsMq5XlA;
携带错误的token是:
eyJhbGciOiJIUzI1NiJ9.eyJ1aWQiOjEsInN0YSI6MTU1NjcxODU2Nzc3NCwiY29tcGFueUlkIjowLCJkZXB0SWQiOjEwMCwidXNlclR5cGUiOm51bGwsImV4cCI6MTU1ODAxNDU2Nzc3NH0.6oXx4Wk-eWHSWTHyJHmoiGowKnAmBdCHIRCzsMq5XlD
携带正确的token:

到这里我么你的整个SpringCloud gateway网关+JWT安全认证就结束啦,非常抱歉,由于项目保密性不能为大家提供项目源码。但是整个过程我已经写的非常详细,也不希望大家做伸手党,如果有各种疑问欢迎留言,我可以帮大家一一解决。
记一次token安全认证的实践的更多相关文章
- NodeJS 实现基于 token 的认证应用
此段摘自 http://zhuanlan.zhihu.com/FrontendMagazine/19920223 英文原文 http://code.tutsplus.com/tutorials/tok ...
- 理解JWT(JSON Web Token)认证
理解JWT(JSON Web Token)认证 最近想做个小程序,需要用到授权认证流程.以前项目都是用的 OAuth2 认证,但是Sanic 使用OAuth2 不太方便,就想试一下 JWT 的认证方式 ...
- 使用 AngularJS & NodeJS 实现基于token 的认证应用(转)
认证是任何 web 应用中不可或缺的一部分.在这个教程中,我们会讨论基于 token 的认证系统以及它和传统的登录系统的不同.这篇教程的末尾,你会看到一个使用 AngularJS 和 NodeJS 构 ...
- ASP.NET WebApi 基于OAuth2.0实现Token签名认证
一.课程介绍 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将是我们需要思考的问题.为了保护我们的WebApi数 ...
- ASP.NET WebApi 基于JWT实现Token签名认证
一.前言 明人不说暗话,跟着阿笨一起玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NET WebServi ...
- ASP.NET WebApi 基于分布式Session方式实现Token签名认证
一.课程介绍 明人不说暗话,跟着阿笨一起学玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NETWebSer ...
- ASP.NET WebApi 基于分布式Session方式实现Token签名认证(发布版)
一.课程介绍 明人不说暗话,跟着阿笨一起学玩WebApi!开发提供数据的WebApi服务,最重要的是数据的安全性.那么对于我们来说,如何确保数据的安全将会是需要思考的问题.在ASP.NETWebSer ...
- 使用 AngularJS & NodeJS 实现基于 token 的认证应用
认证是任何Web应用中不可或缺的一部分.在这个教程中,我们会讨论基于token的认证系统以及它和传统的登录系统的不同.这篇教程的末尾,你会看到一个使用 AngularJS 和 NodeJS 构建的 ...
- JWT(Json web token)认证详解
JWT(Json web token)认证详解 什么是JWT Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该to ...
随机推荐
- 数据库语言-SQL
SQL语言的功能概述 DDL语句引导词:Create(建立),Alter(修改),Drop(撤销) DML语句引导词:Insert,Delete,Update,Select DCL语句引导词:Gran ...
- 【问题篇四】启动报DataSource错误
初建一个简单的spring boot 项目,启动后会报错:就是在项目启动的时候在 resource目录下没有加载到配置信息:如果项目只是想简单的启动运行,不进行数据库操作可以在 启动类上做如下处理便可 ...
- 图解SynchronousQueue原理-公平模式
SynchronousQueue原理详解-公平模式 一.介绍 SynchronousQueue是一个双栈双队列算法,无空间的队列或栈,任何一个对SynchronousQueue写需要等到一个对Sync ...
- ReactNative: 使用Image图片组件
一.简介 在应用程序中,图片组件非常常见,不论是缩略图.大图.还是小图标等等,都需要使用图片组件进行显示.在Web开发中提供了<img/>标签显示图片,在iOS中提供了UIImageVie ...
- SVM算法核函数的选择
SVM支持向量机,一般用于二分类模型,支持线性可分和非线性划分.SVM中用到的核函数有线性核'linear'.多项式核函数pkf以及高斯核函数rbf. 当训练数据线性可分时,一般用线性核函数,直接实现 ...
- Python 调用 ES、Solr、Phoenix
#!/usr/bin/env python # -*- coding:utf-8 -*- # ************************************* # @Time : 2019/ ...
- Spring Boot 2.2.2.RELEASE 版本中文参考文档【3.2 - 3.10】
Spring Boot 2.2.2.RELEASE版本中文文档持续更新中~如有需要获取参考文档文件,关注公众号JavaSo,回复“参考文档”即可. 3.2 结构化代码 Spring Boot不需要任何 ...
- Can not find the tag library descriptor for “http://java.sun.com/jstl/core"
此文原博文地址:https://blog.csdn.net/kolamemo/article/details/51407467 按照查到的资料,JSTL taglib需要jstl.jar来支持.在1. ...
- SSM框架之spring(1)
spring(1) 1.spring概述 Spring是分层的 Java SE/EE应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP( ...
- ZooKeeper 面试题
高强度训练第二十一天总结 1. ZooKeeper 面试题 ZooKeeper 是一个开放源码的分布式协调服务,它是集群的管理者,监视着集群 中各个节点的状态根据节点提交的反馈进行下一步合理操作.最终 ...