文章目录
- 1.权限的管理
- 1.1 什么是权限管理
- 1.2 什么是身份认证
- 1.3 什么是授权
- 2.Shiro概述
- 2.1 什么是Shiro
- 2.2 Shiro 与 SpringSecurity 的对比
- 2.3 基本功能
- 3.shiro的核心架构
- 4.shiro中的认证
- 4.1 认证
- 4.2 shiro中认证的关键对象
- 4.3 身份认证流程
- 4.4.登录认证实例
- 4.5 自定义Realm
- 5.角色、授权
- 5.1 授权概念
- 5.2 授权方式
- 5.3 授权流程
- 5.4 授权实例
- 6.Shiro 加密
- 6.1 Shiro自定义登录认证(带加密的)
- 7.多个 realm 的认证策略设置
- 7.1 多个realm实现原理
- 7.2 多个代码实现
- 8. remember me 功能
- 8.2 1、基本流程
- 9.授权、角色认证
- 9.1 授权
- 9.2 后端接口服务注解
1.权限的管理
1.1 什么是权限管理
(1)基本上涉及到用户参与的系统都要进行权限管理,权限管理属于系统安全的范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己被授权的资源。
(2)权限管理包括用户身份认证和授权两部分,简称认证授权。对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。
1.2 什么是身份认证
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。对于采用指纹等系统,则出示指纹;对于硬件Key等刷卡系统,则需要刷卡。
1.3 什么是授权
授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后需要分配权限才可访问系统的资源,对于某些资源没有权限是无法访问的
2.Shiro概述
2.1 什么是Shiro
(1)Apache Shiro 是一个功能强大且易于使用的 Java 安全(权限)框架。 Shiro 可以完
成:认证、授权、加密、会话管理、与 Web 集成、缓存 等。借助 Shiro 您可以快速轻松
地保护任何应用程序——从最小的移动应用程序到最大的 Web 和企业应用程序。
(2)官网: https://shiro.apache.org/
2.2 Shiro 与 SpringSecurity 的对比
①Spring Security 基于 Spring 开发,项目若使用 Spring 作为基础,配合 Spring
Security 做权限更加方便,而 Shiro 需要和 Spring 进行整合开发;
②Spring Security 功能比 Shiro 更加丰富些,例如安全维护方面;
③Spring Security 社区资源相对比 Shiro 更加丰富;
④Shiro 的配置和使用比较简单, Spring Security 上手复杂些;
⑤Shiro 依赖性低,不需要任何框架和容器,可以独立运行;Spring Security 依赖Spring 容器;
⑥shiro 不仅仅可以使用在 web 中,它还可以工作在任何应用环境中。在集群会话时 Shiro最重要的一个
好处或许就是它的会话是独立于容器的。
2.3 基本功能
(1) Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
(2) Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即
判断用 户是否能进行什么操作,如:验证某个用户是否拥有某个角色。或者细粒度的验证
某个用户 对某个资源是否具有某个权限;
(3) Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的
所有 信息都在会话中;会话可以是普通 JavaSE 环境,也可以是 Web 环境的;
(4) Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存
储;
(5) Web Support: Web 支持,可以非常容易的集成到 Web 环境;
(6) Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这
样可 以提高效率;
(7) Concurrency: Shiro 支持多线程应用的并发验证,即如在一个线程中开启另一个线
程,能把权限自动传播过去;
(8) Testing:提供测试支持;
(9) Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
(10)Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用
登 录了
3.shiro的核心架构
(1)Subject
Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。 Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权
(2)SecurityManager
①SecurityManager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过 SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。
②SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。
(3)Authenticator
Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。
(4)Authorizer
Authorizer即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。
(5)Realm
Realm即领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
注意:不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。
(6)SessionManager
sessionManager即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。
(7)SessionDAO
SessionDAO即会话dao,是对session会话操作的一套接口,比如要将session存储到数据库,可以通过jdbc将会话存储到数据库。
(8) CacheManager
CacheManager即缓存管理,将用户权限数据存储在缓存,这样可以提高性能。
(9)Cryptography
Cryptography即密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能。
4.shiro中的认证
4.1 认证
身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。
4.2 shiro中认证的关键对象
(1)Subject:主体
访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;
(2)Principal:身份信息
是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
(3)credential:凭证信息
是只有主体自己知道的安全信息,如密码、证书等。
4.3 身份认证流程
(1)首先调用 Subject.login(token) 进行登录,其会自动委托给 SecurityManager
(2)SecurityManager 负责真正的身份验证逻辑;它会委托给 Authenticator 进行身份
验证;
(3)Authenticator 才是真正的身份验证者, Shiro API 中核心的身份 认证入口点,此
处可以自定义插入自己的实现;
(4)Authenticator 可能会委托给相应的 AuthenticationStrategy 进 行多 Realm 身份
验证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Realm
身份验证;
(5)Authenticator 会在shiro中会把身份信息以及凭证信息打包成一个令牌即Token 传入
Realm,从 Realm 获取 身份验证信息,如果没有返回/抛出异常表示身份验证失败了。
此处 可以配置多个Realm,将按照相应的顺序及策略进行访问
4.4.登录认证实例
(1)环境准备
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.9.0</version>
</dependency>
</dependencies>
(2)INI 文件
Shiro 获取权限相关信息可以通过数据库获取,也可以通过 ini 配置文件获取
(3) 开发认证代码
public class TestAuthenticator {
public static void main(String[] args) {
//1.创建securityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
defaultSecurityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//2.将安装工具类中设置默认安全管理器
SecurityUtils.setSecurityManager(defaultSecurityManager);
//3.获取主体对象
Subject subject = SecurityUtils.getSubject();
//4.创建token令牌
UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "13");
try {
//5.用户登录
subject.login(token);
System.out.println("登录成功~~");
} catch (UnknownAccountException e) {
e.printStackTrace();
System.out.println("用户名错误!!");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("密码错误!!!");
}
}
}
①DisabledAccountException(帐号被禁用)
②LockedAccountException(帐号被锁定)
③ExcessiveAttemptsException(登录失败次数过多)
④ExpiredCredentialsException(凭证过期)
4.5 自定义Realm
(1)上边的程序使用的是Shiro自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义realm。
(2)创建自定义的 Realm 类,
方式①:继承 org.apache.shiro.realm.AuthenticatingRealm类,
方式②:继承AuthorizingRealm (AuthorizingRealm extends AuthenticatingRealm)
重写方法
/**
* 自定义realm
*/
public class CustomerRealm extends AuthorizingRealm {
//认证方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
//授权方法
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String principal = (String) token.getPrincipal();
if("xiaochen".equals(principal)){
return new SimpleAuthenticationInfo(principal,"123",this.getName());
}
return null;
}
}
(4)使用自定义Realm
方式①:设置为自定义realm获取认证数据
//创建securityManager
DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
//IniRealm realm = new IniRealm("classpath:shiro.ini");
//设置为自定义realm获取认证数据
defaultSecurityManager.setRealm(new CustomerRealm());
方式二:在shiro.ini中添加配置信息
[main]
md5CredentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
md5CredentialsMatcher.hashIterations=3
myrealm=com.shiro.realm.MyRealm
myrealm.credentialsMatcher=$md5CredentialsMatcher
securityManager.realms=$myrealm
[users]
zhangsan=7174f64b13022acd3c56e2781e098a5f,role1,role2
lisi=l4
[roles]
role1=user:insert,user:select
5.角色、授权
5.1 授权概念
(1)授权,也叫访问控制,即在应用中控制谁访问哪些资源(如访问页面/编辑数据/页面操作等)。
在授权中需了解的几个关键对象:主体( Subject)、资源( Resource)、权限( Permission)、
角色( Role)。
(2) 主体(Subject):访问应用的用户,在 Shiro 中使用 Subject 代表该用户。用户只有授权 后
才允许访问相应的资源。
(3)资源(Resource): 在应用中用户可以访问的 URL,比如访问 JSP 页面、查看/编辑某些
数据、访问某个业务方法、打印文本等等都是资源。用户只有授权后才能访问。
(4)权限(Permission):安全策略中的原子授权单位,通过权限我们可以表示在应用中用户
有没有操作某个资源的权力。 即权限表示在应用中用户能不能访问某个资源,如:访问用 户
列表页面查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控 制)等。
权限代表了用户有没有操作某个资源的权利,即反映在某个资源上的操作允不允许。
( 5) Shiro 支持粗粒度权限(如用户模块的所有权限)和细粒度权限(操作某个用户的权
限, 即实例级别的)
( 6) 角色(Role): 权限的集合,一般情况下会赋予用户角色而不是权限,即这样用户可
以拥有 一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、 CTO、开发工
程师等 都是角色,不同的角色拥有一组不同的权限
5.2 授权方式
(1)编程式:通过写if/else 授权代码块完成
( 2) 注解式:通过在执行的Java方法上放置相应的注解完成,没有权限将抛出相应的异常
(3) JSP/GSP 标签:在JSP/GSP 页面通过相应的标签完成
5.3 授权流程
(1)首先调用Subject.isPermitted*/hasRole*
接口,其会委托给SecurityManager,而
SecurityManager接着会委托给 Authorizer;
(2)Authorizer是真正的授权者,如果调用如isPermitted(“user:view”),其首先会通过
PermissionResolver把字符串转换成相应的Permission实例;
(3)在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入
的角色/权限;
(4)Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托
给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole* 会返回true,
否则返回false表示授权失败
5.4 授权实例
(1) 获取角色信息
①给shiro.ini增加角色配置
[users]
zhangsan=13,role1,role2
②给例子添加代码,通过hasRole()判断用户是否有指定角色
subject.login(token);
System.out.println("登录成功");
// 判断角色
boolean hasRole = subject.hasRole("role1");
System.out.println("是否拥有此角色"+hasRole);
(2)判断权限信息信息
①给shiro.ini增加权限配置
[users]
zhangsan=13,role1,role2
[roles]
role1=user:insert,user:select
②给例子添加代码,判断用户是否有指定权限
//判断权限
boolean isPermitted = subject.isPermitted("user:insert");
System.out.println("是否拥有此权限: "+isPermitted);
//也可以用 checkPermission 方法,但没有返回值,没权限会抛 AuthenticationException
// subject.checkPermission("user:select");
6.Shiro 加密
(1)实际系统开发中,一些敏感信息需要进行加密,比如说用户的密码。 Shiro 内嵌很多
常用的加密算法,比如 MD5 加密。 Shiro 可以很简单的使用信息加密。
(2)使用Shiro进行密码加密
public class ShiroMD5 {
public static void main(String[] args) {
//密码明文
String password = "z3";
//使用 md5 加密
Md5Hash md5Hash = new Md5Hash(password);
System.out.println("md5 加密: "+md5Hash.toHex());
//带盐的 md5 加密,盐就是在密码明文后拼接新字符串,然后再进行加密
Md5Hash md5Hash2 = new Md5Hash(password,"salt");
System.out.println("md5 带盐加密: "+md5Hash2.toHex());
//为了保证安全,避免被破解还可以多次迭代加密,保证数据安全
Md5Hash md5Hash3 = new Md5Hash(password,"salt",3);
System.out.println("md5 带盐三次加密: "+md5Hash3.toHex());
//使用父类实现加密(MD5是加密方式)
SimpleHash simpleHash = new SimpleHash("MD5",password,"salt",3);
System.out.println("父类带盐三次加密: "+simpleHash.toHex());
} }
6.1 Shiro自定义登录认证(带加密的)
Shiro 默认的登录认证是不带加密的,如果想要实现加密认证需要自定义登录认证,需要自定义 Realm。
(1)自定义登录认证
public class MyRealm extends AuthenticatingRealm {
//自定义的登录认证方法, Shiro 的 login 方法底层会调用该类的认证方法完成登录认证
//想要配置自定义的 realm 生效,要在ini 文件中配置,或 Springboot 中配置
//该方法只是获取进行对比的信息,认证逻辑还是按照 Shiro 的底层认证逻辑完成认证
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)throws AuthenticationException {
//1 获取身份信息
String principal = authenticationToken.getPrincipal().toString();
//2 获取凭证信息
String password = new String((char[])authenticationToken.getCredentials());
System.out.println("认证用户信息:"+principal+"---"+password);
//3 获取数据库中存储的用户信息
if(principal.equals("zhangsan")){
//3.1 数据库中存储的加盐 3 次迭代的密码
String pwdInfo = "7174f64b13022acd3c56e2781e098a5f";
//3.2 创建封装了校验逻辑的对象,把要比较的数据给该对象
AuthenticationInfo info = new SimpleAuthenticationInfo(
authenticationToken.getPrincipal(),//身份信息
pwdInfo, //密码信息
ByteSource.Util.bytes("salt"), //盐信息
authenticationToken.getPrincipal().toString()); //身份信息对应的字符串
return info;
}
return null;
}
(2)在shiro.ini中添加配置信息
[main]
md5CredentialsMatcher=org.apache.shiro.authc.credential.Md5CredentialsMatcher
md5CredentialsMatcher.hashIterations=3
myrealm=com.shiro.realm.MyRealm
myrealm.credentialsMatcher=$md5CredentialsMatcher
securityManager.realms=$myrealm
[users]
zhangsan=7174f64b13022acd3c56e2781e098a5f,role1,role2
lisi=l4
[roles]
role1=user:insert,user:select
7.多个 realm 的认证策略设置
7.1 多个realm实现原理
(1)当应用程序配置多个 Realm 时,例如:用户名密码校验、手机号验证码校验等等。Shiro 的 ModularRealmAuthenticator会使用内部的 AuthenticationStrategy 组件判断认证是成功还是失败。
(2)AuthenticationStrategy 是一个无状态的组件,它在身份验证尝试中被询问 4 次(这4 次交互
所需的任何必要的状态将被作为方法参数):
①在所有 Realm 被调用之前
②在调用 Realm 的 getAuthenticationInfo 方法之前
③在调用 Realm 的 getAuthenticationInfo 方法之后
④在所有 Realm 被调用之后
(3)认证策略的另外一项工作就是聚合所有 Realm 的结果信息封装至一个
AuthenticationInfo 实例中,并将此信息返回,以此作为 Subject 的身份信息。
(4)Shiro 中定义了 3 种认证策略的实现:
ModularRealmAuthenticator 内置的认证策略默认实现是AtLeastOneSuccessfulStrategy 方式。可以通过配置修改策略
7.2 多个代码实现
//配置 SecurityManager
@Bean
public DefaultWebSecurityManager defaultWebSecurityManager(){
//1 创建 defaultWebSecurityManager 对象
DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
//2 创建认证对象,并设置认证策略
ModularRealmAuthenticator modularRealmAuthenticator = new ModularRealmAuthenticator();
modularRealmAuthenticator.setAuthenticationStrategy(new AllSuccessfulStrategy());
defaultWebSecurityManager.setAuthenticator(modularRealmAuthenticator);
//3 封装 myRealm 集合
List<Realm> list = new ArrayList<>();
list.add(myRealm);
list.add(myRealm2);
//4 将 myRealm 存入 defaultWebSecurityManager 对象
defaultWebSecurityManager.setRealms(list);
//5 返回
return defaultWebSecurityManager;
}
8. remember me 功能
Shiro 提供了记住我(RememberMe)的功能,比如访问一些网站时,关闭了浏览器,
下次再打开时还是能记住你是谁, 下次访问时无需再登录即可访问。
8.2 1、基本流程
(1)首先在登录页面选中 RememberMe 然后登录成功;如果是浏览器登录,一般会
把 RememberMe 的 Cookie 写到客户端并保存下来;
(2)关闭浏览器再重新打开;会发现浏览器还是记住你的;
(3)访问一般的网页服务器端,仍然知道你是谁,且能正常访问;
(4)但是,如果我们访问电商平台时,如果要查看我的订单或进行支付时,此时还
是需要再进行身份认证的,以确保当前用户还是你。
9.授权、角色认证
9.1 授权
用户登录后, 需要验证是否具有指定角色指定权限。 Shiro也提供了方便的工具进行判断。这个工具
就是Realm的doGetAuthorizationInfo方法进行判断。触发权限判断的有两种方式:
(1) 在页面中通过shiro:属性判断
(2) 在接口服务中通过注解@Requires进行判断
9.2 后端接口服务注解
通过给接口服务方法添加注解可以实现权限校验,可以加在控制器方法上,也可以加
在业务方法上,一般加在控制器方法上。常用注解如下:
(1) @RequiresAuthentication
验证用户是否登录,等同于方法subject.isAuthenticated()
(2) @RequiresUser
验证用户是否被记忆:
登录认证成功subject.isAuthenticated()为true
登录后被记忆subject.isRemembered()为true
(3) @RequiresGuest
验证是否是一个guest的请求,是否是游客的请求
此时subject.getPrincipal()为null
(4) @RequiresRoles
验证subject是否有相应角色,有角色访问方法,没有则会抛出异常
AuthorizationException。
例如: @RequiresRoles(“aRoleName”)
void someMethod();
只有subject有aRoleName角色才能访问方法someMethod()
(5) @RequiresPermissions
验证subject是否有相应权限,有权限访问方法,没有则会抛出异常AuthorizationException。
例如: @RequiresPermissions (“file:read”,”wite:aFile.txt”)
void someMethod();
subject必须同时含有file:read和wite:aFile.txt权限才能访问方法someMethod()