一、什么是JWT
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC
7519).定义了一种简洁的,自包含的方法用于通信双方之间以JSON对象的形式安全的传递信息。因为数字签名的存在,这些信息是可信的,JWT可以使用HMAC算法或者是RSA的公私秘钥对进行签名。
二、为什么要使用JWT
1.传统的session认证
http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。
- session缺点
基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户
Session方式存储用户id的最大弊病在于要占用大量服务器内存,对于较大型应用而言可能还要保存许多的状态。
- 基于session认证暴露的问题
Session需要在服务器保存,暂用资源
扩展性 session认证保存在内存中 ,无法扩展到其他机器中
CSRF 基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
2.基于token的鉴权机制
基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。
JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。除了用户id之外,还可以存储其他的和用户相关的信息,例如用户角色,用户性别等。
优点
- 简洁(Compact): 可以通过URL,POST参数或者在HTTP header发送,因为数据量小,传输速度也很快
- 自包含(Self-contained):负载中包含了所有用户所需要的信息,避免了多次查询数据库
- 因为Token是以JSON加密的形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持。
- 不需要在服务端保存会话信息,特别适用于分布式微服务。
三、JWT的结构
JWT是由三段信息构成的,将这三段信息文本用.连接一起就构成了JWT字符串。
就像这样:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
四、JWT的组成
JWT含有三个部分:
- 头部(header)
- 载荷(payload)
- 签证(signature)
1. 头部(header)
头部一般有两部分信息:类型、加密的算法(通常使用HMAC SHA256)
头部一般使用base64加密:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
解密后:
{
"typ":"JWT",
"alg":"HS256"
}
2. 载荷(payload)
该部分一般存放一些有效的信息。JWT的标准定义包含五个字段:
- iss:该JWT的签发者
- sub:该JWT所面向的用户
- aud:接收该JWT的一方
- exp(expires):什么时候过期,这里是一个Unit的时间戳
- iat(issued at):在什么时候签发的
3. 签证(signature)
JWT最后一个部分。该部分是使用了HS256加密后的数据;包含三个部分:
- header(base64后的)
- payload(base64后的)
- secret 私钥
secret是保存在服务器端的,JWT的签发生成也是在服务器端的,secret就是用来进行JWT的签发和JWT的验证,所以,它就是你服务端的秘钥,在任何场景都不应该流露出去。一旦客户端得知这个secret,那就意味着客户端可以自我签发JWT了。
五、JWT的使用
在身份鉴定的实现中,传统的方法是在服务端存储一个 session,给客户端返回一个 cookie,而使用JWT之后,当用户使用它的认证信息登录系统之后,会返回给用户一个JWT, 用户只需要本地保存该 token(通常使用localStorage,也可以使用cookie)即可。
当用户希望访问一个受保护的路由或者资源的时候,通常应该在 Authorization 头部使用 Bearer 模式添加JWT,其内容格式:
Authorization: Bearer <token>
因为用户的状态在服务端内容中是不存储的,所以这是一种无状态的认证机制。服务端的保护路由将会检查请求头 Authorization 中的JWT信息,如果合法,则允许用户的行为。由于JWT是 自包含的,因此,减少了需要查询数据库的需要。
JWT的这些特征使得我们可以完全依赖无状态的特性提供数据API服务。因为JWT并不使用Cookie的,所以你可以在任何域名提供你的API服务而不需要担心跨域资源共享问题(CORS)
六、流程
请求流程
- 用户使用账号和密码发出POST登录请求;
- 服务器使用私钥创建一个JWT;
- 服务器返回这个JWT给浏览器;
- 浏览器将该JWT串放在请求头中向服务器发送请求;
- 服务器验证该JWT;
- 返回响应的资源给浏览器。
应用流程
-
初次登录生成JWT流程图
-
用户访问资源流程图
七、SpringBoot + JWT 实现
一、项目结构
R0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzQzNjgxNzU1,size_16,color_FFFFFF,t_70)
二、pom.xml依赖
1. 引入JWT依赖
<!-- JWT token -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
2. 引入json jar包
<!-- json jar包-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.35</version>
</dependency>
3. 字符串工具类包
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
三、model包 创建配置信息的实体类、用户实体类
1. Audience 配置信息的实体类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@Component
public class Audience {
//代表这个JWT的接收对象,存入audience
private String clientId = "098f6bcd4621d373cade4e832627b4f6";
//密钥, 经过Base64加密, 可自行替换
private String base64Secret = "MDk4ZjZiY2Q0NjIxZDM3M2NhZGU0ZTgzMjYyN2I0ZjY=";
//JWT的签发主体,存入issuer
private String name = "restapiuser";
//过期时间,时间戳
private int expiresSecond = 172800;
}
2. 用户实体类
import lombok.Data;
@Data
public class User {
//主键
private Integer id;
//账户
private String accountName;
//密码
private String password;
//用户姓名
private String userName;
public User(Integer id,String accountName,String password,String userName){
this.id = id;
this.accountName = accountName;
this.password = password;
this.userName = userName;
}
public User(){
}
}
四、annotation包 创建注解类
1. JwtIgnore 注解
import java.lang.annotation.*;
/**
* ========================
* JWT验证忽略注解
* Created with IntelliJ IDEA.
* Date:2002/6/5 9:50
* Version: v1.0
* ========================
*/
@Target({
ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JwtIgnore {
}
五、common包 创建、自定义异常、返回结果、结果常量类
1. exception包 CustomException 自定义异常类
import com.csdn.jwt.common.response.ResultCode;
import java.text.MessageFormat;
/**
* 自定义异常类型
* @author
**/
public class CustomException extends RuntimeException {
//错误代码
ResultCode resultCode;
public CustomException(ResultCode resultCode){
super(resultCode.message());
this.resultCode = resultCode;
}
public CustomException(ResultCode resultCode, Object... args){
super(resultCode.message());
String message = MessageFormat.format(resultCode.message(), args);
resultCode.setMessage(message);
this.resultCode = resultCode;
}
public ResultCode getResultCode(){
return resultCode;
}
}
2. response包 ResultCode 结果常量类
/**
* ========================
* 通用响应状态
* Created with IntelliJ IDEA.
* User:pyy
* Date:2020/6/5
* Time:10:10
* Version: v1.0
* ========================
*/
public enum ResultCode {
/* 成功状态码 */
SUCCESS(200,"操作成功!"),
/* 错误状态码 */
FAIL(-1,"操作失败!"),
/* 参数错误:10001-19999 */
PARAM_IS_INVALID(10001, "参数无效"),
PARAM_IS_BLANK(10002, "参数为空"),
PARAM_TYPE_BIND_ERROR(10003, "参数格式错误"),
PARAM_NOT_COMPLETE(10004, "参数缺失"),
/* 用户错误:20001-29999*/
USER_NOT_LOGGED_IN(20001, "用户未登录,请先登录"),
USER_LOGIN_ERROR(