SpringBoot集成Apache Shiro

笔者因为项目转型的原因,对Apache Shiro安全框架做了一点研究工作,故想写点东西以便将来查阅。之所以选择Shiro也是看了很多人的推荐,号称功能丰富强大,而且易于使用。实践下来的确如大多数人所说简约优美,小巧精悍。

介绍demo项目前,简单说明一下Shiro框架的特性。

1.  Apache Shiro Features

从上图可以看出Shiro具备应用程序安全框架的四大基石”:身份验证、授权、会话管理和密码。

Authentication有时被称为‘登录’,这是需要明确用户是谁

Authorization访问控制,即确定‘谁’对‘什么’有访问权限。

Session Management管理特定用户的会话,即使在非web或EJB应用程序中也是如此。

Cryptography使用加密算法保持数据安全,但易于使用。

在不同的应用程序环境中,还有更多的特性来支持和增强这些关注点,特别是:

Web SupportShiro的Web支持API帮助轻松地保护web应用程序。

Caching缓存是ApacheShiro的API中的第一等公民,以确保安全操作同时保持快速和高效。

ConcurrencyApacheShiro支持具有并发特性的多线程应用程序。

Testing提供测试支持,以帮助编写单元和集成测试,并确保代码如预期的安全。

Run as允许用户假定另一个用户的身份(如果允许的话)的特性,有时在管理场景中很有用。

Remember Me记住用户在会话中的身份,这样他们就只需要在强制的情况下输入口令登录。

2. High-Level Overview

Shiro的体系结构有三个主要概念:Subject、SecurityManager和Realms。下图展现了它的运行原理,

主题:主题本质上是当前正在执行的用户。虽然“用户”这个词通常意味着一个人,一个主题可以是一个人,但它也可以代表一个第三方服务、守护进程帐户、cron作业或任何类似的东西-基本上是任何当前与软件交互的东西。Subject实例都绑定到(并且需要)一个SecurityManager。当与主题交互时,这些交互转化为与SecurityManager的特定主题交互。

SecurityManagerSecurityManager是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配置文件如下:

1 spring.thymeleaf.cache=true
2 spring.thymeleaf.prefix=classpath:/templates/
3 spring.thymeleaf.suffix=.html
4 spring.thymeleaf.mode=HTML5
5 spring.thymeleaf.encoding=UTF-8
6 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

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4 <meta charset="UTF-8"></meta>
 5 <title>Add Page</title>
 6 </head>
 7 <body>
 8     <h1>Add Page</h1>
 9 </body>
10 </html>

add.html

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4 <meta charset="UTF-8"></meta>
 5 <title>Login Page</title>
 6 <style type="text/css">
 7 table {
 8     width: 360px;
 9     min-height: 25px;
10     line-height: 25px;
11     text-align: center;
12     border-color: #b6ff00;
13     border-collapse: collapse;
14
15 }
16 </style>
17 </head>
18 <body>
19     <div>
20         <form action="/home/check" method="post">
21             <table border="1">
22                 <tr>
23                     <th>User Name</th>
24                     <th><input type="text" name="name" /></th>
25                 </tr>
26                 <tr>
27                     <td>Password</td>
28                     <td><input type="password" name="password" /></td>
29                 </tr>
30                 <tr>
31                     <td><input type="submit" value="Submit" /></td>
32                     <td></td>
33                 </tr>
34             </table>
35         </form>
36     </div>
37 </body>
38 </html>

login.html

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4 <meta charset="UTF-8"></meta>
 5 <title>Index Page</title>
 6 <style type="text/css">
 7 p {
 8     font-family: Times, TimesNR, ‘New Century Schoolbook‘, Georgia,
 9         ‘New York‘, serif;
10     font-size: 20px;
11 }
12 </style>
13
14 </head>
15 <body>
16     <h1>This is index page.</h1>
17     <p>
18         Customer Name: <span th:text="${name}"></span> --- Role: <span
19             th:text="${role}"></span>
20     </p>
21     <table border="1">
22         <tr>
23             <th>角色</th>
24             <th>权限</th>
25         </tr>
26         <tr>
27             <td>admin</td>
28             <td>增加,删除,编辑,查看</td>
29         </tr>
30         <tr>
31             <td>operator</td>
32             <td>编辑,查看</td>
33         </tr>
34         <tr>
35             <td>viewer</td>
36             <td>查看</td>
37         </tr>
38     </table>
39     <ul>
40         <li><a th:href="@{/customer/index}">Index</a></li>
41         <li><a th:href="@{/customer/details}">Details</a></li>
42         <li><a th:href="@{/customer/add}">Add</a></li>
43         <li><a th:href="@{/customer/edit}">Edit</a></li>
44         <li><a th:href="@{/customer/delete}">Delete</a></li>
45     </ul>
46 </body>
47 </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

 1 @Service
 2 public class CustomerService {
 3     public Customer findByName(String name) {
 4         // 模拟查询数据库
 5         // tom is admin, alice is operator, lucy is viewer
 6         if (name.equals("alice")) {
 7             return new Customer(name, "123", "operator");
 8         }
 9         return null;
10     }
11 }

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 最重要的两个类如下:

 1 package com.example.demo.config;
 2
 3 import java.util.LinkedHashMap;
 4 import java.util.Map;
 5
 6 import org.apache.shiro.mgt.SecurityManager;
 7 import org.apache.shiro.spring.LifecycleBeanPostProcessor;
 8 import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
 9 import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
10 import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
11 import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
12 import org.springframework.context.annotation.Bean;
13 import org.springframework.context.annotation.Configuration;
14
15 @Configuration
16 public class ShiroConfiguration {
17
18     @Bean
19     public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
20         System.out.println("ShiroConfiguration.shirFilter()");
21         ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
22         shiroFilterFactoryBean.setSecurityManager(securityManager);
23         // 过滤器.
24         Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
25         // 配置不会被拦截的链接 顺序判断
26         filterChainDefinitionMap.put("/static/**", "anon");
27         filterChainDefinitionMap.put("/home/**", "anon");
28         filterChainDefinitionMap.put("/test/**", "anon");
29         filterChainDefinitionMap.put("/customer/**", "authc");
30         shiroFilterFactoryBean.setLoginUrl("/home/login");
31         // 登录成功后要跳转的链接
32         shiroFilterFactoryBean.setSuccessUrl("/customer/index");
33
34         shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
35         return shiroFilterFactoryBean;
36     }
37
38     @Bean
39     public MyShiroRealm myShiroRealm() {
40         MyShiroRealm myShiroRealm = new MyShiroRealm();
41         return myShiroRealm;
42     }
43
44     @Bean
45     public SecurityManager securityManager() {
46         DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
47         securityManager.setRealm(myShiroRealm());
48         return securityManager;
49     }
50
51     // 开启Shiro AOP注解支持.
52     @Bean
53     public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
54         System.out.println("OPNE AOP......");
55         AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
56         authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
57         return authorizationAttributeSourceAdvisor;
58     }
59
60     @Bean
61     public static DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
62         return new DefaultAdvisorAutoProxyCreator();
63     }
64
65     // 管理shiro生命周期
66     @Bean
67     public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
68         return new LifecycleBeanPostProcessor();
69     }
70 }

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。

应用程序则可以通过角色或者权限进行访问控制。

 1 package com.example.demo.config;
 2
 3 import java.util.HashSet;
 4 import java.util.Set;
 5
 6 import org.apache.shiro.authc.AuthenticationException;
 7 import org.apache.shiro.authc.AuthenticationInfo;
 8 import org.apache.shiro.authc.AuthenticationToken;
 9 import org.apache.shiro.authc.SimpleAuthenticationInfo;
10 import org.apache.shiro.authz.AuthorizationInfo;
11 import org.apache.shiro.authz.SimpleAuthorizationInfo;
12 import org.apache.shiro.realm.AuthorizingRealm;
13 import org.apache.shiro.subject.PrincipalCollection;
14 import org.springframework.beans.factory.annotation.Autowired;
15
16 import com.example.demo.model.Customer;
17 import com.example.demo.service.CustomerService;
18
19 public class MyShiroRealm extends AuthorizingRealm {
20
21     @Autowired
22     private CustomerService customerService;
23
24     @Override
25     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
26         System.out.println("MyShiroRealm.doGetAuthenticationInfo()");
27
28         // 获取用户的输入的账号.
29         String name = (String) token.getPrincipal();
30         System.out.println(token.getCredentials());
31         Customer c = customerService.findByName(name);
32         System.out.println("Customer info : " + c);
33         if (c == null) {
34             return null;
35         }
36         SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(c, // 用户名
37                 c.getPassword(), // 密码
38                 getName() // realm name
39         );
40
41         return authenticationInfo;
42     }
43
44     @Override
45     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
46
47         System.out.println("权限管理-->MyShiroRealm.doGetAuthorizationInfo()");
48         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
49         Customer customer = (Customer) principals.getPrimaryPrincipal();
50         System.out.println("Customer is : " + customer);
51         // 权限单个添加;
52         // 添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色
53         // 模拟查询数据库,得到用户角色为admin或者operator或者viewer
54         // admin有所有权限,operator有查看和编辑权限,没有添加和删除权限
55         // viewer只有查看权限
56         authorizationInfo.addRole("operator");
57         // 添加权限
58         Set<String> permissionSet = new HashSet<String>();
59         permissionSet.add("customer:details");
60         permissionSet.add("customer:index");
61         permissionSet.add("customer:edit");
62         //permissionSet.add("customer:add");
63         //permissionSet.add("customer:delete");
64
65         authorizationInfo.setStringPermissions(permissionSet);
66         return authorizationInfo;
67     }
68 }

MyShiroRealm

6. 参考资料

http://shiro.apache.org/introduction.html

原文地址:https://www.cnblogs.com/sankt/p/9278886.html

时间: 2024-10-11 13:13:21

SpringBoot集成Apache Shiro的相关文章

springboot 整合apache shiro

这几天因为项目需要,学习了下shiro,由此留下一些记录,也希望对初学shiro的朋友有帮助. springboot 是这两年新兴起来的一个项目,它的出现是为了减少springmvc开发过程中需要引入各种的jar包,各种xml配置文件,它充分利用了JavaConfig的配置模式以及"约定优于配置"的理念,帮开发者配置大部分需要的东西,在github上的springboot项目里面,提供了很多列子, 而apache shiro 是一个轻量级的身份验证与授权框架,与spring secur

在Spring MVC中使用Apache Shiro安全框架

我们在这里将对一个集成了Spring MVC+Hibernate+Apache Shiro的项目进行了一个简单说明.这个项目将展示如何在Spring MVC 中使用Apache Shiro来构建我们的安全框架. [TOC] 阅读文章前,您需要做以下准备: Maven 3环境Mysql-5.6+JDK1.7+git环境git.oschina.net帐号Apache Tomcat 7+您熟练掌握的编辑工具,推荐使用InterlliJ IDEA 14+开始项目地址git.oschina.net 项目地

SpringBoot:集成Shiro之INI授权篇

前言 图片描述(最多50字)前面说到如何使用INI文件进行登录认证,但是这一篇博客主要讲的是Shiro的另外一个重要功能就是授权操作,这里简单说明一下,授权操作就是来定义合法用户的权限操作.这一篇我们仍然使用INI文件的形式,看一下,我们如何来进行授权操作. RBAC简单阐述基于角色的权限访问控制(Role-Based Access Control)作为传统访问控制(自主访问,强制访问)的有前景的代替受到广泛的关注.在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限.

SpringBoot集成Shiro 实现动态加载权限

一.前言 本文小编将基于 SpringBoot 集成 Shiro 实现动态uri权限,由前端vue在页面配置uri,Java后端动态刷新权限,不用重启项目,以及在页面分配给用户 角色 . 按钮 .uri 权限后,后端动态分配权限,用户无需在页面重新登录才能获取最新权限,一切权限动态加载,灵活配置 基本环境 spring-boot 2.1.7 mybatis-plus 2.1.0 mysql 5.7.24 redis 5.0.5 温馨小提示:案例demo源码附文章末尾,有需要的小伙伴们可参考哦 ~

Springboot集成Shiro和Cas实现单点登录(服务端篇CAS5)

什么是单点登录? 先说一个需求场景,比如:一个企业的内部有N多个子系统,每个子系统都有一套自己的用户名和密码,那么企业的员工要登录N个子系统,这样一个员工 就要记住N个用户名和密码,就算各个子系统的用户名和密码都是统一的,登录每个子系统都要输入用户名和密码进行登录也是一个繁琐的操作过程,那么单点登录功能由此便应运而生了.单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一.SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应

springboot 集成shiro

首先看下shiro configuration 的配置,重要部分用红色标出了 package cn.xiaojf.today.shiro.configuration; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import cn.xiaojf.today.sys.security.credentials.RetryLimitHashedCredentialsMatcher; import cn.xiaojf.today.sys.

Apache Shiro学习笔记(九)Spring集成

鲁春利的工作笔记,好记性不如烂笔头 Integrating Apache Shiro into Spring-based Applications Shiro 的组件都是JavaBean/POJO 式的组件,所以非常容易使用Spring进行组件管理,可以非常方便的从ini配置迁移到Spring进行管理,且支持JavaSE应用及Web 应用的集成. Web Applications 1.web.xml <!-- The filter-name matches name of a 'shiroFil

【Shiro】Apache Shiro架构之集成web

前面两节内容介绍了Shiro中是如何进行身份和权限的认证,但是只是单纯的进行Shiro的验证,简单一点的话,用的是.ini配置文件,也举了个使用jdbc realm的例子,这篇博文主要来总结一下Shiro是如何集成web的,即如何用在web工程中. 写在前面:本文没有使用web框架,比如springmvc或者struts2,用的是原始的servlet,使用的是.ini配置文件,旨在简单粗暴,说明问题.后面会写一些和框架整合的博文. 本文部分参考Apache Shiro的官方文档,文档地址:htt

Apache Shiro 集成-Cas

http://blog.csdn.net/peterwanghao/article/details/8825008 Shiro集成CAS是在1.2版本里新增的功能. Shiro-cas模块将应用作为CAS客户端与CAS SSO服务器一起保护web应用. CAS协议的一个基本理解: 1. 如果你想访问一个被CAS客户端保护的应用,而你还没有进行认证.你讲被重定向到CAS服务端的登录页面.在应用中你需要配置CAS的登录url地址. http://application.examples.com/pr