前提:
大概是这样一个情况,我们有两个平台,一个是运营平台,一个是使用平台,我们的每个运维人员手底下管理者许多的客户的账号,为了方便运维和避免经常问用户索要密码,那么在这种情况下就需要用到我们的一键登录功能啦,
首先我们在客户账号管理系统中添加一个一键登录的按钮,然后点击链接直接登录到使用平台首页。
大致流程是:
1:首先在运营中心添加代码,通过RSA加密指定的token令牌,然后携带令牌和指定登录的用户名称加密之后请求使用平台开放的接口。
2:使用平台添加解密的接口,解密成功之后直接对该用户进行免密登录。
话不多说上代码:
1:首先我们需要重写一下使用平台的密码验证器的doCredentialsMatch,如果我们登录的时候是免密登录的,就通知shiro不需要校验密码直接认证成功,反之则需要校验密码。
package com.zjxf.shiro;
import com.zjxf.bean.common.SysConst;
import com.zjxf.shiro.token.MyUserNamePasswordToken;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.springframework.context.annotation.Configuration;
/**
* created with IntelliJ IDEA
*
* @author: create by limu
* Date: 2021/1/6
* Time:16:41
*/
@Configuration
public class MyRetryLimitCredentialsMatcher extends HashedCredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) {
MyUserNamePasswordToken tk = (MyUserNamePasswordToken) authcToken;
if (tk.getLoginType().equals(SysConst.LoginType.ONE_CLICK.getType())) {
return Boolean.TRUE;
}
return super.doCredentialsMatch(authcToken, info);
}
}
然后配置到ShiroConfig中
/**
* 方法名:
* 功能:凭证匹配器
* 描述: 指定shiro加密方式和次数
*/
@Bean
public MyRetryLimitCredentialsMatcher hashedCredentialsMatcher() {
MyRetryLimitCredentialsMatcher hashedCredentialsMatcher = new MyRetryLimitCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName(SysConst.SHIRO_PASSWORD_TYPE_MD5);
hashedCredentialsMatcher.setHashIterations(SysConst.SHIRO_PASSWORD_COUNT);
return hashedCredentialsMatcher;
}
然后开始写解密的接口:
/**
* 远程一键登录平台
*
* @param authorization 登录凭证参数
* @param timestamp 时间标识
* @param userName 用户名称
* @return Result
*/
@GetMapping("oneClickLogin")
public String oneClickLogin(@RequestParam("authToken") String authorization, @RequestParam("timestamp") String timestamp, @RequestParam("userName") String userName, ServletResponse response) throws Exception {
if (StringUtils.isBlank(authorization) || StringUtils.isBlank(timestamp)) {
log.info("非法请求,禁止访问");
RequestResponseUtil.responseWrite(JSON.toJSONString("非法请求,禁止访问"), response);
} else {
if (DateUtils.isTimeOut(timestamp, 30)) {
RequestResponseUtil.responseWrite(JSON.toJSONString("请求已过时"), response);
} else {
RemoteProperties.RSA rsa = remoteProperties.getRsa();
String privateKey = rsa.getPrivateKey();
String suffix = rsa.getSuffix();
String prefix = rsa.getPrefix();
byte[] bytes = RSAUtils.decryptByPrivateKey(Base64.decode(authorization), Base64.decode(privateKey));
String authToken = new String(bytes);
if (authToken.startsWith(prefix) && authToken.endsWith(suffix)) {
String dateStr = authToken.replace(suffix, StringUtils.EMPTY).replace(prefix, StringUtils.EMPTY);
if (Objects.equals(dateStr, timestamp)) {
log.info("请求认证成功,已经放行");
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated()) {
subject.logout();
}
MyUserNamePasswordToken token = new MyUserNamePasswordToken(userName, SysConst.LoginType.ONE_CLICK.getType());
subject.login(token);
return "html/psychological";
}
} else {
log.info("非法请求,禁止访问");
RequestResponseUtil.responseWrite(JSON.toJSONString("非法请求,禁止访问"), response);
}
}
}
return "login";
}
这样我们一键登录的接口就写好了,为了安全起见,设置了一个超过30秒就直接超时的一个设置,然后解密出来相对应的参数去做登录就可以了
RemoteProperties文件内容如下
package com.zjxf.config.properties;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* created with IntelliJ IDEA
*
* @author: create by limu
* Date: 2019/11/21
* Time:11:50
*/
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "remote.auth")
public class RemoteProperties {
private String host;
private RSA rsa = new RSA();
@Getter
@Setter
public static class RSA {
private String privateKey;
private String publicKey;
private String prefix;
private String suffix;
}
}
RSAUtils内容如下:
package com.zjxf.utils;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
/**
* Created with IntelliJ IDEA.
*
* @author zhanghao
* date: 2018/12/13 16:37
* description: 非对称加密算法RSA算法组件
* 非对称算法一般是用来传送对称加密算法的密钥来使用的,相对于DH算法,RSA算法只需要一方构造密钥,不需要
* 大费周章的构造各自本地的密钥对了。DH算法只能算法非对称算法的底层实现。而RSA算法算法实现起来较为简单
*/
public class RSAUtils {
//非对称密钥算法
private static final String KEY_ALGORITHM = "RSA";
/**
* 密钥长度,DH算法的默认密钥长度是1024
* 密钥长度必须是64的倍数,在512到65536位之间
*/
private static final int KEY_SIZE = 512;
//公钥
private static final String PUBLIC_KEY = "RSAPublicKey";
//私钥
private static final String PRIVATE_KEY = "RSAPrivateKey";
/**
* 初始化密钥对
*
* @return Map 甲方密钥的Map
*/
public static Map<String, Object> initKey() throws Exception {
//实例化密钥生成器
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM);
//初始化密钥生成器
keyPairGenerator.initialize(KEY_SIZE);
//生成密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
//甲方公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
//甲方私钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
//将密钥存储在map中
Map<String, Object> keyMap = new HashMap<String, Object>();
keyMap.put(PUBLIC_KEY, publicKey);
keyMap.put(PRIVATE_KEY, privateKey);
return keyMap;
}
/**
* 私钥加密
*
* @param data 待加密数据
* @param key 密钥
* @return byte[] 加密数据
*/
public static byte[] encryptByPrivateKey(byte[] data, byte[] key) throws Exception {
//取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
//生成私钥
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
//数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 公钥加密
*
* @param data 待加密数据
* @param key 密钥
* @return byte[] 加密数据
*/
public static byte[] encryptByPublicKey(byte[] data, byte[] key) throws Exception {
//实例化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
//初始化公钥
//密钥材料转换
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
//产生公钥
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
//数据加密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
return cipher.doFinal(data);
}
/**
* 私钥解密
*
* @param data 待解密数据
* @param key 密钥
* @return byte[] 解密数据
*/
public static byte[] decryptByPrivateKey(byte[] data, byte[] key) throws Exception {
//取得私钥
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(key);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
//生成私钥
PrivateKey privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
//数据解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
}
/**
* 公钥解密
*
* @param data 待解密数据
* @param key 密钥
* @return byte[] 解密数据
*/
public static byte[] decryptByPublicKey(byte[] data, byte[] key) throws Exception {
//实例化密钥工厂
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
//初始化公钥
//密钥材料转换
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(key);
//产生公钥
PublicKey pubKey = keyFactory.generatePublic(x509KeySpec);
//数据解密
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, pubKey);
return cipher.doFinal(data);
}
/**
* 取得私钥
*
* @param keyMap 密钥map
* @return byte[] 私钥
*/
public static byte[] getPrivateKey(Map<String, Object> keyMap) {
Key key = (Key) keyMap.get(PRIVATE_KEY);
return key.getEncoded();
}
/**
* 取得公钥
*
* @param keyMap 密钥map
* @return byte[] 公钥
*/
public static byte[] getPublicKey(Map<String, Object> keyMap) throws Exception {
Key key = (Key) keyMap.get(PUBLIC_KEY);
return key.getEncoded();
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
//初始化密钥
// //生成密钥对
Map<String, Object> keyMap = RSAUtils.initKey();
// //公钥
byte[] publicKey = RSAUtils.getPublicKey(keyMap);
//
// //私钥
byte[] privateKey = RSAUtils.getPrivateKey(keyMap);
String publicStr = Base64.encodeBase64String(publicKey);
String privateStr = Base64.encodeBase64String(privateKey);
// String publicStr = "MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAMFQhv/9PUaaG/7WfkW3P/6jTa2ed1dTNrr09pw3Jt+VU/etcKwobgpu+QD8brDzFp3TaIhPee+W7b39kCRLzlkCAwEAAQ==";
// String privateStr = "MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAwVCG//09Rpob/tZ+Rbc//qNNrZ53V1M2uvT2nDcm35VT961wrChuCm75APxusPMWndNoiE9575btvf2QJEvOWQIDAQABAkBpuSK76cGTVUEuVBxnAFttZd5br6jRB1+NS997e+Y0rD8tGEEVPkIzX024OikCIjpoRZwZJFZFnTYTg2UM8b+hAiEA/B4OTyvQG3BwNy515diUGUNibmLAtx4g1/Rye1bHbN0CIQDESqXfQGHBMrCup8WJV9OX9JNQwDP8Cz9Y9s07Di3hrQIga02Lf4zBLPyE9idzDFlKZxoz6ZFkPku3ZNJoazA6/o0CIA6ydCb6IBeiHv6Ey1KUQ+CNzHXwXjQR94VGvWkdj6vlAiEAsfZ0Z8JMEp0ywT4FG/z9q1VBHdPAwPSYEVQbmQkGhaw=";
System.out.println("公钥:\n" + publicStr);
System.out.println("私钥:\n" + privateStr);
System.out.println("================密钥对构造完毕 开始进行加密数据的传输=============");
System.out.println("\n===========公钥加密==============");
long timeMillis = System.currentTimeMillis();
System.out.println("timeMillis = " + timeMillis);
String str = "zkdj" + timeMillis + "yuqingguanjia@!123";
System.out.println("原文:" + str);
//公钥加密
byte[] code1 = RSAUtils.encryptByPublicKey(str.getBytes(), Base64.decodeBase64(publicStr));
System.out.println("加密后的数据:" + Base64.encodeBase64String(code1));
System.out.println("===========私钥解密==============");
//乙方进行数据的解密
byte[] decode1 = RSAUtils.decryptByPrivateKey(code1, Base64.decodeBase64(privateStr));
System.out.println("解密后的数据:" + new String(decode1) + "\n\n");
// System.out.println("===========反向进行操作,乙方向甲方发送数据==============\n\n");
//
// str = "乙方向甲方发送数据RSA算法";
//
// System.out.println("原文:" + str);
//
// //乙方使用公钥对数据进行加密
// byte[] code2 = RSAUtils.encryptByPublicKey(str.getBytes(), publicKey);
// System.out.println("===========乙方使用公钥对数据进行加密==============");
// System.out.println("加密后的数据:" + Base64.encodeBase64String(code2));
//
// System.out.println("=============乙方将数据传送给甲方======================");
// System.out.println("===========甲方使用私钥对数据进行解密==============");
//
// //甲方使用私钥对数据进行解密
// byte[] decode2 = RSAUtils.decryptByPrivateKey(code2, privateKey);
//
// System.out.println("甲方解密后的数据:" + new String(decode2));
}
}
application.yml关于remote.auth的配置:
#一键登录公钥私钥
remote:
auth:
host: http://127.0.0.1:8631/login
rsa:
public-key:
private-key:
prefix: zjxf
suffix:
关于public-key和private-key大家可以利用RSA生成一组公钥和私钥,加密的时候用公钥加密,解密的时候用私钥解密,运营平台加密的时候用公钥加密,使用平台解密的时候用私钥解密,
prefix和suffix字段大家可以自定义设置,也可以不设置,这就是一种加密手段。
然后可以直接访问一键登录的地址就可以了:
这样就可以直接登录到使用平台的首页内容了。