用户每次访问后台的时候,如果是一些需要认证的链接,都需要识别用户身份,比如用户抢单、用户中心等,在传统项目中用的是Session,但在微服务中不建议使用Session,使用JWT令牌。

1. 初识JWT

JWT简称JSON Web Token ,也就是通过json形式作为web应用的令牌,用在各方之间安全的将信息作为json对象传输,在数据传输过程中还可以完成数据加密,签名相关处理 。

JWT令牌作用:

  • 身份授权:这是使用jwt的最常见方案,一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该领牌允许的路由,服务和资源,单点登录是的当今广泛使用JWT的一项功能,因为他的开销很小,并且可以在不同的域中使用。

  • 信息交换:JWT令牌是在各方面之间安全地传输信息的好方法,因为可以对JWT进行签名(例如,使用公钥/私钥对),所以您可以确保发件人是他们所说的人,此外由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否遭到篡改。

JWT令牌认证流程:

JWT令牌鉴权流程如上图:

1:用户携带账号密码登录
2:登录通过后,后端服务会封装账号信息,并且采用指定算法进行加密,并将加密后的密文(令牌)返回客户端
3:客户端拿到令牌后,将令牌保存到本地,可以使Cookie,也可以是localStoreage
4:客户端每次发起请求的时候,会将本地令牌写到到请求头中,一起传到后台
5:后台在微服务网关中校验令牌是否正确,如果正确再执行权限校验
6:令牌校验通过、权限校验通过,则执行用户要操作的业务流程
7:令牌校验失败或者权限校验失败,则提示错误信息

JWT令牌校验优势:

1:简洁(Compact):可以通过URL,POST参数或者在HTTP header中发送,因为数据量小,所以传输的速度也快2:自包含(Self-contained): 负载中包含了所有用户所需的信息,避免了多次查询数据库3:因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何Web形式都支持4:不要再服务端保存信息,特别适用于分布式微服务

2. JWT令牌结构

JWT令牌组成有3部分,分别为HeaderPayloadSignature,将三部分组合就是标准的JWT令牌了,三部分组合通常以"."链接,如下:

HJLDISNDSSDYIREWREWRDFDSDSFBH.DSFTIHGNDSFSDREWREWRDFDSFDSFRDNSJDJ.DSVDSFEWEREREWREWRDFDS

Header:

1:通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法,例如HMAC,SHA265或RSA,它会使用Base64编码组成JWT结构的一部分2:注意: Base64是一种编码,也就是说,他是可以被翻译回原来的样子的,他并不是一种加密的过程3:{"alg":"HS365", "typ":"JWT"}

Payload:

1:令牌的第二部分是有效负载,其中包含声明,声明有关实体(通常是指用户)和其他数据的声明。同样的他会使用Base64编码组成JWT结构的第二部分2:{"sub":"12334798", "name":"John Doe", "admin":true}

Signature:

1:前面两部分都是使用Base64进行编码的,即前端可以解开知道里面的信息,Signature需要使用编码后的Header和Payload以及我们提供的一个密钥,然后使用Header中指定的签名算法(HS256)进行签名,没有被篡改过:2:如:HMACSHA256(base64UrlEncode(header)+"."+base64UrlEncode(payLoad).secret)

为什么要签名?

最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被篡改,如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合成新的JWT的话,那么服务器会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的,如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

JWT令牌存在数据安全隐患?

JWT令牌采用了Base64加密,Base64加密数据可以直接解密,因此我们在JWT令牌中尽量不要传输敏感信息,如果非要传输敏感信息,可以把敏感信息进行加密操作,比如AES加密。

3. JWT令牌实现

JWT令牌使用参考地址:https://github.com/auth0/java-jwt

在创建JWT令牌的时候,会有很多属性需要填写,关于JWT令牌中一些属性,我们说明一下:

iss: jwt签发者
sub: 主题
aud: 接收jwt的一方
exp: jwt的过期时间,这个过期时间必须要大于签发时间
nbf: 定义在什么时间之前,该jwt都是不可用的.
iat: jwt的签发时间
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

首先在我们的项目中引入相关依赖:

<!--JWT令牌-->
<dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>3.12.1</version>
</dependency>

然后编写相关工具类用来创建Token令牌和校验。

public class JwtToken {//默认秘钥private static final String DEFAULT_SECRET = "springcloudalibaba";/**** 生成令牌* @param dataMap* @return*/public static String createToken(Map<String, Object> dataMap) {return createToken(dataMap, null);}/**** 生成令牌* @return*/public static String createToken(Map<String, Object> dataMap, String secret) {//秘钥为空就采用默认秘钥if (StringUtils.isEmpty(secret)) {secret = DEFAULT_SECRET;}//创建令牌操作算法Algorithm algorithm = Algorithm.HMAC256(secret);//创建令牌return JWT.create().withClaim("body", dataMap).withIssuer("black jack")            //JWT签发者.withSubject("JWT令牌")       //主题.withAudience("member")      //接收JWT的一方.withExpiresAt(new Date(System.currentTimeMillis() + 3600000))    //过期时间.withNotBefore(new Date(System.currentTimeMillis()))      //指定时间之前JWT令牌是不可用的.withIssuedAt(new Date())    //JWT签发时间.withJWTId(UUID.randomUUID().toString().replace("-", "")) // jwt唯一标识.sign(algorithm);}/**** 解析令牌* @param token* @return*/public static Map<String, Object> parseToken(String token) {return parseToken(token, null);}/**** 令牌校验并解析* @param token* @return*/public static Map<String, Object> parseToken(String token, String secret) {//秘钥为空就采用默认秘钥if (StringUtils.isEmpty(secret)) {secret = DEFAULT_SECRET;}//确认签名算法Algorithm algorithm = Algorithm.HMAC256(secret);//创建令牌校验对象JWTVerifier verifier = JWT.require(algorithm).build(); //Reusable verifier instance//校验解析DecodedJWT jwt = verifier.verify(token);return jwt.getClaim("body").as(Map.class);}/**** 令牌校验解析* @param args*/public static void main(String[] args) throws Exception {Map<String, Object> dataMap = new HashMap<>();dataMap.put("name", "black jack");dataMap.put("age", "26");dataMap.put("address", "南京市");//创建令牌String token = createToken(dataMap);System.out.println(token);//休眠1秒Thread.sleep(1000);//解析令牌Map<String, Object> resultMap = parseToken(token);System.out.println(resultMap);}}

测试结果如下:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJKV1Tku6TniYwiLCJhdWQiOiJtZW1iZXIiLCJuYmYiOjE2Mzk4MDcwNDUsImlzcyI6ImJsYWNrIGphY2siLCJib2R5Ijp7ImFkZHJlc3MiOiLljZfkuqzluIIiLCJuYW1lIjoiYmxhY2sgamFjayIsImFnZSI6IjI2In0sImV4cCI6MTYzOTgxMDY0NSwiaWF0IjoxNjM5ODA3MDQ1LCJqdGkiOiI0MjI2NjgwMTQzNmI0NTQ4OTc0YmUzZTFjZWM3NWYwOSJ9.IO6jV89x8y5sn8mQRE8SZLwwnWyrUiA_Jsl1mzCss2w{address=南京市, name=black jack, age=26}

4. 实战

4.1 令牌颁发

该部分代码直接通过mybatis plus 框架生成相关代码:

整体流程就是用户登陆后,后台根据用户信息封装token返回给客户端。

  • 用户实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "user_info")
public class UserInfo {@TableId(type = IdType.ASSIGN_ID)private String username;private String password;private String phone;private String name;private Integer points;private String roles;
}
  • Dao
public interface UserInfoMapper extends BaseMapper<UserInfo> {}
  • Service
public interface UserInfoService extends IService<UserInfo> {}
@Service
public class UserInfoServiceImpl extends ServiceImpl<UserInfoMapper, UserInfo> implements UserInfoService {}
  • controller
@RestController
@RequestMapping(value = "/user/info")
public class UserInfoController {@Autowiredprivate UserInfoService userInfoService;/***** 登录*/@PostMapping(value = "/login")public RespResult<String> login(@RequestParam String username,@RequestParam String pwd){//登录UserInfo userInfo = userInfoService.getById(username);if(userInfo!=null){//匹配密码是否一致if(userInfo.getPassword().equals(pwd)){//封装用户信息实现加密Map<String,Object> dataMap = new HashMap<String,Object>();dataMap.put("username",userInfo.getUsername());dataMap.put("name",userInfo.getName());dataMap.put("roles",userInfo.getRoles());//创建令牌String token = JwtToken.createToken(dataMap);return RespResult.ok(token);}//账号密码不匹配return RespResult.error("账号或者密码错误");}return RespResult.error("账号不存在");}
}

4.2 令牌安全

我们前面说过,令牌数据不安全,其实除了令牌数据不安全之外,令牌还存在被盗用的风险,例如:

1:张三登录获得令牌 zzz
2:李四盗取了张三的令牌 zzz,并用令牌zzz直接访问后台

上面例子执行,后台只要能识别令牌,是不会拒绝zzz令牌的,这时候就存在盗用风险。

令牌盗用该如何解决?

如果令牌被盗,我们可以通过IP识别令牌是否安全,如上图:

1:每次生成令牌的时候,把用户的IP作为令牌的一部分进行MD5加密,并将密文存入到令牌中
2:用户每次访问API接口的时候,都先获取客户端IP,再将IP进行MD5加密,并和令牌中的IP密文比对
3:如果密文一致,则证明IP没有发生变化,如果密文不一致,则证明IP发生变化,提示重新登录

这种操作在很多大厂中都有应用,平时登录QQ、微信的时候会提示设备终端发生变化,其实和上图操作是一个道理。

4.2.1 令牌封装

在登录接口中对IP地址进行封装:

@PostMapping(value = "/login")
public RespResult<String> login(@RequestParam String username, @RequestParam String pwd, HttpServletRequest request) throws Exception {//登录UserInfo userInfo = userInfoService.getById(username);if (userInfo != null) {//匹配密码是否一致if (userInfo.getPassword().equals(pwd)) {//封装用户信息实现加密Map<String, Object> dataMap = new HashMap<>();dataMap.put("username", userInfo.getUsername());dataMap.put("name", userInfo.getName());dataMap.put("roles", userInfo.getRoles());//获取IPString ip = IPUtils.getIpAddr(request);dataMap.put("ip", MD5.md5(ip));//创建令牌String token = JwtToken.createToken(dataMap);return RespResult.ok(token);}//账号密码不匹配return RespResult.error("账号或者密码错误");}return RespResult.error("账号不存在");
}

工具类如下:

public class IPUtils {/*** 获取用户真实IP地址,不使用request.getRemoteAddr()的原因是有可能用户使用了代理软件方式避免真实IP地址,* 可是,如果通过了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP值** @return ip*/public static String getIpAddr(HttpServletRequest request) {String ip = request.getHeader("x-forwarded-for");if (ip != null && ip.length() != 0 && !"unknown".equalsIgnoreCase(ip)) {// 多次反向代理后会有多个ip值,第一个ip才是真实ipif (ip.indexOf(",") != -1) {ip = ip.split(",")[0];}}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_CLIENT_IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("HTTP_X_FORWARDED_FOR");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("X-Real-IP");}if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return ip;}
}

4.2.2 令牌安全校验

public class AuthorizationInterceptor {/**** 令牌解析*/public static Map<String, Object> jwtVerify(String token, String clientIp) {try {//token解析Map<String, Object> resultMap = JwtToken.parseToken(token);//令牌中的IPString jwtip = resultMap.get("ip").toString();//IP校验clientIp = MD5.md5(clientIp);if (clientIp.equals(jwtip)) {return resultMap;}} catch (Exception e) {e.printStackTrace();}return null;}
}

如下是关键代码:

首先获取客户端IP地址,然后获取客户端传过来的token信息,并进行比较。

//校验其他地址
String clientIp = IPUtil.getIp(request);
//获取令牌
String token = request.getHeaders().getFirst("authorization");
//令牌校验
Map<String, Object> resultMap = AuthorizationInterceptor.jwtVerify(token, clientIp);
if (resultMap == null) {endProcess(exchange, 401, "no token");
}

一篇文章让你学会JWT令牌认证相关推荐

  1. 一篇文章教你学会使用SpringBatch 监听器Listener

    文章目录 一.SpringBatch监听器 二.搭建SpringBatch开发环境 三.监听器详细介绍 1.JobExecutionListener 2.StepExecutionListener 3 ...

  2. 一篇文章教你学会使用SpringBoot实现文件上传和下载

    文章目录 一.搭建SpringBoot开发环境 1.创建项目 2.配置application.properties参数 3.实体响应类和异常信息类 4.创建FileController 二.接口测试 ...

  3. .NET Core + JWT令牌认证 + Vue.js 通用动态权限(RBAC)管理系统框架[DncZeus]开源啦!!!...

    DncZeus 前言 关于 DncZeus DncZeus = Dnc + Zeus "Dnc"--.Net Core 的缩写: "Zeus"--中文译为宙斯, ...

  4. 一篇文章教你学会实现模糊搜索结果的关键词高亮显示

    一篇文章教你学会实现模糊搜索结果的关键词高亮显示 话不多说,先看效果图: 代码如下: <!DOCTYPE html> <html lang="en">< ...

  5. 一篇文章教你学会如何使用CSS中的雪碧图(CSS Sprite)

    一篇文章教你学会如何使用CSS中的雪碧图(CSS Sprite) 一.什么是雪碧图? 雪碧图(CSS Sprite)又叫CSS精灵图,是一种网页图片应用处理方式,他允许你将一个页面设计到 所有零星图片 ...

  6. 什么是JWT令牌认证?

    当下,JWT(JSON Web Token)令牌认证已经变得越来越流行.本文主要介绍JWT令牌认证与传统的Session会话认证机制的区别. 为什么需要认证? HTTP是一种无状态协议,那就意味着当前 ...

  7. 开源干货!.NET Core + JWT令牌认证 + Vue.js通用动态权限(RBAC)管理系统框架[DncZeus]开源

    DncZeus 前言 关于 DncZeus DncZeus = Dnc + Zeus "Dnc"–.Net Core 的缩写: "Zeus"–中文译为宙斯,是古 ...

  8. 一篇文章教你学会Java泛型

    文章目录 一.原生态类型 1.什么是原生态类型 2.使用原生态类型 有什么不好 3.泛型的子类型规则 4.泛型的可擦除性 二.泛型常用形式 1.泛型方法 2.泛型单例工厂 三.有限制的通配符类型 四. ...

  9. html网页怎么向文章,一篇文章教你学会HTML

    html是学习做网页的基础,漂亮的网页与布局就是由有些html代码组成,大家看完这篇文章就可以简单的了解html了,多写多练. 如果你不致力于成为美工的话,那么作为开发人员,可以读懂HTML.必要时能 ...

  10. canvas 圆角矩形填充_一篇文章让你学会你最“害怕”的Canvas,太有意思了

    Canvas画布 基本用法 <canvas id='canvas' width="150" height="150"></canvas> ...

最新文章

  1. 谷歌最新提出无需卷积、注意力,纯MLP构成的视觉架构!网友:MLP is All You Need?
  2. ACL 2019 知识图谱的全方位总结
  3. 风险管理、收尾管理和知识产品管理
  4. 计算机专业英语06章在线测试,《计算机专业英语》第06章在线测试.doc
  5. HEVC-CABAC
  6. catv系统主要有哪三部分组成_光纤放大器在DWDM传输系统中的应用
  7. Android junit单元测试
  8. java保护表格_读密码保护的工作表(版本 - Excel中95,97-2003)的Java
  9. C# 使用PictureBox控件--点击切换图片
  10. 今天小小的总结一下最近的小程序中的问题
  11. 打造一流云计算机房,【迈向“双一流”】为科研插上云计算的“翅膀”
  12. 记录|深度学习100例-卷积神经网络(CNN)minist数字分类 | 第1天
  13. 浅谈IDEA Scratch files万能的临时文件功能
  14. java爬取国家统计局省市县及编码
  15. HTML5 CSS3做的一个静态的苹果官网首页
  16. Cousera- software security
  17. 生产任务计划单,金蝶KIS旗舰版专业版K3WISE,生产管理软件ERP,金蝶生产任务管理,生产计划管理,自动下推生产领料单
  18. linux毁了所有数据,小心!数据在你不注意的时候居然偷偷损毁了
  19. vivado 数码管学习(二)数码管显示一位数字和显示八位数字
  20. 环境监测设备中HCL190FLAS擦写问题定位——BOOT

热门文章

  1. 快点射!(Swift Injection) 不带这样的!
  2. 俄罗斯计算机水平_从四点到三十二点。 俄罗斯计算机和网络的早期
  3. matlab 跳步循环,足球训练:每天10分钟挑战7天球感训练
  4. 《机器学习实战》笔记-介绍
  5. Caterpillar CAT SIS卡特彼勒最新零件目录系统+维修信息
  6. 阅读分享——李开复老师《开工愉快:如何保持每天精力充沛》
  7. 红帽舍弃 KDE 桌面;暴雪与网易共同研发 “暗黑破坏神”手游
  8. Java学习笔记(17)
  9. 新浪微博模拟登陆passwd参数rsa解密
  10. 关于word中的DDE如何查看