单点登录之一(简单签名验证):在自己的平台系统上建立单点登录验证,以便集成第三方系统的接入

1.签名验证

平台提供apiKey和apiSecret给第三方,apiKey可明文传输,表明身份并用作加密参数之一,apiSecret双方都私密保存,根据apiKey查找并用作加密参数之一。
逻辑简单来说就是:双方都知道apiKey(公钥)和apiSecret(密钥),每次调用接口,双方都根据公钥和密钥进行加密,得到sign签名值,第三方把sign做为参数传给平台,平台在后台把自己加密的sign和参数sign对比是否相等即可。

1.1签名加密

为了登录时效性,需要增加参数timestamp(时间戳)以便验证是否登录过期
加密字符串可自定义拼接方式,加密方式可以选任意,比如MD5,如下

	/**
     * 签名 apiKey+"&"+userId+"&"+apiSecret+"&"+timestamp 拼接成的字符串, 然后进行 MD5 运算, 得到 sign 值
     * @return sign
     */
    public static String signature(String apiKey, String userId, String apiSecret, Long timestamp) {
        StringBuffer buffer = new StringBuffer();
        buffer.append(apiKey + "&").append(userId + "&").append(apiSecret + "&").append(String.valueOf(timestamp));
        return Tools.md5Encrypt(buffer.toString());
    }

这里得到的sign值,用于第三方调接口时与参数sign进行比较,相同则通过。

当然,签名也可以按照实际需求增加更多的参数,比如用户类型等,如下:

	/**
     * 签名 apiKey+"&"+userId+"&"+apiSecret+"&"+timestamp+"&"+dataType 拼接成的字符串, 然后进行 MD5 运算, 得到 sign 值
     * @return sign
     */
    public static String signature(String apiKey, String userId, String apiSecret, Long timestamp, Integer dataType) {
        StringBuffer buffer = new StringBuffer();
        buffer.append(apiKey + "&").append(userId + "&").append(apiSecret + "&")
                .append(String.valueOf(timestamp) + "&").append(String.valueOf(dataType));
        return Tools.md5Encrypt(buffer.toString());
    }

完整代码,签名工具类

package net.firstelite.commons.util;

import org.apache.commons.lang3.StringUtils;

/**
 * @Author: Larry
 * @Date: 2023/5/22 11:00
 * 单点登录签名工具类
 */
public class SingleUtil {
    private static final int EXPIRE_TIME = 30; //过期时间 分钟

    /**
     * 验证是否过期
     * @return
     */
    public static boolean isExpire(Long timestamp) {
//        if (StringUtils.isBlank(timestamp)) {
//            return false;
//        }
//        if (!timestamp.matches("^[0-9]*$")) {
//            return false;
//        }
        if (System.currentTimeMillis() - timestamp > EXPIRE_TIME * 60 * 1000) {
            return true; //过期
        }
        return false;
    }

    /**
     * 签名 apiKey+"&"+userId+"&"+apiSecret+"&"+timestamp 拼接成的字符串, 然后进行 MD5 运算, 得到 sign 值
     * @return sign
     */
    public static String signature(String apiKey, String userId, String apiSecret, Long timestamp) {
        StringBuffer buffer = new StringBuffer();
        buffer.append(apiKey + "&").append(userId + "&").append(apiSecret + "&").append(String.valueOf(timestamp));
        return Tools.md5Encrypt(buffer.toString());
    }


    /**
     * 签名 apiKey+"&"+userId+"&"+apiSecret+"&"+timestamp+"&"+dataType 拼接成的字符串, 然后进行 MD5 运算, 得到 sign 值
     * @return sign
     */
    public static String signature(String apiKey, String userId, String apiSecret, Long timestamp, Integer dataType) {
        StringBuffer buffer = new StringBuffer();
        buffer.append(apiKey + "&").append(userId + "&").append(apiSecret + "&")
                .append(String.valueOf(timestamp) + "&").append(String.valueOf(dataType));
        return Tools.md5Encrypt(buffer.toString());
    }

    public static void main(String[] args) {
        System.out.println(System.currentTimeMillis());
        System.out.println(signature("3e44cbf4c78d4d7e891c", "ee8f354ed8634e64bb5c", "4a8aebe1527c471296f3", 1694071099344L));
        System.out.println(signature("3e44cbf4c78d4d7e891c", "6a4d6da287cf4ffd93d8", "4a8aebe1527c471296f3", 1695027256825L, 2));
    }
}

1.2签名验证

第三方接入系统,调用平台接口,传参为signature定义的参数,除过apiSecret,外加1.1提到的sign值,例如获取用户登录信息接口

	@RequestMapping(value = "/getUserInfo", method = RequestMethod.GET)
    @ApiOperation(value = "获取登录用户信息", notes = "获取登录用户信息")
    @ApiResponses({@ApiResponse(code = 200, response = UserInfo.class, message = "返回信息:点击下方Model查看注释详情")})
    public Result getUserInfo(String apiKey, String userId, Long timestamp, String sign) {
        Result result = new Result();
        try {
            if (StringUtils.isBlank(apiKey)) return fail("apiKey不能为空");
            if (StringUtils.isBlank(userId)) return fail("用户ID不能为空");
            if (Objects.isNull(timestamp)) return fail("时间戳不能为空");
            if (StringUtils.isBlank(sign)) return fail("签名不能为空");
            if (SingleUtil.isExpire(timestamp)) return fail("签名已过期,请重新尝试");
            
			//从自身系统根据apiKey查询保密的apiSecret
            RelatedSystemInfo systemInfo = systemInfoService.getOne(new LambdaQueryWrapper<RelatedSystemInfo>()
                    .eq(RelatedSystemInfo::getApiKey, apiKey));
            if (Objects.isNull(systemInfo) || Objects.isNull(systemInfo.getApiSecret())) return fail("未找到相关apiKey");
            String apiSecret = systemInfo.getApiSecret();
            //将参数签名后与sign进行比较,相同则通过,反之验证失败
            if (!Objects.equals(SingleUtil.signature(apiKey, userId, apiSecret, timestamp), sign))
                return fail("签名验证失败");
			//验证通过
			//进入业务逻辑,查询用户信息,根据自身系统自定义UserInfo类
			UserInfo userInfo = new UserInfo();
            //...
            result.setData(userInfo);
        } catch (Exception e) {
            e.printStackTrace();
            return except(e);
        }
        return result;
    }

到这里估计有人就问,这里的userId哪里来的,接着看第二部分,用户数据同步

2.用户数据同步

当我们的签名方式及验证都完成通过之后,就需要把平台的用户数据同步给第三方,这样双方才能知道是谁要登录系统。

2.1增加数据同步接口

这里的接口可按照需求,增加相应的参数来限制数据权限,比如用户类型,用户级别,用户所属单位等。平台不可能把所有数据都给第三方,这里的userId是平台提供给第三方的默认或者初始userId,当然也可以用apiKey代替,意义上就是用来区分需要获取哪些数据返回给第三方。

	@RequestMapping(value = "/getUserData", method = RequestMethod.GET)
    @ApiOperation(value = "获取用户数据", notes = "获取用户数据")
    @ApiResponses({@ApiResponse(code = 200, response = UserDataVO.class, message = "返回信息:点击下方Model查看注释详情")})
    public Result getUserData(String apiKey, Integer dataType, String userId, Long timestamp, String sign) {
        try {
            if (StringUtils.isBlank(apiKey)) return fail("apiKey不能为空");
            if (Objects.isNull(dataType)) return fail("数据类型不能为空");
            if (StringUtils.isBlank(userId)) return fail("用户ID不能为空");
            if (Objects.isNull(timestamp)) return fail("时间戳不能为空");
            if (StringUtils.isBlank(sign)) return fail("签名不能为空");
            if (SingleUtil.isExpire(timestamp)) return fail("签名已过期,请重新尝试");

            RelatedSystemInfo systemInfo = systemInfoService.getOne(new LambdaQueryWrapper<RelatedSystemInfo>()
                    .eq(RelatedSystemInfo::getApiKey, apiKey));
            if (Objects.isNull(systemInfo) || Objects.isNull(systemInfo.getApiSecret())) return fail("未找到相关apiKey");
            String apiSecret = systemInfo.getApiSecret();
            if (!Objects.equals(SingleUtil.signature(apiKey, userId, apiSecret, timestamp, dataType), sign))
                return fail("签名验证失败");
                
			//验证通过
			//进入业务逻辑,查询用户数据信息,根据自身系统自定义UserDataVO类
			UserDataVO userDataVO = new UserDataVO();
            //...
            result.setData(userDataVO);
      		
        } catch (Exception e) {
            e.printStackTrace();
            return except(e);
        }
        return result;
    }

2.2 保证数据的时效性

(1)第三方需要定时任务调用获取数据的接口getUserData,比如每天晚上03:00

(2)第三方getUserInfo获取到登录用户信息时,需要根据用户信息的(updateTime)更新时间字段是否变更,来实时更新当前用户的信息,以保证数据为最新的。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值