Shrio框架实现自定义密码校验规则详解

 更新时间:2026年03月14日 09:16:23   作者:陪妳去流浪丶  
文章主要介绍了Shiro框架的密码校验机制,包括内置的校验规则和自定义校验规则的实现,在Shiro中,密码校验主要通过自定义Realm来实现,具体步骤包括定义用户认证信息和权限信息,并在CredentialsMatcher中实现用户信息校验对比

shrio自己内置一些密码校验规则,也可以实现简单的自定义,比如算法类型,hash次数等,但是有时候我们有一些比较特殊的密码校验规则,需要自定义来实现

1.shiro的密码校验是如何做的?

我们在登录方法内做完参数校验,验证码匹配等基本工作之后,都会将用户名和密码通过subject.login(auth) 传入框架来做用户匹配,但是,是和什么地方的数据进行匹配呢?

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

答案就是在自定义的Realm中定义的AuthenticationInfo 进行匹配

可以通过shiro框架源码AuthenticatingRealm类的 assertCredentialsMatch方法看到,传入了两个参数,token就是我们登陆方法中传入的UsernamePasswordToken对象,而info就是我们在钉钉一Realm中doGetAuthenticationInfo 方法中定义的info对象。

shiro框架校验:

//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.");
        }
    }

这里需要弄清楚 doGetAuthenticationInfo和doGetAuthorizationInfo  前者是定义用户认证信息的,而后者是定义用户权限信息,有点容易混淆。

2.实现自定义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 就是返回的用户认证信息,也是框架中做对比的info

好了,认证流程基本弄明白,我们就需要实现自定义校验,怎么做呢?

3.实现自定义CredentialsMatcher 认证类

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

关键就是这里,CredentialsMatcherAdapter 是个实现了CredentialsMatcher接口的自定义类

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[].");
        }
    }
}

doCredentialsMatch 方法就是实现用户信息校验 对比的方法,上面shiro框架的校验流程(cm.doCredentialsMatch(token, info))就会走到我们这里的方法中

上面credentialsDigest.matches(credentials, plainCredentials); 是我自己实现的密码比较方法

有加盐模式和无盐模式

贴出来给大家参考下

接口

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

实现类

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);
}

主要的自定义代码就在CredentialsMatcherAdapter和CredentialsDigest中。。。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • java报错状态码问题

    java报错状态码问题

    这篇文章主要介绍了java报错状态码问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-05-05
  • 二叉搜索树实例练习

    二叉搜索树实例练习

    一棵二叉查找树是按二叉树结构来组织的。这样的树可以用链表结构表示,其中每一个结点都是一个对象
    2012-11-11
  • springboot自定义yml配置文件及其外部部署过程

    springboot自定义yml配置文件及其外部部署过程

    这篇文章主要介绍了springboot自定义yml配置文件及其外部部署过程,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • Spring Boot 中的任务执行器基本概念及使用方法

    Spring Boot 中的任务执行器基本概念及使用方法

    务执行器是 Spring Boot 中的一个非常实用的模块,它可以简化异步任务的开发和管理,在本文中,我们介绍了任务执行器的基本概念和使用方法,以及一个完整的示例代码,需要的朋友可以参考下
    2023-07-07
  • java代码实现mysql分表操作(用户行为记录)

    java代码实现mysql分表操作(用户行为记录)

    这篇文章主要介绍了java代码实现mysql分表操作(用户行为记录),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • @GrpcServise 注解的作用和使用示例详解

    @GrpcServise 注解的作用和使用示例详解

    @GrpcService 是一个 Spring Boot 处理器,它会查找实现了 grpc::BindableService 接口的类,并将其包装成一个 Spring Bean 对象,这篇文章主要介绍了@GrpcServise 注解的作用和使用,需要的朋友可以参考下
    2023-05-05
  • MyBatis 中  type-aliases-package的作用(简化类型映射)

    MyBatis 中  type-aliases-package的作用(简化类型映射)

    MyBatis中type-aliases-package的作用是简化类型映射,通过指定包路径,MyBatis会自动扫描该包下的所有类并为这些类生成类型别名,减少XML配置文件中的冗长代码,感兴趣的朋友跟随小编一起看看吧
    2024-11-11
  • Java数据结构之栈的线性结构详解

    Java数据结构之栈的线性结构详解

    从数据结构上看栈和队列都是线性表,不过是两种特殊的线性表,栈只允许在的一端进行插人或删除操作,而队列只允许在表的一端进行插人操作、而在另一端进行删除操作,这篇文章主要给大家介绍了关于Java数据结构之栈的线性结构的相关资料,需要的朋友可以参考下
    2021-08-08
  • java使用xpath和dom4j解析xml

    java使用xpath和dom4j解析xml

    XPath是一门在XML文档中查找信息的语言,下面介绍一下java使用xpath和dom4j解析xml的示例,大家参考使用吧
    2014-01-01
  • MyBatis-Plus逆向工程——Generator的使用

    MyBatis-Plus逆向工程——Generator的使用

    这篇文章主要介绍了MyBatis-Plus逆向工程——Generator的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2021-01-01

最新评论