前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JWT单点登录 看这一篇就够了!

JWT单点登录 看这一篇就够了!

作者头像
全栈程序员站长
发布2022-08-26 21:43:42
1.6K0
发布2022-08-26 21:43:42
举报

大家好,又见面了,我是你们的朋友全栈君。

JWT单点登录

什么是JWT?

JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;官网:https://jwt.io

GitHub上jwt的java客户端:https://github.com/jwtk/jjwt

两种登录状态

有状态登录

为了保证客户端cookie的安全性,服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。

例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。

缺点是什么?

  • 服务端保存大量数据,增加服务端压力
  • 服务端保存用户状态,无法进行水平扩展
  • 客户端请求依赖服务端,多次请求必须访问同一台服务器

即使使用redis保存用户的信息,也会损耗服务器资源。

无状态登录

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

  • 服务端不保存任何客户端请求者信息
  • 客户端的每次请求必须具备自描述信息,通过这些信息识别客户端身份

带来的好处是什么呢?

  • 客户端请求不依赖服务端的信息,任何多次请求不需要必须访问到同一台服务
  • 服务端的集群和状态对客户端透明
  • 服务端可以任意的迁移和伸缩
  • 减小服务端存储压力

无状态登录流程

无状态登录的流程:

  • 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
  • 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
  • 以后每次请求,客户端都携带认证的token
  • 服务的对token进行解密,判断是否有效。

流程图:

在这里插入图片描述
在这里插入图片描述

整个登录过程中,最关键的点是什么?

token的安全性

token是识别客户端身份的唯一标示,如果加密不够严密,被人伪造那就完蛋了。

采用何种方式加密才是安全可靠的呢?

我们将采用JWT + RSA非对称加密

jwt实现无状态登录

JWT,全称是Json Web Token, 是JSON风格轻量级的授权和身份认证规范,可实现无状态、分布式的Web应用授权;官网:https://jwt.io

GitHub上jwt的java客户端:https://github.com/jwtk/jjwt

数据格式

JWT包含三部分数据:

  • Header:头部,通常头部有两部分信息:
    • token类型:JWT
    • 加密方式:base64(HS256)
  • Payload:载荷,就是有效数据,一般包含下面信息:
    • 用户身份信息(注意,这里因为采用base64编码,可解码,因此不要存放敏感信息)
    • 注册声明:如token的签发时间,过期时间,签发人等

    这部分也会采用base64编码,得到第二部分数据

  • Signature:签名,是整个数据的认证信息。根据前两步的数据,再加上指定的密钥(secret)(不要泄漏,最好周期性更换),通过base64编码生成。用于验证整个数据完整和可靠性
在这里插入图片描述
在这里插入图片描述

JWT交互流程

流程图:

在这里插入图片描述
在这里插入图片描述

步骤翻译:

  • 1、用户登录
  • 2、服务的认证,通过后根据secret生成token
  • 3、将生成的token返回给浏览器
  • 4、用户每次请求携带token
  • 5、服务端利用公钥解读jwt签名,判断签名有效后,从Payload中获取用户信息
  • 6、处理请求,返回响应结果

因为JWT签发的token中已经包含了用户的身份信息,并且每次请求都会携带,这样服务的就无需保存用户信息,甚至无需去数据库查询,完全符合了Rest的无状态规范。

非对称加密

加密技术是对信息进行编码和解码的技术,编码是把原来可读信息(又称明文)译成代码形式(又称密文),其逆过程就是解码(解密),加密技术的要点是加密算法,加密算法可以分为三类:

  • 对称加密,如AES
    • 基本原理:将明文分成N个组,然后使用密钥对各个组进行加密,形成各自的密文,最后把所有的分组密文进行合并,形成最终的密文。
    • 优势:算法公开、计算量小、加密速度快、加密效率高
    • 缺陷:双方都使用同样密钥,安全性得不到保证
  • 非对称加密,如RSA
    • 基本原理:同时生成两把密钥:私钥和公钥,私钥隐秘保存,公钥可以下发给信任客户端
      • 私钥加密,持有公钥才可以解密
      • 公钥加密,持有私钥才可解密
    • 优点:安全,难以破解
    • 缺点:算法比较耗时
  • 不可逆加密,如MD5,SHA
    • 基本原理:加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,无法根据密文推算出明文。

RSA算法历史:

1977年,三位数学家Rivest、Shamir 和 Adleman 设计了一种算法,可以实现非对称加密。这种算法用他们三个人的名字缩写:RSA

代码实现

本次示例采用SpringBoot工程搭建

核心依赖

代码语言:javascript
复制
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.16.18</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.10.6</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.10.6</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.10.6</version>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>joda-time</groupId>
    <artifactId>joda-time</artifactId>
    <version>2.10.3</version>
</dependency>

核心工具类

如果需要使用JWT单点登录,RAS工具类和JWT工具类是必不可少的

JWT工具类

代码语言:javascript
复制
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Map;

public class JwtUtils { 
   
    /** * 私钥加密token * * @param map 载荷中的数据 * @param expireMinutes 过期时间,单位秒 * @return * @throws Exception */
    public static String generateToken(Map<String, Object> map, PrivateKey key, int expireMinutes) throws Exception { 
   
        return Jwts.builder()
                .setClaims(map)
                .setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate())
                .signWith(key, SignatureAlgorithm.RS256)
                .compact();
    }

    /** * 公钥解析token * * @param token 用户请求中的token * @return * @throws Exception */
    private static Jws<Claims> parserToken(String token, PublicKey key) { 
   
        return Jwts.parser().setSigningKey(key).parseClaimsJws(token);
    }

    /** * 获取token中的用户信息 * * @param token 用户请求中的令牌 * @return 用户信息 * @throws Exception */
    public static Map<String, Object> getInfoFromToken(String token, PublicKey key) throws Exception { 
   
        Jws<Claims> claimsJws = parserToken(token, key);
        return claimsJws.getBody();
    }

}

RSA工具类

代码语言:javascript
复制
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;

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 IOException * @throws NoSuchAlgorithmException */
    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(2048, 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.exists()) { 
   
            dest.createNewFile();
        }
        Files.write(dest.toPath(), bytes);
    }
}

测试

公钥和私钥的区别,其实不用太过于纠结,我们可以理解为:

  • 私钥属于私密,用于生成token,返回加密数据给前端
  • 公钥属于公文,用于解析token,返回用户数据给前端
代码语言:javascript
复制
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.HashMap;
import java.util.Map;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = JwtDemoApplication.class)
public class JwtDemoApplicationTests { 
   

	// 公钥文件生成地址
    private static final String PUB_KEY_PATH = "D:\\IT notes\\jwt\\tool\\rsa.pub";
    // 私钥文件生成地址
    private static final String PRI_KEY_PATH = "D:\\IT notes\\jwt\\tool\\rsa.pri";
    
    private PublicKey publicKey;
    private PrivateKey privateKey;

    // 生成公钥和私钥
    @Test
    public void testRsa() throws Exception { 
   
        RsaUtils.generateKey(PUB_KEY_PATH, PRI_KEY_PATH, "234");
    }

    // 先生成 再获取 生成之前把@Before注释掉!
    // 获取公钥和私钥
    @Before
    public void testGetRsa() throws Exception { 
   
        this.publicKey = RsaUtils.getPublicKey(PUB_KEY_PATH);
        this.privateKey = RsaUtils.getPrivateKey(PRI_KEY_PATH);
    }

    // 生成token
    @Test
    public void testGenerateToken() throws Exception { 
   
        Map<String, Object> map = new HashMap<>();
        map.put("id", "11");
        map.put("username", "liuyan");
        // 生成token
        String token = JwtUtils.generateToken(map, privateKey, 1);
        System.out.println("token = " + token);
    }

    // 解析token
    @Test
    public void testParseToken() throws Exception { 
   
        String token = "eyJhbGciOiJSUzI1NiJ9.eyJpZCI6IjExIiwidXNlcm5hbWUiOiJsaXV5YW4iLCJleHAiOjE1ODc2MjYwODZ9.kO22BSWogDJPD6oG92dfFukothGMJej3FKLIfDJRVjGiF_O7kLPSmycmuyByy8wd7X_nOVDCPoMvvhoUzviDsgzIC0xiILoMobwyUDtbYdStCfiLVikqHmnf0Our5tuxwVaPOK2igoWW3zRRI7HG5RLh0p2pUAQe1C-is_8zczn2T5CQ-7vEwPS6U5FLn7_1y8rHNVsKlHqNBdSDxQn7jLOkHkKnRiShZ2_iBuXTzo6uZt2461IV8qk6Lmn35fyX7JHwHIVvuQyniFEsdYNW5t8P3Eo1UEbL3ZD5ZbhcIsK5gnvpXdsne6uK1jHQzClQi-hcGONuHXpS2IkueWEizg";
        // 解析token
        Map<String, Object> map = JwtUtils.getInfoFromToken(token, publicKey);
        System.out.println("id: " + map.get("id"));
        System.out.println("userName: " + map.get("username"));
    }
}

发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/145057.html原文链接:https://javaforall.cn

本文参与?腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022年5月1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客?前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与?腾讯云自媒体分享计划? ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • JWT单点登录
  • 什么是JWT?
  • 两种登录状态
    • 有状态登录
      • 无状态登录
        • 无状态登录流程
          • 整个登录过程中,最关键的点是什么?
          • jwt实现无状态登录
            • 数据格式
              • JWT交互流程
                • 非对称加密
                • 代码实现
                  • 核心依赖
                    • 核心工具类
                      • JWT工具类
                      • RSA工具类
                    • 测试
                    领券
                    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                    http://www.vxiaotou.com