自定义Realm认证的原理
先看下简单的自定义Realm认证主体身份
package com.hxkj.commons.shiro.realm;
import com.hxkj.dao.UserDao;
import com.hxkj.vo.UserVO;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import javax.annotation.Resource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/** 自定义认证授权的Realm实现AuthorizingRealm的认证和授权的2个方法
* Create by wangbin
* 2019-04-03-16:35
*/
public class AuthRealm extends AuthorizingRealm {
@Resource
private UserDao userDao;
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//获取用户名
String userName = (String) principals.getPrimaryPrincipal();
// 从数据库或缓存中获取角色数据
Set<String> roles = getRolesByUserName(userName);
//从数据库或缓存中获取权限信息
Set<String> permissions = getPermissionsByUserName(userName);
//创建授权对象
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//设置角色和权限信息
authorizationInfo.setRoles(roles);
authorizationInfo.setStringPermissions(permissions);
//返回授权对象就会对主体拥有的角色和权限进行校验
return authorizationInfo;
}
//从数据库或缓存中获取角色信息
private Set<String> getRolesByUserName(String userName) {
System.out.println("从数据库中获取角色权限数据");//测试代码
List<String> list = userDao.queryRolesByUserName(userName);
Set<String> sets = new HashSet<>(list);
return sets;
}
//从数据库或缓存中获取权限信息
private Set<String> getPermissionsByUserName(String userName) {
List<String> list = userDao.queryPermissionsByUserName(userName);
Set<String> sets = new HashSet<>(list);
return sets;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//1.从主体登录时传过来的token中获取主体提交的用户名
String userName = (String) token.getPrincipal();
//2.通过主体提交的用户名去数据库中获取凭证(通过用户名获得密码)
String password = gerPasswordByUserName(userName);//此处模拟从数据库中获得密码
if(password==null){
return null;
}
//创建认证对象
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userName,password,"AuthRealm");
//凭证返回之前设置盐(ByteSource.Util.bytes方法将字符串转成ByteSource对象)
authenticationInfo.setCredentialsSalt(ByteSource.Util.bytes(userName));
//返回凭证的时候就会对密码进行校验,通过就认证成功,密码不匹配就认证失败
//realm中注入的加密器会在返回时将用户在页面输入的密码按照加密规则加密后与authenticationInfo
//中查到的密码匹配,匹配成功则密码验证成功
return authenticationInfo;
}
//数据库或缓存中获取凭证
private String gerPasswordByUserName(String userName) {
UserVO user = userDao.getUserByUserName(userName);
if (user != null){
return user.getPassword();
}
return null;
}
public static void main(String[] args) {
//md5加密并设置加密次数和盐,打印加密后的字符串设置到数据库中,用于匹配验证
Md5Hash md5Hash = new Md5Hash("123456","bin",2);
System.out.println(md5Hash.toString());
}
}
在自定义的Realm中返回return new SimpleAuthenticationInfo()
对象这里打上断点,进行调试,会看到进入了shiro包下的HashedCredentialsMatcher
这个类,里面调用了doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)
这个方法,比对token和info,token中的密码是用户提交的明文密码,会调用加密器中的加密方法和次数,再从info中获取盐值,没有盐值则不获取,对token中的明文密码进行加密,得到tokenHashedCredentials
这个对象,然后获取info中的accountCredentials
对象
如果这两个对象是相等的就会返回true,即主体的身份验证成功
关键代码:HashedCredentialsMatcher中的比对token和info的方法
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
Object tokenHashedCredentials = hashProvidedCredentials(token, info);
Object accountCredentials = getCredentials(info);
return equals(tokenHashedCredentials, accountCredentials);
}
身份验证关键调试截图:
图中,token的凭证(明文加密后的密码)与用户的凭证(数据库中的加密密码)
完全一致,返回true,身份验证成功。
数据库中的用户加密密码
对比成功后返回true,主体的身份校验就玩成了