Shiro自定義驗證器——使用國密sm3


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進制,它們又是相同的,不太明白為什么,,希望有大佬解答一下,感覺是一個字符編碼的問題。

image-20220301204852728


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM