Realm是用来做授权和认证的。
最近项目上有个需求是添加上一个新角色,导致要重写整个登录接口,又不太想修改原来的代码,看着那堆屎山就有点烦,所以就打算直接加一个Realm。
Realm的执行
首先,我们先看Realm是怎么被执行的
Realm是被一个名叫ModularRealmAuthenticator的类执行的
具体细节如下
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
//检验是否有Realm
this.assertRealmsConfigured();
//获取所有的Realm
Collection<Realm> realms = this.getRealms();
//如果Realm只有1个,就只执行那一个,如果Realm有多个,全部都要执行
return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
}
自定义Token
Shiro自带的UserNamePasswordToken感觉不太够用,因为下面我们要根据Token自带的属性进行区分,所以下面需要扩展UserNamePasswordToken。
/**
* @Author: Ember
* @Date: 2021/4/26 17:30
* @Description:
*/
public class Token extends UsernamePasswordToken {
/**
* 用来判断执行哪个Realm
**/
private String loginType;
public Token(String username, String password, String loginType) {
super(username, password);
this.loginType = loginType;
}
public String getLoginType() {
return loginType;
}
public void setLoginType(String loginType) {
this.loginType = loginType;
}
}
自定义ModularRealmAuthenticator
我们从上面可以看到,Realm的执行是跟ModularRealmAuthenticator密切相关的,所以我们要想改变Realm的执行,就必须扩展ModularRealmAuthenticator,然后重写doAuthenticate方法,最后装配上我们自定义的ModularRealmAuthenticator即可。
我们可以通过参考上面的源代码进行对应重写,本质上只是改变realms集合即可,即将想要执行的realm放到新的realms集合里面,执行该realms集合即可
具体扩展如下
/**
* @Author: Ember
* @Date: 2021/4/26 17:36
* @Description:
*/
public class MyDiyModularRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
Token token = (Token) authenticationToken;
//判断是否有Realm装配
this.assertRealmsConfigured();
Collection<Realm> realms = getRealms();
//如果不唯一,分类进行
Collection<Realm> waitChooseRealms = new ArrayList<>();
for (Realm realm : realms) {
//遍历所有Realm
//通过获取Realm的名字,比较token的信息,将想要执行的Realm放到新的Realms组合里面
if(realm.getName().contains(token.getLoginType())){
waitChooseRealms.add(realm);
}
}
//与源码一样,如果只有一个,就执行一个
if (realms.size() == 1){
return doSingleRealmAuthentication((Realm)waitChooseRealms.iterator().next(), token);
}
//与源码一样,如果有多个,就将整个Realms数组放进去
return doMultiRealmAuthentication(waitChooseRealms,token);
}
}
创建多个Realm
我自定义的ModularRealmAuthentic是根据Realm的名字来进行区分的,所以,Realm的名字最后不要出现相同的关键字符串,比如下面我要创一个user、manager和merchant的Realm,那么就不可以出现usermerchant这样的Realm。否则,即会进行userRealm,也会进行merchantRealm。
UserRealm
这是原有的
/**
* @Author: Ember
* @Date: 2021/4/26 17:36
* @Description:
*/
public class MyDiyModularRealmAuthenticator extends ModularRealmAuthenticator {
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
Token token = (Token) authenticationToken;
//判断是否有Realm装配
this.assertRealmsConfigured();
Collection<Realm> realms = getRealms();
//如果不唯一,分类进行
Collection<Realm> waitChooseRealms = new ArrayList<>();
for (Realm realm : realms) {
if(realm.getName().contains(token.getLoginType())){
waitChooseRealms.add(realm);
}
}
if (realms.size() == 1){
return doSingleRealmAuthentication((Realm)waitChooseRealms.iterator().next(), token);
}
return doMultiRealmAuthentication(waitChooseRealms,token);
}
}
ManagerRealm和MerchantRealm
这两个Realm只做测试使用,所以就不详细做认证和授权操作了
/**
* @Author: Ember
* @Date: 2021/4/26 17:47
* @Description:
*/
public class ManagerRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("=======进入了ManagerRealm=========");
return null;
}
}
/**
* @Author: Ember
* @Date: 2021/4/26 17:49
* @Description:
*/
public class MerchantRealm extends AuthorizingRealm {
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("=========进入了MerchantRealm===========");
return null;
}
}
配置ShiroConfig
因为自定义了多个Realm,还有ModularRealmAuthentic,所以这些我们都要装配上
/**
* @Author: Ember
* @Date: 2021/4/2 18:05
* @Description: ShiroConfig
*/
@Configuration
public class MyShiroConfig{
/**
* ShiroFactoryBean装配安全管理
* @param defaultSecurityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultSecurityManager defaultSecurityManager){
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
//这里要让ThredContext对defaultSecurityManager绑定,否则会报错
ThreadContext.bind(defaultSecurityManager);
shiroFilterFactoryBean.setSecurityManager(defaultSecurityManager);
return shiroFilterFactoryBean;
}
/**
* 安全管理装配Realm
* @param realm
* @return
*/
@Bean
public DefaultSecurityManager defaultSecurityManager(
@Qualifier("myRealm") MyUserRealm realm,
@Qualifier("MerchantRealm") MerchantRealm merchantRealm,
@Qualifier("ManagerRealm") ManagerRealm managerRealm,
@Qualifier("authenticator") MyDiyModularRealmAuthenticator authenticator){
//注意这里要是DefaultWebSecurityManager,否则报错
DefaultSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setAuthenticator(authenticator);
//装配上Realm
Collection<Realm> realms = new ArrayList();
realms.add(realm);
realms.add(merchantRealm);
realms.add(managerRealm);
//securityManager.setRealm(realm);
// securityManager.setRealm(merchantRealm);
securityManager.setRealms(realms);
return securityManager;
}
/**
* 自定义的ModularRealmAuthenticator
* @return
*/
@Bean(name = "authenticator")
public MyDiyModularRealmAuthenticator modularRealmAuthenticator(){
MyDiyModularRealmAuthenticator myDiyModularRealmAuthenticator = new MyDiyModularRealmAuthenticator();
myDiyModularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return myDiyModularRealmAuthenticator;
}
/**
* 自定义Realm
* @return
*/
@Bean(name = "MerchantRealm")
public MerchantRealm merchantRealm(){
MerchantRealm realm = new MerchantRealm();
//自定义校验匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法为md5
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//设置加密算法的散列次数
hashedCredentialsMatcher.setHashIterations(1024);
//将自定义匹配添加进Realm中
realm.setCredentialsMatcher(hashedCredentialsMatcher);
//todo 设置缓存
return realm;
}
@Bean(name = "ManagerRealm")
public ManagerRealm managerRealm(){
ManagerRealm realm = new ManagerRealm();
//自定义校验匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法为md5
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//设置加密算法的散列次数
hashedCredentialsMatcher.setHashIterations(1024);
//将自定义匹配添加进Realm中
realm.setCredentialsMatcher(hashedCredentialsMatcher);
//todo 设置缓存
return realm;
}
@Bean(name = "myRealm")
public MyUserRealm realm(){
MyUserRealm realm = new MyUserRealm();
//自定义校验匹配器
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
//设置加密算法为md5
hashedCredentialsMatcher.setHashAlgorithmName("md5");
//设置加密算法的散列次数
hashedCredentialsMatcher.setHashIterations(1024);
//将自定义匹配添加进Realm中
realm.setCredentialsMatcher(hashedCredentialsMatcher);
//todo 设置缓存
return realm;
}
}
如果没有让ThreadContext绑定securityManager,会报错,如下图
测试
这样我们就配置好了,下面进行单元测试一下
@SpringBootTest
class DodudiApplicationTests {
@Test
void contextLoads() {
Subject subject = SecurityUtils.getSubject();
Token token = new Token("123","123","Merchant");
subject.login(token);
}
}
可以看到进入了MerchantRealm
下面将Merchant修改为Manager
正确进入ManagerRealm。