文档章节

密码加密与微服务鉴权JWT详细使用教程

庭前云落
 庭前云落
发布于 2019/12/14 10:51
字数 2040
阅读 76
收藏 0
JWt

[TOC]

1.1、了解微服务状态

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

什么是无状态?

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

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

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

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

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

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

4.减小服务端储存压力

1.2、无状态登录实现原理

  • 服务器端生产唯一标识(注意:最终需要进行校验)
    • 方案1:UUID,数据单一,不能包含种类过多的信息。
    • 方案2:RAS加密,数据多样,需要使用算法,有一定的理解难度。【使用】
  • 浏览器储存和自动携带数据
    • 方案1:使用cookie,有很多局限性(大小,个数)
    • 方案2:请求参数,get请求URL有长度限制,每一个路径都需要处理比较麻烦。
    • 方案3:浏览器localStroage存储,请求头携带。【使用】

2.1、RAS工具

服务与服务之间共享数据,采用JWT先生成数据,在另一个服务中解析数据,为了保证数据安全性,使用RAS对数据进行加密。

使用RAS加密保证token数据在传输过程中不会被篡改

  • RAS:非对称加密算法
    • 特点
      • 同时生产一对密钥:公钥和私钥
      • 公钥秘钥:用于加密
      • 私钥秘钥:用于解密

工具类RasUtils

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;
/**
 * @author 庭前云落
 * @Date 2019/12/13 22:01
 * @description
 */
public class RasUtils {

    /**
     * 从文件中读取公钥
     *
     * @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);
    }
}

2.1.1使用工具

//生成公钥和私钥
RasUtils.generateKey(公钥位置,私钥位置,密码);
RasUtils.generateKey(pubKeyPath,priKeyPath,"234");
//获得公钥
RasUtils.getPublicKey(pubKeyPath);
//获得私钥
RasUtils.getPrivateKey(priKeyPath);
package com.czxy;

import com.czxy.utils.RasUtils;
import org.junit.Test;

import java.security.PrivateKey;
import java.security.PublicKey;

/**
 * @author 庭前云落
 * @Date 2019/12/13 22:07
 * @description
 */
public class TestRAS {

    private static final String pugbKeyPath="D:\\ras\\ras.pub";

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

    @Test
    public void testRas() throws Exception {
        //生产公钥和私钥
        RasUtils.generateKey(pugbKeyPath,priKeyPath,"234");
    }

    @Test
    public void testGetRas() throws Exception {
        //获得公钥和私钥
        PublicKey publicKey = RasUtils.getPublicKey(pugbKeyPath);
        PrivateKey privateKey = RasUtils.getPrivateKey(priKeyPath);
        System.out.println(publicKey.toString());
        System.out.println(privateKey.toString());

    }
}

3、JWT工具

3.1概述

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

  • JWT基于JSON的认证规范。(Json Web Token)
  • 使用JWT目的:生成数据、解析数据

3.2、使用JWT

pom

    <properties>
        <jwt.jjwt.version>0.9.0</jwt.jjwt.version>
        <jwt.joda.version>2.9.7</jwt.joda.version>
        <lombok.version>1.16.20</lombok.version>
        <beanutils.version>1.9.3</beanutils.version>
    </properties>


    <dependencies>
        <!--网关依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <!--添加eureka客户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <!--jwt依赖-->
        <dependency>
            <groupId>commons-beanutils</groupId>
            <artifactId>commons-beanutils</artifactId>
            <version>${beanutils.version}</version>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jwt.jjwt.version}</version>
        </dependency>

        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>${jwt.joda.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
            <scope>provided</scope>
        </dependency>


    </dependencies>

导入工具类

工具类: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.Introspector;
import java.beans.PropertyDescriptor;
import java.security.PrivateKey;
import java.security.PublicKey;

/**
 * @author 庭前云落
 * @Date 2019/12/13 22:01
 * @description
 */
public class JwtUtils {
    /**
     *  私钥加密token
     * @param data 需要加密的数据(载荷内容)
     * @param expireMinutes 过期时间,单位:分钟
     * @param privateKey 私钥
     * @return
     */
    public static String generateToken(Object data, int expireMinutes, PrivateKey privateKey) throws Exception {
        //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();
    }

    /**
     * 通过公钥解析token
     * @param token 需要解析的数据
     * @param publicKey 公钥
     * @param beanClass 封装的JavaBean
     * @return
     * @throws Exception
     */
    public static <T> T  getObjectFromToken(String token, PublicKey publicKey,Class<T> 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;
    }
}

时间处理工具:DateTime

//当前时间
DateTime.now().toDate().toLocaleString()
//当前时间加5分钟
DateTime.now().plusMinutes(5).toDate().toLocaleString()
//当前时间减5分钟
DateTime.now().minusMinutes(5).toDate().toLocaleString()

3.3、测试

//生成数据, UserInfo --> String(加密)
//JwtUtils.generateToken(数据,过期时间(分钟), 私钥)
String token = JwtUtils.generateToken(userInfo,30, RasUtils.getPrivateKey(priKeyPath));

//解析数据, String(加密) --> UserInfo
// JwtUtils.getObjectFromToken(加密数据, 公钥, 封装对象.class);
UserInfo userInfo = JwtUtils.getObjectFromToken(token, RasUtils.getPublicKey(pubKeyPath), UserInfo.class);
  • 生产Token
package com.czxy;

import com.czxy.utils.RasUtils;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.joda.time.DateTime;
import org.junit.Test;

/**
 * @author 庭前云落
 * @Date 2019/12/13 22:32
 * @description
 */
public class TestJWT {

    private static final String pugbKeyPath="D:\\ras\\ras.pub";

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

    @Test
    public void testGenerateToken() throws Exception {
        String str = Jwts.builder()
                .claim("test","庭前云落")
                .setExpiration(DateTime.now().plusMinutes(60).toDate())
                .signWith(SignatureAlgorithm.RS256, RasUtils.getPrivateKey(priKeyPath))
                .compact();
        System.out.println(str);
    }
}

"庭前云落":Token ---》eyJhbGciOiJSUzI1NiJ9.eyJ0ZXN0Ijoi5bqt5YmN5LqR6JC9IiwiZXhwIjoxNTc2MjkzMTEyfQ.a32GamgbG6F1xC-4NtEJNNLX8mcV6Ycyc2bf7_7wX6_xa4LzimqO5ZH9d4bSii-IixYudSreurJ2Rjq72aXvv3nv_VsZasmODeLkBMLtBGhKDztKW3hNQM7rcRLIxL4PFP48xjosJl48F-hXSgEWqYXuC6Voexlk8W4eonRcGqg

  • 解析Token
    @Test
    public void testParseToken() throws Exception {
String token="\n" +
        "eyJhbGciOiJSUzI1NiJ9.eyJ0ZXN0Ijoi5bqt5YmN5LqR6JC9IiwiZXhwIjoxNTc2MjkzMTEyfQ.a32GamgbG6F1xC-4NtEJNNLX8mcV6Ycyc2bf7_7wX6_xa4LzimqO5ZH9d4bSii-IixYudSreurJ2Rjq72aXvv3nv_VsZasmODeLkBMLtBGhKDztKW3hNQM7rcRLIxL4PFP48xjosJl48F-hXSgEWqYXuC6Voexlk8W4eonRcGqg";
        Claims claims = Jwts.parser().setSigningKey(RasUtils.getPublicKey(pubKeyPath)).
                parseClaimsJws(token).getBody();
        String text = claims.get("test",String.class);
        System.out.println(text);
    }

4、生产token和校验token

编写测试对象UserInfo

package com.czxy.domain;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author 庭前云落
 * @Date 2019/12/13 21:55
 * @description
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
     private Long id;
     private String username;
}

测试

package com.czxy;

import com.czxy.domain.UserInfo;
import com.czxy.utils.JwtUtils;
import com.czxy.utils.RasUtils;
import org.junit.Test;

/**
 * @author 庭前云落
 * @Date 2019/12/13 22:32
 * @description
 */
public class TestJWT {

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

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

   
    @Test
    public void testToken() throws Exception {
        UserInfo userInfo = new UserInfo();
        userInfo.setId(10L);
        userInfo.setUsername("庭前云落");
        String token = JwtUtils.generateToken(userInfo, 30, RasUtils.getPrivateKey(priKeyPath));
        System.out.println(token);
    }


    @Test
    public void testParserToken() throws Exception {
        String token="eyJhbGciOiJSUzI1NiJ9.eyJjbGFzcyI6ImNvbS5jenh5LmRvbWFpbi5Vc2VySW5mbyIsImlkIjoxMCwidXNlcm5hbWUiOiLluq3liY3kupHokL0iLCJleHAiOjE1NzYyOTI1Mzd9.LlyCCBeW4f7fjU3LmE7cA8W7aNB1BXp23Yv9WQJouCRCtoD46GiXQAHn2kezuzuPfp2u5G0OXOIeahHtnvRMSDjtQFJ6s-cZcKNupJPOPK8BzuEnladx0ilcrSr5TeWNxujg-svSz5EJRwWj8KbRKhQluohpAg0VhERjJjD5wTY";

        UserInfo userInfo = JwtUtils.getObjectFromToken(token, RasUtils.getPublicKey(pubKeyPath), UserInfo.class);
        System.out.println(userInfo);
    }
}

5、JWT token组成

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

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

个人能力有限,有不懂的地方可以看看这个,思否上面大神写的(有源码)

https://segmentfault.com/a/1190000021134501

© 著作权归作者所有

庭前云落

庭前云落

粉丝 17
博文 59
码字总数 55891
作品 0
南京
程序员
私信 提问
加载中

评论(0)

使用JWT简化http接口鉴权,告别烦人的加密、解密代码吧

在项目开发过程经常对接其他系统和被其他系统对接,发现很多系统都有一套自己的鉴权规则,还不提供库或代码,只有文字描述,经常需要自己实现鉴权方法,这真是太低效了。本文介绍一个在项目中...

辩才
2018/07/27
0
0
基于 JWT + Refresh Token 的用户认证实践

HTTP 是一个无状态的协议,一次请求结束后,下次在发送服务器就不知道这个请求是谁发来的了(同一个 IP 不代表同一个用户),在 Web 应用中,用户的认证和鉴权是非常重要的一环,实践中有多种...

一行代码一首诗
2019/03/10
0
0
学习后端鉴权系列: 基于JWT的会话管理

内容回顾 上一节讲了基于Cookie+Session的认证方案。 由于基于Session方案的一些缺点,基于token的无状态的会话管理方案诞生了,所谓无状态就是指服务端不再存储信息。 基于JWT的简单鉴权流程 ...

小诺哥
2019/10/07
0
0
JSON Web Token 使用详解

JWT是什么? JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。它是有三部分组成,示例如下,具体的讲解如下(jwt是不会有空行的,下面只是为了显示,便使用了换行看着比较方便)。 ...

鹏飞q
2019/09/09
170
0
Koa(2)之——鉴权(Cookie/Session、Token和OAuth)

  前后端未分离以前,页面都是通过后台来渲染的,能不能访问到页面直接由后台逻辑判断。前后端分离以后,页面的元素由页面本身来控制,所以页面间的路由是由前端来控制了。当然,仅有前端做...

zhunny
2019/06/12
0
0

没有更多内容

加载失败,请刷新页面

加载更多

0228 我的潘多拉

我的潘多拉 从一个故事说起。<br />从前,有个Java程序员非常喜欢写程序,喜欢研究源码,读英文文档。但是它在一家小公司里工作,公司的技术栈很陈旧。<br /> <br />单个系统代码中含有很多的...

李福春carter
今天
18
0
OSChina 周六乱弹 —— 屁会不会传染病毒

Osc乱弹歌单(2020)请戳(这里) 【今日歌曲】 @薛定谔的兄弟 :分享洛神有语创建的歌单「我喜欢的音乐」: 《ハレハレヤ(朗朗晴天)》- 猫瑾 手机党少年们想听歌,请使劲儿戳(这里) @空格...

小小编辑
今天
63
1
两个值得注意的问题

对成员变量的操作只能放在方法中,方法可以对成员变量和方法体中自己定义的局部 变量进行操作.在定义类的成员变量时可以同时赋予初值,如 class A { int a=12; float b=12.56f; } 但是不可以这...

咔啡
今天
27
0
第三章 分布式服务框架的选择

1.大项目工程且多人维护的弊端 (1)项目团队协同成本高,业务响应越来越慢 (2)应用复杂度已超出人的认知负载(向杂乱的电线一样) (3)错误难于隔离(一个模块出错,整个系统挂掉) (4...

zxx901221
今天
68
0
eclipse 上传jar到远程仓库

使用maven的项目中,有时需要把本地的项目打成jar包上传到mevan仓库。 操作如下: 前提:pom文件中配置好远程库的地址,否则会报错 一、将maven 中的settings文件配置好用户名和密码,如下:...

文文1
昨天
63
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部