基础的配置不再赘述,仅说明密码加密校验需要增加的配置
1、配置ShiroConfig
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher(){
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//设置加密的次数
hashedCredentialsMatcher.setHashIterations(5);
return hashedCredentialsMatcher;
}
@Bean
public ShiroRealm shiroRealm() {
ShiroRealm shiroRealm = new ShiroRealm();
//设置shiroRealm的密码验证的匹配器
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return shiroRealm;
}
2、注册用户的时候密码加密存入数据库
public AjaxResult register(UserInfo userInfo) {
String userName = userInfo.getName();
String password = userInfo.getPassword();
//判断用户名是否已经存在,如果已存在,那就返回错误信息
UserInfo selectByUsername = userInfoMapper.selectByUsername(userName);
if (selectByUsername!=null){
return AjaxResult.error("改用户名已存在!");
}
String salt = new SecureRandomNumberGenerator().nextBytes().toString();
int times = 5;
String algorithmName = "md5";
//SimpleHash类使用md5加密算法加密两次,把盐加进去,生成新的密码
String encodedPassword = new SimpleHash(algorithmName, password, ByteSource.Util.bytes(salt), times).toString();
userInfo.setSalt(salt);
userInfo.setPassword(encodedPassword);
userInfo.setCreateTime(LocalDateTime.now());
userInfoMapper.insertSelective(userInfo);
return AjaxResult.success();
}
3、登陆的时候使用加密前的密码登陆
配置登陆校验的ShiroRealm
/**
* @Description
* @Author luoss
* @Date 2021/8/216:35
*/
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private UserInfoMapper userInfoMapper;
/**
* 权限信息
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
return info;
}
/**
* 认证信息
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
String username = token.getUsername();
UserInfo userInfo = userInfoMapper.selectByUsername(username);
if (userInfo == null){
throw new AuthenticationException("账号不存在");
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo,userInfo.getPassword(), ByteSource.Util.bytes(userInfo.getSalt()),getName());
//return的时候进行校验
return authenticationInfo;
}
注意:最后的return simpleAuthenticationInfo 的时候就会触发password验证。
我们要知道一个继承关系
shiroRealm----->AuthorizingRealm---->AuthenticatingRealm
当执行"return simpleAuthenticationInfo"之后,会调用AuthenticatingRealm的getAuthenticationInfo()方法
public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
AuthenticationInfo info = this.getCachedAuthenticationInfo(token);
if (info == null) {
info = this.doGetAuthenticationInfo(token);
log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
if (token != null && info != null) {
this.cacheAuthenticationInfoIfPossible(token, info);
}
} else {
log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
}
if (info != null) {
this.assertCredentialsMatch(token, info);
} else {
log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}]. Returning null.", token);
}
return info;
}
上面代码中又调用了assertCredentialsMatch(token, info);
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = getCredentialsMatcher();
if (cm != null) {
//判断验证是否通过,如果不通过则抛出异常(这个异常将在LoginController中捕获并处理)
if (!cm.doCredentialsMatch(token, info)) {
//not successful - throw an exception to indicate this:
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.");
}
}
继续看doCredentialsMatch()的源码
调用的是类HashedCredentialsMatcher的方法(在ShiroConfig里设置)
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenHashedCredentials = hashProvidedCredentials(token, info);//这里将得到页面传递来的password通过加密后的结果
Object accountCredentials = getCredentials(info);//这里得到是数据库的passwrod通过加密后的结果
return equals(tokenHashedCredentials, accountCredentials);
}
到这里就可看到password验证的大致流程,
如果返回true,那么验证就通过了。
如何返回false,那么上面的AuthenticatingRealm.assertCredentialsMatch()方法会抛出 IncorrectCredentialsException异常
4、登陆
@PostMapping("login")
public AjaxResult login(@RequestBody UserInfo userInfo){
try {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(userInfo.getName(),userInfo.getPassword());
subject.login(token);
return AjaxResult.success();
}catch (Exception e){
logger.error("登陆失败:"+e);
return AjaxResult.error("登陆失败:"+e.getMessage());
}
}