zoukankan      html  css  js  c++  java
  • JWT&RSA实现单点登录(详细介绍)

    今天给大家讲一下基于JWT&RSA的单点登录(Single Sign On,简称SSO)解决方案

    概念

    首先要了解几个概念

    • 单点登录(Single Sign On)
    • JWT
    • RSA

    背景

    为什么需要单点登录?简单的来说就是http请求是无状态的,你的上一次请求和下一次请求都是没有关联的,那么怎么让服务器知道你是谁?他是谁?

    会话机制

    说到会话机制你肯定会想到session和cookie

    • session将会话id作为每一个请求的参数,服务器接收请求自然能解析参数获得会话id,判断是否来自同一会话。
    • cookie和session的作用一致,最大的区别就是session保存在浏览器,session保存在服务器

    随着web系统的发展,现在的系统结构已经很复杂了,web系统早已从久远的单系统发展成为如今由多系统组成的应用群,面对如此众多的系统,用户难道要一个一个登录、然后一个一个注销吗?
    因此,我们需要一种全新的登录方式来实现多系统应用群的登录,这就是单点登录

    单点登录

    单点登录说白了就是,把你的登录信息保存在token中,用户发起http请求时把token带上,服务器在接收到请求时去解析token,拿到你的登录信息,就知道你是谁了。
    更多关于单点登录:传送门

    JWT&RSA

    JWT 和 RSA 可以说是单点登录是实现方式

    JWT

    JWT就是一个字符串也可以称为token,它把你的信息加密后生成一个字符串,前端保存这个字符串,在发起请求的时候就带上token,后端接收到token进行解析...
    关于JWT:传送门

    RSA

    RSA是一个加密方式,是一种非对称的加密方式,为什么叫非对称加密方式?
    因为它的加密和解密用的不是同一个密钥
    加密使用公钥加密,解密用过私钥解密,私钥保存到后端,通过RSA就大大的提高了信息的安全性
    关于密钥,私钥,公钥的区别:传送门

    总结

    使用JWT&RSA实现单点登录,通过RSA对公钥和私钥字符串进一步处理,JWT生成Token时,使用公钥进行加密,解析时,JWT通过私钥对Token进行解析(token可以设置过期时间,如果token过期,会自动解析失败)

    代码

    JwtUtils

    package com.example.jwtras.utils;
    
    import com.example.jwtras.entity.Employee;
    
    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;
    
    
    /**
     * @author FENGZENG
     * @date 2021/8/23 17:30
     */
    public class JwtUtils {
    
        private static final String FORMAT_STR = "yyyy-MM-dd HH:mm:ss";
    
    
        /**
         * 生成token
         *
         * @param data          加密的数据
         * @param expireMinutes token过期时间
         * @param privateKey    私钥
         * @return token
         * @throws IntrospectionException
         * @throws InvocationTargetException
         * @throws IllegalAccessException
         */
        public static String generateToken(Object data, int expireMinutes, PrivateKey privateKey) throws IntrospectionException, InvocationTargetException, IllegalAccessException {
            JwtBuilder jwtBuilder = Jwts.builder();
            if (data == null) {
                throw new RuntimeException("数据不能为空");
            }
            //拿到bean信息
            BeanInfo beanInfo = Introspector.getBeanInfo(Employee.class);
            //获取bean的属性
            PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
            //把bean的字符属性放入token中
            for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                //获取bean的属性名
                String name = propertyDescriptor.getName();
                //获取属性对应的value
                Object value = propertyDescriptor.getReadMethod().invoke(data);
                if (value != null) {
                    jwtBuilder.claim(name, value);
                }
            }
            //设置token过期时间
            jwtBuilder.setExpiration(DateTime.now().plusMinutes(expireMinutes).toDate());
            //使用私钥进行加密
            jwtBuilder.signWith(SignatureAlgorithm.RS256, privateKey);
            //生成token
            return jwtBuilder.compact();
        }
    
    
        /**
         * 解析token,获取token中存储的对象
         * @param token token
         * @param publicKey 公钥字符串
         * @param beanClass beanClass
         * @param <T> 泛型
         * @return
         */
        public static <T> T getObjectFromToken(String token, PublicKey publicKey, Class<T> beanClass) {
    
            try {
                //获取token中存储的信息
                Claims body = Jwts.parser().setSigningKey(publicKey).parseClaimsJws(token).getBody();
                //获取bean对象,实例化还为初始化
                T bean = beanClass.newInstance();
               //内省 Java bean 并了解其所有属性、公开的方法和事件
                BeanInfo beanInfo = Introspector.getBeanInfo(beanClass);
    
                //获取bean的属性和value,类似Map结构
                PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
                for (PropertyDescriptor propertyDescriptor : propertyDescriptors) {
                    //拿到属性名
                    String name = propertyDescriptor.getName();
                    //拿到属性名对应的值
                    Object value = body.get(name);
                    if (value != null) {
                        //实例化bean
                        BeanUtils.setProperty(bean, name, value);
                    }
                }
                return bean;
            } catch (Exception e) { //如果token过期,会自动解析失败
                throw new RuntimeException("token已失效");
            }
        }
    
    }
    

    RsaUtils

    package com.example.jwtras.utils;
    
    
    import java.io.File;
    import java.io.IOException;
    import java.nio.file.Files;
    import java.security.KeyFactory;
    import java.security.NoSuchAlgorithmException;
    import java.security.PrivateKey;
    import java.security.PublicKey;
    import java.security.spec.InvalidKeySpecException;
    import java.security.spec.PKCS8EncodedKeySpec;
    import java.security.spec.X509EncodedKeySpec;
    import java.util.Base64;
    
    /**
     * @author FENGZENG
     * @date 2021/8/23 17:44
     */
    public class RsaUtils {
    
        private static String algorithm = "RSA";
    
        /**
         * 获取 私钥对象
         * @param key 私钥字符串
         * @return
         * @throws NoSuchAlgorithmException
         * @throws InvalidKeySpecException
         */
        public static PrivateKey getPrivateKey(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
            //对key进行base64加密
            byte[] decode = Base64.getDecoder().decode(key);
            //使用PKCS8进行加密
            PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(decode);
            //加密方式RSA
            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
            //生成privateKey
            return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        }
    
        /**
         * 获取公钥对象
         * @param key 公钥字符串
         * @return
         * @throws NoSuchAlgorithmException
         * @throws InvalidKeySpecException
         */
        public static PublicKey getPublicKey(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
            // 对key进行base64加密
            byte[] decode = Base64.getDecoder().decode(key);
            //公钥使用X509进行加密
            X509EncodedKeySpec spec = new X509EncodedKeySpec(decode);
            //加密方式RSA
            KeyFactory keyFactory = KeyFactory.getInstance(algorithm);
            //生成publicKey
            return keyFactory.generatePublic(spec);
        }
    
    
        /**
         * 从文件中读取密钥
         * @param filename 传入文件名,相对于classpath
         * @return
         * @throws IOException
         */
        private static byte[] readFile(String filename) throws IOException {
            return Files.readAllBytes(new File(filename).toPath());
        }
    
    }
    
    

    tips

    把个人信息放入到token中,使用的BeanInfoPropertyDescriptor其实是我学习别人的代码的时候碰到的,我还没有去细看,
    另外一个方案就是序列化,可以直接把对象序列化成json串放到token中,拿到token时再反序列化得到序列化信息。
    序列化可以使用Gson,fastJson,jackson

  • 相关阅读:
    NOIp 图论算法专题总结 (3):网络流 & 二分图 简明讲义
    zkw 线段树
    NOIp 图论算法专题总结 (2)
    NOIp 数据结构专题总结 (2):分块、树状数组、线段树
    单调栈、单调队列 思路及例题
    java自动装箱和拆箱
    HashMap和HashTable的异同点
    HttpServletRequest. getParameter获取的参数格式
    关于交换函数(1)
    std::vector::iterator重载了下面哪些运算符
  • 原文地址:https://www.cnblogs.com/Fzeng/p/15179888.html
Copyright © 2011-2022 走看看