Shiro自定義驗證器——使用國密sm3
背景
在搞一個設計類的比賽,要求用國密,網上抄了抄,給Shiro改裝一下,我本來Shiro驗證用的是md5,因為sm3對標的是md5,所以現在就換成sm3
maven依賴
我用的是hutool的工具類,官網上說不需要導sm3那個依賴,但是我試了是不行的,所以還要導bcprov-jdk15on
<!-- shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.7.0</version>
</dependency>
<!-- sm3-->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.68</version>
</dependency>
<!-- Hutool-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.21</version>
</dependency>
登錄的原理就是比對密碼是否相等,我這里是最簡單的——比較加完salt和sm3進行hash后的密文是否和數據庫中用戶的密碼密文相同
修改登錄注冊
//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);
user.setPassword(password);
//1. 生成隨機鹽
String salt = SaltUtil.getSalt(8);
//2. 將生成的隨機鹽放入數據庫
user.setSalt(salt);
//3. 明文密碼進行sm3+salt+hash散列
SM3 sm3 = new SM3(salt.getBytes(StandardCharsets.UTF_8), 1024);
String digest = sm3.digest(user.getPassword()).toString();
user.setPassword(digest);
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);
}
到這邊就可以成功注冊完,就可以登錄了,兩次生成的密文是一樣的就登陸成功
新建驗證器
首先新建自己的驗證器類
@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();
SM3 sm3 = new SM3(salt,0, 1024);
//加密完的密碼
Object tokenCredentials = sm3.digestHex(String.valueOf(token.getPassword()));
Object accountCredentials = getCredentials(info);
// 將密碼加密與系統加密后的密碼校驗,內容一致就返回true,不一致就返回false
return equals(tokenCredentials, accountCredentials);
}
}
一個小問題
我使用Digest.digest()生成的byte[]輸出是不一樣的,但是調用Arrays.equals()他們是相同的,調用Digest.digestHex()以及把byte[]轉16進制,它們又是相同的,不太明白為什么,,希望有大佬解答一下,感覺是一個字符編碼的問題。