今天突然想研究一下shiro怎么匹配用户的密码。
我们使用shiro的API登录时,会先创建一个令牌对象,而经常用的令牌对象是UsernamePasswordToken,把用户输入的用户名和密码作为参数构建一个UsernamePasswordToken,然后通过Subject.login()登录。
// shiro登录认证
// 1、创建令牌
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 获取Subject对象
Subject subject = SecurityUtils.getSubject();
// 登录
subject.login(token);
而在几经周转之下,最后会调用Realm的getAuthenticationInfo()方法获取认证信息。
一般在这里,我们会根据用户输入的用户名/手机号等查询数据库,然后把查询的结果封装成AuthenticationInfo对象返回,一般直接使用其子类SimpleAuthenticationInfo
@Override
protected SimpleAuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
// 得到令牌
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 从token中获取用户名
String username = token.getUsername();
User user = userService.selectByUsername(username);
return new SimpleAuthenticationInfo(user, user.getPassword(), username);
}
然后神奇的事发生了,当密码输入错误的时候,居然会登录失败,我们并没有写对比密码的代码。
那么shiro是如何知道我们输的密码对不对呢?
经过浏览官网的相关说明,发现一个叫CredentialsMatcher的接口,这个接口的中文含义就是密码匹配器
而shiro默认通过其实现类SimpleCredentialsMatcher来进行输入的密码和数据库的密码的比较(简单的等值比较)。
当数据库存储的密码不是明文时,就不能使用默认的SimpleCredentialsMatcher了,否则就算密码对了也会匹配不上。
这时候就需要自定义CredentialsMatcher来完成密码比较了~
import cn.edu.sgu.www.mhxysy.util.PasswordEncoder;
import lombok.extern.slf4j.Slf4j;
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.CredentialsMatcher;
import org.springframework.stereotype.Component;
/**
* 密码匹配器
* @author heyunlin
* @version 1.0
*/
@Slf4j
@Component
public class UserPasswordMatcher implements CredentialsMatcher {
@Override
public boolean doCredentialsMatch(AuthenticationToken authenticationToken, AuthenticationInfo authenticationInfo) {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
SimpleAuthenticationInfo simpleAuthenticationInfo = (SimpleAuthenticationInfo) authenticationInfo;
// 得到用户输入的密码
String password = new String(token.getPassword());
// 得到数据库密码
String encodedPassword = simpleAuthenticationInfo.getCredentials().toString();
return PasswordEncoder.matches(password, encodedPassword);
}
}
最后,在配置Realm的bean时,需要通过@Bean注解定义,因为要设置密码匹配器~
/**
* shiro配置类
* @author heyunlin
* @version 1.0
*/
@Configuration
public class ShiroConfig {
@Bean
public UserRealm userRealm(UserPasswordMatcher passwordMatcher) {
UserRealm userRealm = new UserRealm();
userRealm.setCredentialsMatcher(passwordMatcher);
return userRealm;
}
}