基于角色的权限访问控制(Role-Based Access Control)作为传统访问控制(自主访问,强制访问)的有前景的代替受到广泛的关注。在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。
在一个组织中,角色是为了完成各种工作而创造,用户则依据它的责任和资格来被指派相应的角色,用户可以很容易地从一个角色被指派到另一个角色。角色可依新的需求和系统的合并而赋予新的权限,而权限也可根据需要而从某角色中回收。角色与角色的关系可以建立起来以囊括更广泛的客观情况。
总结:通过给用户授予角色来获得权限。
系统的所有权限信息。权限具有上下级关系,是一个树状的结构。下面来看一个例子:
对于上面的每个权限,又存在两种情况:一个是只是可访问,另一种是可授权。
例如:对于“查看用户”这个权限,如果用户只被授予“可访问”,那么他就不能将他所具有的这个权限分配给其他人。
用户是应用系统的具体操作者,用户可以自己拥有权限信息,可以归属于0~N个角色,可属于0~N个组。他的权限集是自身具有的权限、所属的各角色具有的权限、所属的各组具有的权限的合集。它与权限、角色、组之间的关系都是n对n的关系。
为了对许多拥有相似权限的用户进行分类管理,定义了角色的概念。例如:系统管理员、管理员、用户、访客等角色。
角色具有上下级关系,可以形成树状视图,父级角色的权限是自身及它的所有子角色的权限的综合。父级角色的用户、父级角色的组同理可推。
为了更好地管理用户,对用户进行分组归类,简称为用户分组。
组也具有上下级关系,可以形成树状视图。
在实际情况中,我们知道,组也可以具有自己的角色信息、权限信息。这让我想到我们的QQ用户群,一个群可以有多个用户,一个用户也可以加入多个群。每个群具有自己的权限信息。例如查看群共享。QQ群也可以具有自己的角色信息,例如普通群、高级群等。
权限、角色、组、用户之间都存在n对n的关系。且两两之间都有关系。
创建Physical Data模型。
(1)用户表
CREATE TABLE `t_user`(
`uid` int primary key auto_increment,
`uname` varchar(10),
`password` varchar(20)
);
(2)角色表
CREATE TABLE `t_role`(
`rid` int primary key auto_increment,
`rname` varchar(50),
`permission` varchar(50) #角色权限标记,给shiro使用的
);
(3)权限表
CREATE TABLE `permission`(
`pid` int primary key auto_increment,
`pname` varchar(50), # 权限名
`permission` varchar(50) # 权限
);
(4)用户角色关系表
CREATE TABLE `t_user_role`(
`uid` int refereness `t_user`(`uid`),
`rid` int refereness `t_role`(`rid`)
);
(5)角色权限关系表
CREATE TABLE `t_role_permission`(
`rid` int refereness `t_role`(`rid`),
`pid` int referenecss `t_permission`(`pid`)
);
(1)用户:如系统的使用账户(登录的用户)
在 Shiro 中,代表访问系统的用户,被封装成 安全主体Subject对象;
(2)角色:拥有相同权限的用户
是权限的集合,一种角色可以被很多个用户拥有,一种角色可以包含多种权限;【多对多】
(3)权限:系统中可以被用户操作的元素(如:系统菜单,超链接,文件,按钮等非常具体的)
即操作资源的权利,比如访问某个页面,以及对某个模块的数据的CRUD操作权限。
官网:http://shiro.apache.org/
Apache Shiro是一个Java安全(权限)框架。
Shiro可以非常容易的开发出足够好的应用,不仅可用在JavaSE环境,也可以用在JavaEE环境。
Shiro可以完成:认证、授权、加密、会话管理、与web集成、缓存等。
下载地址:http://shiro.apache.org/download.html
git下载地址:https://github.com/apache/shiro
基本功能点如下图所示:
Authentication:认证器,身份认证/登录,验证用户(Subject)是不是拥有相应的身份;
Authorization:授权器,即权限验证,是一个接口。验证某个已认证的用户是否拥有某个权限;即判断用户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限。
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,他的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的。另外,它可以用在分布式会话中,实现CAS单点登录。
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储。
Web support:Web支持,可以非常容易的集成到Web环境的。
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率。
Concurrency:Shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去。
Testing:提供测试支持。
Run As:允许一个用户假装为另一个用户的身份证进行访问(如果另一个用户允许的情况下)。
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
从外部来看Shiro,即从应用程序角度来观察如何使用Shiro完成工作:
Subject:应用代码直接交互的对象(即认证主体)。 也就是说Shiro的对外API核心就是Subject。Subject代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫、机器人等。与Subject的所有交互都会委托给SecurityManager。
Subject其实是一个门面,SecurityManager才是实际的执行者。
Subject包含 Principals 和 Credentials 两个信息:
Principals:代表身份。可以是用户名、邮件、手机号码等等,用来标识一个登录主体的身份。
Credentials:代表凭证。常见的有密码,数字证书等等。
SecurityManager:安全管理器,即所有与安全有关的操作都会与SecurityManager交互, 且其管理着所有的Subject,可以看出它是Shiro的核心, 它负责与Shiro的其他组件进行交互, 相当于SpringMVC中的DispatcherServlet的角色。
Realm:域,Shiro从Realm获取安全数据(如用户、角色、权限), 就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法,也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作,可以把Realm看成DataSource。
Subject: 主体,任何可以与应用交互的“用户”,接收客户端账号密码,有一系列的认证授权方法。
Security Manager: 所有具体的交互都通过SecurityManager进行控制,它管理着所有的Subject,并负责进行认证、授权、会话及缓存的管理。
Authenticator:负责Subject认证,是一个扩展点,可以自定义实现,可以使用认证策略(Authentication Strategy),即什么情况下算用户认证通过。最简单可以理解为对用户的账号密码进行校验。
Authorizer:授权器,即访问控制器,用来决定主体是否有权限进行相应的操作,即控制着用户能访问应用中的哪些功能。
Realm: 可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的,可以是JDBC实现,也可以是内存实现等等。由用户提供,所以一般在应用中都需要实现自己的Realm。
Session Manager:管理Session生命周期的组件,而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境。类似于Web session域的session管理域。
Cache Manager:缓存控制器,用来管理如用户、角色、权限等的缓存的,因为这些数据基本上很少改变,放到缓存中后可以提高访问的性能。
Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密。
使用Maven搭建一个quickstart工程(Java工程)
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-allartifactId>
<version>1.7.0version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.30version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.30version>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>jcl-over-slf4jartifactId>
<version>2.0.0-alpha1version>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
dependency>
注:上面日志包版本过新会引起Null异常和ClassNotFoundException。
在resources下创建log4j.properties及shiro.ini文件。
在Shiro官网下载包:
解压后打开samples目录,复制quickstart示例中的log4j.properties和shiro.ini文件。
log4j.properties代码:
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
# General Apache libraries
log4j.logger.org.apache=WARN
# Spring
log4j.logger.org.springframework=WARN
# Default Shiro logging
log4j.logger.org.apache.shiro=INFO
# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
shiro.ini代码:
#用户名 = 密码,角色1,角色2...,角色n
[users]
root = 123456, admin
guest = 123456, guest
test = 123456, role1, role2
# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# 角色名=权限1,权限2...权限n
# -----------------------------------------------------------------------------
[roles]
admin = *
guest = guest
role1 = perm1,perm2
role2 = perm3
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ShiroTest {
private static final transient Logger log = LoggerFactory.getLogger(ShiroTest.class);
public static void main(String[] args) {
//1. 这里的SecurityManager是org.apache.shiro.mgt.SecurityManager
// 而不是java.lang.SecurityManager
// 加载配置文件
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath: shiro.ini");
//2.解析配置文件,并且返回一些SecurityManger实例
SecurityManager securityManager = factory.getInstance();
//3.将SecurityManager绑定给SecurityUtils
SecurityUtils.setSecurityManager(securityManager);
// 获取当前Subject,安全操作,Subject是当前登录的用户
Subject currentUser = SecurityUtils.getSubject();
// 获取应用的当前会话,并设置属性
Session session = currentUser.getSession();
//放进去一个key和一个value
session.setAttribute("someKey", "aValue");
//根据key拿到value
String value = (String) session.getAttribute("someKey");
if ("aValue".equals(value)) {//比较拿到的值和原来的值是否一致
log.info("检索到正确的值[" + value + "]");
}
//尝试进行登录用户,如果登录失败了,我们进行一些处理
if (!currentUser.isAuthenticated()) {//用户没有登录过
UsernamePasswordToken token = new UsernamePasswordToken("test", "123456");
token.setRememberMe(true);//是否记住用户
try {
currentUser.login(token);
//当我们获登录用户之后
log.info("用户 [" + currentUser.getPrincipal() + "] 登陆成功");
// 查看用户是否有指定的角色
if (currentUser.hasRole("admin")) {
log.info("您有admin角色");
} else {
log.info("您没有admin角色");
}
if (currentUser.hasRole("role1")) {
log.info("您有role1角色");
} else {
log.info("您没有role1角色");
}
// 查看用户是否有某个权限
if (currentUser.isPermitted("perm1")) {
log.info("您有perm1权限");
} else {
log.info("您没有perm1权限");
}
if (currentUser.isPermitted("guest")) {
log.info("您有guest权限");
} else {
log.info("您没有guest权限");
}
//登出
currentUser.logout();
} catch (UnknownAccountException uae) {
log.info(token.getPrincipal() + "账户不存在");
} catch (IncorrectCredentialsException ice) {
log.info(token.getPrincipal() + "密码不正确");
} catch (LockedAccountException lae) {
log.info(token.getPrincipal() + "用户被锁定了 ");
} catch (AuthenticationException ae) {
//无法判断是什么错了
log.info(ae.getMessage());
}
}
}
}
在Shiro中,用户需要提供principals(身份)和 credentials(证明)给Shiro,从而应用能验证用户身份。
Principals:身份, 即主体的标识属性,如用户名、邮箱、手机号等,唯一即可。
Credentials:证明/凭证, 即只有主体知道的安全值,如密码、数字证书等。
俗称:用户名与密码
从如上代码可总结出身份验证的步骤:
(1)收集用户身份/凭证,即如用户名/密码;
(2)调用Subject.login()进行登录, 如果失败将得到相应的AuthenticationException异常,根据异常提示用户错误信息;否则登录成功。
(3)AuthenticationException表示身份验证失败,其子类:
DisableAccountException 禁用的账号
LockedAccountException 锁定的账号
UnknownAccountException 错误的账号
ExcessiveAttemptsException 登录失败次数过多
IncorrectCredentialsException 错误的凭证
ExpiredCredentialsException 过期的凭证
(4)最后通过调用Subject.logout()进行退出操作。
问题:
(1)用户名/密码硬编码在shiro.ini配置文件中,以后需要改成如数据库存储,且密码需要加密存储。
(2)用户身证Token可能不仅仅是用户名/密码,也可能还有其他的,如登录时允许用户名/邮箱/手机号码同时登录。