基于公私密钥的单点登录

文章介绍了单点登录(SSO)的几种实现方法,包括基于session复制、cookie+redis、token以及使用公私钥加密的DSA算法。讨论了每种方法的实现细节,如通过cookie存储信息、地址栏传递token,以及如何通过公钥验证私钥签名来确保安全性。
摘要由CSDN通过智能技术生成

目前已知的单点登陆方式有:

多个系统集群

建立一个SSO认证中心,用户只需要登录一次就可以访问所有相互信任的应用系统。

1、可以通过session广播机制实现:在一个集群中的一个模块登录后,然后把这个session复制n份,发送到这个集群的其他模块中,就实现了一处登录,处处可用,但缺点是耗费比较大,不推荐使用

2、使用cookie+redis实现:在项目中任何一个模块登录,登录之后,把数据放到这两个地方

(1)redis:在key:生成唯一随机值(ip、用户id等等)  ,在value:用户数据

(2)cookie:把redis里面生成key值放到cookie里面

访问项目中其他模块,发送请求带着cookie进行发送,服务端获取请求中的cookie,把cookie获取值,到redis进行查询,根据key进行查询,如果查询数据就是登录

点对点系统

3、使用token实现

token:按照一定规则生成字符串,字符串可以包含用户信息的令牌

在项目某个模块进行登录,登录之后,按照规则生成字符串,把登陆之后用户包含到生成字符串里面,把字符串返回

(1)可以把字符串通过cookie返回

(2)把字符串通过地址栏返回

2.再去访问项目其他模块,每次访问在地址栏带着生成的字符串,在访问模块里面获取地址字符串,根据字符串获取用户信息。如果可以获取到,就是登录

基于公私密钥的单点登录

针对两个系统之间的单点登陆,使用token类似方式实现是最简单的。这里,我们通过一定的规则构造字符串(必须带有用户账户信息),并根据已有的私钥进行加密签名,返回到url中,请求目标系统,目标系统解析url中的签名,并根据公钥进行验签,通过之后,允许登陆,写入登陆信息到session。

公私秘钥的安全性由算法保证。算法是基于DSA签名, PKCS #8和X509是对应算法的处理类。对私钥按照 PKCS #8 标准编码处理、对公钥是使用了X509。 

DSA算法概述

DSA算法是美国的国家标准数字签名算法,它只能用户数字签名,而不能用户数据加密和密钥交换。

DSA与RSA的生成方式不同,RSA是使用openssl提供的指令一次性的生成密钥(包括公钥),而通常情况下,DSA是先生成DSA的密钥参数,然后根据密钥参数生成DSA密钥(包括公钥),密钥参数决定了DSA密钥的长度,而且一个密钥参数可以生成多对DSA密钥对。

DSA生成的密钥参数是p、q和g,如果要使用一个DSA密钥,需要首先共享其密钥参数。关于DSA加密的原理,请自行查阅。

私钥、公钥的获取可以使用OpenSSL工具生成,文章:https://www.jianshu.com/p/35ae4fb9e4a3

生成加密密钥部分参考

private static final String DSA = "DSA";
private static SimpleDateFormat CRED_TIME_FORMAT = new SimpleDateFormat("yyyyMMddHHmm");
private static String CRED_SEPERATOR = ":";
private static String signatureAlg = "SHA1WithDSA";
private static String SELF_PROJECTENAME = "A";
  
private static String privateKeyStr = "******";
private static String publicKeyStr = "*****"



KeyFactory keyFactory = KeyFactory.getInstance(DSA);
//对私钥解密
byte pri[] = ByteUtils.decodeHex(privateKeyStr);
 //取私钥
PKCS8EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(pri);
PrivateKey priKey = keyFactory.generatePrivate(privKeySpec);Calendar now = Calendar.getInstance();
String minuteStr = CRED_TIME_FORMAT.format(now.getTime());
  
StringBuffer str = new StringBuffer();
//增加判定的其他参数
str.append(minuteStr).append(CRED_SEPERATOR).append(user).append(
CRED_SEPERATOR).append(self).append(CRED_SEPERATOR).append(
target);
//对数据签名
Signature sig = Signature.getInstance(signatureAlg);
sig.initSign(priKey);
sig.update(str.toString().getBytes("UTF8"));
byte[] signature = sig.sign();
  
String signatureStr = ByteUtils.encodeHex(signature);
str.append(CRED_SEPERATOR).append(signatureStr);

解析加密密钥部分参考

KeyFactory keyFactory = KeyFactory.getInstance(DSA);
//对公钥解密
//解析步骤:1 获取p_password参数,检查参数格式是否正确
//        2 将p_password拆解为两部分,最后一个:前一部分是signTarget单点信息摘要,后一部分是signatureTxt签名
//        3 设置算法signature参数,包括公钥,信息摘要和签名,调用验证,返回是否验证通过的结果ok
   String[] segments = cre.split(CRED_SEPERATOR);
        if (segments.length != 5) {
            logger.warn("票据应该是5段");
            return null;
        }
        //时间
        String ticketTime = segments[0];
        //登录用户名
        String ssoUser = segments[1];
        //源系统
        String srcSystem = segments[2];
        //目标系统
        String targetSystem = segments[3];
        //私钥签名
        String signatureTxt = segments[4];
 
 
String signTarget = cre.substring(0, cre.lastIndexOf(CRED_SEPERATOR));
 
 
 
byte pub[] = ByteUtils.decodeHex(publicKeyStr);
//取公钥
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(pub);
PublicKey pubKey = keyFactory.generatePublic(pubKeySpec);
  
byte[] signature = ByteUtils.decodeHex(signatureTxt);
Signature sig = Signature.getInstance(signatureAlg);
sig.initVerify(pubKey);
sig.update(signTarget.getBytes("UTF8"));
//验证签名
boolean ok = sig.verify(signature);

工具类ByteUtil

public class ByteUtils {
 
    /**
     * 转换md5码
     *
     * @param data 要转换的String
     * @return md5 md5码
     */
    public static final String MD5(String data) {
        try {
            MessageDigest digest = MessageDigest.getInstance("MD5");
            digest.update(data.getBytes("utf-8"));
            return encodeHex(digest.digest());
        } catch (Exception e) {
            throw new IllegalStateException(e.getMessage());
        }
    }
 
    /**
     * 将bytes字节转换成md5码
     *
     * @param bytes bytes
     * @return the string
     */
    public static final String encodeHex(byte bytes[]) {
        StringBuffer buf = new StringBuffer(bytes.length * 2);
        for (int i = 0; i < bytes.length; i++) {
            if ((bytes[i] & 0xff) < 16)
                buf.append("0");
            buf.append(Long.toString(bytes[i] & 0xff, 16));
        }
 
        return buf.toString();
    }
 
    /**
     * 字符串编码转为byte字节数组
     *
     * @param hex 要转换的字符串
     * @return byte[] 转换得到的byte编码
     */
    public static final byte[] decodeHex(String hex) {
        char chars[] = hex.toCharArray();
        byte bytes[] = new byte[chars.length / 2];
        int byteCount = 0;
        for (int i = 0; i < chars.length; i += 2) {
            int newByte = 0;
            newByte |= hexCharToByte(chars[i]);
            newByte <<= 4;
            newByte |= hexCharToByte(chars[i + 1]);
            bytes[byteCount] = (byte) newByte;
            byteCount++;
        }
 
        return bytes;
    }
 
    /**
     *
     */
    private static final byte hexCharToByte(char ch) {
        switch (ch) {
            case 48: // '0'
                return 0;
 
            case 49: // '1'
                return 1;
 
            case 50: // '2'
                return 2;
 
            case 51: // '3'
                return 3;
 
            case 52: // '4'
                return 4;
 
            case 53: // '5'
                return 5;
 
            case 54: // '6'
                return 6;
 
            case 55: // '7'
                return 7;
 
            case 56: // '8'
                return 8;
 
            case 57: // '9'
                return 9;
 
            case 97: // 'a'
                return 10;
 
            case 98: // 'b'
                return 11;
 
            case 99: // 'c'
                return 12;
 
            case 100: // 'd'
                return 13;
 
            case 101: // 'e'
                return 14;
 
            case 102: // 'f'
                return 15;
 
            case 58: // ':'
            case 59: // ';'
            case 60: // '<'
            case 61: // '='
            case 62: // '>'
            case 63: // '?'
            case 64: // '@'
            case 65: // 'A'
            case 66: // 'B'
            case 67: // 'C'
            case 68: // 'D'
            case 69: // 'E'
            case 70: // 'F'
            case 71: // 'G'
            case 72: // 'H'
            case 73: // 'I'
            case 74: // 'J'
            case 75: // 'K'
            case 76: // 'L'
            case 77: // 'M'
            case 78: // 'N'
            case 79: // 'O'
            case 80: // 'P'
            case 81: // 'Q'
            case 82: // 'R'
            case 83: // 'S'
            case 84: // 'T'
            case 85: // 'U'
            case 86: // 'V'
            case 87: // 'W'
            case 88: // 'X'
            case 89: // 'Y'
            case 90: // 'Z'
            case 91: // '['
            case 92: // '\\'
            case 93: // ']'
            case 94: // '^'
            case 95: // '_'
            case 96: // '`'
            default:
                return 0;
        }
    }
  

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值