shiro進行登錄認證和權限管理的實現。其中需求涉及使用兩個角色分別是:門店,公司。現在要兩者實現分開登錄。即需要兩個Realm——MyShiroRealmSHOP和MyShiroRealmCOMPANY,分別處理門店,公司的驗證功能。
但是正常情況下,當定義了多個Realm,無論是門店登錄還是公司登錄,都會由這兩個Realm共同處理。這是因為,當配置了多個Realm時,我們通常使用的認證器是shiro自帶的org.apache.shiro.authc.pam.ModularRealmAuthenticator,其中決定使用的Realm的是doAuthenticate()方法,源代碼如下:
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
assertRealmsConfigured();
Collection<Realm> realms = getRealms();
if (realms.size() == 1) {
return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken);
} else {
return doMultiRealmAuthentication(realms, authenticationToken);
}
}
上述代碼的意思就是如果有多個Realm就會使用所有配置的Realm。 只有一個的時候,就直接使用當前的Realm。
為了實現需求,我會創建一個org.apache.shiro.authc.pam.ModularRealmAuthenticator的子類,並重寫doAuthenticate()方法,讓特定的Realm完成特定的功能。如何區分呢?我會同時創建一個org.apache.shiro.authc.UsernamePasswordToken的子類,在其中添加一個字段VirtualType,用來標識登錄的類型,即是門店登錄還是公司登錄。具體步驟如下:
public enum VirtualType {
COMPANY, // 公司
SHOP // 門店
}
接下來新建org.apache.shiro.authc.UsernamePasswordToken的子類UserToken
import org.apache.shiro.authc.UsernamePasswordToken;
public class UserToken extends UsernamePasswordToken {
private VirtualType virtualType;
public UserToken(final String username, final String password, VirtualType virtualType) {
super(username, password);
this.virtualType = virtualType;
}
public VirtualType getVirtualType() {
return virtualType;
}
public void setVirtualType(VirtualType virtualType) {
this.virtualType = virtualType;
}
}
新建org.apache.shiro.authc.pam.ModularRealmAuthenticator的子類UserModularRealmAuthenticator:
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;
public class UserModularRealmAuthenticator extends ModularRealmAuthenticator {
private static final Logger logger = LoggerFactory.getLogger(UserModularRealmAuthenticator.class);
@Override
protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken)
throws AuthenticationException {
logger.info("UserModularRealmAuthenticator:method doAuthenticate() execute ");
// 判斷getRealms()是否返回為空
assertRealmsConfigured();
// 強制轉換回自定義的CustomizedToken
UserToken userToken = (UserToken) authenticationToken;
// 登錄類型
VirtualType virtualType = userToken.getVirtualType();
// 所有Realm
Collection<Realm> realms = getRealms();
// 登錄類型對應的所有Realm
Collection<Realm> typeRealms = new ArrayList<>();
for (Realm realm : realms) {
if (realm.getName().contains(virtualType.toString())) // 注:這里使用類名包含枚舉,區分realm
typeRealms.add(realm);
}
// 判斷是單Realm還是多Realm
if (typeRealms.size() == 1) {
logger.info("doSingleRealmAuthentication() execute ");
return doSingleRealmAuthentication(typeRealms.iterator().next(), userToken);
} else {
logger.info("doMultiRealmAuthentication() execute ");
return doMultiRealmAuthentication(typeRealms, userToken);
}
}
}
創建分別處理門店登錄還是公司登錄的Realm:
import org.apache.shiro.authc.*;
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 org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.Set;
/**公司登陸realm
*/
public class MyShiroRealmCOMPANY extends AuthorizingRealm {
@Autowired
IUserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal(); // 一定是String類型,在SimpleAuthenticationInfo
SystemUser systemUser = userService.getUserByName(username, VirtualType.COMPANY);
if (systemUser == null) {
throw new RuntimeException("system concurrent exception: COMPANY user not found:username=" + username);
}
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set<String> stringPermissions = new HashSet<>(256);
// 字符串資源
authorizationInfo.addStringPermissions(stringPermissions);
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
UserToken token = (UserToken)authenticationToken;
// 邏輯登陸
return new SimpleAuthenticationInfo(token.getPrincipal(), token.getCredentials(), getName());
}
}
import org.apache.shiro.authc.*;
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 org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.Set;
/**門店登陸realm
*/
public class MyShiroRealmSHOP extends AuthorizingRealm {
@Autowired
IUserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal(); // 一定是String類型,在SimpleAuthenticationInfo
SystemUser systemUser = userService.getUserByName(username, VirtualType.SHOP);
if (systemUser == null) {
throw new RuntimeException("system concurrent exception: SHOP user not found:username=" + username);
}
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Set<String> stringPermissions = new HashSet<>(256);
// 字符串資源
authorizationInfo.addStringPermissions(stringPermissions);
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
// UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
UserToken token = (UserToken)authenticationToken;
// 邏輯登陸
return new SimpleAuthenticationInfo(token.getPrincipal(), token.getCredentials(), getName());
}
}
ShiroConfig配置
@Bean("securityManager")
public SecurityManager securityManager(RedisTemplate redisTemplate) { // redisTemplate配置的redis緩存,可忽略
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
List<Realm> realms = new ArrayList<>();
//添加多個Realm
realms.add(myShiroRealmSHOP(redisTemplate));
realms.add(myShiroRealmCOMPANY(redisTemplate));
securityManager.setAuthenticator(modularRealmAuthenticator()); // 需要再realm定義之前
securityManager.setRealms(realms);
securityManager.setSessionManager(myShiroSession(redisTemplate));
return securityManager;
}
/**
* 系統自帶的Realm管理,主要針對多realm 認證
*/
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator() {
//自己重寫的ModularRealmAuthenticator
UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return modularRealmAuthenticator;
}
@Bean("myShiroRealmSHOP")
public MyShiroRealmSHOP myShiroRealmSHOP(RedisTemplate redisTemplate) {
return new MyShiroRealmSHOP();
}
@Bean("myShiroRealmCOMPANY")
public MyShiroRealmCOMPANY myShiroRealmCOMPANY(RedisTemplate redisTemplate) {
return new MyShiroRealmCOMPANY();
}
登陸即可:
subject.login(new UserToken(username, password, virtualType))
這里需要注意的是,上述配置的Authenticator主要針對登陸認證,對於授權時沒有控制的,使用資源注入時會發現,使用的是myShiroRealmSHOP的doGetAuthorizationInfo方法(上面SHOP的定義在前),沒有走對應的realm的授權,產生問題錯亂;
新建org.apache.shiro.authz.ModularRealmAuthorizer子類:
import org.apache.shiro.authz.Authorizer; import org.apache.shiro.authz.ModularRealmAuthorizer; import org.apache.shiro.realm.Realm; import org.apache.shiro.subject.PrincipalCollection;
public class UserModularRealmAuthorizer extends ModularRealmAuthorizer { @Override public boolean isPermitted(PrincipalCollection principals, String permission) { assertRealmsConfigured(); for (Realm realm : getRealms()) { if (!(realm instanceof Authorizer)){ continue;} // todo 授權配置 if (realm.getName().contains(VirtualType.COMPANY.toString())) { // 判斷realm if (permission.contains("company")) { // 判斷是否改realm的資源 return ((MyShiroRealmCOMPANY) realm).isPermitted(principals, permission); // 使用改realm的授權方法 } } if (realm.getName().contains(VirtualType.SHOP.toString())) { if (permission.contains("shop")) { return ((MyShiroRealmSHOP) realm).isPermitted(principals, permission); } } } return false; } }
然后在ShiroConfig更改:
@Bean("securityManager")
public SecurityManager securityManager(RedisTemplate redisTemplate) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 使用注解,@RequiresPermissions,讀取緩存權限信息保存key對象為:{@link org.apache.shiro.subject.SimplePrincipalCollection},所以redis緩存配置的String不能轉換
// securityManager.setCacheManager(redisCacheManager(redisTemplate));
List<Realm> realms = new ArrayList<>();
//添加多個Realm
realms.add(myShiroRealmSHOP(redisTemplate));
realms.add(myShiroRealmCOMPANY(redisTemplate));
securityManager.setAuthenticator(modularRealmAuthenticator());
securityManager.setAuthorizer(modularRealmAuthorizer()); // 這里
securityManager.setRealms(realms);
securityManager.setSessionManager(myShiroSession(redisTemplate));
return securityManager;
}
/**
* 系統自帶的Realm管理,主要針對多realm 認證
*/
@Bean
public ModularRealmAuthenticator modularRealmAuthenticator() {
//自己重寫的ModularRealmAuthenticator
UserModularRealmAuthenticator modularRealmAuthenticator = new UserModularRealmAuthenticator();
modularRealmAuthenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
return modularRealmAuthenticator;
}
/**
* 系統自帶的Realm管理,主要針對多realm 授權
*/
@Bean
public ModularRealmAuthorizer modularRealmAuthorizer() {
//自己重寫的ModularRealmAuthorizer
UserModularRealmAuthorizer modularRealmAuthorizer = new UserModularRealmAuthorizer();
return modularRealmAuthorizer;
}
@Bean("myShiroRealmSHOP")
public MyShiroRealmSHOP myShiroRealmSHOP(RedisTemplate redisTemplate) {
return new MyShiroRealmSHOP();
}
@Bean("myShiroRealmCOMPANY")
public MyShiroRealmCOMPANY myShiroRealmCOMPANY(RedisTemplate redisTemplate) {
return new MyShiroRealmCOMPANY();
}
參考:https://blog.csdn.net/cckevincyh/article/details/79629022
