Shiro custom authenticator - using national secret sm3+ salt

background

When I was working on a government project, I was asked to use state secrets. I copied them online and modified them for Shiro. I originally used md5 for Shiro verification, because sm3 is benchmarked against md5, so I changed it to sm3 now.

maven dependency

I am using the hutool tool class. The official website says that there is no need to import the sm3 dependency, but it didn’t work when I tried it, so I have to import bcprov-jdk15on.

<!-- shiro -->
       <dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-core</artifactId>
			<version>1.12.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.12.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-cas</artifactId>
			<version>1.12.0</version>
			<exclusions>
				<exclusion>
					<groupId>commons-logging</groupId>
					<artifactId>commons-logging</artifactId>
				</exclusion>
			</exclusions>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-web</artifactId>
			<version>1.12.0</version>
		</dependency>
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-ehcache</artifactId>
			<version>1.12.0</version>
		</dependency>
<!--        sm3-->
       	<dependency>
			<groupId>org.bouncycastle</groupId>
			<artifactId>bcprov-jdk15on</artifactId>
			<version>1.70</version>
		</dependency>
<!--        Hutool-->
       	<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.8.11</version>
		</dependency>

The principle of login is to compare whether the password is equal. Here is the simplest one - compare whether the hashed ciphertext after adding salt and sm3 is the same as the user's password ciphertext in the database.

Tools

import org.bouncycastle.crypto.digests.SM3Digest;
import org.bouncycastle.util.encoders.Hex;

import java.security.Security;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

public class SM3SaltEncryption {
    public static void main(String[] args) {
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());

        // 原始数据
        byte[] data = "Hello, World!".getBytes();

        // 生成随机的盐值
        byte[] salt = generateSalt();

        // 将原始数据与盐值拼接
        byte[] dataWithSalt = concatBytes(data, salt);

        // 计算SM3哈希值
        byte[] hash = calculateHash(dataWithSalt);

        // 将盐值和哈希值转换为十六进制字符串
        String saltHex = bytesToHex(salt);
        String hashHex = bytesToHex(hash);

        System.out.println("Salt: " + saltHex);
        System.out.println("Hash: " + hashHex);
    }

    public static String encrypt(String paramStr,byte[]  salt){
        Map<String,String> resultMap=new HashMap<>();
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
        // 原始数据
        byte[] data = paramStr.getBytes();

        // 将原始数据与盐值拼接
        byte[] dataWithSalt = concatBytes(data, salt);

        // 计算SM3哈希值
        byte[] hash = calculateHash(dataWithSalt);

        // 将盐值和哈希值转换为十六进制字符串
        String hashHex = bytesToHex(hash);
        return  hashHex;
    }

     public static String entryptSM3Password(String plainPassword) {
        byte[] bytesSalt = generateSalt();
        String sm3Password= encrypt(plainPassword,bytesSalt);
        return bytesToHex(bytesSalt)+sm3Password;
    }

    public static byte[] generateSalt() {
        byte[] salt = new byte[8];
        new Random().nextBytes(salt);
        return salt;
    }

    private static byte[] concatBytes(byte[] a, byte[] b) {
        byte[] result = Arrays.copyOf(a, a.length + b.length);
        System.arraycopy(b, 0, result, a.length, b.length);
        return result;
    }

    private static byte[] calculateHash(byte[] input) {
        SM3Digest digest = new SM3Digest();
        digest.update(input, 0, input.length);
        byte[] result = new byte[digest.getDigestSize()];
        digest.doFinal(result, 0);
        return result;
    }

    public static String bytesToHex(byte[] bytes) {
        return Hex.toHexString(bytes);
    }
}

Modify login registration

//Json是我自定义的结果类
//用户注册,用hutool里的SM3和自建的盐工具加密,具体可以看点进去看源码 
@Override
    public Json register(String username, String password) {

        if(this.getOne(new QueryWrapper<User>().eq("user_name",username))!=null){
            return Json.fail(ResponseUtil.CREATE_CONFLICT,"用户名重复");
        }

        //处理业务调用dao
        User user=new User();
        user.setId(UUIDUtil.generateRandomUUID());
        user.setUserName(username);
         

        //调用工具类SM3SaltEncryption 实现sm3加盐加密
        user.setPassword(SM3SaltEncryption.entryptSM3Password(password));


        userMapper.insert(user);
        return Json.success("注册成功");
    }

//用户登录,这里和之前比没有区别,因为它们都是调用subject.login()方法,最后会进入realm里执行doGetAuthenticationInfo方法
@Override
    public Json login(String username, String password) {
        String USER_LOGIN_TYPE = LoginType.USER.toString();
        Subject subject = SecurityUtils.getSubject();   //主体
        UserToken token = new UserToken(username,password,USER_LOGIN_TYPE);
        try {
            // 会进入到doGetAuthenticationInfo,进行身份验证
            subject.login(token);
        } catch (UnknownAccountException e) {
            // 账号不存在
            return Json.fail(ResponseUtil.LOGIN_FAILURE);
        } catch (IncorrectCredentialsException e) {
            // 密码错误
            return Json.fail(ResponseUtil.LOGIN_FAILURE);
        }
        // 向token中写入username
        Map<String, String> claims = new HashMap<>();
        claims.put("username", username);
        // 回传token
        Map<String, Object> map = new HashMap<>();
        map.put("token", TokenUtil.generateToken(claims));
        map.put("user", username);
        return Json.result(ResponseUtil.LOGIN_SUCCESS,map);
    }

Modify the self-built Realm class

I only put authentication related things here. Authentication is no different from the original one. The key point is to rewrite setCredentialsMatcher (setting the encryption method of authentication)

//认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

        // 获取身份信息(用户名)
        String principal = (String) authenticationToken.getPrincipal();

        //根据数据库查询用户名信息
        User user = userService
                .getOne(new QueryWrapper<User>().eq("user_name", principal));
        if (user == null) {
            return null;
        }
        return new SimpleAuthenticationInfo(
                principal,                              // 数据库的账号
                user.getPassword(),                     // 加密后的密码
                ByteSource.Util.bytes(user.getSalt()),  // 加上盐值
                getName());
    }


    //设置认证加密方式,登录密码校验的时候就会调用设置好的这个验证类里的验证方法,之前新建验证器里已经写好了
    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {

        SM3CredentialsMatcher sm3CredentialsMatcher = new SM3CredentialsMatcher();
        super.setCredentialsMatcher(sm3CredentialsMatcher);
    }

Once you have successfully registered here, you can log in. If the ciphertext generated twice is the same, you will be logged in successfully.

Create new validator

First create your own validator class

import com.thinkgem.jeesite.common.utils.SM3SaltEncryption;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.springframework.stereotype.Component;

@Component
public class SM3CredentialsMatcher extends SimpleCredentialsMatcher {
    //登录的时候回调用这个方法进行密码比对
    @Override
    public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) {
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
        SimpleAuthenticationInfo simpleAuthenticationInfo = (SimpleAuthenticationInfo) info;
        //获取salt
        byte[] salt = simpleAuthenticationInfo.getCredentialsSalt().getBytes();
        String encrypt = SM3SaltEncryption.encrypt(String.valueOf(token.getPassword()), salt);
        Object accountCredentials = getCredentials(info);
        // 将密码加密与系统加密后的密码校验,内容一致就返回true,不一致就返回false
        return equals(encrypt, accountCredentials);
    }

}

参考:https://www.cnblogs.com/YuChun9293/p/15952616.html 

This guy didn’t encapsulate the tool class and it’s hard to use. The overall idea is right. I personally tested it and it works, haha!

Guess you like

Origin blog.csdn.net/zhaofuqiangmycomm/article/details/133645891