什么是ACL和RBAC#
ACL#
- Access Control list:访问控制列表
- 优点:简单易用,开发便捷
- 缺点:用户和权限直接挂钩,导致在授予时的复杂性,比较分散,不便于管理
- 例子:常见的文件系统权限设计,直接给用户加权限
RBAC#
- Role Based Access Control:基于角色的访问控制
- 权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限
- 优点:简化了用户与权限的管理,通过对用户进行分类,使得角色与权限关联起来
- 缺点:开发比ACL相对复杂
- 例子:基于RBAC模型的权限验证框架,Apache Shiro
什么是Apache Shiro#
官网地址#
点我直达
介绍#
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
什么是身份认证#
Authentication,身份认证,一般就是登陆校验
什么是授权#
Authorization,给用户分配角色或者访问某些资源的权限
什么是会话管理#
Session Management,用户的会话管理员,多数情况下是web session
什么是加密#
Cryptography,数据加密,比如密码加解密
核心概念#
Subject#
我们把用户或者程序称为主体,主体去访问系统或者资源
SecurityManager#
安全管理器,Subject的认证和授权都要在安全管理器下进行
Realm#
数据域,Shiro和安全数据的连接器,通过realm获取认证授权相关信息
Authenticator#
认证器,主要负责Subject的认证
Authorizer#
授权器,主要负责Subject的授权,控制Subject拥有的角色或者权限
Crytography#
加解密,Shiro的包含易于使用和理解的数据加解密方法,简化了很多复杂的API
Cache Manager#
缓存管理器,比如认证或授权信息,通过缓存进行管理,提高性能
快速上手#
构建项目#
认证和授权#
常用API#
//是否有对应的角色
subject.hasRole("root");
//获取subject名
subject.getPrincipal();
//检查是否有对应的角色,无返回值,直接在SecurityManager里面进行判断
subject.checkRole("admin");
//检查是否有对应的角色
subject.hasRole("admin");
//退出登录
subject.logout();
realm实战#
作用#
Shiro从Realm获取安全数据
概念#
- principal:主体的标识,可以有多个,但是需要具有唯一性,如:手机号、邮箱
- credential:凭证,一般就是密码
内置ini realm#
校验权限#
注:配置文件必须ini结尾
内置JdbcRealm#
方式一#
注意#
表名和字段要对应上,否则自定义定,继承:AuthorizingRealm,重写sql查询语句!!!!并重新指定realm类型!!!!
方式二#
自定义realm#
继承AuthorizingRealm,重写授权方法doGetAuthorizationInfo、重写认证方法doGetAuthenticationInfo。
UsernamePasswordToken:对应就是shiro的token中有Principal和Credential。
SimpleAuthorizationInfo:代表用户角色权限信息
SimpleAuthenticationInfo:代表该用户的认证信息
Filter过滤器#
-
核心过滤器
- DefaultFilter,配置那个路径对应那个拦截器进行处理
-
authc:org.apache.shiro.web.filter.authc.FormAuthenticationFilter
- 需要认证登录才能访问
-
user:org.apache.shiro.web.filter.authc.UseerrFilter
- 用户拦截器,表示必须存在用户
-
anon:org.apache.shiro.web.filter.authc.AnonymoousFilter
- 匿名拦截器,不需要登录即可访问的资源,匿名用户或游客,一般用于过滤静态资源。
-
roles:org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
- 角色授权拦截器,验证用户是否拥有角色
- 参数可写多个,表示某些角色才能通过,多个参数时,写roles["root,role1"],当有多个参数时必须每个参数都通过才算通过
-
perms:org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
- 权限授权拦截器,验证用户是否拥有权限
- 参数可写多个,表示需要某些权限才能通过,多个参数写perms["user,admin"],当有多个参数时必须每个参数都通过才算可以
-
authcBasci: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配置路径#
- 路径通配符支持?、*、**,注意通配符匹配不包含目录分隔符“/”
- *:可以匹配所有,不加*,可以进行前缀匹配,但多个冒号就需要多个*来匹配
url权限采取第一次匹配优先的方式 ?:匹配一个字符,如:/user?,匹配:/user1,但不匹配:/user/ *:匹配零个或多个字符串,如:/add*,匹配:/addtest,但不匹配:/user/1 **:匹配路径中的零个或多个路径,如:/user/**将匹配:/user/xxx/yyy
Shiro权限控制注解#
注解方式#
-
@RequiresRoles(value={"admin","editor"},logical=Logical.AND)
- 需要角色:admin和editor两个角色,AND表示两个同时成立
-
RequiresPermissions(value={"user:add","user:del"},logical.OR)
- 需要权限user:add或user:del权限其中一个,OR是或的意思
-
@RequiresAuthentication
- 已经授过权,调用Subject.isAuthenticated()返回true
-
@RequiresUser
- 身份验证或通过记住我登录过的
使用文件的方式#
使用ShiroConfig。
编程方式#
SpringBoot整合Shiro#
技术栈#
前后端分离+SpringBoot+Mysql+Mybatis+Shiro+Redis+JDK8
数据库表#
项目结构#
项目源码#
链接: https://pan.baidu.com/s/1adjwICKge83YcPycE8ZaEQ 密码: if9s
项目postman测试#
127.0.0.1:12888/pub/index
127.0.0.1:12888/pub/not_permit
127.0.0.1:12888/pub/need_login
127.0.0.1:12888/pub/login
127.0.0.1:12888/authc/video/play_record
127.0.0.1:12888/admin/video/video_list
127.0.0.1:12888/video/add
127.0.0.1:12888/video/update
备注#
因为链接较多,就不一一做gif动图了,直接导入项目源码,请求的时候,在header上加入token即可~
Filter过滤器#
业务需求#
- 一个接口,可以让2个角色中的任意一个访问
- 自定义一个类,继承:AuthorizationFilter
package com.ybchen.springboot_shiro.config; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.CollectionUtils; import org.apache.shiro.web.filter.authz.AuthorizationFilter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.util.Set; /** * @Description:自定义Filter * @Author:chenyanbin * @Date:2021/1/4 11:14 下午 * @Versiion:1.0 */ public class CustomRolesOrAuthorizationFilter extends AuthorizationFilter { @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { Subject subject = getSubject(request, response); //filterChainDefinitionMap.put("/admin/**", "roles[admin,user]"); mappedValue <==> admin,user String[] rolesArray = (String[]) mappedValue; if (rolesArray == null || rolesArray.length == 0) { return true; } Set<String> roles = CollectionUtils.asSet(rolesArray); //当前subject是roles中的任意一个,则有权限访问 for (String role : roles) { if (subject.hasRole(role)) { return true; } } return false; } }
@Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { System.out.println("ShiroConfig ShiroFilterFactoryBean 执行"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //设置SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); //如果访问需要登录的某个接口,却没有登录,则调用此接口(如果不是前后端分离,则跳转页面) shiroFilterFactoryBean.setLoginUrl("/pub/need_login"); //shiroFilterFactoryBean.setLoginUrl("/xxx.jsp"); //登录成功后,跳转的链接,若前后端分离,没必要设置这个 //shiroFilterFactoryBean.setSuccessUrl(""); //登录成功,未授权会调用此方法 shiroFilterFactoryBean.setUnauthorizedUrl("/pub/not_permit"); //设置自定义Filter Map<String, Filter> filterMap=new LinkedHashMap<>(); filterMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter()); shiroFilterFactoryBean.setFilters(filterMap); //拦截路径,必须使用:LinkedHashMap,要不然拦截效果会时有时无,因为使用的是无序的Map Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); //key=正则表达式路径,value=org.apache.shiro.web.filter.mgt.DefaultFilter //退出过滤器 filterChainDefinitionMap.put("/logout", "logout"); //匿名可以访问,游客模式 filterChainDefinitionMap.put("/pub/**", "anon"); //登录用户才可以访问 filterChainDefinitionMap.put("/authc/**", "authc"); //管理员角色才能访问 // filterChainDefinitionMap.put("/admin/**", "roles[admin,user]"); filterChainDefinitionMap.put("/admin/**", "roleOrFilter[admin,user]"); //有编辑权限才能访问 filterChainDefinitionMap.put("/video/update", "perms[video_update]"); //authc:url必须通过认证才可以访问 //anon:url可以匿名访问 //过滤链是顺序执行,从上而下,一般把/**,放到最下面 filterChainDefinitionMap.put("/**", "authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; }
Redis整合CacheManager#
原因#
授权的时候每次都去查询数据库,对于频繁访问的接口,性能和响应速度比较慢,此处可以使用缓存,提高响应速度,也可以使用Guava(本地内存缓存)。
Redis(分布式缓存)还不了解的小伙伴,在这里我就不一一讲解了,可以看我以前写过的博客。
- Redis 从入门到精通:点我直达
- Redis 微信抢红包,电商场景下秒杀系统设计:点我直达
- Redis 高级项目实战:点我直达
添加依赖#
<!--shiro+redis--> <dependency> <groupId>org.crazycake</groupId> <artifactId>shiro-redis</artifactId> <version>3.3.1</version> </dependency>
在ShiroConfig中添加如下代码
//使用自定义cacheManager securityManager.setCacheManager(cacheManager()); /** * 配置redisManager * @return */ public RedisManager getRedisManager(){ RedisManager redisManager=new RedisManager(); redisManager.setHost("127.0.0.1:6379"); //连接那个数据库 redisManager.setDatabase(0); //设置密码 // redisManager.setPassword("123"); return redisManager; } /** * 设置具体cache实现类 * @return */ public RedisCacheManager cacheManager(){ RedisCacheManager redisCacheManager=new RedisCacheManager(); redisCacheManager.setRedisManager(getRedisManager()); return redisCacheManager; }
修改CustomRealm
设置redis缓存过期时间
Redis整合SessionManager#
为啥Session也要持久化#
重启应用,用户无感知,可以继续以原先的状态继续访问。
修改shiroconfig
Shiro整合Redis后的源码#
链接: https://pan.baidu.com/s/1cNQfBiw50A-U5izzOQclpw 密码: 6wqt