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

springboot shiro 多realm配置认证、授权的更多相关文章

  1. SpringBoot整合shiro实现用户的认证授权

    * 项目环境搭建 * 配置ShiroConfig,用于shiro的基本配置和注入自定义规则 * 实现自定义的realm,继承AuthorizingRealm * 编写测试controller和页面 基 ...

  2. Shiro学习笔记 三(认证授权)

    第一种首先基于角色的权限控制 1.由于不断的创建SecurityFactory工程等步骤重复多次,所以应该将这些步骤封装成一个工具类 还是首先看一下目录结构 主要用到文件 首先贴一下工具类的方法 pa ...

  3. 30分钟了解Shiro与Springboot的多Realm基础配置

    写在前面的话: 我之前写过两篇与shiro安全框架有关的博文,居然能够广受欢迎实在令人意外.说明大家在互联网时代大伙对于安全和登录都非常重视,无论是大型项目还是中小型业务,普遍都至少需要登录与认证的逻 ...

  4. SpringBoot 集成Shiro之使用Redis缓存授权认证信息

    因为用户认证与授权需要从数据库中查询并验证信息,但是对于权限很少改变的情况,这样不断从数据库中查询角色验证权限,对整个系统的开销很大,对数据库压力也随之增大.因此可以将用户认证和授权信息都缓存起来,第 ...

  5. 【原】无脑操作:IDEA + maven + Shiro + SpringBoot + JPA + Thymeleaf实现基础认证权限

    开发环境搭建参见<[原]无脑操作:IDEA + maven + SpringBoot + JPA + Thymeleaf实现CRUD及分页> 需求: ① 除了登录页面,在地址栏直接访问其他 ...

  6. springboot shiro和freemarker集成之权限控制完全参考手册(跳过认证,登录由三方验证,全网首发)

    本文主要考虑单点登录场景,登录由其他系统负责,业务子系统只使用shiro进行菜单和功能权限校验,登录信息通过token从redis取得,这样登录验证和授权就相互解耦了. 用户.角色.权限进行集中式管理 ...

  7. Spring Cloud之路:(七)SpringBoot+Shiro实现登录认证和权限管理

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/sage_wang/article/details/79592269一.Shiro介绍1.Shiro是 ...

  8. (转)Springboot+shiro配置笔记+错误小结

    springboot不像springmvc,它没有xml配置文件,那该如何配置shiro呢,其实也不难,用java代码+注解来解决这个问题.仅以此篇记录我对shiro的学习,如有对过客造成不便,实在抱 ...

  9. Springboot+shiro配置笔记+错误小结

    软件152 尹以操 springboot不像springmvc,它没有xml配置文件,那该如何配置shiro呢,其实也不难,用java代码+注解来解决这个问题.仅以此篇记录我对shiro的学习,如有对 ...

随机推荐

  1. DB-MySQL:MySQL 事务

    ylbtech-DB-MySQL:MySQL 事务 1.返回顶部 1. MySQL 事务 MySQL 事务主要用于处理操作量大,复杂度高的数据.比如说,在人员管理系统中,你删除一个人员,你即需要删除人 ...

  2. Linux就该这么学 20181009(第十二章 SAMBA)

    参考链接https://www.linuxprobe.com Samba 跨平台的文件共享 linux-linux linux-windows /etc/samba/smb.conf 里面 []这个名 ...

  3. 利用bat批处理——实现数据库的自动备份和删除

    之前见别人一直在玩批处理,最近公司也在用,就顺便学习下: 首先创建一个 txt文件 命名BackupDataBase  并修改后缀为.bat 编写两条命令: sqlcmd -S . -E -Q &qu ...

  4. User_Login_Register_Shopping+装饰器 3.0

    #!/usr/bin/env python# -*- coding: utf-8 -*-# @Time : 2018/5/27 0027 14:07# @Author : Anthony.Waa# @ ...

  5. 动态数组C++实现

    回顾大二的数据结构知识.从数组开始.实现了一个可自动扩充容量的泛型数组. 头文件:Array.h #ifndef Array_hpp #define Array_hpp template <cl ...

  6. SecurityManager USE

    import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; import java.io. ...

  7. 加减法计算器-java

    由于经常进行较大数据的加减法计算,好多计算器都是转换成科学技术法的,所以自己用java写了一个 功能如下: 1,可以输入多个带千位分隔符的数字,进行加减法计算 2,结果展示带千位分隔符 3,结果展示不 ...

  8. 洛谷1099 [NOIP2007] 树网的核

    链接https://www.luogu.org/problemnew/show/P1099 题目描述 设T=(V,E,W)是一个无圈且连通的无向图(也称为无根树),每条边到有正整数的权,我们称TTT为 ...

  9. Java基础学习总结(59)——30 个java编程技巧

    1.return 一个空的集合,而不是 null 如果一个程序返回一个没有任何值的集合,请确保一个空集合返回,而不是空元素.这样你就不用去写一大堆 "if else" 判断null ...

  10. ASP.NET-post、get的区别

    post.get的区别 1.get通过把参数加在浏览器的地址栏中提交(最大2K),用post可以进行文件的提交: 2.使用post提交的页面在点击[刷新]按钮的时候浏览器一般会提示"是否重新 ...