0


二十三、shiro安全框架详解(一)

一、 权限概述

1. 什么是权限

权限管理,一般指根据系统设置的安全策略或者安全规则,用户可以访问而且只能访问自己被授权的资源,不多不少。权限管理几乎出现在任何系统里面,只要有用户和密码的系统。

权限管理在系统中一般分为:

  • 访问权限 一般表示你能做什么样的操作,或者能够访问那些资源。例如:给张三赋予“店铺主管”角色,“店铺主管”具有“查询员工”、“添加员工”、“修改员工”和“删除员工”权限。此时张三能够进入系统,则可以进行这些操作。
  • 数据权限 一般表示某些数据是否属于你,或者属于你可以操作范围。例如:张三是"店铺主管"角色,他可以看他手下客服人员所有的服务的买家订单信息,他的手下只能看自己负责的订单信息。

2. 认证概念

⑴ 什么是认证

身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对用户输入的用户名和密码,看其是否与系统中存储的该用户的用户名和密码一致,来判断用户身份是否正确。例如:密码登录,手机短信验证、三方授权等。

⑵ 认证流程

在这里插入图片描述

⑶ 关键对象

上边的流程图中需要理解以下关键对象:
Subject:主体:访问系统的用户,主体可以是用户、程序等,进行认证的都称为主体;
Principal:身份信息是主体(subject)进行身份认证的标识,标识必须具有唯一性,如用户名、手机号、邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份(Primary Principal)。
credential:凭证信息:是只有主体自己知道的安全信息,如密码、证书等。

3. 授权概念

⑴ 什么是授权

授权,即访问控制,控制谁能访问哪些资源。主体进行身份认证后,系统会为其分配对应的权限,当访问资源时,会校验其是否有访问此资源的权限。

这里首先理解4个对象。

  • 用户对象user:当前操作的用户、程序。
  • 资源对象resource:当前被访问的对象
  • 角色对象role :一组 “权限操作许可权” 的集合。
  • 权限对象permission:权限操作许可权

⑵ 授权流程

在这里插入图片描述

⑶ 关键对象

授权可简单理解为who对what进行How操作
Who: 主体(Subject),可以是一个用户、也可以是一个程序
What: 资源(Resource),如系统菜单、页面、按钮、方法、系统商品信息等。
    访问类型:商品菜单,订单菜单、分销商菜单
    数据类型:我的商品,我的订单,我的评价
How: 权限/许可(Permission)
    我的商品(资源)—>访问我的商品(权限许可)
    分销商菜单(资源)----> 访问分销商列表(权限许可)

二、 Shiro概述

1. Shiro简介

⑴ 什么是Shiro?

Shiro是apache旗下一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限授权、加密、会话管理等功能,组成了一个通用的安全认证框架。

⑵ Shiro 的特点

Shiro 是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加密。如下是它所具有的特点:

  • 易于理解的 Java Security API;
  • 简单的身份认证(登录),支持多种数据源(LDAP,JDBC 等);
  • 对角色的简单的签权(访问控制),也支持细粒度的鉴权;
  • 支持一级缓存,以提升应用程序的性能;
  • 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
  • 异构客户端会话访问;
  • 非常简单的加密 API;
  • 不跟任何的框架或者容器捆绑,可以独立运行。

⑶ 核心组件

1) Shiro架构图

在这里插入图片描述

2) Subject

Subject主体,外部应用与subject进行交互,subject将用户作为当前操作的主体,这个主体:可以是一个通过浏览器请求的用户,也可能是一个运行的程序。Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权。

3) SecurityManager

SecurityManager权限管理器,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权,通过SessionManager进行会话管理等。SecurityManager是一个接口,继承了Authenticator, Authorizer, SessionManager这三个接口。

4) Authenticator

Authenticator即认证器,对用户登录时进行身份认证

5) Authorizer

Authorizer授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。

6) Realm(数据库读取+认证功能+授权功能实现)

Realm领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据
比如:
  如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。
注意:
  不要把realm理解成只是从数据源取数据,在realm中还有认证授权校验的相关的代码。

7) SessionManager

SessionManager会话管理,shiro框架定义了一套会话管理,它不依赖web容器的session,所以shiro可以使用在非web应用上,也可以将分布式应用的会话集中在一点管理,此特性可使它实现单点登录。

8) SessionDAO

SessionDAO即会话dao,是对session会话操作的一套接口
比如:
  可以通过jdbc将会话存储到数据库
  也可以把session存储到缓存服务器

9) CacheManager

CacheManager缓存管理,将用户权限数据存储在缓存,这样可以提高性能

10) Cryptography

Cryptography密码管理,shiro提供了一套加密/解密的组件,方便开发。比如提供常用的散列、加/解密等功能

三、 Shiro入门

1. 身份认证

⑴ 基本流程

在这里插入图片描述
流程如下:

​ 1、Shiro把用户的数据封装成标识token,token一般封装着用户名,密码等信息
​ 2、使用Subject门面获取到封装着用户的数据的标识token
​ 3、Subject把标识token交给SecurityManager,在SecurityManager安全中心中,SecurityManager把标识token委托给认证器Authenticator进行身份验证。认证器的作用一般是用来指定如何验证,它规定本次认证用到哪些Realm
​ 4、认证器Authenticator将传入的标识token,与数据源Realm对比,验证token是否合法

⑵ 案例演示

1) 需求

使用shiro完成一个用户的登录

2) 实现
① 新建项目

shiro-study-project

在这里插入图片描述

② 导入依赖
<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.kejizhentan</groupId><artifactId>shiro-study-project</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.3.2</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version></dependency></dependencies><build><plugins><!--compiler插件,设定JDK版本--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.1</version><configuration><source>8</source><target>8</target><showWarnings>true</showWarnings></configuration></plugin></plugins></build></project>
③ 编写shiro.ini
#声明用户账号[users]zhangsan=123456
④ 编写HelloShiro
packagecom.kejizhentan;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.authc.UsernamePasswordToken;importorg.apache.shiro.config.IniSecurityManagerFactory;importorg.apache.shiro.mgt.SecurityManager;importorg.apache.shiro.subject.Subject;importorg.apache.shiro.util.Factory;publicclassHelloShiro{publicstaticvoidmain(String[]args){//导入权限ini文件构建权限工厂Factory<SecurityManager>factory=newIniSecurityManagerFactory("classpath:shiro.ini");//工厂构建安全管理器SecurityManagersecurityManager=factory.getInstance();//使用SecurityUtils工具生效安全管理器SecurityUtils.setSecurityManager(securityManager);//使用SecurityUtils工具获得主体Subjectsubject=SecurityUtils.getSubject();//构建账号tokenUsernamePasswordTokenusernamePasswordToken=newUsernamePasswordToken("zhangsan","123456");//登录操作subject.login(usernamePasswordToken);System.out.println("是否登录成功:"+subject.isAuthenticated());}}
⑤ 测试

在这里插入图片描述

3) 小结

1、权限定义:ini文件
2、加载过程:
  导入权限ini文件构建权限工厂 工厂构建安全管理器
  使用SecurityUtils工具生效安全管理器 使用SecurityUtils工具获得主体
  使构建账号token用SecurityUtils工具获得主体 构建账号token 登录操作

2. Realm

⑴ Realm接口

在这里插入图片描述
所以,一般在真实的项目中,我们不会直接实现Realm接口,我们一般的情况就是直接继承AuthorizingRealm,能够继承到认证与授权功能。它需要强制重写两个方法

publicclassDefinitionRealmextendsAuthorizingRealm{/***@Description认证*@paramauthcTokentoken对象*@return*/publicabstractAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokenauthcToken){returnnull;}/***@Description鉴权*@paramprincipals令牌*@return*/publicabstractAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipals){returnnull;}}

⑵ 自定义Realm

1) 需求

定义Realm,取得密码用于比较

2) 实现
① 新建项目

shiro-study-project

在这里插入图片描述

② 定义SecurityService

SecurityService

/***@Description:权限服务接口*/publicinterfaceSecurityService{/***@Description查找密码按用户登录名*@paramloginName登录名称*@return*/StringfindPasswordByLoginName(StringloginName);}

SecurityServiceImpl

publicclassSecurityServiceImplimplementsSecurityService{@OverridepublicStringfindPasswordByLoginName(StringloginName){return"123456";}}
③ 定义DefinitionRealm
packagecom.kejizhentan.realm;importcom.kejizhentan.service.SecurityService;importcom.kejizhentan.service.impl.SecurityServiceImpl;importorg.apache.shiro.authc.*;importorg.apache.shiro.authz.AuthorizationInfo;importorg.apache.shiro.realm.AuthorizingRealm;importorg.apache.shiro.subject.PrincipalCollection;publicclassDefinitionRealmextendsAuthorizingRealm{/***@Description认证接口*@paramtoken传递登录token*@return*/@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{//从AuthenticationToken中获得登录名称StringloginName=(String)token.getPrincipal();SecurityServicesecurityService=newSecurityServiceImpl();Stringpassword=securityService.findPasswordByLoginName(loginName);if("".equals(password)||password==null){thrownewUnknownAccountException("账户不存在");}//传递账号和密码returnnewSimpleAuthenticationInfo(loginName,password,getName());}@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipals){returnnull;}}
④ 自定义模拟Controller的类
packagecom.kejizhentan.controller;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.authc.UsernamePasswordToken;importorg.apache.shiro.config.IniSecurityManagerFactory;importorg.apache.shiro.mgt.SecurityManager;importorg.apache.shiro.subject.Subject;importorg.apache.shiro.util.Factory;/***@Auther:kejizhentan*@Date2022/12/3023:14*@Description:模拟controller*/publicclassShiroController{publicstaticvoidmain(String[]args){//导入权限ini文件构建权限工厂Factory<SecurityManager>factory=newIniSecurityManagerFactory("classpath:shiro.ini");//工厂构建安全管理器SecurityManagersecurityManager=factory.getInstance();//使用SecurityUtils工具生效安全管理器SecurityUtils.setSecurityManager(securityManager);//使用SecurityUtils工具获得主体Subjectsubject=SecurityUtils.getSubject();//构建账号tokenUsernamePasswordTokenusernamePasswordToken=newUsernamePasswordToken("zhangsan","123456");//登录操作subject.login(usernamePasswordToken);System.out.println("是否登录成功:"+subject.isAuthenticated());}}
⑤ 编辑shiro.ini
#声明自定义的realm,且为安全管理器指定realms[main]definitionRealm=com.kejizhentan.realm.DefinitionRealmsecurityManager.realms=$definitionRealm
⑥ 测试

在这里插入图片描述

3) 认证源码跟踪

(1)通过debug模式追踪源码subject.login(token) 发现。首先是进入Subject接口的默认实现类。果然,Subject将用户的用户名密码委托给了securityManager去做。
在这里插入图片描述

(2)然后,securityManager说:“卧槽,认证器authenticator小弟,听说你的大学学的专业就是认证呀,那么这个认证的任务就交给你咯”。遂将用户的token委托给内部认证组件authenticator去做
在这里插入图片描述

(3)事实上,securityManager的内部组件一个比一个懒。内部认证组件authenticator说:“你们传过来的token我需要拿去跟数据源Realm做对比,这样吧,这个光荣的任务就交给Realm你去做吧”。Realm对象:“一群大懒虫!”。
在这里插入图片描述

(4)Realm在接到内部认证组件authenticator组件后很伤心,最后对电脑前的你说:“大兄弟,对不住了,你去实现一下呗”。从图中的方法体中可以看到,当前对象是Realm类对象,即将调用的方法是doGetAuthenticationInfo(token)。而这个方法,就是你即将要重写的方法。如果帐号密码通过了,那么返回一个认证成功的info凭证。如果认证失败,抛出一个异常就好了。你说:“什么?最终还是劳资来认证?”没错,就是苦逼的你去实现了,谁叫你是程序猿呢。所以,你不得不查询一下数据库,重写doGetAuthenticationInfo方法,查出来正确的帐号密码,返回一个正确的凭证info
在这里插入图片描述

(5)好了,这个时候你自己编写了一个类,继承了AuthorizingRealm,并实现了上述doGetAuthenticationInfo方法。你在doGetAuthenticationInfo中编写了查询数据库的代码,并将数据库中存放的用户名与密码封装成了一个AuthenticationInfo对象返回。可以看到下图中,info这个对象是有值的,说明从数据库中查询出来了正确的帐号密码
在这里插入图片描述
(6)那么,接下来就很简单了。把用户输入的帐号密码与刚才你从数据库中查出来的帐号密码对比一下即可。token封装着用户的帐号密码,AuthenticationInfo封装着从数据库中查询出来的帐号密码。再往下追踪一下代码,最终到了下图中的核心区域。如果没有报异常,说明本次登录成功。
在这里插入图片描述

3. 编码、散列算法

⑴ 编码与解码

Shiro提供了base64和16进制字符串编码/解码的API支持,方便一些编码解码操作。
Shiro内部的一些数据的【存储/表示】都使用了base64和16进制字符串

1) 需求

理解base64和16进制字符串编码/解码

2) 新建项目

encode-decode-project
在这里插入图片描述

3)添加pom依赖
<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.kejizhentan</groupId><artifactId>encode-decode-project</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.3.2</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version></dependency></dependencies><build><plugins><!--compiler插件,设定JDK版本--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.1</version><configuration><source>8</source><target>8</target><showWarnings>true</showWarnings></configuration></plugin></plugins></build></project>
4)新建EncodesUtil
packagecom.kejizhentan.utils;importorg.apache.shiro.codec.Base64;importorg.apache.shiro.codec.Hex;/***@Description:封装base64和16进制编码解码工具类*/publicclassEncodesUtil{/***@DescriptionHEX-byte[]--String转换*@paraminput输入数组*@returnString*/publicstaticStringencodeHex(byte[]input){returnHex.encodeToString(input);}/***@DescriptionHEX-String--byte[]转换*@paraminput输入字符串*@returnbyte数组*/publicstaticbyte[]decodeHex(Stringinput){returnHex.decode(input);}/***@DescriptionBase64-byte[]--String转换*@paraminput输入数组*@returnString*/publicstaticStringencodeBase64(byte[]input){returnBase64.encodeToString(input);}/***@DescriptionBase64-String--byte[]转换*@paraminput输入字符串*@returnbyte数组*/publicstaticbyte[]decodeBase64(Stringinput){returnBase64.decode(input);}}
5)新建ClientTest
packagecom.kejizhentan.test;importcom.kejizhentan.utils.EncodesUtil;importorg.junit.Test;/***@Description:测试*/publicclassClientTest{/***@Description测试16进制编码*/@TestpublicvoidtestHex(){//自定义字符串Stringval="hello";//先将字符串转换成字节数组,然后通过工具类将字节数组编码成字符串Stringflag=EncodesUtil.encodeHex(val.getBytes());//再通过工具将编码的字符串解码成字节数据,然后再装换成字符串StringvalHandler=newString(EncodesUtil.decodeHex(flag));//比较经编码和解码的字符串是否和原先的字符串一样System.out.println("比较结果:"+val.equals(valHandler));}/***@Description测试base64编码*/@TestpublicvoidtestBase64(){//自定义字符串Stringval="hello";//先将字符串转换成字节数组,然后通过工具类将字节数组编码成字符串Stringflag=EncodesUtil.encodeBase64(val.getBytes());//再通过工具将编码的字符串解码成字节数据,然后再装换成字符串StringvalHandler=newString(EncodesUtil.decodeBase64(flag));//比较经编码和解码的字符串是否和原先的字符串一样System.out.println("比较结果:"+val.equals(valHandler));}}
6) 小结

1、shiro目前支持的编码与解码:
  base64
  (HEX)16进制字符串
2、那么shiro的编码与解码什么时候使用呢?又是怎么使用的呢?

⑵ 散列算法

散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“admin”,产生的散列值是“21232f297a57a5a743894a0e4a801fc3”,可以到一些md5解密网站很容易的通过散列值得到密码“admin”,即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,如salt(即盐);这样散列的对象是“密码+salt”,这样生成的散列值相对来说更难破解。
shiro支持的散列算法:
Md2Hash、Md5Hash、Sha1Hash、Sha256Hash、Sha384Hash、Sha512Hash
在这里插入图片描述

⑶ 在原来的encode-decode-project项目上新建HashUtil工具类

packagecom.kejizhentan.utils;importorg.apache.shiro.crypto.SecureRandomNumberGenerator;importorg.apache.shiro.crypto.hash.SimpleHash;importjava.util.HashMap;importjava.util.Map;/***@Auther:kejizhentan*@Date2022/12/3114:20*@Description:散列算法加密工具类*/publicclassHashUtil{privatestaticfinalStringSHA1="SHA-1";privatestaticfinalIntegerITERATIONS=512;/***@Descriptionsha1方法*@paraminput需要散列字符串*@paramsalt盐字符串*@return*/publicstaticStringsha1(Stringinput,Stringsalt){returnnewSimpleHash(SHA1,input,salt,ITERATIONS).toString();}/***@Description随机获得salt字符串*@return*/publicstaticStringgenerateSalt(){SecureRandomNumberGeneratorrandomNumberGenerator=newSecureRandomNumberGenerator();returnrandomNumberGenerator.nextBytes().toHex();}/***@Description生成密码字符密文和salt密文*@param*@return*/publicstaticMap<String,String>entryptPassword(StringpasswordPlain){Map<String,String>map=newHashMap<>();Stringsalt=generateSalt();Stringpassword=sha1(passwordPlain,salt);map.put("salt",salt);map.put("password",password);returnmap;}}

⑷ 修改测试类ClientTest

packagecom.kejizhentan.test;importcom.kejizhentan.utils.EncodesUtil;importcom.kejizhentan.utils.HashUtil;importorg.junit.Test;importjava.util.Map;/***@Description:测试*/publicclassClientTest{/***@Description测试16进制编码*/@TestpublicvoidtestHex(){//自定义字符串Stringval="hello";//先将字符串转换成字节数组,然后通过工具类将字节数组编码成字符串Stringflag=EncodesUtil.encodeHex(val.getBytes());//再通过工具将编码的字符串解码成字节数据,然后再装换成字符串StringvalHandler=newString(EncodesUtil.decodeHex(flag));//比较经编码和解码的字符串是否和原先的字符串一样System.out.println("比较结果:"+val.equals(valHandler));}/***@Description测试base64编码*/@TestpublicvoidtestBase64(){//自定义字符串Stringval="hello";//先将字符串转换成字节数组,然后通过工具类将字节数组编码成字符串Stringflag=EncodesUtil.encodeBase64(val.getBytes());//再通过工具将编码的字符串解码成字节数据,然后再装换成字符串StringvalHandler=newString(EncodesUtil.decodeBase64(flag));//比较经编码和解码的字符串是否和原先的字符串一样System.out.println("比较结果:"+val.equals(valHandler));}/***@Auther:kejizhentan*@Date2022/12/3114:25*@Description:散列算法加密*/@TestpublicvoidtestDigestsUtil(){Map<String,String>map=HashUtil.entryptPassword("123456");System.out.println("获得结果:"+map.toString());}}

4. Realm使用散列算法

上面我们了解编码,以及散列算法,那么在realm中怎么使用?在shiro-study-project中我们使用的密码是明文的校验方式,也就是SecurityServiceImpl中findPasswordByLoginName返回的是明文123456的密码

publicclassSecurityServiceImplimplementsSecurityService{@OverridepublicStringfindPasswordByLoginName(StringloginName){return"123456";}}

⑴ 新建项目

在这里插入图片描述

⑵ 添加pom依赖

<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.kejizhentan</groupId><artifactId>shiro-ciphertext-realm-project</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.3.2</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version></dependency></dependencies><build><plugins><!--compiler插件,设定JDK版本--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.1</version><configuration><source>8</source><target>8</target><showWarnings>true</showWarnings></configuration></plugin></plugins></build></project>

⑶ 添加shiro.ini

#声明自定义的realm,且为安全管理器指定realms[main]definitionRealm=com.kejizhentan.realm.DefinitionRealmsecurityManager.realms=$definitionRealm

⑷ 添加EncodesUtil.java

packagecom.kejizhentan.utils;importorg.apache.shiro.codec.Base64;importorg.apache.shiro.codec.Hex;/***@Description:封装base64和16进制编码解码工具类*/publicclassEncodesUtil{/***@DescriptionHEX-byte[]--String转换*@paraminput输入数组*@returnString*/publicstaticStringencodeHex(byte[]input){returnHex.encodeToString(input);}/***@DescriptionHEX-String--byte[]转换*@paraminput输入字符串*@returnbyte数组*/publicstaticbyte[]decodeHex(Stringinput){returnHex.decode(input);}/***@DescriptionBase64-byte[]--String转换*@paraminput输入数组*@returnString*/publicstaticStringencodeBase64(byte[]input){returnBase64.encodeToString(input);}/***@DescriptionBase64-String--byte[]转换*@paraminput输入字符串*@returnbyte数组*/publicstaticbyte[]decodeBase64(Stringinput){returnBase64.decode(input);}}

⑸ 添加HashUtil.java

packagecom.kejizhentan.utils;importorg.apache.shiro.crypto.SecureRandomNumberGenerator;importorg.apache.shiro.crypto.hash.SimpleHash;importjava.util.HashMap;importjava.util.Map;/***@Auther:kejizhentan*@Date2022/12/3114:20*@Description:散列算法加密工具类*/publicclassHashUtil{publicstaticfinalStringSHA1="SHA-1";publicstaticfinalIntegerITERATIONS=512;/***@Descriptionsha1方法*@paraminput需要散列字符串*@paramsalt盐字符串*@return*/publicstaticStringsha1(Stringinput,Stringsalt){returnnewSimpleHash(SHA1,input,salt,ITERATIONS).toString();}/***@Description随机获得salt字符串*@return*/publicstaticStringgenerateSalt(){SecureRandomNumberGeneratorrandomNumberGenerator=newSecureRandomNumberGenerator();returnrandomNumberGenerator.nextBytes().toHex();}/***@Description生成密码字符密文和salt密文*@param*@return*/publicstaticMap<String,String>entryptPassword(StringpasswordPlain){Map<String,String>map=newHashMap<>();//随机获得salt字符串Stringsalt=generateSalt();//通过加盐的方式生成加密后的密码Stringpassword=sha1(passwordPlain,salt);map.put("salt",salt);map.put("password",password);returnmap;}}

⑹ 修改SecurityService

SecurityService修改成返回salt和password的map

packagecom.kejizhentan.service;importjava.util.Map;/***@Description:权限服务接口*/publicinterfaceSecurityService{/***@Description查找密码按用户登录名*@paramloginName登录名称*@return*/Map<String,String>findPasswordByLoginName(StringloginName);}
packagecom.kejizhentan.service.impl;importcom.kejizhentan.service.SecurityService;importcom.kejizhentan.utils.HashUtil;importjava.util.Map;publicclassSecurityServiceImplimplementsSecurityService{@OverridepublicMap<String,String>findPasswordByLoginName(StringloginName){//模拟数据库中存储的密文信息returnHashUtil.entryptPassword("123456");}}

⑺ 添加DefinitionRealm.java

packagecom.kejizhentan.realm;importcom.kejizhentan.service.SecurityService;importcom.kejizhentan.service.impl.SecurityServiceImpl;importcom.kejizhentan.utils.HashUtil;importorg.apache.shiro.authc.*;importorg.apache.shiro.authc.credential.HashedCredentialsMatcher;importorg.apache.shiro.authz.AuthorizationInfo;importorg.apache.shiro.realm.AuthorizingRealm;importorg.apache.shiro.subject.PrincipalCollection;importorg.apache.shiro.util.ByteSource;importjava.util.Map;publicclassDefinitionRealmextendsAuthorizingRealm{/***@Description构造函数*/publicDefinitionRealm(){//指定密码匹配方式为sha1HashedCredentialsMatchermatcher=newHashedCredentialsMatcher(HashUtil.SHA1);//指定密码迭代次数matcher.setHashIterations(HashUtil.ITERATIONS);//使用父亲方法使匹配方式生效setCredentialsMatcher(matcher);}/***@Description认证接口*@paramtoken传递登录token*@return*/@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{//从AuthenticationToken中获得登录名称StringloginName=(String)token.getPrincipal();SecurityServicesecurityService=newSecurityServiceImpl();Map<String,String>map=securityService.findPasswordByLoginName(loginName);if(map.isEmpty()){thrownewUnknownAccountException("账户不存在");}Stringsalt=map.get("salt");Stringpassword=map.get("password");//传递账号和密码:参数1:缓存对象,参数2:密文密码,参数三:字节salt,参数4:当前DefinitionRealm名称returnnewSimpleAuthenticationInfo(loginName,password,ByteSource.Util.bytes(salt),getName());}@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipals){returnnull;}}

⑻ 添加ShiroController.java

packagecom.kejizhentan.controller;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.authc.UsernamePasswordToken;importorg.apache.shiro.config.IniSecurityManagerFactory;importorg.apache.shiro.mgt.SecurityManager;importorg.apache.shiro.subject.Subject;importorg.apache.shiro.util.Factory;/***@Auther:kejizhentan*@Date2022/12/3023:14*@Description:模拟controller*/publicclassShiroController{publicstaticvoidmain(String[]args){//导入权限ini文件构建权限工厂Factory<SecurityManager>factory=newIniSecurityManagerFactory("classpath:shiro.ini");//工厂构建安全管理器SecurityManagersecurityManager=factory.getInstance();//使用SecurityUtils工具生效安全管理器SecurityUtils.setSecurityManager(securityManager);//使用SecurityUtils工具获得主体Subjectsubject=SecurityUtils.getSubject();//构建账号tokenUsernamePasswordTokenusernamePasswordToken=newUsernamePasswordToken("zhangsan","123456");//登录操作subject.login(usernamePasswordToken);System.out.println("是否登录成功:"+subject.isAuthenticated());}}

⑼ 测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5. 身份授权

⑴ 基本流程

在这里插入图片描述
1、首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager。
2、SecurityManager接着会委托给内部组件Authorizer;
3、Authorizer再将其请求委托给我们的Realm去做;Realm才是真正干活的;
4、Realm将用户请求的参数封装成权限对象。再从我们重写的doGetAuthorizationInfo方法中获取从数据库中查询到的权限集合。
5、Realm将用户传入的权限对象,与从数据库中查出来的权限对象,进行一一对比。如果用户传入的权限对象在从数据库中查出来的权限对象中,则返回true,否则返回false。
进行授权操作的前提:用户必须通过认证。
在真实的项目中,角色与权限都存放在数据库中。为了快速上手,我们先创建一个自定义DefinitionRealm,模拟它已经登录成功。直接返回一个登录验证凭证,告诉Shiro框架,我们从数据库中查询出来的密码是也是就是你输入的密码。所以,不管用户输入什么,本次登录验证都是通过的。

/***@Description认证接口*@paramtoken传递登录token*@return*/@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{//从AuthenticationToken中获得登录名称StringloginName=(String)token.getPrincipal();SecurityServicesecurityService=newSecurityServiceImpl();Map<String,String>map=securityService.findPasswordByLoginName(loginName);if(map.isEmpty()){thrownewUnknownAccountException("账户不存在");}Stringsalt=map.get("salt");Stringpassword=map.get("password");//传递账号和密码:参数1:用户认证凭证信息,参数2:明文密码,参数三:字节salt,参数4:当前DefinitionRealm名称returnnewSimpleAuthenticationInfo(loginName,password,ByteSource.Util.bytes(salt),getName());}

好了,接下来,我们要重写我们本小节的核心方法了。在DefinitionRealm中找到下列方法:

@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipals){returnnull;}

此方法的传入的参数PrincipalCollection principals,是一个包装对象,它表示"用户认证凭证信息"。包装的是谁呢?没错,就是认证doGetAuthenticationInfo()方法的返回值的第一个参数loginName。你可以通过这个包装对象的getPrimaryPrincipal()方法拿到此值,然后再从数据库中拿到对应的角色和资源,构建SimpleAuthorizationInfo。

/***@Description授权方法*/@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipals){//拿到用户认证凭证信息StringloginName=(String)principals.getPrimaryPrincipal();//从数据库中查询对应的角色和资源SecurityServicesecurityService=newSecurityServiceImpl();List<String>roles=securityService.findRoleByloginName(loginName);List<String>permissions=securityService.findPermissionByloginName(loginName);//构建资源校验SimpleAuthorizationInfoauthorizationInfo=newSimpleAuthorizationInfo();authorizationInfo.addRoles(roles);authorizationInfo.addStringPermissions(permissions);returnauthorizationInfo;}

⑵ 案例演示

1) 需求
1、实现doGetAuthorizationInfo方法实现鉴权2、使用subject类实现权限的校验
2)实现
① 创建项目

新建shiro-authentication-realm-project
在这里插入图片描述

② 导入pom依赖
<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.kejizhentan</groupId><artifactId>shiro-authentication-realm-project</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></properties><dependencies><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.3.2</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version></dependency></dependencies><build><plugins><!--compiler插件,设定JDK版本--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.1</version><configuration><source>8</source><target>8</target><showWarnings>true</showWarnings></configuration></plugin></plugins></build></project>
③ 创建shiro.ini
#声明自定义的realm,且为安全管理器指定realms[main]definitionRealm=com.kejizhentan.realm.DefinitionRealmsecurityManager.realms=$definitionRealm
④ 添加EncodesUtil.java
packagecom.kejizhentan.utils;importorg.apache.shiro.codec.Base64;importorg.apache.shiro.codec.Hex;/***@Description:封装base64和16进制编码解码工具类*/publicclassEncodesUtil{/***@DescriptionHEX-byte[]--String转换*@paraminput输入数组*@returnString*/publicstaticStringencodeHex(byte[]input){returnHex.encodeToString(input);}/***@DescriptionHEX-String--byte[]转换*@paraminput输入字符串*@returnbyte数组*/publicstaticbyte[]decodeHex(Stringinput){returnHex.decode(input);}/***@DescriptionBase64-byte[]--String转换*@paraminput输入数组*@returnString*/publicstaticStringencodeBase64(byte[]input){returnBase64.encodeToString(input);}/***@DescriptionBase64-String--byte[]转换*@paraminput输入字符串*@returnbyte数组*/publicstaticbyte[]decodeBase64(Stringinput){returnBase64.decode(input);}}
⑤ 添加HashUtil.java
packagecom.kejizhentan.utils;importorg.apache.shiro.crypto.SecureRandomNumberGenerator;importorg.apache.shiro.crypto.hash.SimpleHash;importjava.util.HashMap;importjava.util.Map;/***@Auther:kejizhentan*@Date2022/12/3114:20*@Description:散列算法加密工具类*/publicclassHashUtil{publicstaticfinalStringSHA1="SHA-1";publicstaticfinalIntegerITERATIONS=512;/***@Descriptionsha1方法*@paraminput需要散列字符串*@paramsalt盐字符串*@return*/publicstaticStringsha1(Stringinput,Stringsalt){returnnewSimpleHash(SHA1,input,salt,ITERATIONS).toString();}/***@Description随机获得salt字符串*@return*/publicstaticStringgenerateSalt(){SecureRandomNumberGeneratorrandomNumberGenerator=newSecureRandomNumberGenerator();returnrandomNumberGenerator.nextBytes().toHex();}/***@Description生成密码字符密文和salt密文*@param*@return*/publicstaticMap<String,String>entryptPassword(StringpasswordPlain){Map<String,String>map=newHashMap<>();//随机获得salt字符串Stringsalt=generateSalt();//通过加盐的方式生成加密后的密码Stringpassword=sha1(passwordPlain,salt);map.put("salt",salt);map.put("password",password);returnmap;}}
⑥ 创建DefinitionRealm.java
packagecom.kejizhentan.realm;importcom.kejizhentan.service.SecurityService;importcom.kejizhentan.service.impl.SecurityServiceImpl;importcom.kejizhentan.utils.HashUtil;importorg.apache.shiro.authc.*;importorg.apache.shiro.authc.credential.HashedCredentialsMatcher;importorg.apache.shiro.authz.AuthorizationInfo;importorg.apache.shiro.authz.SimpleAuthorizationInfo;importorg.apache.shiro.realm.AuthorizingRealm;importorg.apache.shiro.subject.PrincipalCollection;importorg.apache.shiro.util.ByteSource;importjava.util.List;importjava.util.Map;publicclassDefinitionRealmextendsAuthorizingRealm{/***@Description构造函数*/publicDefinitionRealm(){//指定密码匹配方式为sha1HashedCredentialsMatchermatcher=newHashedCredentialsMatcher(HashUtil.SHA1);//指定密码迭代次数matcher.setHashIterations(HashUtil.ITERATIONS);//使用父亲方法使匹配方式生效setCredentialsMatcher(matcher);}/***@Description认证接口*@paramtoken传递登录token*@return*/@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{//从AuthenticationToken中获得登录名称StringloginName=(String)token.getPrincipal();SecurityServicesecurityService=newSecurityServiceImpl();Map<String,String>map=securityService.findPasswordByLoginName(loginName);if(map.isEmpty()){thrownewUnknownAccountException("账户不存在");}Stringsalt=map.get("salt");Stringpassword=map.get("password");//传递账号和密码:参数1:缓存对象,参数2:密文密码,参数三:字节salt,参数4:当前DefinitionRealm名称returnnewSimpleAuthenticationInfo(loginName,password,ByteSource.Util.bytes(salt),getName());}/***@Description授权方法*/@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipals){//拿到用户认证凭证信息StringloginName=(String)principals.getPrimaryPrincipal();//从数据库中查询对应的角色和资源SecurityServicesecurityService=newSecurityServiceImpl();List<String>roles=securityService.findRoleByloginName(loginName);List<String>permissions=securityService.findPermissionByloginName(loginName);//构建资源校验SimpleAuthorizationInfoauthorizationInfo=newSimpleAuthorizationInfo();authorizationInfo.addRoles(roles);authorizationInfo.addStringPermissions(permissions);returnauthorizationInfo;}}
⑦ 创建SecurityService.java

SecurityService.java

/***@Description:权限服务接口*/publicinterfaceSecurityService{/***@Description查找角色按用户登录名*@paramloginName登录名称*@return*/List<String>findRoleByloginName(StringloginName);/***@Description查找资源按用户登录名*@paramloginName登录名称*@return*/List<String>findPermissionByloginName(StringloginName);/***@Description查找密码按用户登录名*@paramloginName登录名称*@return*/Map<String,String>findPasswordByLoginName(StringloginName);}

SecurityServiceImpl.java

packagecom.kejizhentan.service.impl;importcom.kejizhentan.service.SecurityService;importcom.kejizhentan.utils.HashUtil;importjava.util.ArrayList;importjava.util.List;importjava.util.Map;publicclassSecurityServiceImplimplementsSecurityService{@OverridepublicList<String>findRoleByloginName(StringloginName){List<String>list=newArrayList<>();list.add("admin");list.add("dev");returnlist;}@OverridepublicList<String>findPermissionByloginName(StringloginName){List<String>list=newArrayList<>();list.add("order:add");list.add("order:list");list.add("order:del");returnlist;}@OverridepublicMap<String,String>findPasswordByLoginName(StringloginName){//模拟数据库中存储的密文信息returnHashUtil.entryptPassword("123456");}}
⑧ 创建ShiroController.java
packagecom.kejizhentan.controller;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.authc.UsernamePasswordToken;importorg.apache.shiro.config.IniSecurityManagerFactory;importorg.apache.shiro.mgt.SecurityManager;importorg.apache.shiro.subject.Subject;importorg.apache.shiro.util.Factory;/***@Auther:kejizhentan*@Date2022/12/3023:14*@Description:模拟controller*/publicclassShiroController{publicstaticvoidmain(String[]args){Subjectsubject=shiroLogin("zhangsan","123456");//判断用户是否已经登录System.out.println("是否登录成功:"+subject.isAuthenticated());//---------检查当前用户的角色信息------------System.out.println("是否有管理员角色:"+subject.hasRole("admin"));//---------如果当前用户有此角色,无返回值。若没有此权限,则抛UnauthorizedException------------try{subject.checkRole("coder");System.out.println("有coder角色");}catch(Exceptione){System.out.println("没有coder角色");}//---------检查当前用户的权限信息------------System.out.println("是否有查看订单列表资源:"+subject.isPermitted("order:list"));//---------如果当前用户有此权限,无返回值。若没有此权限,则抛UnauthorizedException------------try{subject.checkPermissions("order:add","order:del");System.out.println("有添加和删除订单资源");}catch(Exceptione){System.out.println("没有有添加和删除订单资源");}}/***@Description登录方法*/privatestaticSubjectshiroLogin(StringloginName,Stringpassword){//导入权限ini文件构建权限工厂Factory<SecurityManager>factory=newIniSecurityManagerFactory("classpath:shiro.ini");//工厂构建安全管理器SecurityManagersecurityManager=factory.getInstance();//使用SecurityUtils工具生效安全管理器SecurityUtils.setSecurityManager(securityManager);//使用SecurityUtils工具获得主体Subjectsubject=SecurityUtils.getSubject();//构建账号tokenUsernamePasswordTokenusernamePasswordToken=newUsernamePasswordToken(loginName,password);//登录操作subject.login(usernamePasswordToken);returnsubject;}}
3)授权源码追踪

(1)客户端调用 subject.hasRole(“admin”),判断当前用户是否有"admin"角色权限。
在这里插入图片描述
(2)Subject门面对象接收到要被验证的角色信息"admin",并将其委托给securityManager中验证。
在这里插入图片描述
(3)securityManager将验证请求再次委托给内部的小弟:内部组件Authorizer authorizer
在这里插入图片描述
(4)内部小弟authorizer也是个混子,将其委托给了我们自定义的Realm去做
在这里插入图片描述
(5) 先拿到PrincipalCollection principal对象,同时传入校验的角色循环校验,循环中先创建鉴权信息
在这里插入图片描述
(6)先看缓存中是否已经有鉴权信息
在这里插入图片描述
在这里插入图片描述
(7)都是一群懒货!!最后干活的还是我这个猴子!
在这里插入图片描述

⑶ 小结

1、鉴权需要实现doGetAuthorizationInfo方法
2、鉴权使用门面subject中方法进行鉴权
  以check开头的会抛出异常
  以is和has开头会返回布尔值

四、Web项目集成Shiro

1.Web集成原理分析

⑴ web集成的配置

还记得吗,以前我们在没有与WEB环境进行集成的时候,为了生成SecurityManager对象,是通过手动读取配置文件生成工厂对象,再通过工厂对象获取到SecurityManager的。就像下面代码展示的那样:

/***@Description登录方法*/privateSubjectshiroLogin(StringloginName,Stringpassword){//导入权限ini文件构建权限工厂Factory<SecurityManager>factory=newIniSecurityManagerFactory("classpath:shiro.ini");//工厂构建安全管理器SecurityManagersecurityManager=factory.getInstance();//使用SecurityUtils工具生效安全管理器SecurityUtils.setSecurityManager(securityManager);//使用SecurityUtils工具获得主体Subjectsubject=SecurityUtils.getSubject();//构建账号tokenUsernamePasswordTokenusernamePasswordToken=newUsernamePasswordToken(loginName,password);//登录操作subject.login(usernamePasswordToken);returnsubject;}

不过,现在我们既然说要与WEB集成,那么首先要做的事情就是把我们的shiro.ini这个配置文件交付到WEB环境中,定义shiro.ini文件如下:

#声明自定义的realm,且为安全管理器指定realms[main]definitionRealm=com.kejizhentan.realm.DefinitionRealmsecurityManager.realms=$definitionRealm
1) 新建项目

创建shiro-web-project项目
在这里插入图片描述

2) pom.xml配置
<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.kejizhentan</groupId><artifactId>shiro-web-project</artifactId><version>1.0-SNAPSHOT</version><packaging>war</packaging><name>shiro-web-projectMavenWebapp</name><!--FIXMEchangeittotheproject'swebsite--><url>http://www.example.com</url><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.source>1.7</maven.compiler.source><maven.compiler.target>1.7</maven.compiler.target></properties><dependencies><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.3.2</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><version>1.3.2</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version></dependency></dependencies><build><plugins><!--tomcat7插件,命令:mvntomcat7:run-DskipTests--><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version><configuration><uriEncoding>utf-8</uriEncoding><port>8080</port><path>/platform</path></configuration></plugin><!--compiler插件,设定JDK版本--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.1</version><configuration><source>8</source><target>8</target><showWarnings>true</showWarnings></configuration></plugin></plugins></build></project>
3) web.xml配置
<web-appxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"version="3.0"><display-name>shiro-web-project</display-name><!--初始化SecurityManager对象所需要的环境--><context-param><param-name>shiroEnvironmentClass</param-name><param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value></context-param><!--指定Shiro的配置文件的位置--><context-param><param-name>shiroConfigLocations</param-name><param-value>classpath:shiro.ini</param-value></context-param><!--监听服务器启动时,创建shiro的web环境。即加载shiroEnvironmentClass变量指定的IniWebEnvironment类--><listener><listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class></listener><!--shiro的l过滤入口,过滤一切请求--><filter><filter-name>shiroFilter</filter-name><filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class></filter><filter-mapping><filter-name>shiroFilter</filter-name><!--过滤所有请求--><url-pattern>/*</url-pattern></filter-mapping></web-app>

⑵ SecurityManager对象创建

上面我们集成shiro到web项目了,下面我们来追踪下源码,看下SecurityManager对象是如何创建的

(1)我启动了服务器,监听器捕获到了服务器启动事件。我现在所处的位置EnvironmentLoaderListener监听器的入口处
在这里插入图片描述

(2)进入方法内查看,它先根据我们的shiroEnvironmentClass变量的值org.apache.shiro.web.env.IniWebEnvironment,初始化一个shiro环境对象
在这里插入图片描述
(3)最后在创建一个SecurityManager对象,再将其绑定到刚才通过字节码创建的Shiro环境对象中
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
到这来SecurityManager就完成了初始化

2. Shiro默认过滤器

Shiro内置了很多默认的过滤器,比如身份验证、授权等相关的。默认过滤器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举过滤器
在这里插入图片描述

⑴ 认证相关

过滤器过滤器类说明默认authcFormAuthenticationFilter基于表单的过滤器;如“/=authc”,如果没有登录会跳到相应的登录页面登录无logoutLogoutFilter退出过滤器,主要属性:redirectUrl:退出成功后重定向的地址,如“/logout=logout”/anonAnonymousFilter匿名过滤器,即不需要登录即可访问;一般用于静态资源过滤;示例“/static/=anon”无

⑵ 授权相关

过滤器过滤器类说明默认rolesRolesAuthorizationFilter角色授权拦截器,验证用户是否拥有所有角色;主要属性: loginUrl:登录页面地址(/login.jsp);unauthorizedUrl:未授权后重定向的地址;示例“/admin/=roles[admin]”无permsPermissionsAuthorizationFilter权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例“/user/=perms[“user:create”]”无portPortFilter端口拦截器,主要属性:port(80):可以通过的端口;示例“/test= port[80]”,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样无restHttpMethodPermissionFilterrest风格拦截器,自动根据请求方法构建权限字符串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串;示例“/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete”权限字符串进行权限匹配(所有都得匹配,isPermittedAll)无sslSslFilterSSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口(443);其他和port拦截器一样;无

3. Web集成完整案例

⑴ 项目结构如下

在这里插入图片描述

⑵ 添加pom.xml依赖

<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.kejizhentan</groupId><artifactId>shiro-web-project</artifactId><version>1.0-SNAPSHOT</version><packaging>war</packaging><name>shiro-web-projectMavenWebapp</name><!--FIXMEchangeittotheproject'swebsite--><url>http://www.example.com</url><dependencies><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.3.2</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><version>1.3.2</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.0.1</version><scope>provided</scope></dependency><dependency><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><dependency><groupId>taglibs</groupId><artifactId>standard</artifactId><version>1.1.2</version></dependency></dependencies><build><plugins><!--tomcat7插件,命令:mvntomcat7:run-DskipTests--><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version><configuration><uriEncoding>utf-8</uriEncoding><port>8080</port><path>/platform</path></configuration></plugin><!--compiler插件,设定JDK版本--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.1</version><configuration><source>8</source><target>8</target><showWarnings>true</showWarnings></configuration></plugin></plugins></build></project>

⑶ 编写shiro.ini文件

#声明自定义的realm,且为安全管理器指定realms[main]definitionRealm=com.kejizhentan.realm.DefinitionRealmsecurityManager.realms=$definitionRealm#用户退出后跳转指定JSP页面logout.redirectUrl=/login.jsp#若没有登录,则被authc过滤器重定向到login.jsp页面authc.loginUrl=/login.jsp[urls]/login=anon#发送/home请求需要先登录/home=authc#发送/order/list请求需要先登录/order-list=roles[admin]#提交代码需要order:add权限/order-add=perms["order:add"]#更新代码需要order:del权限/order-del=perms["order:del"]#发送退出请求则用退出过滤器/logout=logout

⑷ 相关工具类

封装base64和16进制编码解码工具类EncodesUtil.java

packagecom.kejizhentan.utils;importorg.apache.shiro.codec.Base64;importorg.apache.shiro.codec.Hex;/***@Description:封装base64和16进制编码解码工具类*/publicclassEncodesUtil{/***@DescriptionHEX-byte[]--String转换*@paraminput输入数组*@returnString*/publicstaticStringencodeHex(byte[]input){returnHex.encodeToString(input);}/***@DescriptionHEX-String--byte[]转换*@paraminput输入字符串*@returnbyte数组*/publicstaticbyte[]decodeHex(Stringinput){returnHex.decode(input);}/***@DescriptionBase64-byte[]--String转换*@paraminput输入数组*@returnString*/publicstaticStringencodeBase64(byte[]input){returnBase64.encodeToString(input);}/***@DescriptionBase64-String--byte[]转换*@paraminput输入字符串*@returnbyte数组*/publicstaticbyte[]decodeBase64(Stringinput){returnBase64.decode(input);}}

散列算法加密工具类HashUtil.java

packagecom.kejizhentan.utils;importorg.apache.shiro.crypto.SecureRandomNumberGenerator;importorg.apache.shiro.crypto.hash.SimpleHash;importjava.util.HashMap;importjava.util.Map;/***@Auther:kejizhentan*@Date2022/12/3114:20*@Description:散列算法加密工具类*/publicclassHashUtil{publicstaticfinalStringSHA1="SHA-1";publicstaticfinalIntegerITERATIONS=512;/***@Descriptionsha1方法*@paraminput需要散列字符串*@paramsalt盐字符串*@return*/publicstaticStringsha1(Stringinput,Stringsalt){returnnewSimpleHash(SHA1,input,salt,ITERATIONS).toString();}/***@Description随机获得salt字符串*@return*/publicstaticStringgenerateSalt(){SecureRandomNumberGeneratorrandomNumberGenerator=newSecureRandomNumberGenerator();returnrandomNumberGenerator.nextBytes().toHex();}/***@Description生成密码字符密文和salt密文*@param*@return*/publicstaticMap<String,String>entryptPassword(StringpasswordPlain){Map<String,String>map=newHashMap<>();//随机获得salt字符串Stringsalt=generateSalt();//通过加盐的方式生成加密后的密码Stringpassword=sha1(passwordPlain,salt);map.put("salt",salt);map.put("password",password);returnmap;}}

⑸ 创建DefinitionRealm.java

packagecom.kejizhentan.realm;importcom.kejizhentan.service.SecurityService;importcom.kejizhentan.service.impl.SecurityServiceImpl;importcom.kejizhentan.utils.HashUtil;importorg.apache.shiro.authc.*;importorg.apache.shiro.authc.credential.HashedCredentialsMatcher;importorg.apache.shiro.authz.AuthorizationInfo;importorg.apache.shiro.authz.SimpleAuthorizationInfo;importorg.apache.shiro.realm.AuthorizingRealm;importorg.apache.shiro.subject.PrincipalCollection;importorg.apache.shiro.util.ByteSource;importjava.util.List;importjava.util.Map;publicclassDefinitionRealmextendsAuthorizingRealm{/***@Description构造函数*/publicDefinitionRealm(){//指定密码匹配方式为sha1HashedCredentialsMatchermatcher=newHashedCredentialsMatcher(HashUtil.SHA1);//指定密码迭代次数matcher.setHashIterations(HashUtil.ITERATIONS);//使用父亲方法使匹配方式生效setCredentialsMatcher(matcher);}/***@Description认证接口*@paramtoken传递登录token*@return*/@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{//从AuthenticationToken中获得登录名称StringloginName=(String)token.getPrincipal();SecurityServicesecurityService=newSecurityServiceImpl();Map<String,String>map=securityService.findPasswordByLoginName(loginName);if(map.isEmpty()){thrownewUnknownAccountException("账户不存在");}Stringsalt=map.get("salt");Stringpassword=map.get("password");//传递账号和密码:参数1:缓存对象,参数2:密文密码,参数三:字节salt,参数4:当前DefinitionRealm名称returnnewSimpleAuthenticationInfo(loginName,password,ByteSource.Util.bytes(salt),getName());}/***@Description授权方法*/@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipals){//拿到用户认证凭证信息StringloginName=(String)principals.getPrimaryPrincipal();//从数据库中查询对应的角色和资源SecurityServicesecurityService=newSecurityServiceImpl();List<String>roles=securityService.findRoleByloginName(loginName);List<String>permissions=securityService.findPermissionByloginName(loginName);//构建资源校验SimpleAuthorizationInfoauthorizationInfo=newSimpleAuthorizationInfo();authorizationInfo.addRoles(roles);authorizationInfo.addStringPermissions(permissions);returnauthorizationInfo;}}

⑹ 编写LoginService

packagecom.kejizhentan.service;importorg.apache.shiro.authc.UsernamePasswordToken;/***@Description:登录服务*/publicinterfaceLoginService{/***@Description登录方法*@paramtoken登录对象*@return*/booleanlogin(UsernamePasswordTokentoken);/***@Description登出方法*/voidlogout();}
packagecom.kejizhentan.service.impl;importcom.kejizhentan.service.LoginService;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.authc.UsernamePasswordToken;importorg.apache.shiro.subject.Subject;/***@Description:登录服务*/publicclassLoginServiceImplimplementsLoginService{@Overridepublicbooleanlogin(UsernamePasswordTokentoken){Subjectsubject=SecurityUtils.getSubject();try{subject.login(token);}catch(Exceptione){returnfalse;}returnsubject.isAuthenticated();}@Overridepublicvoidlogout(){Subjectsubject=SecurityUtils.getSubject();subject.logout();}}

⑺ 编写SecurityService

packagecom.kejizhentan.service;importjava.util.List;importjava.util.Map;/***@Description:权限服务接口*/publicinterfaceSecurityService{/***@Description查找角色按用户登录名*@paramloginName登录名称*@return*/List<String>findRoleByloginName(StringloginName);/***@Description查找资源按用户登录名*@paramloginName登录名称*@return*/List<String>findPermissionByloginName(StringloginName);/***@Description查找密码按用户登录名*@paramloginName登录名称*@return*/Map<String,String>findPasswordByLoginName(StringloginName);}
packagecom.kejizhentan.service.impl;importcom.kejizhentan.service.SecurityService;importcom.kejizhentan.utils.HashUtil;importjava.util.ArrayList;importjava.util.List;importjava.util.Map;/***@Description:权限服务层*/publicclassSecurityServiceImplimplementsSecurityService{@OverridepublicMap<String,String>findPasswordByLoginName(StringloginName){returnHashUtil.entryptPassword("123456");}@OverridepublicList<String>findRoleByloginName(StringloginName){List<String>list=newArrayList<>();if("admin".equals(loginName)){list.add("admin");}list.add("dev");returnlist;}@OverridepublicList<String>findPermissionByloginName(StringloginName){List<String>list=newArrayList<>();if("zhangsan".equals(loginName)){list.add("order:list");list.add("order:add");list.add("order:del");}returnlist;}}

⑻ 添加web层内容

1) LoginServlet
packagecom.kejizhentan.servlet;importcom.kejizhentan.service.LoginService;importcom.kejizhentan.service.impl.LoginServiceImpl;importorg.apache.shiro.authc.UsernamePasswordToken;importjavax.servlet.ServletException;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***@Description:登录方法*/@WebServlet(urlPatterns="/login")publicclassLoginServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{doPost(req,resp);}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{//获取输入的帐号密码Stringusername=req.getParameter("loginName");Stringpassword=req.getParameter("password");//封装用户数据,成为Shiro能认识的token标识UsernamePasswordTokentoken=newUsernamePasswordToken(username,password);LoginServiceloginService=newLoginServiceImpl();//将封装用户信息的token进行验证booleanisLoginSuccess=loginService.login(token);if(!isLoginSuccess){//重定向到未登录成功页面resp.sendRedirect("login.jsp");return;}req.getRequestDispatcher("/home").forward(req,resp);}}
2)HomeServlet
packagecom.kejizhentan.servlet;importjavax.servlet.ServletException;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***@Description:系统home页面*/@WebServlet(urlPatterns="/home")publicclassHomeServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{doPost(req,resp);}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{req.getRequestDispatcher("home.jsp").forward(req,resp);}}
3)OrderAddServlet
packagecom.kejizhentan.servlet;importjavax.servlet.ServletException;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***@Description:添加页码*/@WebServlet(urlPatterns="/order-add")publicclassOrderAddServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{doPost(req,resp);}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{req.getRequestDispatcher("order-add.jsp").forward(req,resp);}}
4)OrderListServlet
packagecom.kejizhentan.servlet;importjavax.servlet.ServletException;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***@Description:订单列表*/@WebServlet(urlPatterns="/order-list")publicclassOrderListServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{doPost(req,resp);}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{req.getRequestDispatcher("order-list.jsp").forward(req,resp);}}
5)LogoutServlet
packagecom.kejizhentan.servlet;importcom.kejizhentan.service.LoginService;importcom.kejizhentan.service.impl.LoginServiceImpl;importjavax.servlet.ServletException;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***@Description:登出*/@WebServlet(urlPatterns="/logout")publicclassLogoutServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{doPost(req,resp);}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{LoginServiceloginService=newLoginServiceImpl();loginService.logout();}}
6)web.xml
<web-appxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"version="3.0"><display-name>shiro-web-project</display-name><!--初始化SecurityManager对象所需要的环境--><context-param><param-name>shiroEnvironmentClass</param-name><param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value></context-param><!--指定Shiro的配置文件的位置--><context-param><param-name>shiroConfigLocations</param-name><param-value>classpath:shiro.ini</param-value></context-param><!--监听服务器启动时,创建shiro的web环境。即加载shiroEnvironmentClass变量指定的IniWebEnvironment类--><listener><listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class></listener><!--shiro的l过滤入口,过滤一切请求--><filter><filter-name>shiroFilter</filter-name><filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class></filter><filter-mapping><filter-name>shiroFilter</filter-name><!--过滤所有请求--><url-pattern>/*</url-pattern></filter-mapping></web-app>

⑼ 添加JSP

login.jsp登录页面

<%@pagecontentType="text/html;charset=UTF-8"language="java"%><html><head><metahttp-equiv="Content-Type"content="text/html; charset=UTF-8"><title>Title</title></head><body><formmethod="post"action="${pageContext.request.contextPath}/login"><table><tr><th>登陆名称</th><td><inputtype="text"name="loginName"></td></tr><tr><th>密码</th><td><inputtype="password"name="password"></td></tr><tr><tdcolspan="2"><inputtype="submit"value="提交"/></td></tr></table></form></body></html>

home.jsp系统页

<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%><%@pagecontentType="text/html;charset=UTF-8"language="java"%><html><head><title></title></head><body><h6><ahref="${pageContext.request.contextPath}/logout">退出</a><ahref="${pageContext.request.contextPath}/order-list">列表</a><ahref="${pageContext.request.contextPath}/order-add">添加</a></h6></body></html>

order-add.jsp订单添加(伪代码)

<%@pagecontentType="text/html;charset=UTF-8"language="java"%><html><head><metahttp-equiv="Content-Type"content="text/html; charset=UTF-8"><title>Title</title></head><body>添加页面</body></html>

order-list.jsp订单列表

<%@pagecontentType="text/html;charset=UTF-8"language="java"%><%--导入jstl标签库--%><%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%><html><head><metahttp-equiv="Content-Type"content="text/html; charset=UTF-8"><title>用户列表jsp页面</title><style>table{border:1pxsolid#000000}tableth{border:1pxsolid#000000}tabletd{border:1pxsolid#000000}</style></head><body><tablecellpadding="0"cellspacing="0"width="80%"><tr><th>编号</th><th>公司名称</th><th>信息来源</th><th>所属行业</th><th>级别</th><th>联系地址</th><th>联系电话</th></tr><tr><td>1</td><td>柯基侦探</td><td>网络营销</td><td>互联网</td><td>普通客户</td><td>北京市东城区</td><td>8888888888</td></tr><tr><td>2</td><td>柯基侦探微信公众号</td><td>j2ee</td><td>互联网</td><td>VIP客户</td><td>北京市东城区</td><td>8888888887</td></tr><tr><td>3</td><td>柯基</td><td>大数据</td><td>互联网</td><td>VIP客户</td><td>北京市东城区</td><td>888888888888</td></tr></table></body></html>

⑽ 测试

1) 启动操作

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
点击apply然后点击OK
在这里插入图片描述

2) 登录过滤

访问http://localhost:8080/platform/home的时候,会被拦截,跳转到登录页面
在这里插入图片描述

在这里插入图片描述

3) 角色过滤

使用

admin

用户登录,密码:

123456

在这里插入图片描述
根据SecurityServiceImpl我们可以知道使用admin账号

在这里插入图片描述
登录成功之后:

在这里插入图片描述

此时点击“列表”,因为当前admin用户是有admin角色

在这里插入图片描述

所以能正常访问“列表”中的内容

在这里插入图片描述
点击“添加”,因为当前admin用户是没有order:add的资源

在这里插入图片描述
在这里插入图片描述

所以回401

在这里插入图片描述

4) 资源过滤

点击“退出”,使用“zhangsan”用户登录,密码为123456 ,点击“添加”
在这里插入图片描述

因为SecurityServiceImpl中为zhangsan用户添加如下的资源

在这里插入图片描述
点击“添加”之后正常访问

在这里插入图片描述
点击“列表”之后,因为“zhangsan”用户没有“admin”角色,所以访问受限

在这里插入图片描述

4. web项目授权

前面我们学习了基于

ini

文件配置方式来完成授权,下面我们来看下其他2种方式的授权

⑴ 基于代码

1) 登录相关

Subject 登录相关方法描述

isAuthenticated() 

返回true 表示已经登录,否则返回false。

2)角色相关

Subject 角色相关方法描述

hasRole(String roleName) 

如果Subject 被分配了指定的角色,返回true ,否则返回false。

hasRoles(List<String> roleNames) 

如果Subject 被分配了所有指定的角色,返回true ,否则返回false。

hasAllRoles(Collection<String>roleNames)

返回一个与方法参数中目录一致的hasRole 结果的集合。有性能的提高如果许多角色需要执行检查(例如,当自定义一个复杂的视图)。

checkRole(String roleName)

没有返回值,如果Subject 被分配了指定的角色,不然的话就抛出AuthorizationException。

checkRoles(Collection<String>roleNames)

没有返回值,如果Subject 被分配了所有的指定的角色,不然的话就抛出AuthorizationException。

checkRoles(String… roleNames)

与上面的checkRoles 方法的效果相同,但允许Java5 的var-args 类型的参数

3) 资源相关

Subject 资源相关方法描述

isPermitted(Permission p)

如果该Subject 被允许执行某动作或访问被权限实例指定的资源,返回true,否则返回false

isPermitted(List<Permission> perms) 

返回一个与方法参数中目录一致的isPermitted 结果的集合。

isPermittedAll(Collection<Permission>perms) 

如果该Subject 被允许所有指定的权限,返回true,否则返回false有性能的提高如果需要执行许多检查(例如,当自定义一个复杂的视图)

isPermitted(String perm)

如果该Subject 被允许执行某动作或访问被字符串权限指定的资源,返回true,否则返回false。

isPermitted(String…perms)

返回一个与方法参数中目录一致的isPermitted 结果的数组。有性能的提高如果许多字符串权限检查需要被执行(例如,当自定义一个复杂的视图)。

isPermittedAll(String…perms)

如果该Subject 被允许所有指定的字符串权限,返回true,否则返回false。

checkPermission(Permission p)

没有返回值,如果Subject 被允许执行某动作或访问被特定的权限实例指定的资源,不然的话就抛出AuthorizationException 异常。

checkPermission(String perm)

没有返回值,如果Subject 被允许执行某动作或访问被特定的字符串权限指定的资源,不然的话就抛出AuthorizationException 异常。

checkPermissions(Collection<Permission> perms)

没有返回值,如果Subject 被允许所有的权限,不然的话就抛出AuthorizationException 异常。有性能的提高如果需要执行许多检查(例如,当自定义一个复杂的视图)

checkPermissions(String… perms)

和上面的checkPermissions 方法效果相同,但是使用的是基于字符串的权限。

4) 案例如下
① 项目结构如下

在这里插入图片描述

② 添加pom.xml依赖
<?xmlversion="1.0"encoding="UTF-8"?><projectxmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.kejizhentan</groupId><artifactId>shiro-web-project</artifactId><version>1.0-SNAPSHOT</version><packaging>war</packaging><name>shiro-web-projectMavenWebapp</name><!--FIXMEchangeittotheproject'swebsite--><url>http://www.example.com</url><dependencies><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1.3</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-core</artifactId><version>1.3.2</version></dependency><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-web</artifactId><version>1.3.2</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.0.1</version><scope>provided</scope></dependency><dependency><groupId>jstl</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><dependency><groupId>taglibs</groupId><artifactId>standard</artifactId><version>1.1.2</version></dependency></dependencies><build><plugins><!--tomcat7插件,命令:mvntomcat7:run-DskipTests--><plugin><groupId>org.apache.tomcat.maven</groupId><artifactId>tomcat7-maven-plugin</artifactId><version>2.2</version><configuration><uriEncoding>utf-8</uriEncoding><port>8080</port><path>/platform</path></configuration></plugin><!--compiler插件,设定JDK版本--><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>3.1</version><configuration><source>8</source><target>8</target><showWarnings>true</showWarnings></configuration></plugin></plugins></build></project>
③ 编写shiro.ini文件
#声明自定义的realm,且为安全管理器指定realms[main]definitionRealm=com.kejizhentan.realm.DefinitionRealmsecurityManager.realms=$definitionRealm#用户退出后跳转指定JSP页面logout.redirectUrl=/login.jsp#若没有登录,则被authc过滤器重定向到login.jsp页面authc.loginUrl=/login.jsp[urls]/login=anon#发送退出请求则用退出过滤器/logout=logout
④ 相关工具类

封装base64和16进制编码解码工具类EncodesUtil.java

packagecom.kejizhentan.utils;importorg.apache.shiro.codec.Base64;importorg.apache.shiro.codec.Hex;/***@Description:封装base64和16进制编码解码工具类*/publicclassEncodesUtil{/***@DescriptionHEX-byte[]--String转换*@paraminput输入数组*@returnString*/publicstaticStringencodeHex(byte[]input){returnHex.encodeToString(input);}/***@DescriptionHEX-String--byte[]转换*@paraminput输入字符串*@returnbyte数组*/publicstaticbyte[]decodeHex(Stringinput){returnHex.decode(input);}/***@DescriptionBase64-byte[]--String转换*@paraminput输入数组*@returnString*/publicstaticStringencodeBase64(byte[]input){returnBase64.encodeToString(input);}/***@DescriptionBase64-String--byte[]转换*@paraminput输入字符串*@returnbyte数组*/publicstaticbyte[]decodeBase64(Stringinput){returnBase64.decode(input);}}

散列算法加密工具类HashUtil.java

packagecom.kejizhentan.utils;importorg.apache.shiro.crypto.SecureRandomNumberGenerator;importorg.apache.shiro.crypto.hash.SimpleHash;importjava.util.HashMap;importjava.util.Map;/***@Auther:kejizhentan*@Date2022/12/3114:20*@Description:散列算法加密工具类*/publicclassHashUtil{publicstaticfinalStringSHA1="SHA-1";publicstaticfinalIntegerITERATIONS=512;/***@Descriptionsha1方法*@paraminput需要散列字符串*@paramsalt盐字符串*@return*/publicstaticStringsha1(Stringinput,Stringsalt){returnnewSimpleHash(SHA1,input,salt,ITERATIONS).toString();}/***@Description随机获得salt字符串*@return*/publicstaticStringgenerateSalt(){SecureRandomNumberGeneratorrandomNumberGenerator=newSecureRandomNumberGenerator();returnrandomNumberGenerator.nextBytes().toHex();}/***@Description生成密码字符密文和salt密文*@param*@return*/publicstaticMap<String,String>entryptPassword(StringpasswordPlain){Map<String,String>map=newHashMap<>();//随机获得salt字符串Stringsalt=generateSalt();//通过加盐的方式生成加密后的密码Stringpassword=sha1(passwordPlain,salt);map.put("salt",salt);map.put("password",password);returnmap;}}
⑤ 创建DefinitionRealm.java
packagecom.kejizhentan.realm;importcom.kejizhentan.service.SecurityService;importcom.kejizhentan.service.impl.SecurityServiceImpl;importcom.kejizhentan.utils.HashUtil;importorg.apache.shiro.authc.*;importorg.apache.shiro.authc.credential.HashedCredentialsMatcher;importorg.apache.shiro.authz.AuthorizationInfo;importorg.apache.shiro.authz.SimpleAuthorizationInfo;importorg.apache.shiro.realm.AuthorizingRealm;importorg.apache.shiro.subject.PrincipalCollection;importorg.apache.shiro.util.ByteSource;importjava.util.List;importjava.util.Map;publicclassDefinitionRealmextendsAuthorizingRealm{/***@Description构造函数*/publicDefinitionRealm(){//指定密码匹配方式为sha1HashedCredentialsMatchermatcher=newHashedCredentialsMatcher(HashUtil.SHA1);//指定密码迭代次数matcher.setHashIterations(HashUtil.ITERATIONS);//使用父亲方法使匹配方式生效setCredentialsMatcher(matcher);}/***@Description认证接口*@paramtoken传递登录token*@return*/@OverrideprotectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokentoken)throwsAuthenticationException{//从AuthenticationToken中获得登录名称StringloginName=(String)token.getPrincipal();SecurityServicesecurityService=newSecurityServiceImpl();Map<String,String>map=securityService.findPasswordByLoginName(loginName);if(map.isEmpty()){thrownewUnknownAccountException("账户不存在");}Stringsalt=map.get("salt");Stringpassword=map.get("password");//传递账号和密码:参数1:缓存对象,参数2:密文密码,参数三:字节salt,参数4:当前DefinitionRealm名称returnnewSimpleAuthenticationInfo(loginName,password,ByteSource.Util.bytes(salt),getName());}/***@Description授权方法*/@OverrideprotectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipals){//拿到用户认证凭证信息StringloginName=(String)principals.getPrimaryPrincipal();//从数据库中查询对应的角色和资源SecurityServicesecurityService=newSecurityServiceImpl();List<String>roles=securityService.findRoleByloginName(loginName);List<String>permissions=securityService.findPermissionByloginName(loginName);//构建资源校验SimpleAuthorizationInfoauthorizationInfo=newSimpleAuthorizationInfo();authorizationInfo.addRoles(roles);authorizationInfo.addStringPermissions(permissions);returnauthorizationInfo;}}
⑥ 编写LoginService
packagecom.kejizhentan.service;importorg.apache.shiro.authc.UsernamePasswordToken;/***@Description:登录服务*/publicinterfaceLoginService{/***@Description登录方法*@paramtoken登录对象*@return*/booleanlogin(UsernamePasswordTokentoken);/***@Description登出方法*/voidlogout();}
packagecom.kejizhentan.service.impl;importcom.kejizhentan.service.LoginService;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.authc.UsernamePasswordToken;importorg.apache.shiro.subject.Subject;/***@Description:登录服务*/publicclassLoginServiceImplimplementsLoginService{@Overridepublicbooleanlogin(UsernamePasswordTokentoken){Subjectsubject=SecurityUtils.getSubject();try{subject.login(token);}catch(Exceptione){returnfalse;}returnsubject.isAuthenticated();}@Overridepublicvoidlogout(){Subjectsubject=SecurityUtils.getSubject();subject.logout();}}
⑦ 编写SecurityService
packagecom.kejizhentan.service;importjava.util.List;importjava.util.Map;/***@Description:权限服务接口*/publicinterfaceSecurityService{/***@Description查找角色按用户登录名*@paramloginName登录名称*@return*/List<String>findRoleByloginName(StringloginName);/***@Description查找资源按用户登录名*@paramloginName登录名称*@return*/List<String>findPermissionByloginName(StringloginName);/***@Description查找密码按用户登录名*@paramloginName登录名称*@return*/Map<String,String>findPasswordByLoginName(StringloginName);}
packagecom.kejizhentan.service.impl;importcom.kejizhentan.service.SecurityService;importcom.kejizhentan.utils.HashUtil;importjava.util.ArrayList;importjava.util.List;importjava.util.Map;/***@Description:权限服务层*/publicclassSecurityServiceImplimplementsSecurityService{@OverridepublicMap<String,String>findPasswordByLoginName(StringloginName){returnHashUtil.entryptPassword("123456");}@OverridepublicList<String>findRoleByloginName(StringloginName){List<String>list=newArrayList<>();if("admin".equals(loginName)){list.add("admin");}list.add("dev");returnlist;}@OverridepublicList<String>findPermissionByloginName(StringloginName){List<String>list=newArrayList<>();if("zhangsan".equals(loginName)){list.add("order:list");list.add("order:add");list.add("order:del");}returnlist;}}
⑧ 添加web层内容
○ LoginServlet
packagecom.kejizhentan.servlet;importcom.kejizhentan.service.LoginService;importcom.kejizhentan.service.impl.LoginServiceImpl;importorg.apache.shiro.authc.UsernamePasswordToken;importjavax.servlet.ServletException;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***@Description:登录方法*/@WebServlet(urlPatterns="/login")publicclassLoginServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{doPost(req,resp);}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{//获取输入的帐号密码Stringusername=req.getParameter("loginName");Stringpassword=req.getParameter("password");//封装用户数据,成为Shiro能认识的token标识UsernamePasswordTokentoken=newUsernamePasswordToken(username,password);LoginServiceloginService=newLoginServiceImpl();//将封装用户信息的token进行验证booleanisLoginSuccess=loginService.login(token);if(!isLoginSuccess){//重定向到未登录成功页面resp.sendRedirect("login.jsp");return;}req.getRequestDispatcher("/home").forward(req,resp);}}
○ 登录相关 HomeServlet
packagecom.kejizhentan.servlet;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.subject.Subject;importjavax.servlet.ServletException;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***@Description:系统home页面*/@WebServlet(urlPatterns="/home")publicclassHomeServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{doPost(req,resp);}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{//通过subjectd对象去判断是否登录Subjectsubject=SecurityUtils.getSubject();booleanflag=subject.isAuthenticated();if(flag){resp.sendRedirect("home.jsp");}else{req.getRequestDispatcher("/login").forward(req,resp);}}}

访问http://localhost:8080/platform/home 进行debug

在这里插入图片描述
此时我们通过subject.isAuthenticated()判断是否登录,如果登录则重定向到home.jsp,如果没有登录则转发到/login对应的servlet

○ 资源相关OrderAddServlet
packagecom.kejizhentan.servlet;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.subject.Subject;importjavax.servlet.ServletException;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***@Description:添加页码*/@WebServlet(urlPatterns="/order-add")publicclassOrderAddServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{doPost(req,resp);}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{Subjectsubject=SecurityUtils.getSubject();//判断是否有对应资源booleanflag=subject.isPermitted("order:add");if(flag){req.getRequestDispatcher("order-add.jsp").forward(req,resp);}else{req.getRequestDispatcher("/login").forward(req,resp);}}}

访问http://localhost:8080/platform/order-add

在这里插入图片描述
因为此时我未登录,也就是说当前没有order:add资源,通过 subject.isPermitted(“order:add”)返回false

○ 角色相关 OrderListServlet
packagecom.kejizhentan.servlet;importorg.apache.shiro.SecurityUtils;importorg.apache.shiro.subject.Subject;importjavax.servlet.ServletException;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***@Description:订单列表*/@WebServlet(urlPatterns="/order-list")publicclassOrderListServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{doPost(req,resp);}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{Subjectsubject=SecurityUtils.getSubject();//判断当前角色booleanflag=subject.hasRole("admin");if(flag){req.getRequestDispatcher("order-list.jsp").forward(req,resp);}else{req.getRequestDispatcher("/login").forward(req,resp);}}}

访问 http://localhost:8080/platform/order-list

在这里插入图片描述
因为此时我未登录,也就是说当前没有admin角色,这是通过subject.hasRole(“admin”)返回未false

○ LogoutServlet
packagecom.kejizhentan.servlet;importcom.kejizhentan.service.LoginService;importcom.kejizhentan.service.impl.LoginServiceImpl;importjavax.servlet.ServletException;importjavax.servlet.annotation.WebServlet;importjavax.servlet.http.HttpServlet;importjavax.servlet.http.HttpServletRequest;importjavax.servlet.http.HttpServletResponse;importjava.io.IOException;/***@Description:登出*/@WebServlet(urlPatterns="/logout")publicclassLogoutServletextendsHttpServlet{@OverrideprotectedvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{doPost(req,resp);}@OverrideprotectedvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException{LoginServiceloginService=newLoginServiceImpl();loginService.logout();}}
○ web.xml
<web-appxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://java.sun.com/xml/ns/javaee"xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"version="3.0"><display-name>shiro-web-project</display-name><!--初始化SecurityManager对象所需要的环境--><context-param><param-name>shiroEnvironmentClass</param-name><param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value></context-param><!--指定Shiro的配置文件的位置--><context-param><param-name>shiroConfigLocations</param-name><param-value>classpath:shiro.ini</param-value></context-param><!--监听服务器启动时,创建shiro的web环境。即加载shiroEnvironmentClass变量指定的IniWebEnvironment类--><listener><listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class></listener><!--shiro的l过滤入口,过滤一切请求--><filter><filter-name>shiroFilter</filter-name><filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class></filter><filter-mapping><filter-name>shiroFilter</filter-name><!--过滤所有请求--><url-pattern>/*</url-pattern></filter-mapping></web-app>
○ 添加JSP

login.jsp登录页面

<%@pagecontentType="text/html;charset=UTF-8"language="java"%><html><head><metahttp-equiv="Content-Type"content="text/html; charset=UTF-8"><title>Title</title></head><body><formmethod="post"action="${pageContext.request.contextPath}/login"><table><tr><th>登陆名称</th><td><inputtype="text"name="loginName"></td></tr><tr><th>密码</th><td><inputtype="password"name="password"></td></tr><tr><tdcolspan="2"><inputtype="submit"value="提交"/></td></tr></table></form></body></html>

home.jsp系统页

<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%><%@pagecontentType="text/html;charset=UTF-8"language="java"%><html><head><title></title></head><body><h6><ahref="${pageContext.request.contextPath}/logout">退出</a><ahref="${pageContext.request.contextPath}/order-list">列表</a><ahref="${pageContext.request.contextPath}/order-add">添加</a></h6></body></html>

order-add.jsp订单添加(伪代码)

<%@pagecontentType="text/html;charset=UTF-8"language="java"%><html><head><metahttp-equiv="Content-Type"content="text/html; charset=UTF-8"><title>Title</title></head><body>添加页面</body></html>

order-list.jsp订单列表

<%@pagecontentType="text/html;charset=UTF-8"language="java"%><%--导入jstl标签库--%><%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%><html><head><metahttp-equiv="Content-Type"content="text/html; charset=UTF-8"><title>用户列表jsp页面</title><style>table{border:1pxsolid#000000}tableth{border:1pxsolid#000000}tabletd{border:1pxsolid#000000}</style></head><body><tablecellpadding="0"cellspacing="0"width="80%"><tr><th>编号</th><th>公司名称</th><th>信息来源</th><th>所属行业</th><th>级别</th><th>联系地址</th><th>联系电话</th></tr><tr><td>1</td><td>柯基侦探</td><td>网络营销</td><td>互联网</td><td>普通客户</td><td>北京市东城区</td><td>8888888888</td></tr><tr><td>2</td><td>柯基侦探微信公众号</td><td>j2ee</td><td>互联网</td><td>VIP客户</td><td>北京市东城区</td><td>8888888887</td></tr><tr><td>3</td><td>柯基</td><td>大数据</td><td>互联网</td><td>VIP客户</td><td>北京市东城区</td><td>888888888888</td></tr></table></body></html>

⑵ 基于Jsp标签

1) 使用方式

Shiro提供了一套JSP标签库来实现页面级的授权控制, 在使用Shiro标签库前,首先需要在JSP引入shiro标签:

<%@taglibprefix="shiro"uri="http://shiro.apache.org/tags"%>
2) 相关标签

标签说明

< shiro:guest >

验证当前用户是否为“访客”,即未认证(包含未记住)的用户

< shiro:user > 

认证通过或已记住的用户

< shiro:authenticated > 

已认证通过的用户。不包含已记住的用户,这是与user标签的区别所在

< shiro:notAuthenticated >

未认证通过用户。与guest标签的区别是,该标签包含已记住用户

< shiro:principal />

输出当前用户信息,通常为登录帐号信息

< shiro:hasRole name="角色">

验证当前用户是否属于该角色

< shiro:lacksRole name="角色">

与hasRole标签逻辑相反,当用户不属于该角色时验证通过

< shiro:hasAnyRoles name="a,b">

验证当前用户是否属于以下任意一个角色

<shiro:hasPermission name=“资源”>

验证当前用户是否拥有制定权限

<shiro:lacksPermission name="资源">

与permission标签逻辑相反,当前用户没有制定权限时,验证通过

3) 案例
① 修改home.jsp
<%@taglibprefix="c"uri="http://java.sun.com/jsp/jstl/core"%><%@taglibprefix="shiro"uri="http://shiro.apache.org/tags"%><%@pagecontentType="text/html;charset=UTF-8"language="java"%><html><head><title></title></head><body><h6><ahref="${pageContext.request.contextPath}/logout">退出</a><shiro:hasRolename="admin"><ahref="${pageContext.request.contextPath}/order-list">列表</a></shiro:hasRole><shiro:hasPermissionname="order:add"><ahref="${pageContext.request.contextPath}/order-add">添加</a></shiro:hasPermission></h6></body></html>
② 测试

访问http://localhost:8080/platform/login

使用admin/123456登录

在这里插入图片描述
这个时候我们只能看见“列表”,看不见“添加”,点击“退出”

使用zhangsan/123456登录
在这里插入图片描述
这个时候我们只能看见“添加”,看不见“列表”,点击“退出”

需要注意的是,这里只是页面是否显示内容,不能防止盗链的发生


本文转载自: https://blog.csdn.net/kejizhentan/article/details/128449538
版权归原作者 kejizhentan 所有, 如有侵权,请联系我们删除。

“二十三、shiro安全框架详解(一)”的评论:

还没有评论