首先shiro是啥我就不多說了,總得概括就是,以subject為主,在調(diào)用subject的時候,都會將任務(wù)委托給SecurityManager,SecurityManager類似于一個中轉(zhuǎn)站,不過在寫代碼時一般不需要去管他,因為shiro主張的是將主體的認證和權(quán)限管理交由用戶自己定義,所以只需要自定以完realm后注入到securityManager就行。
上jeecg代碼,這里做了修改注入了多個realm
@Bean("securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
List<Realm> realms = new ArrayList<>();
//添加多個Realm
realms.add(new FrontShiroRealm());
realms.add(new ShiroRealm());
securityManager.setRealms(realms);
/*
* 關(guān)閉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);
//自定義緩存實現(xiàn),使用redis
securityManager.setCacheManager(redisCacheManager());
return securityManager;
}
上面說到在調(diào)用subject時,會把任務(wù)委托給securityManager,具體是怎么實現(xiàn)的呢?
下面是重點:
在shiro實現(xiàn)登錄功能時,Subject的實現(xiàn)類會調(diào)用Authenticator這個接口的默認實現(xiàn)類ModularRealmAuthenticator來進行帳號密碼以及驗證碼的驗證,非常重要的一點是,和 Realm 交互的 ModularRealmAuthenticator 按迭代(iteration) 順序執(zhí)行。ModularRealmAuthenticator 可以訪問為SecurityManager 配置的 Realm 實例,當嘗試一次驗證時,它將在集合中遍歷,支持對提交的 AuthenticationToken 處理的每個 Realm 都將執(zhí)行 Realm 的 getAuthenticationInfo 方法。
參考博客:mkdeveloper的博客http://developer.mksoft.cn/
下面的方法注入了一個自定義的ModularRealmAuthenticator,并配置了驗證策略為AtLeastOneSuccessfulStrategy
/**
* 系統(tǒng)自帶的Realm管理,主要針對多realm
* */
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator(){
//自己重寫的ModularRealmAuthenticator
UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());//這里為默認策略:如果有一個或多個Realm驗證成功,所有的嘗試都被認為是成功的,如果沒有一個驗證成功,則該次嘗試失敗
return modularRealmAuthenticator;
}
下圖是shiro支持的多realm認證策略:

那么我們下面重新自定義一個ModularRealmAuthenticator,根據(jù)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的實現(xiàn)類作為Map傳入,這樣便可以分清除前后臺登錄
*/
private Map<String, Object> defineRealms;
/**
* 調(diào)用單個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);
}
}
/**
* 這個方法比較重要,用來判斷此次調(diào)用是前臺還是后臺
*/
@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<String, Object> defineRealms) {
this.defineRealms = defineRealms;
}
public Map<String, Object> getDefineRealms() {
return defineRealms;
}
}
最后將其注入到SecurityManager中就大功告成啦
@Bean("securityManager")
public DefaultWebSecurityManager securityManager(FrontShiroRealm frontShiroRealm,ShiroRealm shiroRealm,DefineModularRealmAuthenticator defineModularRealmAuthenticator) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setAuthenticator(defineModularRealmAuthenticator);
List<Realm> realms = new ArrayList<>();
//添加多個Realm
realms.add(frontShiroRealm);
realms.add(shiroRealm);
securityManager.setRealms(realms);
/*
* 關(guān)閉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);
//自定義緩存實現(xiàn),使用redis
securityManager.setCacheManager(redisCacheManager());
return securityManager;
}
總結(jié)下:

2019-11-02 晴
今天天氣很棒,陽光正好,坐在窗口碼代碼,望著外面的天空,夾雜著細細碎碎的聲音,生活要是一直如此簡單多好