首先shiro是啥我就不多说了,总得概括就是,以subject为主,在调用subject的时候,都会将任务委托给SecurityManager,SecurityManager类似于一个中转站,不过在写代码时一般不需要去管他,因为shiro主张的是将主体的认证和权限管理交由用户自己定义,所以只需要自定以完realm后注入到securityManager就行。
上jeecg代码,这里做了修改注入了多个realm
@Bean("securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
List realms = new ArrayList<>();
//添加多个Realm
realms.add(new FrontShiroRealm());
realms.add(new ShiroRealm());
securityManager.setRealms(realms);
/*
* 关闭shiro自带的session,详情见文档
* http://shiro.apache.org/session-management.html#SessionManagement-
* StatelessApplications%28Sessionless%29
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
//自定义缓存实现,使用redis
securityManager.setCacheManager(redisCacheManager());
return securityManager;
}
上面说到在调用subject时,会把任务委托给securityManager,具体是怎么实现的呢?
下面是重点:
在shiro实现登录功能时,Subject的实现类会调用Authenticator这个接口的默认实现类ModularRealmAuthenticator来进行帐号密码以及验证码的验证,非常重要的一点是,和 Realm 交互的 ModularRealmAuthenticator 按迭代(iteration) 顺序执行。ModularRealmAuthenticator 可以访问为SecurityManager 配置的 Realm 实例,当尝试一次验证时,它将在集合中遍历,支持对提交的 AuthenticationToken 处理的每个 Realm 都将执行 Realm 的 getAuthenticationInfo 方法。
参考博客:mkdeveloper的博客http://developer.mksoft.cn/
下面的方法注入了一个自定义的ModularRealmAuthenticator,并配置了验证策略为AtLeastOneSuccessfulStrategy
/**
* 系统自带的Realm管理,主要针对多realm
* */
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator(){
//自己重写的ModularRealmAuthenticator
UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());//这里为默认策略:如果有一个或多个Realm验证成功,所有的尝试都被认为是成功的,如果没有一个验证成功,则该次尝试失败
return modularRealmAuthenticator;
}
下图是shiro支持的多realm认证策略:
那么我们下面重新自定义一个ModularRealmAuthenticator,根据tokn中的type来判断登陆是来自前端还是后端。
package org.jeecg.config;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.authc.pam.UnsupportedTokenException;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.util.CollectionUtils;
import org.jeecg.common.util.LoginTypeEnum;
import org.jeecg.modules.shiro.authc.JwtToken;
@Slf4j
public class DefineModularRealmAuthenticator extends ModularRealmAuthenticator {
/**
* 将Realm的实现类作为Map传入,这样便可以分清除前后台登录
*/
private Map defineRealms;
/**
* 调用单个Realm来进行验证
*/
@Override
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm,AuthenticationToken token) {
if (!realm.supports(token)) {
String msg = "Realm ["
+ realm
+ "] does not support authentication token ["
+ token
+ "]. Please ensure that the appropriate Realm implementation is "
+ "configured correctly or that the realm accepts AuthenticationTokens of this type.";
throw new UnsupportedTokenException(msg);
}
AuthenticationInfo info = null;
try {
info = realm.getAuthenticationInfo(token);
if (info == null) {
String msg = "Realm [" + realm
+ "] was unable to find account data for the "
+ "submitted AuthenticationToken [" + token + "].";
throw new UnknownAccountException(msg);
}
} catch (IncorrectCredentialsException e) {
throw e;
} catch (UnknownAccountException e) {
throw e;
}catch (Throwable throwable) {
if (log.isDebugEnabled()) {
String msg = "Realm ["
+ realm
+ "] threw an exception during a multi-realm authentication attempt:";
log.debug(msg,throwable );
}
}
return info;
}
/**
* 判断Realm是不是null
*/
@Override
protected void assertRealmsConfigured() throws IllegalStateException {
defineRealms = getDefineRealms();
if (CollectionUtils.isEmpty(defineRealms)) {
String msg = "Configuration error: No realms have been configured! One or more realms must be "
+ "present to execute an authentication attempt.";
throw new IllegalStateException(msg);
}
}
/**
* 这个方法比较重要,用来判断此次调用是前台还是后台
*/
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
throws AuthenticationException {
assertRealmsConfigured();
JwtToken jwtToken = (JwtToken) authenticationToken;
Realm realm = null;
// 前端登录
if (StringUtils.equals(jwtToken.getLoginType(),
LoginTypeEnum.FRONT.toString())) {
realm = (Realm) defineRealms.get("customerRealm");
}
// 后台登录
if (StringUtils
.equals(jwtToken.getLoginType(), LoginTypeEnum.BACK.toString())) {
realm = (Realm) defineRealms.get("adminRealm");
}
if(realm==null){
return null;
}
return doSingleRealmAuthentication(realm, authenticationToken);
}
public void setDefineRealms(Map defineRealms) {
this.defineRealms = defineRealms;
}
public Map getDefineRealms() {
return defineRealms;
}
}
最后将其注入到SecurityManager中就大功告成啦
@Bean("securityManager")
public DefaultWebSecurityManager securityManager(FrontShiroRealm frontShiroRealm,ShiroRealm shiroRealm,DefineModularRealmAuthenticator defineModularRealmAuthenticator) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setAuthenticator(defineModularRealmAuthenticator);
List realms = new ArrayList<>();
//添加多个Realm
realms.add(frontShiroRealm);
realms.add(shiroRealm);
securityManager.setRealms(realms);
/*
* 关闭shiro自带的session,详情见文档
* http://shiro.apache.org/session-management.html#SessionManagement-
* StatelessApplications%28Sessionless%29
*/
DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
securityManager.setSubjectDAO(subjectDAO);
//自定义缓存实现,使用redis
securityManager.setCacheManager(redisCacheManager());
return securityManager;
}
总结下:
2019-11-02 晴
今天天气很棒,阳光正好,坐在窗口码代码,望着外面的天空,夹杂着细细碎碎的声音,生活要是一直如此简单多好