The Shrio framework implements custom password verification rules

Shrio has some built-in password verification rules, and can also implement simple customization, such as algorithm type, hash times, etc., but sometimes we have some special password verification rules that need to be customized to achieve

1. How is Shiro's password verification done?

After we finish the basic work such as parameter verification and verification code matching in the login method, we will pass the username and password into the framework through subject.login(auth) for user matching, but where is the data to be matched? ?

UsernamePasswordToken auth = new UsernamePasswordToken(username, password,false);
Subject subject = SecurityUtils.getSubject();
subject.login(auth);

The answer is to match the AuthenticationInfo defined in the custom Realm

You can see through the assertCredentialsMatch method of the Shiro framework source code AuthenticatingRealm class that two parameters are passed in, token is the UsernamePasswordToken object passed in our login method, and info is the info object we defined in the doGetAuthenticationInfo method in DingTalk-Realm.

Shiro framework verification:

//shiro源码--用户信息认证 对比
 protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
        CredentialsMatcher cm = this.getCredentialsMatcher();
        if (cm != null) {
            //信息认证
            if (!cm.doCredentialsMatch(token, info)) {
                String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
                throw new IncorrectCredentialsException(msg);
            }
        } else {
            throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify credentials during authentication.  If you do not wish for credentials to be examined, you can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
        }
    }

It needs to be clarified here that the former of doGetAuthenticationInfo and doGetAuthorizationInfo defines user authentication information, while the latter defines user authority information, which is a bit confusing.

2. Implement a custom Realm:

public class ShiroFileRealm extends AuthorizingRealm implements InitializingBean {
	
	@Autowired
	public void setCredentialsDigest(CredentialsDigest credentialsDigest) {
		this.credentialsDigest = credentialsDigest;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken authcToken) throws AuthenticationException {
		UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
		User user = userService.getUser(token.getUsername());
		if (user != null) {
            //此处返回的对象就是上面的info
			return new SimpleAuthenticationInfo(new ShiroUser(user.getUsername()), user.getPassword(),getName());
		}
		return null;
	}

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		//自定义用户权限
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		
		return info;
	}
	/**
	 * 设定密码校验规则
	 */
	public void afterPropertiesSet() throws Exception {
		CredentialsMatcher matcher = new CredentialsMatcherAdapter(credentialsDigest);
		setCredentialsMatcher(matcher);
	}

}

SimpleAuthenticationInfo in the above code is the returned user authentication information, and it is also the info for comparison in the framework

Well, the authentication process is basically understood, we need to implement custom verification, how to do it?

3. Implement a custom CredentialsMatcher authentication class

/**
	 * 设定密码校验规则
	 */
	public void afterPropertiesSet() throws Exception {
		CredentialsMatcher matcher = new CredentialsMatcherAdapter(credentialsDigest);
		//设置自定义的用户信息认证类
        setCredentialsMatcher(matcher);
	}

The key is here, CredentialsMatcherAdapter is a custom class that implements the CredentialsMatcher interface

public class CredentialsMatcherAdapter implements CredentialsMatcher {
    private CredentialsDigest credentialsDigest;

    public CredentialsMatcherAdapter(CredentialsDigest credentialsDigest) {
        Assert.notNull(credentialsDigest, "The argument must not be null");
        this.credentialsDigest = credentialsDigest;
    }

    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        String plainCredentials, credentials;
        byte[] saltByte = null;
        plainCredentials = toStringCredentials(token.getCredentials());
        if (info instanceof SaltedAuthenticationInfo) {
            ByteSource salt = ((SaltedAuthenticationInfo) info).getCredentialsSalt();
            if (salt != null) saltByte = salt.getBytes();
        }
        credentials = toStringCredentials(info.getCredentials());
        if(saltByte != null){
            return credentialsDigest.matches(credentials, plainCredentials, saltByte);
        }
        return credentialsDigest.matches(credentials, plainCredentials);
    }

    private static String toStringCredentials(Object credentials) {
        if (credentials == null) {
            return null;
        } else if (credentials instanceof String) {
            return (String) credentials;
        } else if (credentials instanceof char[]) {
            return new String((char[]) credentials);
        } else {
            throw new IllegalArgumentException("credentials only support String or char[].");
        }
    }
}

The doCredentialsMatch method is a method for verifying and comparing user information. The verification process of the Shiro framework above (cm.doCredentialsMatch(token, info)) will come to our method here.

The above credentialsDigest.matches(credentials, plainCredentials); is my own password comparison method

There are salt-added mode and no-salt mode

Post it for your reference

interface

public interface CredentialsDigest {
    String digest(String plainCredentials, byte[] salt);
    boolean matches(String credentials, String plainCredentials, byte[] salt);
    boolean matches(String credentials, String plainCredentials);
}

Implementation class

public  abstract class HashCredentialsDigest implements CredentialsDigest {
    static final int HASH_ITERATIONS = 1024;
    public String digest(String plainCredentials, byte[] salt) {
        if (StringUtils.isBlank(plainCredentials)) return null;
        byte[] hashPassword = digest(plainCredentials.getBytes(StandardCharsets.UTF_8), salt);
        return Hex.encodeHexString(hashPassword);
    }
    public boolean matches(String credentials, String plainCredentials, byte[] salt) {
        if (StringUtils.isBlank(credentials) && StringUtils.isBlank(plainCredentials)) return true;
        return StringUtils.equals(credentials, digest(plainCredentials, salt));
    }
    public boolean matches(String credentials, String plainCredentials) {
        if (StringUtils.isBlank(credentials) && StringUtils.isBlank(plainCredentials)) return true;
        return StringUtils.equals(credentials, plainCredentials);
    }
    protected abstract byte[] digest(byte[] input, byte[] salt);
}

The main custom code is in the CredentialsMatcherAdapter and CredentialsDigest. . .

Guess you like

Origin blog.csdn.net/a619602087/article/details/130687398