官网:JSON Web Tokens - jwt.io

为什么学习JWT?

        微服务集群中的每个服务,对外提供的都是Rest风格的接口,而Rest风格的一个最重要的规范就是:服务de无状态性。

什么是无状态?

1. 服务端不保存任何客户端请求者信息

2. 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份

无状态,在微服务开放中,优势是?

1. 客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务

2. 服务端的是否集群对客户端透明

3. 服务端可以任意的迁移和伸缩

4. 减小服务端存储压力

无状态登录实现原理:

服务器端生产唯一标识(注意:最终需要进行校验)

方案1:UUID,数据单一,不能包含种类过多的信息。

方案2:JWT 生成唯一标识,数据可以自定义。【使用】

为了保证JWT生成数据安全性,采用RSA加密。

浏览器存储和自动携带数据

方案1:使用cookie,有很多局限性(大小,个数)。

方案2:请求参数,get请求URL有长度限制,每一个路径都需要处理比较麻烦。

方案3:浏览器localStorage/sessionStorage存储,通过ajax的请求头携带。【使用】

什么是JWT?

        JWT,全称是Json Web Token,是JSON风格轻量级的授权和身份认证规格,可实现无状态、分布式的Web应用授权。

token需要加密,进行保护,采用RSA加密

使用RSA工具

        RSA加密是一种非对称加密,可以在不直接传递密钥的情况下完成解密,确保信息的安全性,避免了直接传递密钥所造成的被破解的风险。这种加密算法是由一对密钥来进行加解密的过程,分别称为公钥和私钥。通常个人保存私钥,公钥是公开的(可能同时多人持有)。RSA加密算法的原理就是对一极大整数做因数分解的困难性来保证安全性。

生成公钥和私钥

搭建环境:

父项目坐标:

org.springframework.cloud

spring-cloud-build

2.3.5.RELEASE

Hoxton.SR12

2.2.7.RELEASE

3.4.0

1.1.10

2.7.0

0.9.0

2.9.7

1.9.3

1.0-SNAPSHOT

org.springframework.boot

spring-boot-dependencies

${spring-boot.version}

pom

import

org.springframework.cloud

spring-cloud-dependencies

${spring.cloud.version}

pom

import

com.alibaba.cloud

spring-cloud-alibaba-dependencies

${spring.cloud.alibaba.version}

pom

import

com.baomidou

mybatis-plus-boot-starter

${mybatis.plus.starter.version}

com.baomidou

mybatis-plus-annotation

${mybatis.plus.starter.version}

com.alibaba

druid-spring-boot-starter

${durid.starter.version}

io.springfox

springfox-swagger2

${swagger.version}

io.springfox

springfox-swagger-ui

${swagger.version}

commons-beanutils

commons-beanutils

${beanutils.version}

io.jsonwebtoken

jjwt

${jwt.jjwt.version}

joda-time

joda-time

${jwt.joda.version}

网关坐标:

org.springframework.cloud

spring-cloud-starter-gateway

com.alibaba.cloud

spring-cloud-starter-alibaba-nacos-discovery

org.springframework.boot

spring-boot-starter-test

commons-beanutils

commons-beanutils

io.jsonwebtoken

jjwt

joda-time

joda-time

添加工具类:

RsaUtils:

package com.czxy.utils;

import java.io.File;

import java.io.IOException;

import java.nio.file.Files;

import java.security.*;

import java.security.spec.PKCS8EncodedKeySpec;

import java.security.spec.X509EncodedKeySpec;

/**

* Created by liangtong.

*/

public class RsaUtils {

/**

* 从文件中读取公钥

*

* @param filename 公钥保存路径,相对于classpath

* @return 公钥对象

* @throws Exception

*/

public static PublicKey getPublicKey(String filename) throws Exception {

byte[] bytes = readFile(filename);

return getPublicKey(bytes);

}

/**

* 从文件中读取密钥

*

* @param filename 私钥保存路径,相对于classpath

* @return 私钥对象

* @throws Exception

*/

public static PrivateKey getPrivateKey(String filename) throws Exception {

byte[] bytes = readFile(filename);

return getPrivateKey(bytes);

}

/**

* 获取公钥

*

* @param bytes 公钥的字节形式

* @return

* @throws Exception

*/

public static PublicKey getPublicKey(byte[] bytes) throws Exception {

X509EncodedKeySpec spec = new X509EncodedKeySpec(bytes);

KeyFactory factory = KeyFactory.getInstance("RSA");

return factory.generatePublic(spec);

}

/**

* 获取密钥

*

* @param bytes 私钥的字节形式

* @return

* @throws Exception

*/

public static PrivateKey getPrivateKey(byte[] bytes) throws Exception {

PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(bytes);

KeyFactory factory = KeyFactory.getInstance("RSA");

return factory.generatePrivate(spec);

}

/**

* 根据密文,生存rsa公钥和私钥,并写入指定文件

*

* @param publicKeyFilename 公钥文件路径

* @param privateKeyFilename 私钥文件路径

* @param secret 生成密钥的密文

* @throws Exception

*/

public static void generateKey(String publicKeyFilename, String privateKeyFilename, String secret) throws Exception {

KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");

SecureRandom secureRandom = new SecureRandom(secret.getBytes());

keyPairGenerator.initialize(1024, secureRandom);

KeyPair keyPair = keyPairGenerator.genKeyPair();

// 获取公钥并写出

byte[] publicKeyBytes = keyPair.getPublic().getEncoded();

writeFile(publicKeyFilename, publicKeyBytes);

// 获取私钥并写出

byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();

writeFile(privateKeyFilename, privateKeyBytes);

}

private static byte[] readFile(String fileName) throws Exception {

return Files.readAllBytes(new File(fileName).toPath());

}

private static void writeFile(String destPath, byte[] bytes) throws IOException {

File dest = new File(destPath);

//创建父文件夹

if(!dest.getParentFile().exists()){

dest.getParentFile().mkdirs();

}

//创建需要的文件

if (!dest.exists()) {

dest.createNewFile();

}

Files.write(dest.toPath(), bytes);

}

}

JwtUtils:

package com.czxy.utils;

import io.jsonwebtoken.Claims;

import io.jsonwebtoken.JwtBuilder;

import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.SignatureAlgorithm;

import org.apache.commons.beanutils.BeanUtils;

import org.joda.time.DateTime;

import java.beans.BeanInfo;

import java.beans.IntrospectionException;

import java.beans.Introspector;

import java.beans.PropertyDescriptor;

import java.lang.reflect.InvocationTargetException;

import java.security.PrivateKey;

import java.security.PublicKey;

/**

* Created by liangtong.

*/

public class JwtUtils {

/**

* 私钥加密token

* @param data 需要加密的数据(载荷内容)

* @param expireMinutes 过期时间,单位:分钟

* @param privateKey 私钥

* @return

*/

public static String generateToken(Object data, int expireMinutes, PrivateKey privateKey) {

try {

//1 获得jwt构建对象

JwtBuilder jwtBuilder = Jwts.builder();

//2 设置数据

if( data == null ) {

throw new RuntimeException("数据不能为空");

}

BeanInfo beanInfo = Introspector.getBeanInfo(data.getClass());

PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {

// 获得属性名

String name = propertyDescriptor.getName();

// 获得属性值

Object value = propertyDescriptor.getReadMethod().invoke(data);

if(value != null) {

jwtBuilder.claim(name,value);

}

}

//3 设置过期时间

jwtBuilder.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate());

//4 设置加密

jwtBuilder.signWith(SignatureAlgorithm.RS256, privateKey);

//5 构建

return jwtBuilder.compact();

} catch (Exception e) {

throw new RuntimeException(e);

}

}

/**

* 通过公钥解析token

* @param token 需要解析的数据

* @param publicKey 公钥

* @param beanClass 封装的JavaBean

* @return

* @throws Exception

*/

public static T getObjectFromToken(String token, PublicKey publicKey,Class beanClass) throws Exception {

//1 获得解析后内容

Claims body = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token).getBody();

//2 将内容封装到对象JavaBean

T bean = beanClass.newInstance();

BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);

PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();

for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {

// 获得属性名

String name = propertyDescriptor.getName();

// 通过属性名,获得对应解析的数据

Object value = body.get(name);

if(value != null) {

// 将获得的数据封装到对应的JavaBean中

BeanUtils.setProperty(bean,name,value);

}

}

return bean;

}

}

测试:

package com.czxy;

import com.czxy.utils.RsaUtils;

import org.junit.Test;

import java.security.PrivateKey;

import java.security.PublicKey;

public class TestRSA {

private static final String pubKeyPath = "D:\\rsa\\rsa.pub";

private static final String priKeyPath = "D:\\rsa\\rsa.pri";

/*

*生成公钥、私钥

* * @throws Exception */

@Test

public void testRsaGenerate() throws Exception {

RsaUtils.generateKey(pubKeyPath,priKeyPath,"1234");

}

/*

* 测试

* * @throws Exception*/

@Test

public void testGet() throws Exception {

PublicKey publicKey = RsaUtils.getPublicKey(pubKeyPath);

System.out.println(publicKey);

PrivateKey privateKey = RsaUtils.getPrivateKey(priKeyPath);

System.out.println(privateKey);

}

}

运行结果:

testRsaGenerate:

testGet:

使用JWT工具

上方已经导入JWT工具

创建User类:

@Data

public class User {

private String uid;

private String username;

private String password;

private Boolean gender;

private String image;

}

生成token

private static final String pubKeyPath = "D:\\rsa\\rsa.pub";

private static final String priKeyPath = "D:\\rsa\\rsa.pri";

/**

* 生成token

* @throws Exception

* */

@Test

public void testCreateToken() throws Exception {

User user = new User();

user.setPassword("1234");

user.setUsername("xxx");

String token = JwtUtils.generateToken(user,30, RsaUtils.getPrivateKey(priKeyPath));

System.out.println(token);

}

运行:

解析token(校验)

/**

* 解析token

* @throws Exception

* */

@Test

public void testParseToken() throws Exception {

String token = "eyJhbGciOiJSUzI1NiJ9.eyJjbGFzcyI6ImNvbS5jenh5LmRvbWFpbi5Vc2VyIiwicGFzc3dvcmQiOiIxMjM0IiwidXNlcm5hbWUiOiJ4eHgiLCJleHAiOjE3MDI1NDM4ODh9.nAYbi_JJnjLDuZJBjQRab9pIOkA97fsOq-moZbZnoihzdRlRA-qFYPqMilwpZi-1KwjO1_73V6e91bDt8p9S4ANJZZUnSiOLNKBgQvcSpcYIyHF6Xon9qRQS16CSIMx0meOyc_w_Umwzk-vfylud7BPcSvWoBEpVNc0i2qKVV7Y";

User user = JwtUtils.getObjectFromToken(token, RsaUtils.getPublicKey(pubKeyPath), User.class);

System.out.println(user);

}

运行:

JWT token组成

JWT的token包含三部分数据:头部、载荷、签名。

名称 描述 组成部分 头部(Header) 通常头部有两部分信息 1. 声明类型,这里是JW 2. 加密算法,自定义 载荷(Payload) 就是有效数据 1. 用户身份信息 2. 注册声明 签名(Signature) 整个数据的认证信息 一般根据前两步的数据,再加上服务的的密钥(secret),通过加密算法生成。用于验证整个数据完整和可靠性

参考文章

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。