Shiro超详细学习笔记(附源码)
引言:
本文主要分享了Shiro和权限管理相关的知识,包括权限管理的简介、权限认证、权限授权、权限系统(演变思路)、基于URL的拦截思路、Shiro的简介、Shiro的内部结构(7板块)、初始的Shiro案例、带有角色权限的案例、自定义安全策略的shiro应用案例、自定义安全策略的shiro加入MD5的应用案例(均附源码);
@[toc]
在分享Shiro前先说说权限管理
1. 权限管理
1.1 权限管理简介
根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源不多不少;权限包括用户认证和授权两部分,对于需要访问控制的资源用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问;
每个系统都有权限管理,只不过力度不同
功能:权限验证、用户注册、登录、分配权限、角色管理
授权(是否有权限,权限校验)、认证(处理登录)、加密、会话(用户状态信息的管理)
安全管理器
- 认证管理器
- 权限管理器
- Session管理器
- 加密
- Realms访问数据源的组件(访问用户数据源)
1.2 认证
认证就是判断一个用户是否是合法用户的一个过程,通过核对相应的信息,来与系统中的信息进行比较是否一致,其中包括用户名密码、指纹、人脸、二维码、刷卡......
认证流程:
- 访问系统资源
- 判断是否可以匿名访问(可以继续访问,不可以进行下一步)
- 判断用户是否登录(登录继续访问,没有登录进行下一步)
- 输入用户名密码进行身份认证
- 判断是否通过(通过认证继续访问,没有通过继续认证)
1.3 授权
授权就是访问控制,控制谁能访问那些资源,相当于检票的过程,访问控制就是主体进行身份认证后,需要分配权限,没有权限不可以访问;
授权流程:
- 访问系统资源
- 进行身份认证
- 判断是否通过认证(没有通过继续认证,通过进行下一步)
- 权限控制(从数据拿出,分配权限)
- 判断是否拥有访问权限(有权限继续访问,没有权限拒绝访问)
资源访问分为三级:
- 匿名可访问
- 认证可访问
- 拥有特定权限可访问
1.4 权限系统
1.4.1 涉及的对象及关系(7张表)
我们知道对象就是实体,包括用户、权限、资源、角色
权限系统涉及的关系:
- 用户-->权限:多对多
- 用户-->角色:多对多
- 角色-->权限:多对多
- 权限-->资源:多对一(一个资源多个权限增删查改)
1.4.2 权限管理的设计演变思路
- 七表:用户、角色、权限、资源各一张表,用户与角色之间有用户角色的关系表、角色权限之间有角色权限表、用户和权限有一张表;
- 六表:在七表的基础上省略了用户和权限之间的表,直接将权限授予角色,角色在授予用户;(一个用户可以单独授权又可以是某个角色)
- 五表:将多对一变为一对一,权限和资源融合,简化为五张表;
1.4.3 权限控制
1.4.3.1 基于角色的访问控制
RBAC基于角色的访问控制(Role-Based Access Control)以角色为中心进行访问控制;
缺点:
- 系统扩展性较差
- 以角色进行访问控制粒度较粗
1.4.3.2 基于资源的访问控制
RBAC基于资源的访问控制(Resouce-Based Access Control)以资源为中心进行访问控制;
优点:
- 系统设计时定义好权限标识
- 系统可扩展性强
1.5 基于URL拦截
基于url拦截是企业中常用的权限管理方法,将系统操作的每个url配置在权限表中,将权限对应到角色,将角色分配给用户,用户访问系统功能通过Filter进行过滤,Filter获取用户访问的url,只要访问的url是用户分配角色中的url则放行继续访问;
URL拦截的流程:
- 访问系统资源
- 获取访问的url
- 判断url是否是公开地址(是继续访问,不是进行下一步)
- 判断是否存在session(没有输入用户名密码,进行身份认证,直到成功并将用户信息url记录到session中;存在进行下一步)
- 授权过滤器拦截获取访问url
- 判断url是否是公开地址(是:继续访问;不是:进行下一步)
- 判断url是否是只要登录就可以访问,无需分配权限(是:继续访问;不是:进行下一步)
- 判断url是否存在权限(存在:放行继续访问;不存在提示无权访问)
2. Shiro
2.1 Shiro简介以及内部结构
Shiro是一个Java的安全框架,提供了认证、授权、加密和会话管理功能;是开发权限的快速框架,应用广泛(Web、非Web、集群分布式应用)
- Subject:请求主体,外部应用与 subject进行交互,subject记录当前操作用户,用户就是当前操作的主体;外部程序通过subject进行认证授权,subject通过Security Manager安全管理器进行认证授权;
- SecurityManager:安全管理器,相当于SpringMVC中的DispatcherServlet是Shiro的心脏;所有的交互都通过SecurityManager进行控制,管理着所有Subject负责进行认证和授权、会话及缓存的管理;是一个接口,继承了Authenticator、Authorizer、SessionManager这三个接口;
- Authenticator:认证器,负责主体(subject)认证的,是一个接口,shiro提供了ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数的需求,也可以自定义认证器;
- Authorizer:授权器、访问控制器,用来决定主体是否有权限进行相应的操作,即控制着用户能访问应用中的哪些功能,在访问功能时需要通过授权器判断用户是否有此功能的操作权限;
- Realm:域,可以有1个或多个Realm;是安全实体数据源,用于获取安全实体的,Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;realm不只是从数据源取数据,在realm中还有认证授权校验相关的代码;
- SessionManager:会话管理,shiro框架提供了一套会话管理,它不依赖web容器的session,自己抽象了一个自己的Session来管理主体与应用之间交互的数据,所以shiro可以使用在非web环境中,也可以将分布式应用的会话集中在一点管理,可使它实现单点登录;
- SessionDAO:会话管理DAO,是对session会话管理操作的一套接口,比如要将session存储到数据库,可以使用jdbc将会话存储到数据库;此外SessionDAO中可以使用Cache进行缓存,以提高性能;
- CacheManager:缓存管理,将用户权限存储在缓存,可以提高性能;
- Crypography:密码管理,Shiro提供了一套加密/解密的组件,方便开发。提供了常用的散列、加/解密算法;
2.2 环境的搭建
新建普通的Maven项目
在pom文件中导入依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.3.2</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
<artifactId>commons-httpclient</artifactId>
<version>3.1</version>
</dependency>
</dependencies>
2.3 简单的Shiro测试案例
2.3.1 配置初始化文件
在resources文件下配置初始化文件——shiro-first.ini
#配置用户名,带用户数据表
[users]
kaka=1234
taotao=4567
2.3.2 编写测试文件
在test文件下编写测试类
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.junit.Test;
/**
* Created by Kak on 2020/9/2.
*/
public class TestShiroBase {
@Test
//认证处理
public void testShiroFirst(){
//根据ini初始化文件创建SecurityManager工厂
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-first.ini");
//生成安全管理器,创建securityManager实例
SecurityManager securityManager = factory.createInstance();
//使用shiro提供的工具装配安全管理器
SecurityUtils.setSecurityManager(securityManager);
//获取shiro访问的主体对象
Subject subject = SecurityUtils.getSubject();
//模拟用户登录操作创建访问主体的令牌
AuthenticationToken token = new UsernamePasswordToken("kaka", "1234");
try{
subject.login(token);
}catch (IncorrectCredentialsException icex){
System.out.println("用户口令错误!!!");
}catch (UnknownAccountException uaEx){
System.out.println("用户名错误!!!");
}catch (AuthenticationException aex){
System.out.println("用户认证失败!!!");
}
if (subject.isAuthenticated()){
System.out.println("用户登录成功!!!");
}
}
}
2.3.3 运行结果
2.4 带角色权限的认证
2.4.1 配置初始化文件
在resources文件下配置初始化文件——shiro-perms.ini
[users]
kaka=1234,role1,role2
taotao=12345,role2,role3
[roles]
role1=stu:select,stu:insert
role2=stu:update,stu:delete
role3=emp:select
2.4.2 编写测试文件
在test文件下编写测试类
@Test
//加入权限
public void testShiroPerms(){
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-perms.ini");
SecurityManager securityManager = factory.createInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("kaka","1234");
try{
subject.login(token);
}catch (AuthenticationException aex){
System.out.println("用户认证错误,登录失败!!!");
}
if(subject.isAuthenticated()){
System.out.println("用户登录成功!!!");
if(subject.isPermitted("stu:select")){
System.out.println("用户有查询权限stu:select");
}else{
System.out.println("无权访问stu:select!!!");
}
String[] perms = {
"stu:select","stu:delete"};
boolean[] permitted = subject.isPermitted(perms);//我们需要判断的权限
if (permitted[1]){
System.out.println("用户有权访问stu:delete");
}
if(!subject.isPermittedAll(perms)){
System.out.println("用户无权访问!!!");
}
if (subject.hasRole("role1")){
System.out.println("kaka有role1角色");
}
}
}
2.4.3 运行结果
2.5 自定义realm的shiro案例
2.5.1 配置初始化文件
在resources文件下配置初始化文件——shiro-realm.ini
[main]
#自定义realm
myRealm=com.sx.kak.shiro.MyRealm
#在securityManager中设置自定义的realm
securityManager.realms=$myRealm
2.5.2 自定义安全策略
创建shiro包,在包下创建MyRealm.java
package com.sx.kak.shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import java.util.HashSet;
import java.util.Set;
/**
* Created by Kak on 2020/9/2.
*/
public class MyRealm extends AuthorizingRealm{
//shiro中的授权实现
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
Object principal = principalCollection.getPrimaryPrincipal();
//根据用户信息查询数据库中的权限
String userName = (String) principal;
//创建用户授权信息对象
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
//创建权限集合
Set<String> perms = new HashSet<String>();
perms.add("stu:select");
perms.add("stu:update");
authorizationInfo.setStringPermissions(perms);
return authorizationInfo;
}
//shiro中的认证实现
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//获取用户信息
String username = (String)authenticationToken.getPrincipal();
//获取密码
Object credentials = authenticationToken.getCredentials();
System.out.println("userName:" + username+"password:"+credentials);
//根据用户名查询用户对象信息获取用密码
String userName="kaka";
String password="1234";
//将送来用户账号及根据账号查出的密码(凭证)封装成一个AuthenticationInfo对象,返回
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(userName, password, getName());
//将外部获取的用户信息及凭证信息 封装送给Authenticator对象进行认证
return simpleAuthenticationInfo;
}
}
2.5.3 编写测试文件
在test文件下编写测试类
@Test
//自定义安全策略的shiro
public void testShiroRealm(){
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-realm.ini");
SecurityManager securityManager = factory.createInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("kaka", "1234");
try{
subject.login(token);
}catch (UnknownAccountException uaex){
System.out.println("账户名不存在!!!");
}catch (IncorrectCredentialsException ice){
System.out.println("凭证错误!!!");
}catch (AuthenticationException ae){
System.out.println("认证失败!!!");
}
if(subject.isAuthenticated()){
System.out.println("用户登录成功!!!");
}else{
System.out.println("用户登录失败!!!");
}
if(subject.isPermittedAll("stu:select")){
System.out.println("kaka用户有权访问stu:select");
}else{
System.out.println("kaka用户无权访问stu:select");
}
}
2.5.4 运行结果
2.6 MD5密钥
一种被广泛使用的密码散列函数,可以产生出一个128位(16字节)的散列值(hash value),用于确保信息传输完整一致;
//生成MD5密钥
public void testMd5(){
String pwd = "1234";
String salt = "kaka";
Md5Hash md5Hash1 = new Md5Hash(pwd);
String md5Str1 = md5Hash1.toString();
System.out.println("md5加密:" + md5Str1);
Md5Hash md5Hash2 = new Md5Hash(pwd, salt, 1);
String md5Str2 = md5Hash2.toString();
System.out.println("加盐:"+md5Str2);
Md5Hash md5Hash3 = new Md5Hash(pwd, salt, 1024);
String md5Str3 = md5Hash3.toString();
System.out.println("多次加盐:"+md5Str3);
}
md5加密:81dc9bdb52d04dc20036dbd8313ed055
加盐:4fa51ff55b001ea9b7c55338b76834f7
多次加盐:eaee658c75dc83917d7be1bd689ff15e
2.7 自定义realm的shiro加入MD5案例
2.7.1 配置初始化文件
[main]
#定义凭证匹配器
credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
#设置散列算法
credentialsMatcher.hashAlgorithmName=md5
#设置散列次数
credentialsMatcher.hashIterations=1
#将凭证匹配器设置到realm
myRealm=com.sx.kak.shiro.MyRealmMd5
#在securityManager中设置自定义的realm
securityManager.realms=$myRealm
myRealm.credentialsMatcher=$credentialsMatcher
2.7.2 自定义安全策略(MD5)
创建shiro包,在包下创建MyRealmMd5.java
package com.sx.kak.shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
/**
* Created by Kak on 2020/9/2.
*/
public class MyRealmMd5 extends AuthorizingRealm{
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//用户名
String principal = (String)authenticationToken.getPrincipal();
//根据用户名查询用户信息(密码)
String hashedCredentials ="4fa51ff55b001ea9b7c55338b76834f7";
ByteSource credentialsSalt = ByteSource.Util.bytes("kaka");
/**
* principal:用户信息
* hashedCredentials:加密之后的密文
* credentialsSalt:加密时加的盐
* AuthorizingRealm 的派生类名称
*/
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(principal, hashedCredentials, credentialsSalt, getName());
return authenticationInfo;
}
}
2.7.3 编写测试文件
在test文件下编写测试类
@Test
//自定义安全策略的shiro加密钥
public void testShiroMd5(){
IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro-realm-md5.ini");
SecurityManager securityManager = factory.createInstance();
SecurityUtils.setSecurityManager(securityManager);
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken("kaka", "1234");
try{
subject.login(token);
}catch (UnknownAccountException uaex){
System.out.println("账户名不存在!!!");
}catch (IncorrectCredentialsException ice){
System.out.println("凭证错误!!!");
}catch (AuthenticationException ae){
System.out.println("认证失败!!!");
}
if(subject.isAuthenticated()){
System.out.println("用户登录成功!!!");
}else{
System.out.println("用户登录失败!!!");
}
}