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. FCN与U-Net语义分割算法

    FCN与U-Net语义分割算法 图像语义分割(Semantic Segmentation)是图像处理和是机器视觉技术中关于图像理解的重要一环,也是 AI 领域中一个重要的分支.语义分割即是对图像中每一 ...

  2. OSPF-三张表+路由器角色+router-id

    验证理论: 1.剖析OSPF的三张表:邻居表,拓扑表,路由表 dis ospf peer brief dis ospf lsdb dis ip routing-table protocol ospf ...

  3. .NET平台系列18 .NET5的超强优势

    系列目录     [已更新最新开发文章,点击查看详细] 支持所有 .NET 应用程序类型 .NET5 统一版本之后将支持所有 .NET 应用程序类型:Xamarin.ASP.NET.IoT 和桌面.此 ...

  4. 【NX二次开发】uf5945获得旋转矩阵、uf5947根据变换矩阵移动或复制对象

    返回一个矩阵,可以绕任意轴旋转. 与uf5947结合可以将对象沿着任意轴进行旋转.不是所有对象都能用uf5947变换,带参的实体.部件都不可以用此函数变换.下面是旋转WCS的例子. extern Dl ...

  5. ES6中的新数据类型——Symbol

    今天小编和大家来聊一聊es6中新增的一个原始数据类型Symbol.在es5中原始数据类型(基本数据类型)有以下六种:Undefind.Null.Bool. String.Number.Object.今 ...

  6. centos 7 增加一块硬盘的步骤

    1) 增加一块硬盘 1G [到设置中添加一块硬盘即可] 2) 重启一下服务器 lsblk -f 查看硬盘 3) 给 sdb 硬盘分区 fdisk /dev/sdb n,p,enter,enter,w ...

  7. ES2015+ 常用新特性一口气看个够

    ES2015 也叫 ES6,区别只是一个是以发布的年份来命名,一个是以版本号来命名 从那以后组织每年都会发布一个新版本,根据这个规则,ES2016 === ES7... ES2020 === ES11 ...

  8. es6 快速入门 系列 —— promise

    其他章节请看: es6 快速入门 系列 Promise Promise 是一种异步编程的选择 初步认识Promise 用 Promise 来实现这样一个功能:发送一个 ajax,返回后输出 json ...

  9. hackthebox TheNotebook

    前言 只拿到了user,提权没成功--有wp说是CVE-2019-5736,我没打成. 打点 nmap-sV -v -A 10.10.10.230 端口扫描结果: PORT STATE SERVICE ...

  10. npm ERR! Unexpected end of JSON input while parsing near '...'解决方法

    npm install时出现npm err! Unexpected end of JSON input while parsing near'...'错误 输入  npm cache clean -- ...