JWT详解
传统的session认证
安全性
CSRF攻击因为基于cookie来进行用户识别, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。
扩展性
对于分布式应用,需要实现 session 数据共享
性能
每一个用户经过后端应用认证之后,后端应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大,与REST风格不匹配。因为它在一个无状态协议里注入了状态
JWT
官网
JWT
JWT是一种用于双方之间传递安全信息的简洁的、URL安全的声明规范。定义了一种简洁的,自包含的方法用于通信双方之间以Json对象的形式安全的传递信息。特别适用于分布式站点的单点登录(SSO)场景
优点:
无状态
适合移动端应用
单点登录友好
原理
服务器认证以后,生成一个 JSON 对象,发回给用户,就像下面这样
{
"姓名": "张三",
"角色": "管理员",
"到期时间": "2030年7月1日0点0分"
}
注意
用户与服务端通信的时候,都要发回这个 JSON 对象。服务器完全只靠这个对象认定用户身份。为了防止用户篡改数据,服务器在生成这个对象的时候会加上签名,服务器就不保存任何 session 数据了,也就是说,服务器变成无状态了,从而比较容易实现扩展
JWT的结构
头部(header)/载荷(payload)/签证(signature)
它是一个很长的字符串,中间用点(.)分隔成三个部分。注意,JWT 内部是没有换行的,这里只是为了便于展示,将它写成了几行
JWT 的使用方式
客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage。此后,客户端每次与服务器通信,都要带上这个 JWT。你可以把它放在 Cookie 里面自动发送,但是这样不能跨域,所以更好的做法是放在 HTTP 请求的头信息Authorization字段里面
JWT项目搭建
创建maven项目,名字为cloud-jkw
pom.xml
<dependencies>
<!-- JWT -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.79</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.7.0</version>
</dependency>
<!-- redis -->
<!-- <dependency>-->
<!-- <groupId>org.springframework.boot</groupId>-->
<!-- <artifactId>spring-boot-starter-data-redis</artifactId>-->
<!-- </dependency>-->
<!-- eureka client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- Actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- SpringMVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
</dependency>
</dependencies>
application.yml
server:
port: 6500
spring:
application:
name: cloud-jwt
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka/
启动类
@SpringBootApplication
@EnableEurekaClient
public class Jwt6500 {
public static void main(String[] args) {
SpringApplication.run(Jwt6500.class,args);
}
}
Jwt工具类
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTDecodeException;
import java.util.Date;
/**
* JWT工具类
*/
public class JWTUtil {
//签发人
private static final String ISS_USER="jkw";
//令牌过期时间(五分钟)
private static final long TOKEN_EXPIRE_TIME=5*60*1000;
//签名秘钥(最好写的复杂一点)
private static final String KEY="jkw-123456";
/**
* 生成令牌(签名)
* @return
*/
public static String token(){
Date now=new Date();//时间
Algorithm algorithm_key=Algorithm.HMAC256(KEY);//加密秘钥
//创建JWT
String token = JWT.create()
//签发人
.withIssuer(ISS_USER)
//签发时间(当前时间)
.withIssuedAt(now)
//过期时间(当前时间+过期时间)
.withExpiresAt(new Date(now.getTime() + TOKEN_EXPIRE_TIME))
//加密过的签名秘钥
.sign(algorithm_key);
return token;
}
/**
* 校验令牌(签名)
* @param token 传来的令牌
* @return
*/
public static boolean verify(String token){
try {
Algorithm algorithm_key=Algorithm.HMAC256(KEY);//加密秘钥
//校验令牌(校验 签发人/签名秘钥)
JWTVerifier verifier = JWT.require(algorithm_key)
//签发人
.withIssuer(ISS_USER)
.build();
verifier.verify(token);
return true;
//如果校验有问题,就会抛出异常
}catch (IllegalArgumentException e){
e.printStackTrace();
}catch (JWTDecodeException e){
e.printStackTrace();
}
return false;
}
//测试
public static void main(String[] args) {
//1.生成令牌
//String token = token();
//System.out.println(token);
//eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ5dWx1IiwiZXhwIjoxNjc4MjAwMDM1LCJpYXQiOjE2NzgxOTk3MzV9.al7qL_RHDvLHLx38J-5WGcsrK9UnDEpSWM1L0I3iWT4
boolean verify = verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJ5dWx1IiwiZXhwIjoxNjc4MjAwMDM1LCJpYXQiOjE2NzgxOTk3MzV9.al7qL_RHDvLHLx38J-5WGcsrK9UnDEpSWM1L0I3iWT4");
System.out.println(verify);
}
}
统一返回结果集
/**
* 统一返回结果实体类
*/
@Data
@AllArgsConstructor
public class BaseResult<T> implements Serializable {
//状态码(成功:200,失败:其他)
private Integer code;
//提示信息
private String message;
//返回数据
private T data;
//构建成功结果
public static <T> BaseResult<T> ok(){
return new BaseResult(CodeEnum.SUCCESS.getCode(),CodeEnum.SUCCESS.getMessage(),null);
}
//构建带有数据的成功结果
public static <T> BaseResult<T> ok(T data){
return new BaseResult(CodeEnum.SUCCESS.getCode(),CodeEnum.SUCCESS.getMessage(),data);
}
}
@Getter
@AllArgsConstructor
public enum CodeEnum {
// 正常
SUCCESS(200, "OK"),
// 系统异常
SYSTEM_ERROR(500, "系统异常"),
// 业务异常
PARAMETER_ERROR(601, "参数异常"),
INSERT_PRODUCT_TYPE_ERROR(602, "该商品类型不能添加子类型"),
DELETE_PRODUCT_TYPE_ERROR(603, "该商品类型有子类型,无法删除"),
UPLOAD_FILE_ERROR(604, "文件上传失败"),
/**
* user
*/
REGISTER_CODE_ERROR(605,"验证码不正确"),
REGISTER_REPEAT_PHONE_ERROR(606,"手机号已存在"),
REGISTER_REPEAT_NAME_ERROR(607,"用户名已存在"),
LOGIN_NAME_PASSWORD_ERROR(608,"用户名或密码错误"),
LOGIN_CODE_ERROR(609,"验证码不正确"),
VERIFY_TOKEN_ERROR(610,"签名解析失败"),
QR_CODE_ERROR(611,"二维码错误"),
CHECK_SIGN_ERROR(612,"验签失败"),
NO_STOCK_ERROR(613,"库存不足"),
ORDER_EXPIRED_ERROR(614,"订单过期")
;
private final Integer code;
private final String message;
}
控制器接口
@RestController
@RequestMapping("/user")
public class UserController {
/**
* 登陆
* @param username 用户名
* @param password 密码
*/
@PostMapping("/login")
public BaseResult login(String username, String password){
//1.验证用户名和密码
if("user".equals(username)&&"user".equals(password)){
//2.生成令牌
String token = JWTUtil.token();
return BaseResult.ok(token);
}else {
return new BaseResult(CodeEnum.Login_ERROR.getCode(), CodeEnum.Login_ERROR.getMessage(), null);
}
}
}
访问post【postman测试】
localhost:6500/user/login?username=jkw&password=jkw
配置路由
#cloud-jwt
- id: cloud-jwt
uri: lb://cloud-jwt
predicates:
- Path=/user/*
访问post
localhost:9527/user/login?username=jkw&password=jkw