springboot shiro 多realm配置认证、授权
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配置认证、授权的更多相关文章
- SpringBoot整合shiro实现用户的认证授权
* 项目环境搭建 * 配置ShiroConfig,用于shiro的基本配置和注入自定义规则 * 实现自定义的realm,继承AuthorizingRealm * 编写测试controller和页面 基 ...
- Shiro学习笔记 三(认证授权)
第一种首先基于角色的权限控制 1.由于不断的创建SecurityFactory工程等步骤重复多次,所以应该将这些步骤封装成一个工具类 还是首先看一下目录结构 主要用到文件 首先贴一下工具类的方法 pa ...
- 30分钟了解Shiro与Springboot的多Realm基础配置
写在前面的话: 我之前写过两篇与shiro安全框架有关的博文,居然能够广受欢迎实在令人意外.说明大家在互联网时代大伙对于安全和登录都非常重视,无论是大型项目还是中小型业务,普遍都至少需要登录与认证的逻 ...
- SpringBoot 集成Shiro之使用Redis缓存授权认证信息
因为用户认证与授权需要从数据库中查询并验证信息,但是对于权限很少改变的情况,这样不断从数据库中查询角色验证权限,对整个系统的开销很大,对数据库压力也随之增大.因此可以将用户认证和授权信息都缓存起来,第 ...
- 【原】无脑操作:IDEA + maven + Shiro + SpringBoot + JPA + Thymeleaf实现基础认证权限
开发环境搭建参见<[原]无脑操作:IDEA + maven + SpringBoot + JPA + Thymeleaf实现CRUD及分页> 需求: ① 除了登录页面,在地址栏直接访问其他 ...
- springboot shiro和freemarker集成之权限控制完全参考手册(跳过认证,登录由三方验证,全网首发)
本文主要考虑单点登录场景,登录由其他系统负责,业务子系统只使用shiro进行菜单和功能权限校验,登录信息通过token从redis取得,这样登录验证和授权就相互解耦了. 用户.角色.权限进行集中式管理 ...
- Spring Cloud之路:(七)SpringBoot+Shiro实现登录认证和权限管理
版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/sage_wang/article/details/79592269一.Shiro介绍1.Shiro是 ...
- (转)Springboot+shiro配置笔记+错误小结
springboot不像springmvc,它没有xml配置文件,那该如何配置shiro呢,其实也不难,用java代码+注解来解决这个问题.仅以此篇记录我对shiro的学习,如有对过客造成不便,实在抱 ...
- Springboot+shiro配置笔记+错误小结
软件152 尹以操 springboot不像springmvc,它没有xml配置文件,那该如何配置shiro呢,其实也不难,用java代码+注解来解决这个问题.仅以此篇记录我对shiro的学习,如有对 ...
随机推荐
- 光标属性CSS cursor 属性
CSS cursor 属性 CSS cursor属性,以前不知道,如果以后用到自己看的 <html> <body> <p>请把鼠标移动到单词上,可以看到鼠标指针发生 ...
- ZYQAssetPickerController的使用,不错的图片选择
import UIKit class ViewController: UIViewController,ZYQAssetPickerControllerDelegate,UIImagePickerCo ...
- PHPMailer使用说明
PHPMailer是一个用来发送电子邮件的函数包,远比PHP提供的mail()方便易用. 邮件格式说明 一封普通的电子邮件,通常是由发件人.收件人.抄送人.邮件标题.邮件内容.附件等内容构成.以下是一 ...
- 构建工具系列二--Grunt
本文地址: http://www.cnblogs.com/blackmanba/p/frontend-scaffold-grunt.html或者http://forkme.info/frontend- ...
- 什么时候使用try-catch
上篇文章叙述了try-catch的效率问题,以及StackOverflow上各路大神们指出使用try-catch应该注意的一些问题. 这篇文章作为补充,转述下StackOverflow上各路大神对于何 ...
- CentOS 5/6 下添加epel源
如果既想获得 RHEL 的高质量.高性能.高可靠性,又需要方便易用(关键是免费)的软件包更新功能,那么 Fedora Project 推出的 EPEL(Extra Packages for Enter ...
- SQL Server中怎样可以从SELECT语句的结果集中删除重复行
首先要分析出现重复记录的原因,是不是有一些where条件没有加上,把该加的条件都加上如果还有结果集重复,考虑以下方法去重: 结果集中去除重复行可以使用函数[distinct]也可以使用分组语句[gro ...
- ssh: connect to host port 22: Connection refused
配置Linux的互信,互信完成后,连接需要+端口才可以已经将端口修改为7777,但依旧走的是默认的22端口修改方式:[root@yoon ssh]# vi /etc/servicesssh ...
- Day 05 流程控制
流程控制 if 判断 语法: if if...else if...elif...else if判断是干什么的呢?if判断其实是在模拟人做判断.就是说如果这样干什么,如果那样干什么. if...else ...
- echart的tooltip自定义换行
自定义换行,内容很长的时候 tooltip : { trigger: 'axis', axisPointer : { // 坐标轴指示器,坐标轴触发有效 type : 'shadow' // 默认为直 ...