1.权限管理的概念
基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。
权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
2.认证
2.1.认证概念
**认证:**用户访问系统的控制
2.2.认证抽取出的对象
-
Subject:主体相当于之前的user
-
Principal:身份信息 是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
-
credential:凭证信息 是只有主体自己知道的安全信息,如密码、证书等。
-
token:令牌 包含身份信息和凭证信息
3.授权
3.1.概念
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。
3.2.授权抽象出的对象
授权可简单理解为who对what(which)进行How操作:
-
Who,即主体(Subject),主体需要访问系统中的资源。
-
What,即资源(Resource),如系统菜单、页面、按钮、类方法、系统商品信息等。资源包括资源类型和资源实例,比如商品信息为资源类型,类型为t01的商品为资源实例,编号为001的商品信息也属于资源实例。
-
How,权限/许可(Permission),规定了主体对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个类方法的调用权限、编号为001用户的修改权限等,通过权限可知主体对哪些资源都有哪些操作许可。
权限分为粗颗粒和细颗粒,粗颗粒权限是指对资源类型的权限,细颗粒权限是对资源实例的权限
4.权限模型
5.权限控制方案
用户拥有了权限即可操作权限范围内的资源,系统不知道主体是否具有访问权限需要对用户的访问进行控制。
5.1.基于角色的权限控制
RBAC基于角色的访问控制(Role-Based Access Control)是以角色为中心进行访问控制,比如:主体的角色为总经理可以查询企业运营报表,查询员工工资信息等,访问控制流程如下:
上图中的判断逻辑代码可以理解为:
if(主体.hasRole(“总经理角色id”)){
查询工资
}
缺点:以角色进行访问控制粒度较粗,如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为“判断主体的角色是否是总经理或部门经理”,系统可扩展性差。
修改代码如下:
if(主体.hasRole(“总经理角色id”) || 主体.hasRole(“部门经理角色id”)){
查询工资
}
5.2.基于资源的权限控制
RBAC基于资源的访问控制(Resource-Based Access Control)是以资源为中心进行访问控制,比如:主体必须具有查询工资权限才可以查询员工工资信息等,访问控制流程如下:
上图中的判断逻辑代码可以理解为:
if(主体.hasPermission(“查询工资权限标识”)){
查询工资
}
优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也只需要将“查询工资信息权限”添加到“部门经理角色”的权限列表中,判断逻辑不用修改,系统可扩展性强。
6.Shiro
Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.
Apache Shiro™是一个功能强大且易于使用的Java安全框架,用于执行身份验证,授权,加密和会话管理。 使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序-从最小的移动应用程序到最大的Web和企业应用程序。
Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。
6.1.为什么要学习shiro
既然shiro将安全认证相关的功能抽取出来组成一个框架,使用shiro就可以非常快速的完成认证、授权等功能的开发,降低系统成本。
shiro使用广泛,shiro可以运行在web应用,非web应用,集群分布式应用中越来越多的用户开始使用shiro。
java领域中spring security(原名Acegi)也是一个开源的权限管理框架,但是spring security依赖spring运行,而shiro就相对独立,最主要是因为shiro使用简单、灵活,所以现在越来越多的用户选择shiro。
6.2.shiro核心架构图
7.shiro第一个程序
7.1.创建一个Maven的项目或者springBoot项目
7.2.导入相关jar
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
</dependency>
7.3.第一个认证程序
@Test
public void testShiro() {
//获取安全管理器工厂
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
//通过安全管理器工厂获取安全管理器
SecurityManager securityManager = factory.getInstance();
//将安全管理器交给安全工具类
SecurityUtils.setSecurityManager(securityManager);
//根据安全工具类获取主体对象
Subject subject = SecurityUtils.getSubject();
//创建用户的token令牌 token=用户身份信息+凭证信息
AuthenticationToken token=new UsernamePasswordToken("bobo","123456");
//主体携带token认证
try {
subject.login(token);
} catch (UnknownAccountException e) {
System.out.println("未知账号异常");
} catch (IncorrectCredentialsException e) {
System.out.println("不正确的证书异常");
}
//UnknownAccountException 未知账号异常 用户名不正确
//IncorrectCredentialsException 不正确的证书异常 密码错误
//是否认证通过
boolean authenticated = subject.isAuthenticated();
System.out.println("是否认证通过:"+authenticated);
}
7.4.配置配置文件
创建一个 shiro.ini
[users]
xiaohei=111111
bobo=123456
**注意:**shiro中的主题认证是否成功是通过抛异常的形式体现的,一般会抛出两个异常
-
UnknownAccountException 未知账号异常 用户名不正确
-
IncorrectCredentialsException 不正确的证书异常 密码错误
-
其他异常
-
DisabledAccountException(帐号被禁用)
LockedAccountException(帐号被锁定)
ExcessiveAttemptsException(登录失败次数过多)
ExpiredCredentialsException(凭证过期)等
8.源码追踪中的关键类
//抽象类
AuthenticatingRealm
//凭证匹配器 数据比对
private CredentialsMatcher credentialsMatcher;
//抽象方法
protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException;
}
//
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = this.getCredentialsMatcher(); //装配凭证匹配器
// 默认提供的 SimpleCredentialsMatcher
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) { //将token和info传进去对比返回布尔类型的值
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg); //抛出不正确的凭证信息异常
}
} else {
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify credentials during authentication. If you do not wish for credentials to be examined, you can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
}
}