SpringBoot integrates Shiro to achieve one-click login and password-free login functions

premise:

It is probably such a situation. We have two platforms, one is the operation platform and the other is the use platform. Each of our operation and maintenance personnel manages many customer accounts under their hands, in order to facilitate operation and maintenance and avoid frequently asking users for passwords. , Then in this case we need to use our one-click login function,

First, we add a one-click login button in the customer account management system, and then click the link to log in directly to the homepage of the platform.

The general process is:

1: First add the code in the operation center, encrypt the designated token through RSA, and then carry the token and the designated login user name to encrypt and request to use the platform open interface.

2: Use the platform to add the decryption interface, and directly log in to the user without secret after the decryption is successful.

Not much to say about the code:

1: First, we need to rewrite the doCredentialsMatch that uses the platform's password verifier. If we log in without password, we will notify Shiro that the password is not required to verify the password and the authentication is successful, otherwise, the password needs to be verified.

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);
    }
}

Then configure into 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;
    }

Then start writing the decrypted interface:

/**
     * 远程一键登录平台
     *
     * @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";
    }

In this way, the interface for our one-click login is written. For the sake of safety, a setting that will time out directly after more than 30 seconds is set, and then the corresponding parameters are decrypted to log in.

The contents of the RemoteProperties file are as follows
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 content is as follows:

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 configuration of remote.auth:

#一键登录公钥私钥
remote:
  auth:
    host: http://127.0.0.1:8631/login
    rsa:
      public-key: 
      private-key:
      prefix: zjxf
      suffix: 

Regarding public-key and private-key, you can use RSA to generate a set of public and private keys. When encrypting, use the public key to encrypt, when decrypting, use the private key to decrypt, when operating the platform, use the public key to encrypt, and use the platform to decrypt. Use the private key to decrypt,

You can customize the prefix and suffix fields, or you don't need to set them. This is an encryption method.

Then you can directly access the one-click login address:

http://127.0.0.1:8631/apis/remote/oneClickLogin?authToken=SUEgd2h7ApystCI2kEu8aYVPSvcs/JaDcfpmRn5z7EFANq4mOqsbmy0d3GEdSawV8HjXjDIEORAQ5ihUdb2EmQ==&timestamp=1609920158950&userName=admin

In this way, you can log in directly to the content of the homepage of the platform.

Guess you like

Origin blog.csdn.net/qq_38821574/article/details/112285080