Shiro 管理多個realm 實現前後臺分離
阿新 • • 發佈:2019-01-24
使用shiro 由於公司的業務上的需求前後臺公用的一張表,要實現前臺使用者和後臺使用者的分離攔截需要書寫多個realm 用來驗證前臺使用者還是後臺使用者。直接上程式碼
1.書寫一個自定的token UsernamePasswordUsertypeToken 繼承UsernamePasswordToken 用來判斷使用者型別UsernamePasswordUsertypeToken 多出一個欄位用來區分使用者型別
package com.jscredit.zxypt.shiro;
import org.apache.shiro.authc.UsernamePasswordToken;
/**
* 拓展shiro 中的UsernamePasswordToken
* @author liyaqiang
*
*/
public class UsernamePasswordUsertypeToken extends UsernamePasswordToken {
private static final long serialVersionUID = 1L;
private String usertype ;
public String getUsertype() {
return usertype;
}
public void setUsertype(String usertype) {
this .usertype = usertype;
}
public UsernamePasswordUsertypeToken(String loginName, String password, String usertype) {
super(loginName, password);
this.usertype = usertype;
}
}
定義前臺的驗證的realm
public class UserLoginRealm extends AuthorizingRealm{
private final Logger LOGGER = LoggerFactory.getLogger(UserLoginRealm.class);
private static final String ALGORITHM = "MD5";
@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
@Autowired
private UserKzService userKzService;
public UserLoginRealm(){
super();
}
/**
* 為當前登入的Subject授予角色和許可權 ,該方法的呼叫時機為需授權資源被訪問時
* 並且每次訪問需授權資源時都會執行該方法中的邏輯,這表明本例中預設並未啟用AuthorizationCache
* 個人感覺若使用了Spring3.1開始提供的ConcurrentMapCache支援,則可靈活決定是否啟用AuthorizationCache
* 比如說這裡從資料庫獲取許可權資訊時,先去訪問Spring3.1提供的快取,而不使用Shior提供的AuthorizationCache
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
ShiroLoginUser shiroLoginUser = (ShiroLoginUser) principals.fromRealm(getName()).iterator().next();
String username = shiroLoginUser.getAccount();
if (username != null) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 查詢使用者授權資訊
List<Permission> perList = permissionService.getShiro("O");
if (perList != null && perList.size() != 0) {
for (Permission permission : perList) {
info.addStringPermission(permission.getPmsnCode());
}
return info;
}
}
return null;
}
/**
* 驗證前臺當前登入的Subject 本例中該方法的呼叫時機為UserLoginController.login()方法中執行Subject.login()時
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException {
// 這個authcToken是從userLoginController裡面currentUser.login(token)傳過來的
UsernamePasswordUsertypeToken fronttoken = (UsernamePasswordUsertypeToken) authcToken;
LOGGER.debug("驗證當前Subject時獲取到token為"
+ ReflectionToStringBuilder.toString(fronttoken, ToStringStyle.MULTI_LINE_STYLE));
User users = userService.getUserByName(fronttoken.getUsername());
UserKz userKz= userKzService.selectByPrimaryKey(users.getUserId());
String usertype =userKz.getUserType();
// Shiro完成對比邏輯,返回和令牌相關的正確的驗證資訊,第一個引數填登入使用者名稱,第二個引數填合法的登入密碼
if (users != null&&usertype!="00") {
ShiroLoginUser shiroLoginUser = new ShiroLoginUser(users.getUserId(), users.getAccount(),userKz.getUserType());
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(shiroLoginUser,users.getPassword(), getName());
this.setSession("shiroLoginUser", shiroLoginUser);
return authcInfo;
} else {
throw new AuthenticationException();
}
// 沒有返回登入使用者名稱對應的SimpleAuthenticationInfo物件時,就會在LoginController中丟擲UnknownAccountException異常
}
/**
* 更新使用者授權資訊快取.
*/
public void clearCachedAuthorizationInfo(String principal) {
SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
clearCachedAuthorizationInfo(principals);
}
/**
* 清除所有使用者授權資訊快取.
*/
public void clearAllCachedAuthorizationInfo() {
Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
if (cache != null) {
for (Object key : cache.keys()) {
cache.remove(key);
}
}
}
@PostConstruct
public void initCredentialsMatcher() {// MD5加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(ALGORITHM);
setCredentialsMatcher(matcher);
}
/**
* 將一些資料放到ShiroSession中,以便於其它地方使用
*
* 比如Controller,使用時直接用HttpSession.getAttribute(key)就可以取到
*/
private void setSession(Object key, Object value) {
Subject subject = SecurityUtils.getSubject();
if (null != subject) {
Session session = subject.getSession();
LOGGER.debug("Session預設超時時間為[" + session.getTimeout() + "]毫秒");
if (null != session) {
session.setAttribute(key, value);
}
}
}
}
2.後臺realm
public class LoginRealm extends AuthorizingRealm {
private final Logger LOGGER = LoggerFactory.getLogger(LoginRealm.class);
private static final String ALGORITHM = "MD5";
@Autowired
private UserService userService;
@Autowired
private PermissionService permissionService;
@Autowired
private UserKzService userKzService;
public LoginRealm() {
super();
}
/**
* 驗證當前登入的Subject 本例中該方法的呼叫時機為LoginController.login()方法中執行Subject.login()時
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
throws AuthenticationException {
// 這個authcToken是從LoginController裡面currentUser.login(token)傳過來的
UsernamePasswordUsertypeToken token = (UsernamePasswordUsertypeToken) authcToken;
LOGGER.debug("驗證當前Subject時獲取到token為"
+ ReflectionToStringBuilder.toString(token, ToStringStyle.MULTI_LINE_STYLE));
User users = userService.getUserByName(token.getUsername());
UserKz userKz =userKzService.selectByPrimaryKey(users.getUserId());
String usertype =userKz.getUserType();
// Shiro完成對比邏輯,返回和令牌相關的正確的驗證資訊,第一個引數填登入使用者名稱,第二個引數填合法的登入密碼
if (users != null&&usertype.equals("00")) {
ShiroUser shiroUser = new ShiroUser(users.getUserId(), users.getAccount());
AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(shiroUser,
users.getPassword(), getName());
this.setSession("shiroUser", shiroUser);
return authcInfo;
} else {
throw new AuthenticationException();
}
// 沒有返回登入使用者名稱對應的SimpleAuthenticationInfo物件時,就會在LoginController中丟擲UnknownAccountException異常
}
/**
* 為當前登入的Subject授予角色和許可權 ,該方法的呼叫時機為需授權資源被訪問時
* 並且每次訪問需授權資源時都會執行該方法中的邏輯,這表明本例中預設並未啟用AuthorizationCache
* 個人感覺若使用了Spring3.1開始提供的ConcurrentMapCache支援,則可靈活決定是否啟用AuthorizationCache
* 比如說這裡從資料庫獲取許可權資訊時,先去訪問Spring3.1提供的快取,而不使用Shior提供的AuthorizationCache
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
ShiroUser shiroUser = (ShiroUser) principals.fromRealm(getName()).iterator().next();
String username = shiroUser.getAccount();
if (username != null) {
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 查詢使用者授權資訊
List<Permission> perList = permissionService.getShiro("O");
if (perList != null && perList.size() != 0) {
for (Permission permission : perList) {
info.addStringPermission(permission.getPmsnCode());
}
return info;
}
}
return null;
}
/**
* 更新使用者授權資訊快取.
*/
public void clearCachedAuthorizationInfo(String principal) {
SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName());
clearCachedAuthorizationInfo(principals);
}
/**
* 清除所有使用者授權資訊快取.
*/
public void clearAllCachedAuthorizationInfo() {
Cache<Object, AuthorizationInfo> cache = getAuthorizationCache();
if (cache != null) {
for (Object key : cache.keys()) {
cache.remove(key);
}
}
}
@PostConstruct
public void initCredentialsMatcher() {// MD5加密
HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(ALGORITHM);
setCredentialsMatcher(matcher);
}
/**
* 將一些資料放到ShiroSession中,以便於其它地方使用
*
* 比如Controller,使用時直接用HttpSession.getAttribute(key)就可以取到
*/
private void setSession(Object key, Object value) {
Subject subject = SecurityUtils.getSubject();
if (null != subject) {
Session session = subject.getSession();
LOGGER.debug("Session預設超時時間為[" + session.getTimeout() + "]毫秒");
if (null != session) {
session.setAttribute(key, value);
}
}
}
}
3.書寫總的管理realm
public class DefautModularRealm extends org.apache.shiro.authc.pam.ModularRealmAuthenticator {
private Map<String, Object> definedRealms;
/**
* 多個realm實現
*/
@Override
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
return super.doMultiRealmAuthentication(realms, token);
}
/**
* 呼叫單個realm執行操作
*/
@Override
protected AuthenticationInfo doSingleRealmAuthentication(Realm realm,AuthenticationToken token) {
// 如果該realms不支援(不能驗證)當前token
if (!realm.supports(token)) {
throw new ShiroException("token錯誤!");
}
AuthenticationInfo info = null;
try {
info = realm.getAuthenticationInfo(token);
if (info == null) {
throw new ShiroException("token不存在!");
}
} catch (Exception e) {
throw new ShiroException("使用者名稱或者密碼錯誤!");
}
return info;
}
/**
* 判斷登入型別執行操作
*/
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)throws AuthenticationException {
this.assertRealmsConfigured();
Realm realm = null;
UsernamePasswordUsertypeToken token = (UsernamePasswordUsertypeToken) authenticationToken;
//判斷是否是後臺使用者
if (token.getUsertype().equals("admin")) {
realm = (Realm) this.definedRealms.get("loginRealm");
}
else{
realm = (Realm) this.definedRealms.get("userloginRealm");
}
return this.doSingleRealmAuthentication(realm, authenticationToken);
}
/**
* 判斷realm是否為空
*/
@Override
protected void assertRealmsConfigured() throws IllegalStateException {
this.definedRealms = this.getDefinedRealms();
if (CollectionUtils.isEmpty(this.definedRealms)) {
throw new ShiroException("值傳遞錯誤!");
}
}
public Map<String, Object> getDefinedRealms() {
return this.definedRealms;
}
public void setDefinedRealms(Map<String, Object> definedRealms) {
this.definedRealms = definedRealms;
}
}
4.書寫包含使用者型別的token繼承 UsernamePasswordToken
public class UsernamePasswordUsertypeToken extends UsernamePasswordToken {
private static final long serialVersionUID = 1L;
private String usertype ;
public String getUsertype() {
return usertype;
}
public void setUsertype(String usertype) {
this.usertype = usertype;
}
public UsernamePasswordUsertypeToken(String loginName, String password, String usertype) {
super(loginName, password);
this.usertype = usertype;
}
}
4.將配置寫入到shiro的配置檔案中
<!-- 使用者授權資訊Cache 快取在本機記憶體,不支援叢集 -->
<bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"/>
<!-- 繼承自AuthorizingRealm的自定義Realm,即指定Shiro驗證使用者登入的類為自定義的ShiroDbRealm.java -->
<bean id="loginRealm" class="com.jscredit.base.shiro.LoginRealm">
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!-- 繼承自AuthorizingRealm的自定義Realm,即指定Shiro驗證前臺使用者登入的類為自定義的ShiroDbRealm.java -->
<bean id="userloginRealm" class="com.jscredit.zxypt.shiro.UserLoginRealm">
<property name="cacheManager" ref="cacheManager"/>
</bean>
<!--多個realm 的集中管理 -->
<bean id="defineModularRealmAuthenticator" class=" com.jscredit.zxypt.shiro.DefautModularRealm">
<property name="definedRealms">
<map>
<entry key="loginRealm" value-ref="loginRealm" />
<entry key="userloginRealm" value-ref="userloginRealm" />
</map>
</property>
<property name="authenticationStrategy">
<bean class="org.apache.shiro.authc.pam.FirstSuccessfulStrategy" />
</property>
</bean>
<!-- Shiro預設會使用Servlet容器的Session,可通過sessionMode屬性來指定使用Shiro原生Session -->
<!-- 即<property name="sessionMode" value="native"/>,詳細說明見官方文件 -->
<!-- 這裡主要是設定自定義的單Realm應用,若有多個Realm,可使用'realms'屬性代替 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="authenticator" ref="defineModularRealmAuthenticator" />
<!-- <property name="realm" ref="loginRealm"/> -->
<property name="realms" >
<list>
<bean id="loginRealm" class="com.jscredit.base.shiro.LoginRealm" />
<bean id="userloginRealm" class="com.jscredit.zxypt.shiro.UserLoginRealm" />
</list>
</property>
<property name="cacheManager" ref="cacheManager"/>
</bean>
這樣就可以在controller 中根據前後臺傳入不同的type 值 ,然後總的realm 會根據當前型別來判斷執行哪個realm 進而執行不同的驗證。然後我們再在realm中進行前後臺使用者的區分。進而執行自己的業務邏輯啦