一:什么是ACL和RBAC:

ACL: Access Control List 访问控制列表
  以前盛行的一种权限设计,它的核心在于用户直接和权限挂钩
  优点:简单易用,开发便捷
  缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理
  例子:常见的文件系统权限设计, 直接给用户加权限
RBAC: Role Based Access Control
  基于角色的访问控制系统。权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限
  优点:简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来
  缺点:开发对比ACL相对复杂
  例子:基于RBAC模型的权限验证框架与应用 Apache Shiro、spring Security
  BAT企业 ACL,一般是对报表系统,阿里的ODPS

总结:不能过于复杂,规则过多,维护性和性能会下降, 更多分类 ABAC、PBAC等

二:Apache Shiro基础知识和架构

Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能

shiro包含四大核心模块:身份认证,授权,会话管理和加密

直达Apache Shiro官网 http://shiro.apache.org/introduction.html
什么是身份认证
  Authentication,身份证认证,一般就是登录
什么是授权
  Authorization,给用户分配角色或者访问某些资源的权限
什么是会话管理
  Session Management, 用户的会话管理员,多数情况下是web session
什么是加密
  Cryptography, 数据加解密,比如密码加解密等

shiro架构图

三.用户访问Shiro权限控制运行流程

Subject
  我们把用户或者程序称为主体(如用户,第三方服务,cron作业),主体去访问系统或者资源
SecurityManager
  安全管理器,Subject的认证和授权都要在安全管理器下进行
Authenticator
  认证器,主要负责Subject的认证
Realm
  数据域,Shiro和安全数据的连接器,好比jdbc连接数据库; 通过realm获取认证授权相关信息
Authorizer
  授权器,主要负责Subject的授权, 控制subject拥有的角色或者权限
Cryptography
  加解密,Shiro的包含易于使用和理解的数据加解密方法,简化了很多复杂的api
Cache Manager
  缓存管理器,比如认证或授权信息,通过缓存进行管理,提高性能

四.Springboot2.x整合Apache Shiro实战

项目环境:

  Maven3.5 + Jdk8 + Springboot 2.X + IDEA

相关依赖

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<!--注释掉-->
<!--<scope>runtime</scope>-->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<!--阿里巴巴druid数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.6</version>
</dependency>

<!--spring整合shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.16</version>
</dependency>

<!-- shiro+redis缓存插件 主要用于CacheManager,以及缓存sessionManage中配置session持久化-->
<dependency>
<groupId>org.crazycake</groupId>
<artifactId>shiro-redis</artifactId>
<version>3.1.0</version>
</dependency>

Shiro各个组件说明

Realm:

realm作用:Shiro 从 Realm 获取安全数据
  默认自带的realm:idae查看realm继承关系,有默认实现和自定义继承的realm
两个概念
  principal : 主体的标示,可以有多个,但是需要具有唯一性,常见的有用户名,手机号,邮箱等
  credential:凭证, 一般就是密码
  所以一般我们说 principal + credential 就账号 + 密码
开发中,往往是自定义realm , 即继承 AuthorizingRealm,重写doGetAuthenticationInfo(认证方法)和doGetAuthorizationInfo(授权方法)

当用户登陆的时候会调用 doGetAuthenticationInfo
进行权限校验的时候会调用: doGetAuthorizationInfo

UsernamePasswordToken : 对应就是 shiro的token中有Principal和Credential
  UsernamePasswordToken-》HostAuthenticationToken-》AuthenticationToken
SimpleAuthorizationInfo:代表用户角色权限信息
SimpleAuthenticationInfo :代表该用户的认证信息

Shiro内置的Filter

核心过滤器类:DefaultFilter, 配置哪个路径对应哪个拦截器进行处理
authc:org.apache.shiro.web.filter.authc.FormAuthenticationFilter
  需要认证登录才能访问
user:org.apache.shiro.web.filter.authc.UserFilter
  用户拦截器,表示必须存在用户。
anon:org.apache.shiro.web.filter.authc.AnonymousFilter
  匿名拦截器,不需要登录即可访问的资源,匿名用户或游客,一般用于过滤静态资源。
roles:org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
  角色授权拦截器,验证用户是或否拥有角色。
  参数可写多个,表示某些角色才能通过,多个参数时写 roles["admin,user"],当有多个参数时必须每个
  参数都通过才算通过
perms:org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
  权限授权拦截器,验证用户是否拥有权限
  参数可写多个,表示需要某些权限才能通过,多个参数时写 perms["user, admin"],当有多个参数时必
  须每个参数都通过才算可以

authcBasic:org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
  httpBasic 身份验证拦截器。
logout:org.apache.shiro.web.filter.authc.LogoutFilter
  退出拦截器,执行后会直接跳转到shiroFilterFactoryBean.setLoginUrl(); 设置的 url
port:org.apache.shiro.web.filter.authz.PortFilter
  端口拦截器, 可通过的端口。
ssl:org.apache.shiro.web.filter.authz.SslFilter
  ssl拦截器,只有请求协议是https才能通过。

  Filter的配置路径说明:

  /admin/video /user /pub
  路径通配符支持 ?、*、**,注意通配符匹配不 包括目录分隔符“/”
  心 可以匹配所有,不加*可以进行前缀匹配,但多个冒号就需要多个 * 来匹配
  URL权限采取第一次匹配优先的方式
    ? : 匹配一个字符,如 /user? , 匹配 /user3,但不匹配/user/;
    * : 匹配零个或多个字符串,如 /add* ,匹配 /addtest,但不匹配 /user/1
    ** : 匹配路径中的零个或多个路径,如 /user/** 将匹 配 /user/xxx 或 /user/xxx/yyy
  例子
  /user/**=filter1
  /user/add=filter2
  请求 /user/add 命中的是filter1拦截器
  性能问题:通配符比字符串匹配会复杂点,所以性能也会稍弱,推荐是使用字符串匹配方式

数据加解密器CredentialsMatche 

一般会自定义验证规则
  @Bean
  public HashedCredentialsMatcher hashedCredentialsMatcher(){
    HashedCredentialsMatcher hashedCredentialsMatcher = new
    HashedCredentialsMatcher();
    //散列算法,使用MD5算法;
    hashedCredentialsMatcher.setHashAlgorithmName("md5");
    //散列的次数,比如散列两次,相当于 md5(md5("xxx"));
    hashedCredentialsMatcher.setHashIterations(2);
    return hashedCredentialsMatcher;
}

Shiro的缓存模块CacheManager

  shiro中提供了对认证信息和授权信息的缓存。

  默认是关闭认证信息缓存的,对于授权信息的缓存shiro默认开启的(因为授权的数据量大,每次都要查询数据库,性能受到影响)
  AuthenticatingRealm 及 AuthorizingRealm 分别提供了对AuthenticationInfo 和 AuthorizationInfo 信息的缓
存。

Shiro中的SessionManager

  用户和程序直接的链接,程序可以根据session识别到哪个用户,和javaweb中的session类似

  什么是会话管理器SessionManager
    会话管理器管理所有subject的所有操作,是shiro的核心组件,前后端分离的应用中session通常用token代替,用于会话管理

RBAC权限控制架构设计

  1.数据库设计:老五张表

    用户,角色,权限,用户角色表,角色权限表

  2.shiro相关配置

    -配置ShiroFilterFactoryBean   

      配置流程和思路
      shiroFilterFactoryBean-》
        -SecurityManager-》
          -CustomSessionManager
          -CustomRealm-》hashedCredentialsMatcher
      SessionManager
        -DefaultSessionManager: 默认实现,常用于javase
        -ServletContainerSessionManager: web环境
        -DefaultWebSessionManager:常用于自定义实现(一般用这种)

ShiroConfig配置

@Configuration
public class ShiroConfig { /**
* 配置ShiroFilterFactoryBean
*
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { System.out.println("执行 ShiroFilterFactoryBean shiroFilter");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //必须设置securityManager
shiroFilterFactoryBean.setSecurityManager(securityManager); //需要登录的接口:如果访问某个接口,需要登录却没有登录,则调用此接口,如果前端后端不分离,则跳转到html页面
shiroFilterFactoryBean.setLoginUrl("/pub/need_login"); //登录成功 跳转url,如果前后端分离,则没这个调用 --这里设置为首页就行了
shiroFilterFactoryBean.setSuccessUrl("/"); //登录成功,但是没有权限,未授权就会调用这个接口,如果不是前后端分离,则跳转到403页面
shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit"); //设置自定义filter
Map<String, Filter> filterMap = new LinkedHashMap<>();
filterMap.put("roleOrFilter", new CustomRoleFilter()); //shiroFilterFactoryBean绑定自定义的filter
shiroFilterFactoryBean.setFilters(filterMap); //过滤器链的map
//拦截器(过滤器路径,坑一必须要用LinkedhashMap),部分路径无法进行拦截
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); /*
********************************************
通常是配置这些过滤器,也可以用个数据库的动态加载,这些数据
********************************************/
//退出过滤器
filterChainDefinitionMap.put("/logout", "logout"); //匿名可以访问,也就是游客模式
filterChainDefinitionMap.put("/pub/**", "anon"); //登录用户才可以访问
filterChainDefinitionMap.put("/authc/**", "authc"); //管理员角色才可以访问
filterChainDefinitionMap.put("/admin/**", "roles[admin]"); //有编辑权限才可以访问
filterChainDefinitionMap.put("/video/update", "perms[video_update]"); //坑二:过滤器是顺序执行,从上而下,一般来说/** 放到最下面 //authc: url定义必须通过认证才可以访问
//anno: url可以匿名访问
filterChainDefinitionMap.put("/**", "authc"); //配置过滤器
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean;
} /**
* 数据域
*
* @return
*/
@Bean
public CustomRealm customRealm() { CustomRealm customRealm = new CustomRealm(); //设置加密器--因为数据库中的密码不是明文存储
customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
return customRealm;
}
/**
* 自定义seesionManager
* -配置session持久化
* @return
*/
@Bean
public SessionManager sessionManager() {
//自定义CustomSessionManager 继承 DefaultWebSessionManager
CustomSessionManager customSessionManager = new CustomSessionManager(); //配置session持久化
customSessionManager.setSessionDAO(redisSessionDAO()); //超时时间,默认 30分钟,会话超时;方法里面的单位是毫秒
customSessionManager.setGlobalSessionTimeout(20000); return customSessionManager;
} /**
* 密码加解密规则 CredentialMatcher-配置在数据域中,用于数据的加解密
*
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); //设置散列算法:这里使用MD5算法
hashedCredentialsMatcher.setHashAlgorithmName("md5"); //散列次数,好比散列两次 相当于md5(md5(x))
hashedCredentialsMatcher.setHashIterations(2); return hashedCredentialsMatcher;
}
/**
* 配置redisManager
*/
@Bean
public RedisManager getRedisManager() { RedisManager redisManager = new RedisManager(); //默认就是localhost:6379 不写也行
redisManager.setHost("localhost");
redisManager.setPort(6379);
return redisManager;
}
/**
* 配置具体cache实现类RedisCacheManager
* 为什么要使用缓存:
* 缓存组件位于SecurityManager中,在CustomRealm数据域中,由于授权方法中每次都要查询数据库,性能受影响,因此将数据缓存起来,提高查询效率
* 除了使用Redis缓存,还能使用shiro-ehcache
*
* @return
*/
public RedisCacheManager redisCacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(getRedisManager());
//设置过期时间,单位是秒,20s,
redisCacheManager.setExpire(20);
return redisCacheManager;
}
/**
* 自定义session持久化
*
* @return
*/
public RedisSessionDAO redisSessionDAO() { /*
为啥session也要持久化?
重启应用,用户无感知,可以继续以原先的状态继续访问
注意点:
DO对象需要实现序列化接口 Serializable
logout接口和以前一样调用,请求logout后会删除redis里面的对应的key,即删除对应的token
*/
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(getRedisManager()); //配置自定义sessionId,shiro自动生成色sessionId不满足条件时可以使用
redisSessionDAO.setSessionIdGenerator(new CustomSessionIdGenerator());
return redisSessionDAO;
}
/**
* LifecycleBeanPostProcessor
* 管理shiro一些bean的生命周期 即bean初始化 与销毁
*
* @return
*/
@Bean
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
} /**
* AuthorizationAttributeSourceAdvisor
* 作用:加入shiro注解的使用,不加入这个AOP注解不生效(shiro的注解 例如 @RequiresGuest)
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new
AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
return authorizationAttributeSourceAdvisor;
} /**
* DefaultAdvisorAutoProxyCreator
* 作用: 用来扫描上下文寻找所有的Advistor(通知器), 将符合条件的Advisor应用到切入点的Bean中,需
* 要在LifecycleBeanPostProcessor创建后才可以创建
*
* @return
*/
@Bean
@DependsOn("lifecycleBeanPostProcessor")
public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new
DefaultAdvisorAutoProxyCreator();
defaultAdvisorAutoProxyCreator.setUsePrefix(true);
return defaultAdvisorAutoProxyCreator;
}
}

自定义Filter==>>>CustomRoleFilter 

/**
* 自定义Filter
* 问题:为什么要自定义filter:
* 因为配置role[admin,root] ,只有同时满足admin和root才能够访问,显然这是不合理的
* 实际是超级管理员有全部权限
*/
public class CustomRoleFilter extends AuthorizationFilter { @Override
public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
//获取当前访问路径所有角色的集合
Subject subject = getSubject(request, response);
String[] rolesArray = (String[]) mappedValue; //没有角色显示,可以直接访问
if (rolesArray == null || rolesArray.length == 0) {
//no roles specified, so nothing to check - allow access.
return true;
} //当前subject是roles中的任意一个,则有权限访问
Set<String> roles = CollectionUtils.asSet(rolesArray); for (String role : roles) {
if (subject.hasRole(role)) {
return true;
}
}
return subject.hasAllRoles(roles);
}
}

自定义SessionManager:===>>CustomSessionManager

 /** @Discription 自定义SessionMananger 为什么要自定义自定义SessionMananger?
* 原因:
* 因为前后端分离的情况下 不是靠session,而是靠token去交互,因此需要自定义这个sessionId的获取
* 即重写父类的方法(父类是从头中拿sessionId)
*
*/
public class CustomSessionManager extends DefaultWebSessionManager { //这个key 放在请求头中,可以自己定义 ,通常是设置为token或者authorization
private static final String AUTHORIZATION = "token"; public CustomSessionManager() {
super();
} @Override
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//将ServletRequest转换成HttpServletRequest
String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
if (sessionId != null) {
// request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "cookie");
//
// request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
// //automatically mark it valid here. If it is invalid, the
// //onUnknownSession method below will be invoked and we'll remove the attribute at that time.
// request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE); request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,
ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId); //automatically mark it valid here. If it is invalid, the
//onUnknownSession method below will be invoked and we'll remove the attribute at that time.
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return sessionId;
} else {
return super.getSessionId(request, response);
}
}
}

自定义数据域 CustomRealm

**
* 自定义的realm 数据域
*/ public class CustomRealm extends AuthorizingRealm { @Autowired
private UserService userService; /**
* 权限校验的时候会调用
*
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("授权 doGetAuthorizationInfo"); //从token中获取用户信息,token代表用户输入
// String username = (String) principals.getPrimaryPrincipal();
User newUser = (User) principals.getPrimaryPrincipal(); // 使用原因?
// 授权的时候每次都去查询数据库,对于频繁访问的接口,性能和响应速度比较慢,所以使用缓存 //提高性能的方法1-使用redis缓存:
// 将信息放到缓存,例如redis,但是要设置缓存失效时间,因为可能更新数据库了,但是缓存没有更新
//提高性能的方法2-使用shiro-redis集成的缓存:
// shiro-redis的缓存配置在SecurityManager中
User user = userService.findAllUserInfoByUsername(newUser.getUsername()); List<String> stringRoleList = new ArrayList<>();
List<String> stringPermissionList = new ArrayList<>(); List<Role> roleList = user.getRoleList(); for (Role role : roleList) {
stringRoleList.add(role.getName()); List<Permission> permissionList = role.getPermissionList(); for (Permission p : permissionList) {
if (p != null) {
stringPermissionList.add(p.getName());
}
}
}
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //将用户对应的角色和权限信息 放到权限器中
simpleAuthorizationInfo.addStringPermissions(stringPermissionList);
simpleAuthorizationInfo.addRoles(stringRoleList); return simpleAuthorizationInfo;
} /**
* 用户登录的时候会调用
*
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("认证 doGetAuthenticationInfo"); //从token中获取用户信息,token代表用户输入
String username = (String) token.getPrincipal(); User user = userService.findAllUserInfoByUsername(username); //取密码
String password = user.getPassword(); if (password == null || "".equals(password)) {
return null;
} return new SimpleAuthenticationInfo(user, password, this.getClass().getName());
}
} /*
* 原有的问题
class java.lang.String must has getter for field: authCacheKey or id\nWe need a
field to identify this Cache Object in Redis. So you need to defined an id field
which you can get unique id to identify this principal. For example, if you use
UserInfo as Principal class, the id field maybe userId, userName, email, etc. For
example, getUserId(), getUserName(), getEmail(), etc.\nDefault value is
authCacheKey or id, that means your principal object has a method called
\"getAuthCacheKey()\" or \"getId()\""
改造原有的逻辑,修改缓存的唯一key doGetAuthorizationInfo 方法
原有:
String username = (String)principals.getPrimaryPrincipal();
User user = userService.findAllUserInfoByUsername(username);
改为
User newUser = (User)principals.getPrimaryPrincipal();
User user = userService.findAllUserInfoByUsername(newUser.getUsername()); doGetAuthenticationInfo方法
原有:
return new SimpleAuthenticationInfo(username, user.getPassword(),
this.getClass().getName());
改为
return new SimpleAuthenticationInfo(user, user.getPassword(),
this.getClass().getName());
*
*
* */

代码地址:

https://github.com/AlenYang123456/Springboot-Shiro

 

最新文章

  1. css引入方式
  2. 获取div或者元素相对于屏幕坐上角的绝对位置
  3. xml/map转换器,递归设计思路
  4. 据说练就了一指禅神功的觅闻实时手机新闻网,正以每天2000+IP的用户量递增。有智能手机的可以当场进行体验,没有的就算了哈
  5. Linux内核系列之Block块层(一)
  6. 了解mongodb
  7. redis 验证消息队列也是写磁盘的
  8. 探索Windows Azure 监控和自动伸缩系列2 - 获取虚拟机的监控定义和监控数据
  9. subline text3常用插件介绍
  10. VB6之WM_COPYDATA
  11. Docker安装及基础使用
  12. 【Shell脚本学习指南笔记】重定向文件描述符 2&gt;&amp;1
  13. [Swift]LeetCode1034.边框着色 | Coloring A Border
  14. [Swift]LeetCode94. 二叉树的中序遍历 | Binary Tree Inorder Traversal
  15. POJ 2299 -Ultra-QuickSort-树状数组求逆序数
  16. jQuery的介绍
  17. Python实现C代码统计工具(四)
  18. [C#]获取连接MySql数据库及常用的CRUD操作
  19. nyoj-1250-exgcd
  20. Echart 仪表盘和柱形图

热门文章

  1. fastjson 反弹shell
  2. Chrome OS超便捷安装指南
  3. java安全初学之动态代理
  4. mongodb导入,导出实例
  5. kubernetes生产实践之redis-cluster
  6. 2019 GDUT Rating Contest I : Problem A. The Bucket List
  7. 2019 GDUT Rating Contest I : Problem C. Mooyo Mooyo
  8. python网络编程TCP服务多客户端的服务端开发
  9. Java 操作PPT数字签名(一):添加、检测、删除签名
  10. FutureTask核心源码分析