架构探险笔记12-安全控制框架Shiro
什么是Shiro
Shiro是Apache组织下的一款轻量级Java安全框架。Spring Security相对来说比较臃肿。
Shiro提供的服务

1.Authentication(认证)
2.Authorization(授权)
3.Session Management(会话管理)
4.Cryptography(加密)
5.Web Integration(web集成)
6.Integrations(集成)
首先,她提供了Authentication(认证)服务,也就是说,通过她可以完成身份认证,让她去判断用户是否为真实的会员。
其次,她还提供了Authorization(授权)服务,其实就是访问控制服务,也就是让她来识别用户是否可以做某件事情,毕竟不同的用户是拥有不同的权限的。
还提供了Session Management(会话管理)服务。这个就厉害了,这并不是用户熟知的HTTP Session,而是一个独立的Session管理框架,不管是否为Web应用,都可以用这套框架。
最后,还提供了Crytography(加密)服务,封装了许多密码学算法,琳琅满目,应有尽有。
除了以上4个基本服务,她也提供了很好的系统集成方案,用户可以轻松将其运用到Web应用中,可能这也是用户最关心的;此外,还可以集成第三方框架,例如:Spring、Guice、CAS等。
Hello Shiro
Maven,在pom.xml加入坐标
<!--SLFJ-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.4</version>
</dependency>
<!--Shiro核心包,一定要用到的-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.2.3</version>
</dependency>
Shiro依赖于SLFJ日志框架,而SLFJ只是一个接口,并没有提供具体的实现。可以选择Log4J作为它的实现,正好SLFJ也提供了一个slf4j-log4j12的Artifact,所以这里就可以用上了。
Maven示意图如下:

使用Log4J,就应该在classpath下提供一个log4j.properties文件:
log4j.rootLogger=ERROR,console,file log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%-5p %c(%L) -%m%n
log4j.logger.org.autumn=DEBUG
通过上卖弄的配置将日志输出到控制台上,并配置了日志输出格式。
同样,既然使用了Shiro,那么久应该在classpath下提供一个shiro.ini文件:
[users]
shiro = 1995
我们配置一个用户名为shiro,密码为1995的用户。这里仅为演示,在实际项目中肯定不会把用户信息定义在配置文件中。
测试一下Shiro的认证服务功能,用main方法测试
package org.autumn; import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory; /**
* Created by Administrator on 2019/1/28.
*/
public class HelloShiro {
private static final Logger LOGGER = LoggerFactory.getLogger(HelloShiro.class); public static void main(String[] args) {
//初始化SecurityManager
Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
SecurityManager securityManager = factory.getInstance();
SecurityUtils.setSecurityManager(securityManager); //获取当前用户
Subject subject = SecurityUtils.getSubject();
//登录
UsernamePasswordToken token = new UsernamePasswordToken("shiro","19950");
try {
subject.login(token);
}catch (AuthenticationException ae){
LOGGER.info("登录失败");
return ;
}
LOGGER.info("登录成功!Hello "+subject.getPrincipal());
subject.logout();
}
}
结果

解析:
(1)需要读取classpath下的shiro.ini配置文件,并通过工厂类创建SecurityManager对象,最终将其放入SecurityUtils中,供Shiro框架随时获取。
(2)同样通过SecurityUtils类获取Subject对象,其实就是当前用户,只不过在Shiro的世界里优雅地将其称为Subject(主体);
(3)首先使用一个Username与Password来创建一个UsernamePasswordToken对象,然后通过这个Token对象调用Subject对象的login方法,让Shiro进行用户身份认证。
(4)当登录失败时,可以使用AuthenticationException来捕获这个异常;当登录成功时,可以调用Subject对象的getPrincipal方法来获取Username,此时Shiro已经创建了一个Session。
(5)最后还是通过Subject对象的logout方法来注销本次Session。
通过Subject调用SecurityManager,通过SecurityManager调用Realm。这个Realm感觉有点生僻,其实就是提供用户信息的数据源。上面的Shiro中叫IniRealm。除此以外,Shiro还提供了其他几种Realm:PropertiesRealm、JdbcRealm、JndiLdapRealm、ActiveDirectoryReam等;当然也可以定制Realm来满足业务需求。
不难发现,SecurityManager才是Shiro的真正的核心,只需通过Subject就可以操作SecurityManager,尤其是在Web应用中。
在Web开发中使用Shiro
我们可以直接在Web应用中使用Shiro官方提供的Web模块--- shiro-web。
只需要在pom.xml中增加如下配置:
<!--支持Web需要引入的Shiro包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.2.3</version>
</dependency>
在web.xml中加入如下配置
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
version="3.1"> <context-param>
<param-name>shiroEnvironmentClass</param-name>
<param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
</context-param>
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>classpath:shiro.ini</param-value>
</context-param> <!--监听器,用来读取上面的ini配置文件-->
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<!--过滤器-->
<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>
实际上就是通过EnvironmentLoaderListener这个监听器来初始化SecurityManager,并通过ShiroFilter来完成认证与授权。
可以使用以下数据结构来存放用户机器权限的相关数据,这就是RBAC模型

然后通过Shiro的JdbcReam来进行认证与授权,只需要在shiro.ini中做如下配置:
[main]
authc.loginUrl = /login #配置跳转的的URL地址,通过Servlet映射后可定位到login页面 ds = org.apache.commons.dbcp.BasicDataSource #JDBC数据库配置
ds.driverClassName = com.mysql.jdbc.Driver
ds.url=jdbc:mysql://localhost:3306/bookkeeping
ds.username=root
ds.password=root jdbcRealm = org.apache.shiro.realm.jdbc.JdbcRealm #realm为jdbc
#将上面配置的jdbc赋给realm
jdbcRealm.dataSource = $ds
#提供身份认证,即通过username查询password
jdbcRealm.authenticationQuery = SELECT pwd FROM users where userCode = ?
#基于角色的授权验证(粗粒度),通过username查询role_name
jdbcRealm.userRolesQuery = SELECT sys_role.roleName FROM sys_role,sys_user_role,users WHERE sys_role.roleCode = sys_user_role.roleCode AND sys_user_role.userCode = users.id AND users.userCode = ?
#基于权限的授权验证(粗粒度),通过role_name查询model_name,此时需要开启permissionsLookupEnabled开关,默认是关上的
jdbcRealm.permissionsQuery = SELECT sys_module.moduleName FROM sys_module, sys_role, sys_role_module where sys_module.moduleCode = sys_role_module.moduleCode AND sys_role_module.roleCode = sys_role.roleCode and sys_role.roleName = ? jdbcRealm.permissionsLookupEnabled = true
securityManager.realms = $jdbcRealm [urls]
/=anon #对于"/"请求(首页)可以匿名访问;
/space/**=authc #对于以"/space/"开头的请求,均由authc过滤器处理,也就是完成身份认证操作。
还要补充说明的是,在permission表中存放了所有的权限名,实际上是一个权限字符串,推荐使用“资源:操作”这种格式来命名,例如:product:view(查看产品权限)、product:edit(产品编辑权限)、product:delete(产品删除权限)等。
| 过滤器名称 | 功能 | 配置项(及默认值) |
| anon | 确保只有未登录(匿名)的用户发送的请求才能通过 | - |
| authc | 去报只有已认证的用户发送的请求才能通过(若未认证,则跳转到登录页面) |
authc.loginUrl = /login.jsp authc.successUrl = / authc.usernameParam = username authc.passwordParam = password authc.rememerMeParam = rememberMe authc.failureKeyAttribute = shiroLoginFailure |
| authcBasic | 提供BasicHttp认证功能(在浏览器中弹出一个登录对话框) | authcBasic.applicationName = application |
| logout | 接收结束会话的请求 | logout.redirectUrl = / |
| noSessionCreation | 提供No Session解决方案(若有Session就会报错) | - |
| perms | 确保只有拥有特定权限的用户发送的请求才能通过 | - |
| port | 确保只有特定端口的请求才能通过 | port = 80 |
| rest | 提供REST解决方案(根据REST URL计算权限字符串) | - |
| roles | 确保只有拥有特定角色的用户发送的请求才能通过 | - |
| ssl | 确保只有HTTPS的请求才能通过 | - |
| user | 确保只有已登录的用户发送的请求才能通过(包括:已认证或已记住) | - |
在index.jsp中判断该用户是游客还是已登录的用户:
<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
<html>
<head>
<title>首页</title>
</head>
<body>
<shiro:guest>
<p>身份:游客</p>
<a href="<c:url value="/login"/>">登录</a>
<a href="<c:url value="/register"/>">注册</a>
</shiro:guest> <shiro:user>
<p>身份:<shiro:principal/></p>
<a href="<c:url value="/space"/>">空间</a>
<a href="<c:url value="/logout"/>">退出</a>
</shiro:user>
</body>
</html>
需要使用Shiro提供的JSP标签:
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
随后就可以使用shiro标签的相关功能了。
| 标签 | 功能 |
| <shiro:guest></shiro:guest> | 判断当前用户是否为游客 |
| <shiro:user></shiro:user> |
判断当前用户是否已登录 (包括认证或已认证) |
| <shiro:authecticated></shiro:authecticated> | 判断当前用户是否已认证通过(不包括已记住) |
| <shiro:notAuthecticated></shiro:notAuthecticated> | 判断当前用户是否未认证通过 |
| <shiro:hasRole name="foo"></shiro:hasRole> | 判断当前用户是否具有某种角色 |
| <shiro:lacksRole name="foo"></shiro:lacksRole> | 判断当前用户是否缺少某种角色 |
| <shiro:hasAnyRoles name="foo,bar"></shiro:hasAnyRoles> | 判断当前用户是否具有任意一种角色(foo或bar) |
| <shiro:hasPermission name="foo"></shiro:hasPermission> | 判断当前用户是否具有某种权限 |
| <shiro:lacksPermission name="foo"></shiro:lacksPermission> | 判断当前用户是否缺少某种权限 |
| <shiro:principal/> | 获取当前用户的相关信息,例如:用户名 |
依葫芦画瓢,可以实现其他需要的shiro标签
除了JSP标签,Shiro还提供了Java注解,只需将这些注解定义在想要安全控制的方法上即可。
| 注解 | 功能 |
| RequiresGuest | 确保被标注的方法可被匿名用户访问 |
| RequiresUser | 确保被标注的方法只能被已登录的用户访问(包括:已认证或已记住) |
| RequiresAuthentication | 确保被标注的方法只能被已认证的用户访问(不包括已记住) |
| RequiresRoles | 确保被标注的方法仅被指定角色的用户访问 |
| RequiresPermissions | 确保被标注的方法仅被指定权限的用户访问 |
注意:当使用了RequiresRoles与RequiresPermissions注解,也就意味着把代码写死了;这样如果数据库里的Role或Permission更改了,代码也就无效了。这或许是Shiro的一点不完美的地方,不过瑕不掩瑜。
每次认证与授权都需要与数据库打交道,这会对性能产生一定的开销:关于这一点Shiro也为我们先到了,只需在main片段中增加如下配置即可:
cacheManager=org.apache.shiro.cache.MemoryConstrainedCacheManager
securityManager.cacheManager=$cacheManager
此时Shiro就会在内存中使用一个Map来缓存查询结果,从而减少了数据库的操作次数,提高了查询方面的性能。Shiro也提供了E和Cache的扩展,为缓存提供了更加“高大上”的解决方案。
目前在数据库里保存的是明文的密码,这样不太安全,如何将其加密呢?Shiro同样提供了非常优雅的解决方案,只需在[amin]片段下增加如下配置即可:
passwordMatcher = org.apache.shiro.authc.credential.PasswordMatcher
jdbcRealm.credentialsMatcher=$passwordMatcher
其实,Shiro对密码的加密与解密提供了非常强大的支持,这里仅仅是一种最简单的情况。需要确保在创建密码的时候使用对应的加密算法,Shiro给我们提供了PasswordService接口,可以这样来使用:
PasswordService passwordService = new DefaultPasswordService();
String encryptedPassword = passwordService.encryptPassword(plantextPassword);
只需将这个encryptedPassword存入数据库即可。
其实关于Shiro的用法还有很多,我们可以从Shrio的官网上学到更多的特性,包括集成EhCache、集成Spring、集成CAS等。
最后结果如下

架构探险笔记12-安全控制框架Shiro的更多相关文章
- 架构探险笔记4-使框架具备AOP特性(上)
对方法进行性能监控,在方法调用时统计出方法执行时间. 原始做法:在内个方法的开头获取系统时间,然后在方法的结尾获取时间,最后把前后台两次分别获取的系统时间做一个减法,即可获取方法执行所消耗的总时间. ...
- 架构探险笔记5-使框架具备AOP特性(下)
开发AOP框架 借鉴SpringAOP的风格,写一个基于切面注解的AOP框架.在进行下面的步骤之前,确保已经掌了动态代理技术. 定义切面注解 /** * 切面注解 */ @Target(Element ...
- 架构探险笔记3-搭建轻量级Java web框架
MVC(Model-View-Controller,模型-视图-控制器)是一种常见的设计模式,可以使用这个模式将应用程序进行解耦. 上一章我们使用Servlet来充当MVC模式中的Controller ...
- 架构探险笔记11-与Servlet API解耦
Servlet API解耦 为什么需要与Servlet API解耦 目前在Controller中是无法调用Servlet API的,因为无法获取Request与Response这类对象,我们必须在Di ...
- 架构探险笔记6-ThreadLocal简介
什么是ThreadLocal? ThreadLocal直译为“线程本地”或“本地线程”,如果真的这么认为,那就错了!其实它就是一个容器,用于存放线程的局部变量,应该叫ThreadLocalVariab ...
- 读《架构探险——从零开始写Java Web框架》
内容提要 <架构探险--从零开始写Java Web框架>首先从一个简单的 Web 应用开始,让读者学会如何使用 IDEA.Maven.Git 等开发工具搭建 Java Web 应用:接着通 ...
- Java安全(权限)框架 - Shiro 功能讲解 架构分析
Java安全(权限)框架 - Shiro 功能讲解 架构分析 作者 : Stanley 罗昊 [转载请注明出处和署名,谢谢!] 简述Shiro Shiro出自公司Apache(阿帕奇),是java的一 ...
- 【EatBook】-NO.3.EatBook.3.JavaArchitecture.2.001-《架构探险:从零开始写Java Web框架》-
1.0.0 Summary Tittle:[EatBook]-NO.3.EatBook.3.JavaArchitecture.2.001-<架构探险:从零开始写Java Web框架>- S ...
- 2017.12.12 架构探险-第一章-从一个简单的web应用开始
参考来自:<架构探险>黄勇 著 1 使用IDEA搭建MAVEN项目 1.1 搭建java项目 (1)创建java项目 为了整个书籍的项目,我创建了一个工程,在这个工程里创建了每个章节的mo ...
随机推荐
- Python3 tkinter基础 Entry get 点击按钮 将输入框中文字输出到控制台
Python : 3.7.0 OS : Ubuntu 18.04.1 LTS IDE : PyCharm 2018.2.4 Conda ...
- P2473 [SCOI2008]奖励关
思路 n<=15,所以状压 因为求期望,所以采用逆推的思路,设\(f[i][S]\)表示1~i的宝物获得情况是S,i+1~k的期望 状态转移是当k可以取时,\(f[i][S]+=max(f[i+ ...
- Latex citation using natbib and footnotesize
References: Natbib bibliography styles How to change font size for bibliography? Latex citation usin ...
- win10 右键菜单很慢的解决方式
本来想用 win7 的,不想花很多时间折腾了.现在新电脑主板硬盘CPU都在排挤 win7 ,真是可怜呀.正题: 新电脑的性能应该还算不错的, 18 年跑分 29w 以上,但在图标上面右键却都要转圈几秒 ...
- 【OJ】 : 容斥原理计算出 1< =n < 1e9 中是2,3,5倍数的整数的数量
最近ACM时遇到个题,题意如下. 问题描述: 有个1到n的数列,数一下其中能够被 2, 的时候有 ,,,,.这5个数满足条件,所以我们应该输出 5 . 输入 多组输入到文件尾,每组输入一个 n (n ...
- Java中泛型Class<T>、T与Class<?>、 Object类和Class类、 object.getClass()和Object.class
一.区别 单独的T 代表一个类型(表现形式是一个类名而已) ,而 Class<T>代表这个类型所对应的类(又可以称做类实例.类类型.字节码文件), Class<?>表示类型不确 ...
- _itemmod_rate_stone
`entry`几率宝石物品ID `type` 1--合成对应_itemmod_exchange_item 2--强化对应_itemmod_exchange_item 3-附魔(除itemMask = ...
- CIKM 18 | 蚂蚁金服论文:基于异构图神经网络的恶意账户识别方法
小蚂蚁说: ACM CIKM 2018 全称是 The 27th ACM International Conference on Information and Knowledge Managemen ...
- 在使用Java8并行流时的问题分析
最近在使用Java8的并行流时遇到了坑,线上排查问题时花了较多时间,分享出来与大家一起学习与自查 // 此处为坑 List<Java8Demo> copy = Lists.newArray ...
- P1330 封锁阳光大学
传送门 思路: 依题意可知,在图中的每一条边有且只有一个点被选中(阻止老曹刷街),那么就可以对其采取二分图染色,一条边中:一个点为黑色,另一个点为白色:如果一条边中的两个端点的颜色相同,则说明无解,输 ...