一、前言
虽然 Shiro 给我们提供了很多内置的 Realm,但在企业开发中,这往往不适用于项目中。
所以,都需要自定义 Realm 来使用。
二、步骤
- 创建一个类 ,继承 AuthorizingRealm
(继承关系:自定义Realm->AuthorizingRealm->AuthenticatingRealm->CachingRealm->Realm)
- 重写认证方法 doGetAuthenticationInfo
重写授权方法 doGetAuthorizationInfo
- 在方法中分别返回对应的对象
SimpleAuthenticationInfo :代表该用户的认证信息
SimpleAuthorizationInfo:代表用户角色权限信息
三、代码实现
注:
本文的案例中没有从数据库中查询真实的数据,所以使用集合模拟的数据。
在实际开发中,只需要 service 层写好的方法从数据库查询数据即可。
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.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* 自定义的 Redlm 步骤:
* 1.继承AuthorizingRealm
* 2.重写授权方法 doGetAuthorizationInfo
* 重写认证方法 doGetAuthenticationInfo
* 3.在方法中分别返回对应的对象
* SimpleAuthorizationInfo:代表用户角色权限信息
SimpleAuthenticationInfo :代表该用户的认证信息
*/
public class CustomRealm extends AuthorizingRealm {
private final Map<String,String> userInfoMap = new HashMap<>();
{
userInfoMap.put("jack","123");
userInfoMap.put("xdclass","456");
}
//user -> role
private final Map<String,Set<String>> roleMap = new HashMap<>();
{
Set<String> set1 = new HashSet<>();
Set<String> set2 = new HashSet<>();
set1.add("role1");
set1.add("role2");
set2.add("root");
roleMap.put("jack",set1);
roleMap.put("xdclass",set2);
}
//role -> permission
private final Map<String,Set<String>> permissionMap = new HashMap<>();
{
Set<String> set1 = new HashSet<>();
Set<String> set2 = new HashSet<>();
set1.add("video:find");
set1.add("video:buy");
set2.add("video:add");
set2.add("video:delete");
permissionMap.put("jack",set1);
permissionMap.put("xdclass",set2);
}
/**
* 执行认证操作时,SecurityManager 会执行此方法
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// UsernamePasswordToken -> HostAuthenticationToken -> AuthenticationToken
// 在实际开发的时候,authenticationToken是需要前端传递过来的用户输入的账号和密码
// 获取用户唯一标识(俗称“账号”)
String name = (String)authenticationToken.getPrincipal();
// 根据账号从数据库中获取密码
String pwd = getPwdByUserNameFromDB(name);
if( pwd == null || "".equals(pwd)){
return null;
}
// 将账号和密码封装到 SimpleAuthenticationInfo 对象中进行返回
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(name, pwd, this.getName());
return simpleAuthenticationInfo;
}
/**
* 执行授权操作时,SecurityManager 会执行此方法
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
// 获取“账号”
String name = (String)principals.getPrimaryPrincipal();
// 模拟从数据库中获取角色
Set<String> roles = getRolesByNameFromDB(name);
// 模拟从数据库中获取权限
//(另外,此处本来是应该的根据角色获取权限的,但因为本来也只是模拟,所以为了简便,则直接根据账号获取的权限)
Set<String> permissions = getPermissionsByNameFromDB(name);
// 将角色和权限等信息封装到 SimpleAuthorizationInfo 进行返回
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
/**
* 模拟从数据库中获取密码
* @param name
* @return
*/
private String getPwdByUserNameFromDB(String name) {
return userInfoMap.get(name);
}
/**
* 模拟从数据库获取用户角色集合
* @param name
* @return
*/
private Set<String> getRolesByNameFromDB(String name) {
return roleMap.get(name);
}
/**
* 模拟从数据库获取权限集合
* @param name
* @return
*/
private Set<String> getPermissionsByNameFromDB(String name) {
return permissionMap.get(name);
}
}
测试代码如下:
/**
* 测试自定义的Realm
*/
public class CustomRealmTest {
private CustomRealm customRealm = new CustomRealm();
private DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
@Before
public void init(){
//构建环境
defaultSecurityManager.setRealm(customRealm);
SecurityUtils.setSecurityManager(defaultSecurityManager);
}
@Test
public void testAuthentication() {
UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("jack", "123");
Subject subject = SecurityUtils.getSubject();
subject.login(usernamePasswordToken);
System.out.println(" 认证结果:"+subject.isAuthenticated());
System.out.println(" getPrincipal=" + subject.getPrincipal());
System.out.println("是否有对应的角色:"+subject.hasRole("role1"));
System.out.println("是否有对应的权限:"+subject.isPermitted("video:add"));
}
}
四、代码解析
(一)方法
当用户登陆的时候会调用 doGetAuthenticationInfo
进行权限校验的时候会调用: doGetAuthorizationInfo
(二)代码中的类(对象)
- UsernamePasswordToken :用来封装用户(Subject)传递过来的账号和密码
(继承体系:UsernamePasswordToken -> HostAuthenticationToken -> AuthenticationToken)
SimpleAuthenticationInfo :用来封装用户的认证信息
SimpleAuthorizationInfo:用来封装用户的角色权限信息
五、认证和授权流程源码分析
在编辑器中通过 Debug 方式则能分析出认证和授权的大致流程如下:
(一)认证
认证流程解读:
subject.login(usernamePasswordToken);
DelegatingSubject->login()
DefaultSecurityManager->login()
AuthenticatingSecurityManager->authenticate()
AbstractAuthenticator->authenticate()
ModularRealmAuthenticator->doAuthenticate()
ModularRealmAuthenticator->doSingleRealmAuthentication()
AuthenticatingRealm->getAuthenticationInfo()
(二)授权
授权流程解读(查询角色):
subject.checkRole("admin")
DelegatingSubject->checkRole()
AuthorizingSecurityManager->checkRole()
ModularRealmAuthorizer->checkRole()
AuthorizingRealm->hasRole()
AuthorizingRealm->doGetAuthorizationInfo()
授权流程解读(查询权限):
subject.isPermitted("具体权限")
DelegatingSubject->isPermitted()
AuthorizingSecurityManager->isPermitted()
ModularRealmAuthorizer->isPermitted()
AuthorizingRealm->isPermitted()
AuthorizingRealm->getAuthorizationInfo()
CustomRealm->doGetAuthorizationInfo
六、总结
Realm 就是用来从数据库中获取用户的角色、权限等数据的,不要被这个名字给吓唬到。
Java新手,若有错误,欢迎指正!