SpringBoot集成Apache Shiro
笔者因为项目转型的原因,对Apache Shiro安全框架做了一点研究工作,故想写点东西以便将来查阅。之所以选择Shiro也是看了很多人的推荐,号称功能丰富强大,而且易于使用。实践下来的确如大多数人所说简约优美,小巧精悍。
介绍demo项目前,简单说明一下Shiro框架的特性。
1. Apache Shiro Features
从上图可以看出Shiro具备应用程序安全框架的四大基石”:身份验证、授权、会话管理和密码。
Authentication:有时被称为‘登录’,这是需要明确用户是谁
Authorization:访问控制,即确定‘谁’对‘什么’有访问权限。
Session Management:管理特定用户的会话,即使在非web或EJB应用程序中也是如此。
Cryptography:使用加密算法保持数据安全,但易于使用。
在不同的应用程序环境中,还有更多的特性来支持和增强这些关注点,特别是:
Web Support:Shiro的Web支持API帮助轻松地保护web应用程序。
Caching:缓存是ApacheShiro的API中的第一等公民,以确保安全操作同时保持快速和高效。
Concurrency:ApacheShiro支持具有并发特性的多线程应用程序。
Testing:提供测试支持,以帮助编写单元和集成测试,并确保代码如预期的安全。
Run as:允许用户假定另一个用户的身份(如果允许的话)的特性,有时在管理场景中很有用。
Remember Me:记住用户在会话中的身份,这样他们就只需要在强制的情况下输入口令登录。
2. High-Level Overview
Shiro的体系结构有三个主要概念:Subject、SecurityManager和Realms。下图展现了它的运行原理,

主题:主题本质上是当前正在执行的用户。虽然“用户”这个词通常意味着一个人,一个主题可以是一个人,但它也可以代表一个第三方服务、守护进程帐户、cron作业或任何类似的东西-基本上是任何当前与软件交互的东西。Subject实例都绑定到(并且需要)一个SecurityManager。当与主题交互时,这些交互转化为与SecurityManager的特定主题交互。
SecurityManager:SecurityManager是Shiro体系结构的核心,它将其内部安全组件协调在一起形成一个对象图。然而,一旦为应用程序配置了SecurityManager及其内部对象图,它通常会被单独使用,应用程序开发人员将几乎所有的时间都花在Subject API上。当与一个主题交互时,实际上是幕后的SecurityManager为任何主题安全操作做了所有繁重的工作。
领域:领域充当Shiro和应用程序安全数据之间的“桥梁”或“连接器”。当涉及到实际与用户帐户等安全相关的数据交互以执行身份验证(登录)和授权(访问控制)时,Shiro从一个或多个为应用程序配置的领域中查找数据。从这个意义上说,领域本质上是一个特定于安全的DAO:它封装数据源的连接细节,并根据需要将相关数据提供给Shiro。配置Shiro时,必须指定至少一个用于身份验证和/或授权的域。SecurityManager可以配置多个Realm,但至少需要一个。Shiro提供了开箱即用的领域,以连接到许多安全数据源(也称为目录),如LDAP、关系数据库(JDBC)、INI和属性文件等文本配置源。
3. Detailed Architecture

4. 过滤器
当 Shiro 被运用到 web 项目时,Shiro 会自动创建一些默认的过滤器对客户端请求进行过滤。以下是 Shiro 内置过滤器:
|
过滤器简称 |
对应的 Java 类 |
|
anon |
org.apache.shiro.web.filter.authc.AnonymousFilter |
|
authc |
org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
|
authcBasic |
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
|
perms |
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
|
port |
org.apache.shiro.web.filter.authz.PortFilter |
|
rest |
org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
|
roles |
org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
|
ssl |
org.apache.shiro.web.filter.authz.SslFilter |
|
user |
org.apache.shiro.web.filter.authc.UserFilter |
|
logout |
org.apache.shiro.web.filter.authc.LogoutFilter |
项目中常用的解释一下,
/test/**=anon, 所有url 可以匿名访问
/test/**=authc, url需要认证才能访问
/test/**=perms[user:add], url需要认证用户拥有 user:add 权限才能访问
/test/**=roles[admin], url需要认证用户拥有 admin 角色才能访问
/test/**=user, url 需要认证或通过记住我认证才能访问
5. DEMO
开发工具为Eclipse+Maven,新建Springboot项目,版本号为1.5.14
模板引擎使用Thymeleaf
为了突出shiro,数据访问层略过,服务层模拟查询数据,Realm里面硬编码权限和角色信息简化代码。
整个系统的核心在于两个class(MyShiroRealm + ShiroConfiguration), 项目结构如下,

5.1 在pom.xml里面添加好shiro-core, shiro-spring,
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.0</version>
</dependency>
<!-- shiro权限控制框架 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
5.2 显示层
模板引擎Thymeleaf,故application配置文件如下:
spring.thymeleaf.cache=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html
5.3 显示层静态页面如下:
403.html
add.html
delete.html
details.html
edit.html
index.html
login.html
logout.html
5.4 几乎所有静态页面就是一个空壳,大体如add.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"></meta>
<title>Add Page</title>
</head>
<body>
<h1>Add Page</h1>
</body>
</html>
add.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"></meta>
<title>Login Page</title>
<style type="text/css">
table {
width: 360px;
min-height: 25px;
line-height: 25px;
text-align: center;
border-color: #b6ff00;
border-collapse: collapse; }
</style>
</head>
<body>
<div>
<form action="/home/check" method="post">
<table border="1">
<tr>
<th>User Name</th>
<th><input type="text" name="name" /></th>
</tr>
<tr>
<td>Password</td>
<td><input type="password" name="password" /></td>
</tr>
<tr>
<td><input type="submit" value="Submit" /></td>
<td></td>
</tr>
</table>
</form>
</div>
</body>
</html>
login.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8"></meta>
<title>Index Page</title>
<style type="text/css">
p {
font-family: Times, TimesNR, 'New Century Schoolbook', Georgia,
'New York', serif;
font-size: 20px;
}
</style> </head>
<body>
<h1>This is index page.</h1>
<p>
Customer Name: <span th:text="${name}"></span> --- Role: <span
th:text="${role}"></span>
</p>
<table border="1">
<tr>
<th>角色</th>
<th>权限</th>
</tr>
<tr>
<td>admin</td>
<td>增加,删除,编辑,查看</td>
</tr>
<tr>
<td>operator</td>
<td>编辑,查看</td>
</tr>
<tr>
<td>viewer</td>
<td>查看</td>
</tr>
</table>
<ul>
<li><a th:href="@{/customer/index}">Index</a></li>
<li><a th:href="@{/customer/details}">Details</a></li>
<li><a th:href="@{/customer/add}">Add</a></li>
<li><a th:href="@{/customer/edit}">Edit</a></li>
<li><a th:href="@{/customer/delete}">Delete</a></li>
</ul>
</body>
</html>
index.html
5.5 Model
public class Customer implements Serializable {
private static final long serialVersionUID = 7429292944316962328L;
private String name;
private String password;
private String role;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public Customer(String name, String password, String role) {
super();
this.name = name;
this.password = password;
this.role = role;
}
@Override
public String toString() {
return "Name : --- " + name + ", Password : --- " + password + ", Role : *** " + role;
}
}
Customer.java
5.6 Service
@Service
public class CustomerService {
public Customer findByName(String name) {
// 模拟查询数据库
// tom is admin, alice is operator, lucy is viewer
if (name.equals("alice")) {
return new Customer(name, "123", "operator");
}
return null;
}
}
CustomerService.java
5.7 Controller
@Controller
@RequestMapping("customer")
public class CustomerController {
@RequestMapping("/index")
@RequiresPermissions("customer:index")//权限管理;
public String index(Model model) {
Subject subject = SecurityUtils.getSubject();
Customer customer = (Customer)subject.getPrincipal();
if(customer != null) {
model.addAttribute("name", customer.getName());
model.addAttribute("role", customer.getRole());
}
return "index";
} @RequestMapping("/details")
@RequiresPermissions("customer:details")//权限管理;
public String details() {
return "details";
} @RequestMapping("/add")
@RequiresRoles("admin")
public String add() {
return "add";
} @RequestMapping("/edit")
@RequiresPermissions("customer:edit")//权限管理;
public String edit() {
return "edit";
} @RequestMapping("/delete")
@RequiresPermissions("customer:delete")//权限管理;
public String delete() {
return "delete";
}
}
CustomerController.java
@Controller
@RequestMapping("home")
public class HomeController {
@RequestMapping("/login")
public String login() {
return "login";
} @RequestMapping("/check")
public String check(HttpServletRequest request) throws Exception {
System.out.println("HomeController.check()"); String name = request.getParameter("name");
String password = request.getParameter("password");
UsernamePasswordToken token = new UsernamePasswordToken(name, password);
Subject subject = SecurityUtils.getSubject(); try {
subject.login(token);
} catch (Exception ex) {
System.out.println(ex.getMessage());
System.out.println(ex.getStackTrace());
return "login";
} return "redirect:/customer/index";
}
}
HomeController.java
5.8 最重要的两个类如下:
package com.example.demo.config; import java.util.LinkedHashMap;
import java.util.Map; import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; @Configuration
public class ShiroConfiguration { @Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
System.out.println("ShiroConfiguration.shirFilter()");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 过滤器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/home/**", "anon");
filterChainDefinitionMap.put("/test/**", "anon");
filterChainDefinitionMap.put("/customer/**", "authc");
shiroFilterFactoryBean.setLoginUrl("/home/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/customer/index"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
} @Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
} @Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
} // 开启Shiro AOP注解支持.
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
System.out.println("OPNE AOP......");
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
} @Bean
public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
} // 管理shiro生命周期
@Bean
public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
}
ShiroConfiguration.java
因为在Shiro中,最终是通过Realm来获取应用程序中的用户、角色及权限信息的。通常情况下,在Realm中会直接从我们的数据源中获取Shiro需要的验证信息。可以说,Realm是专用于安全框架的DAO.
Shiro的认证过程最终会交由Realm执行,这时会调用Realm的getAuthenticationInfo(token)方法。
该方法主要执行以下操作:
1) 根据口令信息检查标识主体(帐户标识信息)
2) 在数据源中查找相应的帐户信息
3) 确保令牌提供的凭据与存储在数据存储中的凭据匹配
4) 如果凭证匹配,则返回一个AuthenticationInfo实例,该实例将帐户数据封装为Shiro理解的格式
5) 如果凭证不匹配,则引发身份验证异常
在应用程序中需要自定义一个Realm类,继承AuthorizingRealm抽象类,覆盖doGetAuthenticationInfo(),重写获取用户信息的方法。
shiro的权限授权是通过继承AuthorizingRealm抽象类,覆盖doGetAuthorizationInfo()。当访问到页面的时候,URL配置了相应的权限或者shiro标签才会执行此方法否则不会执行,所以如果只是简单的身份认证没有权限的控制的话,那么这个方法可以不进行实现,直接返回null即可。
在这个方法中主要是使用类:SimpleAuthorizationInfo进行角色的添加和权限的添加。SecurityManager将权限或角色检查的任务委托给Authorizer,默认为ModularRealmAuthorizer。
应用程序则可以通过角色或者权限进行访问控制。
package com.example.demo.config; import java.util.HashSet;
import java.util.Set; 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 org.springframework.beans.factory.annotation.Autowired; import com.example.demo.model.Customer;
import com.example.demo.service.CustomerService; public class MyShiroRealm extends AuthorizingRealm { @Autowired
private CustomerService customerService; @Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("MyShiroRealm.doGetAuthenticationInfo()"); // 获取用户的输入的账号.
String name = (String) token.getPrincipal();
System.out.println(token.getCredentials());
Customer c = customerService.findByName(name);
System.out.println("Customer info : " + c);
if (c == null) {
return null;
}
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(c, // 用户名
c.getPassword(), // 密码
getName() // realm name
); return authenticationInfo;
} @Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("权限管理-->MyShiroRealm.doGetAuthorizationInfo()");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
Customer customer = (Customer) principals.getPrimaryPrincipal();
System.out.println("Customer is : " + customer);
// 权限单个添加;
// 添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色
// 模拟查询数据库,得到用户角色为admin或者operator或者viewer
// admin有所有权限,operator有查看和编辑权限,没有添加和删除权限
// viewer只有查看权限
authorizationInfo.addRole("operator");
// 添加权限
Set<String> permissionSet = new HashSet<String>();
permissionSet.add("customer:details");
permissionSet.add("customer:index");
permissionSet.add("customer:edit");
//permissionSet.add("customer:add");
//permissionSet.add("customer:delete"); authorizationInfo.setStringPermissions(permissionSet);
return authorizationInfo;
}
}
MyShiroRealm
6. 参考资料
http://shiro.apache.org/introduction.html
SpringBoot集成Apache Shiro的更多相关文章
- springboot 整合apache shiro
这几天因为项目需要,学习了下shiro,由此留下一些记录,也希望对初学shiro的朋友有帮助. springboot 是这两年新兴起来的一个项目,它的出现是为了减少springmvc开发过程中需要引入 ...
- SpringBoot整合Apache Shiro权限验证框架
比较常见的权限框架有两种,一种是Spring Security,另一种是Apache Shiro,两种框架各有优劣,个人感觉Shiro更容易使用,更加灵活,也更符合RABC规则,而且是java官方更推 ...
- SpringBoot整合Apache Shiro
Subject 用户主体 (把操作交给SecurityManager)SecurityManager 安全管理器 (关联Realm)Realm Shiro连接数据的桥梁 引入maven依赖 < ...
- SpringBoot 集成Apache Kafak 消息队列
Kafka is a distributed,partitioned,replicated commit logservice.它提供了类似于JMS的特性,但是在实现上完全不同,此外它并不是JMS规范 ...
- SpringBoot集成Shiro安全框架
跟着我的步骤:先运行起来再说 Spring集成Shiro的GitHub:https://github.com/yueshutong/shiro-imooc 一:导包 <!-- Shiro安全框架 ...
- Apache Shiro:【1】Shiro基础及Web集成
Apache Shiro:[1]Shiro基础及Web集成 Apache Shiro是什么 Apache Shiro是一个强大且易于使用的Java安全框架,提供了认证.授权.加密.会话管理,与spri ...
- 快速搭建Spring Boot + Apache Shiro 环境
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.Apache Shiro 介绍及概念 概念:Apache Shiro是一个强大且易用的Java安全框 ...
- Apache Shiro:【2】与SpringBoot集成完成登录验证
Apache Shiro:[2]与SpringBoot集成完成登录验证 官方Shiro文档:http://shiro.apache.org/documentation.html Shiro自定义Rea ...
- Springboot集成权限管理框架apache shiro
一.名词解释 网上一大堆 二.pom依赖 <dependency> <groupId>org.apache.shiro</groupId> <artifact ...
随机推荐
- VS调试IDAPython脚本
本文最后修改时间:20180213 1.安装VS插件PTVS , 这一步与第2步中安装版本应该一致,否则最后调试时会连不上 https://github.com/Microsoft/PTVS/ 2.安 ...
- scrapy入门:安装scrapy
1.安装Scrapy pip 安装: pip install scrapy 要求pip至少是18.1版本的,10.x版本会提示更新pip 更新pip命令: python -m pip install ...
- Lua中的#
Lua中的 对字符串来说,#取字符串的长度,但对于table需要注意. lua的table可以用数字或字符串等作为key, #号得到的是用整数作为索引的最开始连续部分的大小, 如果t[1] == ni ...
- [HBase_1] HBase安装与配置
0. 说明 1. 简介 1.1 简介 基于 HDFS 的大表软件(实时数据库) 十亿行 x 百万列 x 上千个版本 版本是通过 mvcc 技术控制:multiple version concurren ...
- windowsserver2016系统性能和功能对比介绍
一. 性能和可扩性 特征描述 Windows Server 2012/2012 R2 标准版和数据中心 Windows Server 2016 标准版和数据中心 物理内存(主机)支持 每个物理服 ...
- LeetCode算法题-Word Pattern(Java实现)
这是悦乐书的第202次更新,第212篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第68题(顺位题号是290).给定一个模式和一个字符串str,找到str是否完全匹配该模 ...
- 各种缓存(Memcached、Redis、RabbitMQ、SQLlchemy)
Memcached Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载.它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态.数据库驱动网站的速度 ...
- 假设在本地搭一个server和mysql数据库环境,假设使用java来訪问数据库
我们能够使用speedamp来搭一个server环境,能够在http://download.csdn.net/detail/baidu_nod/7630265下载 解压后无需安装直接能够使用.点击Sp ...
- UVA1608-Non-boring sequences(分治)
Problem UVA1608-Non-boring sequences Accept: 227 Submit: 2541Time Limit: 3000 mSec Problem Descript ...
- UVA12558-Efyptian Fractions(HARD version)(迭代加深搜索)
Problem UVA12558-Efyptian Fractions(HARD version) Accept:187 Submit:3183 Time Limit: 3000 mSec Pro ...
