记一次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 ...
随机推荐
- 华为hcnp r&s考试一共有三门,R&S-IERS,R&S-IENP,R&S-IEEP
大纲1.HCNP-R&S-IEEP V2.0考试大纲 考试内容:HCNP-R&S-IEEP考试覆盖对企业网络的规划.设计.实施.维护.排障.优化以及网络割接等. 知识点: 网络规划:1 ...
- SQL Server 索引分析开关
set statistics io onset statistics profile on
- 巧妙利用label标签实现input file上传文件自定义样式
提到上传文件,一般会想到用input file属性来实现,简单便捷,一行代码即可 但input file原生提供的默认样式大多情况下都不符合需求,且在不同浏览器上呈现的样式也不尽相同 我们往 ...
- JavPlayer:AI破坏马赛克,大量马赛克破坏版影片流出
这是最近几个月业界讨论比较火的话题,发酵到现在, 终于可以给大家总结下最近的马赛克破坏版影片到底是怎么回事? 马赛克破坏版,简单讲就是利用AI技术,在打有马赛克影片的马赛克基础上进行修复操作, 来实现 ...
- goroutine,channel
Go语言中有个概念叫做goroutine, 这类似我们熟知的线程,但是更轻. 以下的程序,我们串行地去执行两次loop函数: package main import "fmt" f ...
- .NET Core 实现 腾讯云云解析简单客户端
一.说明 腾讯云的.NET SDK虽然非常强大,但是对他的产品支持不是很完全,域名的云解析就没有SDK,所以自己写了一个,初衷是用来做动态DNS的,也准备接入多个云厂商,但是我自己本身仅仅只有腾讯云这 ...
- Selenium(七):选择框(radio框、checkbox框、select框)
1. 选择框 本章使用的html代码: <!DOCTYPE html> <html lang="en"> <head> <meta cha ...
- Java生鲜电商平台-电商虚拟币的充值与消费思考
Java生鲜电商平台-电商虚拟币的充值与消费思考 项目背景 最近由于项目业务原因,需要为系统设计虚拟币的充值及消费功能.公司内已经有成熟的支付网关服务,所以重点变成了如何设计项目内虚拟币的充值流程,让 ...
- Place an Action in a Different Location 设置按钮的显示位置
In this lesson, you will learn how to place an Action in the required place. For this purpose, the C ...
- 【Angular】学习笔记-环境部署、项目建立相关
Angular官网>搭建环境 首先要安装Node.js.官网>Download 一直next就好了. 安装node.js的目的是使用npm这些命令啦 然后这里推荐下载使用git SCM 也 ...