Spring Boot Shiro 使用教程

Apache Shiro 已经大名鼎鼎,搞 Java 的没有不知道的,这类似于 .Net 中的身份验证 form 认证。跟 .net core 中的认证授权策略基本是一样的。当然都不知道也没有关系,因为所有的权限都是模拟的人或机构的社会行为。

本系列从简单的权限讲起,主要涉及到 Shiro、Spring Security、Jwt、OAuth2.0及其他自定义权限策略。

本章主要讲解 Shiro 的基本原理与如何使用,本章主要用到以下基础设施:

  • jdk1.8+
  • spring boot 2.1.6
  • idea 2018.1

本项目源码下载

1 Spring Boot 快速集成 Shiro 示例

首先我们来一段真实的代码演示下 Spring Boot 如何集成 Shiro 。本代码示例暂时没有使用到数据库相关知识,本代码主要使用到:

  1. shiro
  2. thymeeaf

本示例演示了网站用户 admin 密码 123456 的用户使用用户名密码登录网站,经过 Shiro 认证后,获取了授权权限列表,演示了权限的使用。

本项目源码下载

1.1 新建 Spring Boot Maven 示例工程项目

  1. File > New > Project,如下图选择 Spring Initializr 然后点击 【Next】下一步
  2. 填写 GroupId(包名)、Artifact(项目名) 即可。点击 下一步
    groupId=com.fishpro
    artifactId=shiro
  3. 选择依赖 Spring Web Starter 前面打钩。
  4. 项目名设置为 spring-boot-study-shiro.

1.2 依赖引入 Pom.xml

本代码主要使用到:

  1. shiro
  2. thymeeaf

在Pom.xml中加入以下代码

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="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>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.fishpro</groupId>
    <artifactId>shiro</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>shiro</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--shiro 1.4.0 thymeleaf-extras-shiro 2.0.0 组合-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!--shiro for thymeleaf 生效需要加入 spring boot 2.x 请使用 2.0.0 版本 否则使用 1.2.1版本-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

1.3 配置 application.yml

本示例使用默认配置,暂不需要在 application.yml 中配置 shiro。我们把 application.properties 改成了 application.yml (习惯问题) ,修改了默认端口

server:
  port: 8086

1.4 自定义 Realm 领域 UserRealm 实现自定义认证与授权

在 src/main/java/com/java/fishpro/shiro/config(config是新增的包名) 新增 UserRealm.java 文件
UserRealm 是一种安全数据源,用户登录认证核心在此类中实现,用户授权也在此类中实现,具体看代码注释

  1. 重写了 doGetAuthenticationInfo 实现对用户名密码的认证,返回一个 SimpleAuthenticationInfo 对象。*注意,因为 shiro 是一个安全框架,具体的身份证明的认证就要交给我们自己去实现,实际上认证是业务逻辑,最好我们自己实现。
  2. 重写了 doGetAuthorizationInfo 实现对当前用户的授权,返回一个 SimpleAuthorizationInfo 对象,注意,授权就是从业务系统数据库中查询到当前用户的已知权限列表,写在当前会话中,以便在使用的时候去做匹配,匹配成功表示授权成功,匹配失败表示没授权
//定义一个实体对象用于存储用户信息
public class UserDO {
    private Integer id;
    private String userName;//就是 shiro 中的身份,系统中唯一的存在
    private String password; //就是 shiro 中的证明

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

在包 config(没有就新建 config) 下建立 UserRealm

package com.fishpro.shiro.config;

import com.fishpro.shiro.domain.UserDO;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;

import java.security.Principal;
import java.util.*;

/**
 * 用户领域 重写了 AuthorizingRealm ,AuthorizingRealm(授权) 其实是继承了 AuthenticatingRealm(认证)
 * 所在在这里只要继承 AuthorizingRealm(授权),主要实现 授权和认证的方法重写
 * 1.doGetAuthenticationInfo 重写认证
 * 2.doGetAuthorizationInfo 重写授权
 * */
public class UserRealm extends AuthorizingRealm {
    /**
     * doGetAuthenticationInfo 重写认证
     * @param authenticationToken token
     * @return 返回认证信息实体(好看身份和证明) AuthenticationInfo
     * */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String username=(String)authenticationToken.getPrincipal();//身份 例如 用户名
        Map<String ,Object> map=new HashMap<>(16);
        map.put("username",username);
        String password  =new String((char[]) authenticationToken.getCredentials());//证明 例如 密码
        //对身份+证明的数据认证 这里模拟了一个数据源
        //如果是数据库 那么这里应该调用数据库判断用户名密码是否正确
        if(!"admin".equals(username) || !"123456".equals(password)){
            throw new IncorrectCredentialsException("账号或密码不正确");
        }
        //认证通过
        UserDO user=new UserDO();
        user.setId(1);//假设用户ID=1
        user.setUserName(username);
        user.setPassword(password);
        //建立一个 SimpleAuthenticationInfo 认证模块,包括了身份】证明等信息
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
        return info;
    }

    /**
     * 重写授权 doGetAuthorizationInfo 返回  授权信息对象 AuthorizationInfo
     * @param  principalCollection 身份信息
     * @return  返回  授权信息对象 AuthorizationInfo
     * */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        UserDO userDO  = (UserDO)principalCollection.getPrimaryPrincipal();
        Integer userId= userDO.getId();//转成 user 对象
        //授权 新建一个授权模块 SimpleAuthorizationInfo 把 权限赋值给当前的用户
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //设置当前会话拥有的角色 实际场景根据业务来如从数据库获取角色列表
        Set<String> roles=new HashSet<>();
        roles.add("admin");
        roles.add("finance");
        info.setRoles(roles);

        //设置当前会话可以拥有的权限 实际场景根据业务来如从数据库获取角色列表下的权限列表
        Set<String> permissions=new HashSet<>();
        permissions.add("system:article:article");
        permissions.add("system:article:add");
        permissions.add("system:article:edit");
        permissions.add("system:article:remove");
        permissions.add("system:article:batchRemove");
        info.setStringPermissions(permissions);
        return  info;
    }

}

1.6 shiro 实现登录认证

这里主要是显示 login.html 与 LoginController

新增文件 resources/templates/login.html 表示登录页面,这里使用 jquery 来实现逻辑

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>使用 shiro 登录页面</title>
</head>
<body>
<div>
    <input id="userName" name="userName" value="">
</div>
<div>
    <input id="password" name="password" value="">
</div>
<div>
    <input type="button" id="btnSave"  value="登录">
</div>
<script src="https://cdn.bootcss.com/jquery/1.11.3/jquery.js"></script>
<script>
    $(function() {
        $("#btnSave").click(function () {
            var username=$("#userName").val();
            var password=$("#password").val();
            $.ajax({
                cache: true,
                type: "POST",
                url: "/login",
                data: "userName=" + username + "&password=" + password,
                dataType: "json",
                async: false,
                error: function (request) {
                    console.log("Connection error");
                },
                success: function (data) {
                    if (data.status == 0) {
                        window.location = "/index";
                        return false;

                    } else {
                        alert(data.message);
                    }

                }
            });
        });
    });
</script>
</body>
</html>

1.7 shiro 实现Controller层方法授权

这里需要增加几个页面来实现这个功能

1.7.1 resources/templates/index.html 登陆成功后跳转的页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>通过登录验证后跳转到此页面</title>
</head>
<body>
    通过登录验证后跳转到此页面
<div>
    <a href="/article">前往文章页面</a>
</div>
    <div>
        <a href="/setting">前往设置页面</a>
    </div>
</body>
</html>

1.7.2 resources/templates/article.html 已授权访问的页面

<!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>必须获取 app:article 授权</title>
 </head>
 <body>
 必须获取 app:article 授权 才会显示
 </body>
 </html>

1.7.3 resources/templates/setting.html 未授权访问的页面

 <!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>必须获取 app:setting 授权 </title>
 </head>
 <body>
 必须获取 app:setting 授权 才会显示
 </body>
 </html>

1.7.4 resources/templates/error/403.html 未授权统一吹页面

<!DOCTYPE html>
 <html lang="en">
 <head>
     <meta charset="UTF-8">
     <title>403 没有授权</title>
 </head>
 <body>
 你访问的页面没有授权
 </body>
 </html>

1.7.5 controller/UserController.java Controller层方法

如下源代码

  1. 方法 article 需要权限 app:article:article 才能进入
  2. 方法 setting 需要权限 app:setting:setting 才能进入

@Controller
public class UserController {
    //shiro 认证成功后默认跳转页面
    @GetMapping("/index")
    public String index(){
        return "index";
    }

    @GetMapping("/403")
    public String err403(){
        return "403";
    }
    /**
     * 根据权限授权使用注解 @RequiresPermissions
     * */
    @GetMapping("/article")
    @RequiresPermissions("app:article:article")
    public String article(){
        return "article";
    }

    /**
     * 根据权限授权使用注解 @RequiresPermissions
     * */
    @GetMapping("/setting")
    @RequiresPermissions("app:setting:setting")
    public String setting(){
        return "setting";
    }
    @GetMapping("/login")
    public String login(){
        return "login";
    }

    @PostMapping("/login")
    @ResponseBody
    public Object loginsubmit(@RequestParam String userName,@RequestParam String password){
        Map<String,Object> map=new HashMap<>();
        //把身份 useName 和 证明 password 封装成对象 UsernamePasswordToken
        UsernamePasswordToken token=new UsernamePasswordToken(userName,password);
        //获取当前的 subject
        Subject subject = SecurityUtils.getSubject();
        try{
            subject.login(token);
            map.put("status",0);
            map.put("message","登录成功");
            return map;
        }catch (AuthenticationException e){
            map.put("status",1);
            map.put("message","用户名或密码错误");
            return map;
        }

    }
}

1.8 shiro 实现前端页面中授权

我们使用了 Thymeleaf 作为前端的模板引擎,您也可以使用 JSP,FreeMarker 等引擎。Shiro 已经能够很好的在 Thymeleaf 中使用,如下代码我在首页中使用

使用 shiro 可以有多种方式进行授权
|方式|注解|示例|
|---|---|---|
|验证是否登录|@RequiresAuthentication|@RequiresAuthentication|
|是否记住我|@RequiresUser||
|是否游客身份|@RequiresGuest|@RequiresGuest|
|是否拥有角色|@RequiresRoles|@RequiresRoles("admin")|
|是否拥有权限|@RequiresPermissions|@RequiresPermissions("perm")|

为什么我的注解没生效?

要使用 shiro 注解来授权 Controller 的方法,那么需要在 ShiroConfig 中加入以下代码

/**
     *  开启shiro aop注解支持 如@RequiresRoles,@RequiresPermissions
     *  使用代理方式;所以需要开启代码支持;
     * @param securityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

1.9 登出/注销

调用 subject 的logout 方法进行注销

@GetMapping("/logout")
    String logout(HttpSession session, SessionStatus sessionStatus, Model model) {
        //会员中心退出登录 当使用这两属性session属性退出
        session.removeAttribute("userData");
        sessionStatus.setComplete();
        SecurityUtils.getSubject().logout();
        return "redirect:/login";

    }

1.10 问题

  1. @RequiresPermissions 注解无效
    注解无效,没有走到注解,基本就是AOP拦截问题,需要在 ShiroConfig 配置中增加配置
  1. spring boot shiro Not authorized to invoke method 当 @RequiresPermissions 中的权限没有的时候发生
    @RequiresPermissions 既然生效了,那为什么又会报错呢,按道理已经登录,但是没有权限的方法体,应该跳转到/403 页面才对。
    这里应该也是没有拦截到方法这个错。这个在 Spring Boot 全局异常处理 中讲过,需要使用到异常捕捉机制,捕捉到这个异常 org.apache.shiro.authz.UnauthorizedException ,然后做统一处理。
@ControllerAdvice(annotations = Controller.class)
public class MyExceptionController {
    private static final Logger logger= LoggerFactory.getLogger(MyExceptionController.class);
    public static final String DEFAULT_ERROR_VIEW = "error";

    @ExceptionHandler(value = UnauthorizedException.class)//处理访问方法时权限不足问题
    public String defaultErrorHandler(HttpServletRequest req, Exception e)  {
        return "error/403";
    }
}
  1. shiro:hasPermission 标签在 thymeleaf 中不生效问题

shiro:hasPermission 标签应用在 thymeleaf ,由于涉及到两个框架,如果原生不支持,那么比如要引入第三方控件。

/**
     * ShiroDialect,为了在thymeleaf里使用shiro的标签的bean
     * @return
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
  1. 有的时候回报错 org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘shiroDialect‘

这个应该是第三方插件 com.github.theborakompanioni 与 spring boot 版本兼容性问题。我改为了以下版本 shiro for thymeleaf 生效需要加入 spring boot 2.x 请使用 2.0.0 版本

 <!--shiro 1.4.0 thymeleaf-extras-shiro 2.0.0 组合-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!--shiro for thymeleaf 生效需要加入 spring boot 2.x 请使用 2.0.0 版本 否则使用 1.2.1版本-->
        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>2.0.0</version>
        </dependency>

原文地址:https://www.cnblogs.com/fishpro/p/spring-boot-study-study.html

时间: 2024-10-11 07:41:29

Spring Boot Shiro 使用教程的相关文章

spring boot shiro -权限管理

spring boot shiro -权限管理 定义 java最常用的框架有spring security 和Apache shiro,因为spring security 庞大和负责,一般使用都是Apache shiro. Apache shiro 是一个功能强大,灵活,开源的安全框架,它可以处理身份验证,授权,企业会话管理,加密等. shiro 易于理解和使用,一个好的框架 应该屏蔽复杂性,向外提供简单,直观的api,简化开发人员实行应用程序安全所发费的时间和精力. shiro 可以做什么:

Spring Boot 2.x教程-Thymeleaf 原理是什么

layout: post title: Spring Boot 2.x教程-Thymeleaf 原理是什么 categories: SpringBoot description: Spring Boot 2.x教程-Thymeleaf 原理是什么 keywords: SpringBoot, Spring, Thymeleaf --- 如要要理清楚 Thymeleaf 的原理,那么就要从模板引擎的原理说起.Thymeleaf只不过是众多模板中的一员,功能是一致的. 例如 JSP 也是一种模板. 1

Spring Boot JPA 使用教程

JPA 是 Spring Boot 官方推荐的数据库访问组件,其充分体现了面向对象编程思想,有点像 asp.net 的 EFCore.JPA 也是众多 ORM 的抽象. 从本系列开始,都需要用到 mysql 数据库 和其他一些参考的数据库.请准备相关环节.本章需要以下环境支撑: mysql 5.6+ jdk1.8+ spring boot 2.1.6 idea 2018.1 本项目源码下载 1 数据准备 数据库教程系列都是使用相同的数据,如在 Spring Boot JDBC 使用教程使用的一样

Spring Boot Shiro 权限管理 【转】

http://blog.csdn.net/catoop/article/details/50520958 主要用于备忘 本来是打算接着写关于数据库方面,集成MyBatis的,刚好赶上朋友问到Shiro权限管理,就先总结下发出来了. 使用Shiro之前用在spring MVC中,是通过XML文件进行配置. 既然现在在写Spring Boot的帖子,就将Shiro应用到Spring Boot中,我本地已经完成了SpringBoot使用Shiro的实例,将配置方法共享一下. 先简单介绍一下Shiro,

十、 Spring Boot Shiro 权限管理

使用Shiro之前用在spring MVC中,是通过XML文件进行配置. 将Shiro应用到Spring Boot中,本地已经完成了SpringBoot使用Shiro的实例,将配置方法共享一下. 先简单介绍一下Shiro,对于没有用过Shiro的朋友,也算是做个简介吧. Shiro是Apache下的一个开源项目,我们称之为Apache Shiro.它是一个很易用与Java项目的的安全框架,提供了认证.授权.加密.会话管理,与 Spring Security 一样都是做一个权限的安全框架,但是与S

Spring Boot RestApi 测试教程 Mock 的使用

测试 Spring Boot Web 的时候,我们需要用到 MockMvc,即系统伪造一个 mvc 环境.本章主要编写一个基于 RESTful API 正删改查操作的测试用例.本章最终测试用例运行结果如下: 1 MockMvc 简介 Spring Boot Web 项目中我们采用 MockMvc 进行模拟测试 方法 说明 mockMvc.perform 执行一个请求 MockMvcRequestBuilders.get("XXX") 构造一个请求 ResultActions.param

Spring Boot + Shiro 实现前后端分离、权限控制

本文总结自实习中对项目的重构.原先项目采用Springboot+freemarker模版,开发过程中觉得前端逻辑写的实在恶心,后端Controller层还必须返回Freemarker模版的ModelAndView,逐渐有了前后端分离的想法,由于之前,没有接触过,主要参考的还是网上的一些博客教程等,初步完成了前后端分离,在此记录以备查阅. 一.前后端分离思想 前端从后端剥离,形成一个前端工程,前端只利用Json来和后端进行交互,后端不返回页面,只返回Json数据.前后端之间完全通过public A

spring boot + shiro 登录验证

form提交 <form th:action="@{/login}" method="POST"> <div class="form-group has-feedback"> <input name="username" type="text" class="form-control" placeholder="用户账户" require

Spring Boot 集成Shiro和CAS

请大家在看本文之前,先了解如下知识点: 1.Shiro 是什么?怎么用? 2.Cas 是什么?怎么用? 3.最好有Spring基础 可以先看看这两篇文章,按照这2篇文章的内容做一遍: Spring Boot Shiro 权限管理 CAS单点登录 首先看一下下面这张图: 第一个流程是单纯使用Shiro的流程. 第二个流程是单纯使用Cas的流程. 第三个图是Shiro集成Cas后的流程. [流程图高清图连接:http://img.blog.csdn.net/20160117224937078] PS