shiro(三)自定义密码比较器

前文中已经实现了基本的权限控制,使用的是shiro默认提供的密码比较器,但是在有些情况下,可能需要一些自定义。比如说,想使用自己的加密方式,再者使用ladp进行用户认证等等。

一、查看源码

查看HashedCredentialsMatcher的继承结构可以看出,他是继承自SimpleCredentialsMatcher,也就是说只要继承SimpleCredentialsMatcher,重写doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)这个方法即可。

二、具体实现

package com.wangcongming.crm.config.shiro;

import com.wangcongming.crm.rao.RedisRao;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Objects;

/**
 * 自定义密码比较规则
 * @author Administrator
 *
 */
public class CredentialsMatcher extends SimpleCredentialsMatcher{
	
	@Autowired
	private RedisRao redisRao;

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    	UsernamePasswordToken utoken=(UsernamePasswordToken) token;
        //获得用户输入的密码:(可以采用加盐(salt)的方式去检验)
        String inPassword = new String(utoken.getPassword());
        String username = utoken.getUsername();
        //获得数据库中的密码
        String dbPassword = (String) info.getCredentials();
        SimpleAuthenticationInfo saInfo = (SimpleAuthenticationInfo)info;
//        ByteSource salt = saInfo.getCredentialsSalt();
        ByteSource salt = ByteSource.Util.bytes(username);
        inPassword = new SimpleHash("sha-1", //加密方式
        		inPassword,//密码原值
				salt,//盐值
				916//加密次数  
				).toString();
        //进行密码的比对
        boolean flag = Objects.equals(inPassword, dbPassword);
        return flag;
    }
}
这里可以看出只是重写了doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)这个方法。在这里可以自定义密码验证,返回值是boolean类型,返回true则是密码比较通过,验证通过,可以成功登录,返回false则是密码校验失败,登录失败

这里只是编码完成了,还需要修改配置文件

<!-- 密码验证器 -->
<bean id="credentialsMatcher" class="com.wangcongming.crm.config.shiro.CredentialsMatcher"></bean>
<!-- 授权 认证 -->
<bean id="NDShiroRealm" class="com.wangcongming.crm.config.shiro.NDShiroRealm" >
	<property name="credentialsMatcher" ref="credentialsMatcher"/>
</bean>

三、密码输入错误多次锁住用户功能实现

在很多网站中都可以看到,密码输入次数过多,将会锁住用户一段时间,这样也是为了保护用户,防止账号被盗,具体实现如下:

  • 修改realm域
@Autowired
private UserDao userDao;
@Autowired
private RedisRao redisRao;

public static final String USER_LOGIN_COUNT = "crm:login:count:";//用户登录次数计数
public static final String USER_IS_LOCK = "crm:login:lock:";//用户登录是否被锁定    一小时
public static final String USER_LOCK = "LOCK";

/**
 * 认证信息.(身份验证) : Authentication 是用来验证用户身份
 * 
 * @param authcToken
 * @return
 * @throws AuthenticationException
 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
		AuthenticationToken authcToken) throws AuthenticationException {
	UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
	String name = token.getUsername();
	String password = String.valueOf(token.getPassword());
	//访问一次,计数一次
	String key = USER_LOGIN_COUNT + name;
	long loginCount = redisRao.increment(key, 1L);
	long expire = redisRao.getExpireStr(key);
	if(expire == -1 || expire == -2){
		redisRao.setExpireStr(key, 1L, TimeUnit.HOURS);
	}
	logger.info("用户:{}密码错误次数:{}",name,loginCount);
	//计数大于5时,设置用户被锁定一小时
	String isLockKey = USER_IS_LOCK + name;
	if(loginCount > 5){
		logger.info("密码错误5次,用户锁定");
		redisRao.setString(isLockKey, USER_LOCK);
		redisRao.setExpireStr(isLockKey, 1, TimeUnit.HOURS);
	}
	
	if (USER_LOCK.equals(redisRao.getString(isLockKey))){
		throw new DisabledAccountException("密码输入错误次数大于5次,请一个小时之后重试!");
	}
	User user = userDao.findUserByUsername(name);
	if (null == user) {
		throw new AccountException("帐号或密码不正确!");
	}
	
	if("0".equals(user.getStatus())){
		throw new DisabledAccountException("此帐号已经被禁止登录,请联系管理员");
	}
	ByteSource salt = ByteSource.Util.bytes(name);
	return new SimpleAuthenticationInfo(user, user.getPassword(),salt, getName());
}

只需要修改登录认证这一块即可

  • 修改密码比较器
package com.wangcongming.crm.config.shiro;

import com.wangcongming.crm.rao.RedisRao;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.Objects;

/**
 * 自定义密码比较规则
 * @author Administrator
 *
 */
public class CredentialsMatcher extends SimpleCredentialsMatcher{
	
	@Autowired
	private RedisRao redisRao;

    public static final String USER_LOGIN_COUNT = "crm:login:count:";//用户登录次数计数

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    	UsernamePasswordToken utoken=(UsernamePasswordToken) token;
        //获得用户输入的密码:(可以采用加盐(salt)的方式去检验)
        String inPassword = new String(utoken.getPassword());
        String username = utoken.getUsername();
        //获得数据库中的密码
        String dbPassword = (String) info.getCredentials();
        SimpleAuthenticationInfo saInfo = (SimpleAuthenticationInfo)info;
//        ByteSource salt = saInfo.getCredentialsSalt();
        ByteSource salt = ByteSource.Util.bytes(username);
        inPassword = new SimpleHash("sha-1", //加密方式
        		inPassword,//密码原值
				salt,//盐值
				916//加密次数  
				).toString();
        //进行密码的比对
        boolean flag = Objects.equals(inPassword, dbPassword);
        if(flag){
        	//密码正确
            String key = USER_LOGIN_COUNT + username;
        	redisRao.deleteStr(key);
        }
        return flag;
    }
}

实现思路就是,在realm登录认证的时候,判断登录次数是否达到锁住用户的值,没有达到则记录一次登录次数,并且设置有效时间为一小时,然后每次+1操作,在密码比较器中,密码比较成功之后,将登陆失败记录次数删除即可

猜你喜欢

转载自blog.csdn.net/linhui258/article/details/81139402