Shiro超详细学习笔记(附源码)

tech2022-08-25  120

Shiro超详细学习笔记(附源码)

引言:

      本文主要分享了Shiro和权限管理相关的知识,包括权限管理的简介、权限认证、权限授权、权限系统(演变思路)、基于URL的拦截思路、Shiro的简介、Shiro的内部结构(7板块)、初始的Shiro案例、带有角色权限的案例、自定义安全策略的shiro应用案例、自定义安全策略的shiro加入MD5的应用案例(均附源码);


文章目录

Shiro超详细学习笔记(附源码)在分享Shiro前先说说权限管理 1. 权限管理1.1 权限管理简介1.2 认证1.3 授权1.4 权限系统1.4.1 涉及的对象及关系(7张表)1.4.2 权限管理的设计演变思路1.4.3 权限控制1.4.3.1 基于角色的访问控制1.4.3.2 基于资源的访问控制 1.5 基于URL拦截 2. Shiro2.1 Shiro简介以及内部结构2.2 环境的搭建新建普通的Maven项目 2.3 简单的Shiro测试案例2.3.1 配置初始化文件2.3.2 编写测试文件2.3.3 运行结果 2.4 带角色权限的认证2.4.1 配置初始化文件2.4.2 编写测试文件2.4.3 运行结果 2.5 自定义realm的shiro案例2.5.1 配置初始化文件2.5.2 自定义安全策略2.5.3 编写测试文件2.5.4 运行结果 2.6 MD5密钥2.7 自定义realm的shiro加入MD5案例2.7.1 配置初始化文件2.7.2 自定义安全策略(MD5)2.7.3 编写测试文件2.7.4 运行结果

在分享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("用户登录失败!!!"); } }

2.7.4 运行结果

最新回复(0)