Shiro自定义验证器——使用国密sm3+盐

背景

在搞一个政府类的项目时,要求用国密,网上抄了抄,给Shiro改装一下,我本来Shiro验证用的是md5,因为sm3对标的是md5,所以现在就换成sm3

maven依赖

我用的是hutool的工具类,官网上说不需要导sm3那个依赖,但是我试了是不行的,所以还要导bcprov-jdk15on

java 复制代码
<!-- 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>

登录的原理就是比对密码是否相等,我这里是最简单的------比较加完salt和sm3进行hash后的密文是否和数据库中用户的密码密文相同

工具类

java 复制代码
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);
    }
}

修改登录注册

java 复制代码
//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);
    }

修改自建Realm类

我这里只放认证相关,认证和原来比没有区别,重点是重写setCredentialsMatcher(设置认证的加密方式)

//认证
    @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);
    }

到这边就可以成功注册完,就可以登录了,两次生成的密文是一样的就登陆成功

新建验证器

首先新建自己的验证器类

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

这哥们没封装工具类不好用,整体思路是对的,我这亲测好用 ,哈哈!

相关推荐
吾日三省吾码27 分钟前
JVM 性能调优
java
stm 学习ing32 分钟前
FPGA 第十讲 避免latch的产生
c语言·开发语言·单片机·嵌入式硬件·fpga开发·fpga
湫ccc1 小时前
《Python基础》之字符串格式化输出
开发语言·python
弗拉唐1 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi772 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
mqiqe2 小时前
Python MySQL通过Binlog 获取变更记录 恢复数据
开发语言·python·mysql
AttackingLin2 小时前
2024强网杯--babyheap house of apple2解法
linux·开发语言·python
少说多做3432 小时前
Android 不同情况下使用 runOnUiThread
android·java