JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案,本文介绍它的原理和用法

一、跨域认证遇到的问题

由于多终端的出现,很多的站点通过 web api restful 的形式对外提供服务,采用了前后端分离模式进行开发,因而在身份验证的方式上可能与传统的基于 cookie 的 Session Id 的做法有所不同,除了面临跨域提交 cookie 的问题外,更重要的是,有些终端可能根本不支持 cookie。

JWT(JSON Web Token) 是一种身份验证及授权方案,简单的说就是调用端调用 api 时,附带上一个由 api 端颁发的 token,以此来验证调用者的授权信息。

一般流程是下面这样:

1. 用户向服务器发送用户名和密码。

2. 服务器验证通过后,在当前对话(session)里面保存相关数据,比如用户角色、登录时间等等。

3. 服务器向用户返回一个 session_id,写入用户的 Cookie。

4. 用户随后的每一次请求,都会通过 Cookie,将 session_id 传回服务器。

5. 服务器收到 session_id,找到前期保存的数据,由此得知用户的身份。

这种模式的问题在于扩展性不好。单机没有问题,如果是服务器集群、跨域的服务导向架构或者用户禁用了 cookie ,就不行了。

二、解决方案

1. 单机和分布式应用下登录校验,session 共享

场景一:单机和多节点 tomcat 应用登录检验

①、单机 tomcat 应用登录,sesssion 保存在浏览器和应用服务器会话之间,用户登录成功后,服务端会保证一个 session,也会给客户端一个 sessionId,客户端会把 sessionId 保存在 cookie 中,用户每次请求都会携带这个 sessionId。

②、多节点 tomcat 应用登录,开启 session 数据共享后,每台服务器都能够读取 session。缺点是每个 session 都是占用内存和资源的,每个服务器节点都需要同步用户的数据,即一个数据需要存储多份到每个服务器,当用户量到达百万、千万级别的时,占用资源就严重,用户体验特别不好!!

场景二:分布式应用中 session 共享

①、真实的应用不可能单节点部署,所以就有个多节点登录 session 共享的问题需要解决。tomcat 支持 session 共享,但是有广播风暴;用户量大的时候,占用资源就严重,不推荐

②、Reids 集群,存储登陆的 token,向外提供服务接口,Redis 可设置过期时间(服务端使用 UUID生成随机 64 位或者 128 位 token ,放入 Redis 中,然后返回给客户端并存储)。

③、用户第一次登录成功时,需要先自行生成 token,然后将 token 返回到浏览器并存储在 cookie 中,

并在 Redis 服务器上以 token 为 key,用户信息作为 value 保存。后续用户再操作,可以通过 HttpServletRequest 对象直接读取 cookie 中的 token,并在 Redis 中取得相对应的用户数据进行比较(用户每次访问都携带此 token,服务端去 Redis 中校验是否有此用户即可)。

④、 缺点:必须部署 Redis,每次必须访问 Redis,IO 开销特别大。

2. 最终解决方案:使用 JWT 实现 Token 认证

JWT 的原理

服务器认证以后,生成一个 JSON 对象发回给用户,以后用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候,会加上签名。也就是说服务器就不保存任何 session 数据了,即服务器变成无状态了,从而比较容易实现扩展。

简单来说,就是通过一定规范来生成 token,然后可以通过解密算法逆向解密 token,这样就可以获取用户信息

优点和缺点

优点:生产的 token 可以包含基本信息,比如 id、用户昵称、头像等信息,避免再次查库;存储在客户端,不占用服务端的内存资源

缺点:token 是经过 base64 编码,所以可以解码,因此 token 加密前的对象不应该包含敏感信息(如用户权限,密码等)

JWT 格式组成:头部+负载+签名 ( header + payload + signature )

头部:主要是描述签名算法。

负载:主要描述是加密对象的信息,如用户的 id 等,也可以加些规范里面的东西,如 iss 签发者,exp 过期时间,sub 面向的用户。

签名:主要是把前面两部分进行加密,防止别人拿到 token 进行base 解密后篡改 token。

入门代码案例:

第一:导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.bilibili</groupId>
<artifactId>token</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>token</name>
<description>Demo project for Spring Boot</description> <properties>
<java.version>1.8</java.version>
</properties> <dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency> <dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency> <dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency> <dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.7.0</version>
</dependency> <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies> <build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build> </project>

第二步:写一个实体类

package com.bilibili.pojo;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString; import java.io.Serializable;
import java.util.Date; public class User implements Serializable {
private Integer id;
private String openid;
private String name;
private String headImg;
private String phone;
private String sign;
private Integer sex;
private String city;
private Date createTime; public User() {
} public User(Integer id, String openid, String name, String headImg, String phone, String sign, Integer sex, String city, Date createTime) {
this.id = id;
this.openid = openid;
this.name = name;
this.headImg = headImg;
this.phone = phone;
this.sign = sign;
this.sex = sex;
this.city = city;
this.createTime = createTime;
} public Integer getId() {
return id;
} public void setId(Integer id) {
this.id = id;
} public String getOpenid() {
return openid;
} public void setOpenid(String openid) {
this.openid = openid;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public String getHeadImg() {
return headImg;
} public void setHeadImg(String headImg) {
this.headImg = headImg;
} public String getPhone() {
return phone;
} public void setPhone(String phone) {
this.phone = phone;
} public String getSign() {
return sign;
} public void setSign(String sign) {
this.sign = sign;
} public Integer getSex() {
return sex;
} public void setSex(Integer sex) {
this.sex = sex;
} public String getCity() {
return city;
} public void setCity(String city) {
this.city = city;
} public Date getCreateTime() {
return createTime;
} public void setCreateTime(Date createTime) {
this.createTime = createTime;
} @Override
public String toString() {
return "User{" +
"id=" + id +
", openid='" + openid + '\'' +
", name='" + name + '\'' +
", headImg='" + headImg + '\'' +
", phone='" + phone + '\'' +
", sign='" + sign + '\'' +
", sex=" + sex +
", city='" + city + '\'' +
", createTime=" + createTime +
'}';
}
}

第三步:写一个JwtUtils工具类

package com.bilibili.utils;

import com.bilibili.pojo.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm; import java.util.Date; public class JwtUtils {
public static final String SUBJECT = "RookieLi";
public static final String SECRETKEY = "Rookie666"; // 密钥
public static final long EXPIRE = 1000 * 60 * 60 * 24 * 7; //过期时间,毫秒,一周 public static String getJsonWebToken(User user) {
if(user == null || user.getId() == null || user.getName() == null || user.getHeadImg() == null) {
return null;
}
String token = Jwts.builder()
.setSubject(SUBJECT)
.claim("id", user.getId())
.claim("name", user.getName())
.claim("img", user.getHeadImg())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRE))
.signWith(SignatureAlgorithm.HS256, SECRETKEY).compact();
return token;
} public static Claims chekcJwt(String token) {
try {
final Claims claims = Jwts.parser().setSigningKey(SECRETKEY).parseClaimsJws(token).getBody();
return claims;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

第四步:测试一下

package com.bilibili.token;

import com.bilibili.pojo.User;
import com.bilibili.utils.JwtUtils;
import io.jsonwebtoken.Claims;
import org.junit.Test; public class JwtUtilTest {
@Test
public void testGeneJwt() {
User user = new User();
user.setId(999);
user.setHeadImg("I'm busy");
user.setName("Rookie");
String token = JwtUtils.getJsonWebToken(user);
System.out.println(token);
} @Test
public void testCheck() {
// 下面此 token 字符串是上面的结果生成的,每次不一样,不是写死的
String token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJSb29raWVMaSIsImlkIjo5OTksIm5hbWUiOiJSb29raWUiLCJpbWciOiJJJ20gYnVzeSIsImlhdCI6MTU5OTcyMzg1NiwiZXhwIjoxNjAwMzI4NjU2fQ.b83hvcgTClyGBnRipUMrvkZUgHPCe_jVfowMbMA__rk";
Claims claims = JwtUtils.chekcJwt(token);
if (claims != null) {
String name = (String) claims.get("name");
String img = (String) claims.get("img");
int id = (Integer) claims.get("id");
System.out.println(name);
System.out.println(img);
System.out.println(id);
} else {
System.out.println("非法token");
}
}
}

此例子非常适合入门Token学习,希望能够先了解token的相关概念,然后上手写一下上面的例子。这样会对token有更进一步认识

入门 - SpringBoot 2.x 使用 JWT的更多相关文章

  1. 玩转 SpringBoot 2 之整合 JWT 下篇

    前言 在<玩转 SpringBoot 2 之整合 JWT 上篇> 中介绍了关于 JWT 相关概念和JWT 基本使用的操作方式.本文为 SpringBoot 整合 JWT 的下篇,通过解决 ...

  2. 玩转 SpringBoot 2 之整合 JWT 上篇

    前言 该文主要带你了解什么是 JWT,以及JWT 定义和先关概念的介绍,并通过简单Demo 带你了解如何使用 SpringBoot 2 整合 JWT. 介绍前在这里我们来探讨一下如何学习一门新的技术, ...

  3. springboot + 注解 + 拦截器 + JWT 实现角色权限控制

    1.关于JWT,参考: (1)10分钟了解JSON Web令牌(JWT) (2)认识JWT (3)基于jwt的token验证 2.JWT的JAVA实现 Java中对JWT的支持可以考虑使用JJWT开源 ...

  4. springBoot整合spring security+JWT实现单点登录与权限管理--筑基中期

    写在前面 在前一篇文章当中,我们介绍了springBoot整合spring security单体应用版,在这篇文章当中,我将介绍springBoot整合spring secury+JWT实现单点登录与 ...

  5. springboot、springsecurity、jwt权限验证

    1.背景 基于前后端分离项目的后端模块: 2.相关技术 springboot全家桶 web模块 security模块:用于权限的验证 mongodb 模块:集成mogodb模块 jwt 用于token ...

  6. 搭建一个入门springboot工程

    springboot工程搭建(入门案例) 第一步:创建maven工程 第二步:设置项目信息 第三步:默认项目名称,不用改动(第二步已填写)  第三步:在pom.xml中导入依赖 SpringBoot要 ...

  7. (入门SpringBoot)SpringBoot来临(一)

    .创建独立的Spring应用程序. .嵌入tomcat,Jetty或者Undertow,无需部署war文件; .允许通过Maven来获取starter; .尽可能的自动配置Spring. .提供生产就 ...

  8. SpringBoot整合SpringSecurity实现JWT认证

    目录 前言 目录 1.创建SpringBoot工程 2.导入SpringSecurity与JWT的相关依赖 3.定义SpringSecurity需要的基础处理类 4. 构建JWT token工具类 5 ...

  9. (入门SpringBoot)SpringBoot发送邮件(十一)

    SpringBoot配置邮件服务: 1.引入jar <!-- 邮件 --> <dependency>    <groupId>org.springframework ...

随机推荐

  1. i.MX6UL: i.MX 6UltraLite处理器 - 低功耗,安全,Arm® Cortex®-A7内核

    i.MX6UL: i.MX 6UltraLite处理器 - 低功耗,安全,Arm Cortex-A7内核 概述 MX6UltraLite作为i.MX6系列的扩展,一系列高性能.超高效的处理器,采用先进 ...

  2. 自定义 DataLoader

    自定义 DataLoader 如 数据输入 一文所介绍,OneFlow 支持两种数据加载方式:直接使用 NumPy 数据或者使用 DataLoader 及其相关算子. 在大型工业场景下,数据加载容易成 ...

  3. PyTorch 进行 Neural-Transfer

    PyTorch 进行 Neural-Transfer 1.简介 本文讲解如何实现由 Leon A. Gatys,Alexander S. Ecker和Matthias Bethge提出的Neural- ...

  4. YOLO、SSD、FPN、Mask-RCNN检测模型对比

    YOLO.SSD.FPN.Mask-RCNN检测模型对比 一.YOLO(you only look once) YOLO 属于回归系列的目标检测方法,与滑窗和后续区域划分的检测方法不同,他把检测任务当 ...

  5. 小白自制Linux开发板 一. 瞎抄原理图与乱画PCB

    因为墨云是基于高中物理水平的电路知识来学习.而且此前也就玩过树莓派.Esp8266之类的开发板,水平基础趋近于零,所以在写这个系列的时候抱着记录的心态.还望不足之处还望大佬们指正. <论语> ...

  6. 虹软人脸识别SDK接入Milvus实现海量人脸快速检索

    一.背景 人脸识别是近年来最热门的计算机视觉领域的应用之一,而且现在已经出现了非常多的人脸识别算法,如:DeepID.FaceNet.DeepFace等等.人脸识别被广泛应用于景区.客运.酒店.办公室 ...

  7. spring——自动装配【非常详细】

    什么是自动装配? 自动装配就是让应用程序上下文为你找出依赖项的过程.说的通俗一点,就是Spring会在上下文中自动查找,并自动给bean装配与其关联的属性! spring中实现自动装配的方式有两种,一 ...

  8. 【NX二次开发】PMI线性标注

    PMI线性标注,二次开发的难点在于控制尺寸的位置,多花点儿时间都能搞出来,想走捷径最下面就是源码. 只需要摆好工作坐标,然后指定你要标注尺寸的两个点,就可以很方便得利用这个封装函数做出你想要的PMI. ...

  9. 「模拟8.19 A嚎叫..(set) B主仆..(DFS) C征程..(DP+堆优化)」

    为啥这一套题目背景感到很熟悉. T1  嚎叫响彻在贪婪的厂房 考试一个小时没调出来,自闭了.......... 正解很好想,最后实在打不出来了只好暴力骗分了... 联想到以前做的题:序列(涉及质因数分 ...

  10. 【Python报错】ValueError: If using all scalar values, you must pass an index

    问题: 1.ValueError: If using all scalar values, you must pass an index.意思是:如果使用所有标量值,则必须传递索引 2.再看错误发生代 ...