rooobot / architecture-training

Architecture training camp homework
0 stars 2 forks source link

架构师训练营-第十一周练习:作业 #24

Open rooobot opened 4 years ago

rooobot commented 4 years ago

问题

请用熟悉的语言编写一个密码验证函数,返回密码是否正确的boolean值,密码加密算法使用你认为合适的加密算法。

rooobot commented 4 years ago

问题中函数的签名直接是三个参数,因为我平时做这个功能时,salt值不是通过ID来按一定的规则生成的,而是直接使用UUID随机生成的,所以,我这里的实现函数签名有一点不太一样。

直接上代码:

密码校验工具类代码如下:

/**
 * @author zhaoyang on 2020-08-25.
 */
public class PasswdCheckUtils {

    private static final String PASSWD_ECD_TPL = "_(%s:%s)_";

    /**
     * 密码校验:
     *   校验时,第一个参数传入的是前端传过来的密码,一般也不会传明文,会用一定的策略进行单向散列处理。
     *   因为我个人在存储用户密码时,使用的 salt 是通过 UUID 生成的纯随机字符串,不是按某种相同的
     *   规则来生成的,所以,这里将存储的用户密码和 salt 包装到 UserInfo 类中,这种方式可以最大化
     *   随机性。
     * @param plainPasswd 密码明文
     * @param userInfo 封装了用户ID、密码和 salt
     * @return boolean 校验结果
     */
    public boolean checkPW(String plainPasswd, UserInfo userInfo) {
        assert StringUtils.isNotBlank(plainPasswd);
        assert userInfo != null;
        assert StringUtils.isNotBlank(userInfo.getPasswd());
        assert StringUtils.isNotBlank(userInfo.getSalt());
        String encodedPasswd = encode(plainPasswd, userInfo.getSalt());
        if (encodedPasswd.equals(userInfo.getPasswd())) {
            return true;
        }
        return false;
    }

    private String encode(String plainPasswd, String salt) {
        String passwd = plainPasswd;
        int times = 3;
        while (--times >= 0) {
            passwd = StringUtils.md5(String.format(PASSWD_ECD_TPL, passwd, salt));
        }
        return passwd;
    }

}

辅助用户类代码:

import java.io.Serializable;

/**
 * @author zhaoyang on 2020-08-25.
 */
public class UserInfo implements Serializable {

    /**
     * 用户ID
     */
    private String userID;
    /**
     * 加密后的用户密码
     */
    private String passwd;
    /**
     * 密码加密的盐值,每个用户不同,UUID生成
     */
    private String salt;

    public String getUserID() {
        return userID;
    }

    public void setUserID(String userID) {
        this.userID = userID;
    }

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String passwd) {
        this.passwd = passwd;
    }

    public String getSalt() {
        return salt;
    }

    public void setSalt(String salt) {
        this.salt = salt;
    }
}

工具类:

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * @author zhaoyang on 2020-08-25.
 */
public final class StringUtils {

    private static final char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    private static MessageDigest md5;

    static {
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalArgumentException("Initialize MD5 with MessageDigest exception", e);
        }
    }

    public static boolean isBlank(String str) {
        return str == null || "".equals(str);
    }

    public static boolean isNotBlank(String str) {
        return !isBlank(str);
    }

    public static String md5(String key) {
        try {
            md5 = MessageDigest.getInstance("MD5");
            byte[] buf = key.getBytes(StandardCharsets.UTF_8);
            md5.update(buf, 0, buf.length);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }

        byte[] encodedValue = md5.digest();

        int j = encodedValue.length;
        char finalValue[] = new char[j * 2];
        int k = 0;
        for (int i = 0; i < j; i++) {
            byte encoded = encodedValue[i];
            finalValue[k++] = hexDigits[encoded >> 4 & 0xf];
            finalValue[k++] = hexDigits[encoded & 0xf];
        }

        return new String(finalValue);
    }

}

测试用例:

import org.junit.Before;
import org.junit.Test;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

/**
 * @author zhaoyang on 2020-08-25.
 */
public class TestPasswdCheckUtils {

    private UserInfo mockUserInfo;

    @Before
    public void setUp() {
        mockUserInfo = new UserInfo();
        mockUserInfo.setUserID("tom");
        // plain password is "aaa"
        mockUserInfo.setPasswd("058451f1fa61e38931ed6c5861094b75");
        mockUserInfo.setSalt("bbb");
    }

    @Test
    public void TestCheckPwTrue() {
        PasswdCheckUtils checkUtils = new PasswdCheckUtils();
        boolean result = checkUtils.checkPW("aaa", mockUserInfo);
        assertTrue("password is correct", result);
    }

    @Test
    public void TestCheckPwFalse() {
        PasswdCheckUtils checkUtils = new PasswdCheckUtils();
        boolean result = checkUtils.checkPW("aaaa", mockUserInfo);
        assertFalse("password is not correct", result);
    }

}

以上为全部代码。

rooobot commented 4 years ago

测试用例的测试结果:

Testing started at 11:13 上午 ...
> Task :cleanTest
> Task :compileJava UP-TO-DATE
> Task :processResources UP-TO-DATE
> Task :classes UP-TO-DATE
> Task :compileTestJava
> Task :processTestResources NO-SOURCE
> Task :testClasses
> Task :test
me.zy.passwdchk.TestPasswdCheckUtils > TestCheckPwTrue PASSED
me.zy.passwdchk.TestPasswdCheckUtils > TestCheckPwFalse PASSED
BUILD SUCCESSFUL in 1s
5 actionable tasks: 3 executed, 2 up-to-date
11:13:03 上午: Tasks execution finished ':cleanTest :test --tests "me.zy.passwdchk.TestPasswdCheckUtils"'.

两个测试用例全部测试通过。