背景
在搞一个政府类的项目时,要求用国密,网上抄了抄,给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
这哥们没封装工具类不好用,整体思路是对的,我这亲测好用 ,哈哈!