shiro進行登陸認證和權限管理的實現。其中需求涉及使用兩個角色分別是:門店,公司。如今要二者實現分開登陸。即須要兩個Realm——MyShiroRealmSHOP和MyShiroRealmCOMPANY,分別處理門店,公司的驗證功能。java
可是正常狀況下,當定義了多個Realm,不管是門店登陸仍是公司登陸,都會由這兩個Realm共同處理。這是由於,當配置了多個Realm時,咱們一般使用的認證器是shiro自帶的org.apache.shiro.authc.pam.ModularRealmAuthenticator,其中決定使用的Realm的是doAuthenticate()方法,源代碼以下:redis
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。spring
爲了實現需求,我會建立一個org.apache.shiro.authc.pam.ModularRealmAuthenticator的子類,並重寫doAuthenticate()方法,讓特定的Realm完成特定的功能。如何區分呢?我會同時建立一個org.apache.shiro.authc.UsernamePasswordToken的子類,在其中添加一個字段VirtualType,用來標識登陸的類型,便是門店登陸仍是公司登陸。具體步驟以下:apache
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:ide
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: ui
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配置this
@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(); }
登錄便可:.net
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