Shiro 之 HashedCredentialsMatcher 认证匹配

前言

Shiro 提供了用于加密密码验证密码服务的 CredentialsMatcher 接口,而 HashedCredentialsMatcher 正是 CredentialsMatcher 的一个实现类。写项目的话,总归会用到用户密码的非对称加密,目前主流的非对称加密方式是 MD5 ,以及在 MD5 上的加盐处理,而 HashedCredentialsMatcher 也允许我们指定自己的算法和盐。本文将介绍 HashedCredentialsMatcher 的使用,以及对相关源码的进行解析,MD5 等加密知识请自行查阅。

HashedCredentialsMatcher 的使用

要使用 HashedCredentialsMatcher ,那么首先要进行配置。当然,前提是你已经引入了 Shiro 库。总共有三种配置方式:

  1. XML 格式

    <bean id="credentialsMatcher"
            class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
      		<!-- 加密方式 -->
            <property name="hashAlgorithmName" value="MD5" />
      		<!-- 加密次数 -->
            <property name="hashIterations" value="2" />
      		<!-- 存储散列后的密码是否为16进制 -->
            <property name="storedCredentialsHexEncoded" value="true" />
    </bean>
    
  2. ini 等配置文件

    首先在 web.xml 中自定义 shiro.ini 位置

    <filter>
        <filter-name>ShiroFilter</filter-name>
        <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
        <init-param>
            <param-name>configPath</param-name>
            <param-value>/WEB-INF/shiro.ini</param-value>
        </init-param>
    </filter>
    

    然后除了 shiro 的通常配置之外,需加上:

    credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
    ## 加密方式
    credentialsMatcher.hashAlgorithmName=md5
    ## 加密次数
    credentialsMatcher.hashIterations=2
    ## 存储散列后的密码是否为16进制 
    credentialsMatcher.storedCredentialsHexEncoded=true
    
  3. 建立 ShiroConfiguration 配置类,除了 shiro 的通常配置之外,需加上:

        @Bean
        public HashedCredentialsMatcher hashedCredentialsMatcher(){
            HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
          	//加密方式
            hashedCredentialsMatcher.setHashAlgorithmName("md5");
          	//加密次数
            hashedCredentialsMatcher.setHashIterations(2);
          	//存储散列后的密码是否为16进制
          	//hashedCredentialsMatcher.isStoredCredentialsHexEncoded();
            return hashedCredentialsMatcher;
        }
    

然后,在登录方法或者自定义的输入中获取登录 token,我选择的方式是在/login中获取:

    public Object login(@RequestBody User userParam, HttpSession session) {
        String name =  userParam.getName();
        name = HtmlUtils.htmlEscape(name);
        Subject subject = SecurityUtils.getSubject();
      	// 生成token
        UsernamePasswordToken token = new UsernamePasswordToken(name, userParam.getPassword());
        try {
          	// 从自定义Realm获取安全数据进行验证
            subject.login(token);
            User user = userService.getByName(name);
            session.setAttribute("user", user);
            return Result.success();
        } catch (AuthenticationException e) {
            String message ="账号密码错误";
            return Result.fail(message);
        }
    }

接下来,是自定义 Realm,之前我也有博文写过相关知识,所以只贴下代码作参考:

public class JPARealm extends AuthorizingRealm {

	@Autowired
	private UserService userService;

	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
		// 权限分配的相关知识在此不做介绍,重点在验证方面
		SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
		return s;
	}

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
		String userName = token.getPrincipal().toString();
		User user = userService.getByName(userName);
		String passwordInDB = user.getPassword();
		String salt = user.getSalt();
      	// 认证信息token里存放账号密码, getName() 是当前Realm的继承方法,通常返回当前类名
      	// 盐也放进去
        // 这样通过配置中的 HashedCredentialsMatcher 进行自动校验
		SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName, passwordInDB, ByteSource.Util.bytes(salt),
				getName());
		return authenticationInfo;
	}
}

HashedCredentialsMatcher 的源码分析

从开发者的角度来看,我们可以自己实现 CredentialsMatcher 的一个类来实现定制化的账户密码验证机制,例如:

public class MyCredentialsMatcher extends SimpleCredentialsMatcher {
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
          Object tokenCredentials = getCredentials(token);
        Object accountCredentials = getCredentials(info);
        return super.equals(tokenCredentials, accountCredentials);
    }
}

SimpleCredentialsMatcher 是 CredentialsMatcher 这个类的默认实现,相信我,你基本上不会去自己实现 getCredentials 这种涉及到底层编码的方法,重点在于重写 doCredentialsMatch ,在这里你可以自定义账户密码验证机制。

不过实现 doCredentialsMatch 你还是有可能觉得麻烦,HashedCredentialsMatcher 封装好了 doCredentialsMatch() 方法,你可以完全不用管它。

上一节的使用中,流程就是:使用 token 类将用户输入的信息封装,然后采用 token 进行 login 操作。此时 shiro 将使用 token 中携带的用户信息调用 Realm 中自定义的 doGetAuthenticationInfo 方法进行校验比对,比对成功则登录成功。

参考

  1. Shiro+SpringMVC 实现更安全的登录(加密匹配&登录失败超次数锁定帐号)—— 小灯光环
  2. shiro学习之HashedCredentialsMatcher密码匹配过程 —— MR.HE
  3. Shiro笔记(三)----Shiro配置文件ini详解 —— lfendo
  4. shiro-密码比较的设计 CredentialsMatcher -为什么Java中的密码优先使用 char[] 而不是String? —— 汪小哥
  5. 自定义shiro的Realm实现和CredentialsMatcher实现以及Token实现 —— 蓝萝卜blu
原文地址:https://www.cnblogs.com/Sherlock-J/p/12925945.html