百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

极简教程:Spring Boot 整合 Shiro

ccwgpt 2024-09-27 07:31 30 浏览 0 评论

Shiro 概述

Apache Shiro 是一款 Java 安全框架,不依赖任何容器,可以运行在 Java SE 和 Java EE 项目中,它的主要作用是用来做身份认证、授权、会话管理和加密等操作。

什么意思?大白话就是判断用户是否登录、是否拥有某些操作的权限等。

其实不用 Shiro,我们使用原生 Java API 就可以完成安全管理,很简单,使用过滤器去拦截用户的各种请求,然后判断是否登录、是否拥有某些权限即可。

我们完全可以完成这些操作,但是对于一个大型的系统,分散去管理编写这些过滤器的逻辑会比较麻烦,不成体系,所以需要使用结构化、工程化、系统化的解决方案。

任何一个业务逻辑,一旦上升到企业级的体量,就必须考虑使用系统化的解决方案,也就是框架,否则后期的开发成本是相当巨大的,Shiro 就是来解决安全管理的系统化框架。


Shiro 核心组件

1、UsernamePasswordToken,Shiro 用来封装用户登录信息,使用用户的登录信息创建令牌 Token,登录的过程即 Shiro 验证令牌是否具有合法身份以及相关权限。

2、 SecurityManager,Shiro 的核心部分,负责安全认证与授权。

3、Subject,Shiro 的一个抽象概念,包含了用户信息。

4、Realm,开发者自定义的模块,根据项目的需求,验证和授权的逻辑在 Realm 中实现。

5、AuthenticationInfo,用户的角色信息集合,认证时使用。

6、AuthorizationInfo,角色的权限信息集合,授权时使用。

7、DefaultWebSecurityManager,安全管理器,开发者自定义的 Realm 需要注入到 DefaultWebSecurityManager 进行管理才能生效。

8、ShiroFilterFactoryBean,过滤器工厂,Shiro 的基本运行机制是开发者定制规则,Shiro 去执行,具体的执行操作就是由 ShiroFilterFactoryBean 创建一个个 Filter 对象来完成。

Shiro 的运行机制如下图所示。

Shiro 整合 Spring Boot

1、我们使用 Spring Boot 集成 Shiro 的方式快速构建工程,创建 Spring Boot Initializr 工程,使用最新版的 Spring Boot 2.3.0。

2、选择需要添加的 dependencies 依赖。

3、我们会发现 Spring Boot 官方的 Security 依赖库中并没有 Shiro,而是其他的框架。

也就是说 Spring Boot 官方并没有纳入 Shiro,怎么解决?很简单,官方不提供支持,我们就自己手动在 pom.xml 中添加依赖,如下所示,我们全部选择最新版。

<!-- Shiro整合Spring -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.5.3</version>
</dependency>


自定义 Shiro 过滤器

对 URL 进行拦截,没有认证的需要认证,认证成功的则可以根据需要判断角色及权限。

这个过滤器需要开发者自定义,然后去指定认证和授权的逻辑,继承抽象类 AuthorizingRealm,实现两个抽象方法分别完成授权和认证的逻辑。

首先来完成认证的逻辑,需要连接数据库,这里我们使用 MyBatis Plus 来完成,pom.xml 中添加 MyBatis Plus 依赖,如下所示。

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.20</version>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.3.1.tmp</version>
</dependency>

创建数据表 account,添加两条记录,SQL 如下所。

CREATE TABLE `account` (
  `id` int NOT NULL AUTO_INCREMENT,
  `username` varchar(20) DEFAULT NULL,
  `password` varchar(20) DEFAULT NULL,
  `perms` varchar(20) DEFAULT NULL,
  `role` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
LOCK TABLES `account` WRITE;
INSERT INTO `account` VALUES (1,'zs','123123','',''),(2,'ls','123123','manage',''),(3,'ww','123123','manage','administrator');
UNLOCK TABLES;

创建实体类 Account。

@Data
public class Account {
    private Integer id;
    private String username;
    private String password;
    private String perms;
    private String role;
}

创建 AccountMapper 接口。

public interface AccountMapper extends BaseMapper<Account> {
}

创建 application.yml。

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/test
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

启动类添加 @MapperScan 注解扫描 Mapper 接口。

@SpringBootApplication
@MapperScan("com.southwind.springbootshirodemo.mapper")
public class SpringbootshirodemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringbootshirodemoApplication.class, args);
    }
}

首先通过单元测试调试 AccoutMapper 接口。

@SpringBootTest
class AccountMapperTest {

    @Autowired
    private AccountMapper accountMapper;

    @Test
    void test(){
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("username","user");
        Account account = accountMapper.selectOne(wrapper);
        System.out.println(account);
    }
}

返回上图表示调试成功,MyBatis Plus 调试成功,接下来完成 Service 层代码编写。

public interface AccountService {
    public Account findByUsername(String username);
}
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountMapper accountMapper;

    @Override
    public Account findByUsername(String username) {
        QueryWrapper wrapper = new QueryWrapper();
        wrapper.eq("username",username);
        return accountMapper.selectOne(wrapper);
    }
}

回到 Shiro 完成用户认证,在 MyRealm 中完成代码的编写。

public class MyRealm extends AuthorizingRealm {

    @Autowired
    private AccountService accountService;

    /**
     * 授权
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /**
     * 认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        Account account = accountService.findByUsername(token.getUsername());
        if(account != null){
            return new SimpleAuthenticationInfo(account,account.getPassword(),getName());
        }
        return null;
    }
}

客户端传来的 username 和 password 会自动封装到 token,先根据 username 进行查询,如果返回 null,则表示用户名错误,直接 return null 即可,Shiro 会自动抛出 UnknownAccountException 异常。

如果返回不为 null,则表示用户名正确,再验证密码,直接返回 SimpleAuthenticationInfo 对象即可,如果密码验证成功,Shiro 认证通过,否则返回 IncorrectCredentialsException 异常。

自定义过滤器创建完成之后,需要进行配置才能生效,在 Spring Boot 应用中,不需要任何的 XML 配置,直接通过配置类进行装配,代码如下所示。

@Configuration
public class ShiroConfig {

    @Bean
    public ShiroFilterFactoryBean filterFactoryBean(@Qualifier("manager") DefaultWebSecurityManager manager){
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(manager);
        return factoryBean;
    }


    @Bean
    public DefaultWebSecurityManager manager(@Qualifier("myRealm") MyRealm myRealm){
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(myRealm);
        return manager;
    }

    @Bean
    public MyRealm myRealm(){
        return new MyRealm();
    }
}

这个配置类中一共自动装配了 3 个 bean 实例,第一个是自定义过滤器 MyRealm,我们的业务逻辑全部定义在这个 bean 中。

然后需要创建第二个 bean 示例 DefaultWebSecurityManager,并且将 MyRealm 注入到 DefaultWebSecurityManager bean 中,完成注册。

最终需要装配第三个 bean ShiroFilterFactoryBean,这是 Shiro 自带的一个 Filter 工厂实例,所有的认证和授权判断都是由这个 bean 生成的 Filter 对象来完成的,这就是 Shiro 框架的运行机制,开发者只需要定义规则,进行配置,具体的执行者全部由 Shiro 自己创建的 Filter 来完成。

所以我们需要给 ShiroFilterFactoryBean 实例注入认证及授权规则,如下所示。

认证过滤器:anon:无需认证即可访问,游客身份。
authc:必须认证(登录)才能访问。
authcBasic:需要通过 httpBasic 认证。
user:不一定已通过认证,只要是曾经被 Shiro 记住过登录状态的用户就可以正常发起请求,比如 rememberMe。

授权过滤器:
perms:必须拥有对某个资源的访问权限(授权)才能访问。
role:必须拥有某个角色权限才能访问。
port:请求的端口必须为指定值才可以访问。
rest:请求必须是 RESTful,method 为 post、get、delete、put。
ssl:必须是安全的 URL 请求,协议为 HTTPS。

比如,我们创建三个页面,main.html、manage.html、administrator.html,要求如下:

1、必须是登录状态才可以访问 main.html。

2、用户必须拥有 manage 授权才可以访问 manage.html。

3、用户必须拥有 administrator 角色才能访问 administrator.html。

代码如下所示。

@Bean
public ShiroFilterFactoryBean filterFactoryBean(@Qualifier("manager") DefaultWebSecurityManager manager){
    ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
    factoryBean.setSecurityManager(manager);
    Map<String,String> map = new HashMap<>();
    map.put("/main","authc");
    map.put("/manage","perms[manage]");
    map.put("/administrator","roles[administrator]");
    factoryBean.setFilterChainDefinitionMap(map);
    //设置登录页面
    factoryBean.setLoginUrl("/login");
    //未授权页面
    factoryBean.setUnauthorizedUrl("/unauth");
    return factoryBean;
}

Controller 如下所示。

@Controller
public class MyController {

    @GetMapping("/{url}")
    public String redirect(@PathVariable("url") String url){
        return url;
    }

    @PostMapping("/login")
    public String login(String username, String password, Model model){
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username,password);
        try {
            subject.login(token);
            return "index";
        } catch (UnknownAccountException e) {
            model.addAttribute("msg","用户名错误");
            return "login";
        } catch (IncorrectCredentialsException e) {
            model.addAttribute("msg", "密码错误");
            return "login";
        }
    }

    @RequestMapping("/unauth")
    @ResponseBody
    public String unauth(){
        return "未授权没有访问权限";
    }
}

现在只需要登录就可以访问 main.html,但是无法访问 manage.html,这是因为没有授权,接下来我们完成授权操作,回到 MyRealm,代码如下所示。

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    //获取当前登录对象
    Subject subject = SecurityUtils.getSubject();
    Account account = (Account) subject.getPrincipal();

    //设置角色
    Set<String> roles = new HashSet<>();
    roles.add(account.getRole());
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);

    //设置权限
    info.addStringPermission(account.getPerms());
    return info;
}

数据库数据如下所示。

zs 没有权限和角色,所以登录之后只能访问 main.html。

ls 拥有 manage 权限,没有角色,所以登录之后可以访问 main.html、manage.html。

ww 拥有 manage 权限和 administrator 角色,所以登录之后可以访问 main.html、manage.html、administrator.html。


Shiro 整合 Thymeleaf

1、pom.xml 中引入依赖。

<!-- Shiro整合Thymeleaf -->
<dependency>
    <groupId>com.github.theborakompanioni</groupId>
    <artifactId>thymeleaf-extras-shiro</artifactId>
    <version>2.0.0</version>
</dependency>

2、配置类添加 ShiroDialect。

@Bean
public ShiroDialect shiroDialect(){
    return new ShiroDialect();
}

3、Controller 登录成功后将用户信息存入 session,同时添加退出操作。

@PostMapping("/login")
public String login(String username, String password, Model model){
    Subject subject = SecurityUtils.getSubject();
    UsernamePasswordToken token = new UsernamePasswordToken(username,password);
    try {
        subject.login(token);
        Account account = (Account) subject.getPrincipal();
        subject.getSession().setAttribute("account",account);
        return "index";
    } catch (UnknownAccountException e) {
        model.addAttribute("msg","用户名错误");
        return "login";
    } catch (IncorrectCredentialsException e) {
        model.addAttribute("msg", "密码错误");
        return "login";
    }
}

@GetMapping("/logout")
public String logout(){
    Subject subject = SecurityUtils.getSubject();
    subject.logout();
    return "login";
}

4、index.html。

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="shortcut icon" href="#"/>
</head>
<body>
    <div th:if="${session.account == null}">
        <a href="/login">login</a>
    </div>
    <div th:if="${session.account != null}">
        欢迎回来!<span th:text="${session.account.username}"></span><a href="/logout">退出</a>
        <div>
            <a href="/main">main</a>
        </div>

        <div shiro:hasPermission="manage">
            <a href="/manage">manage</a>
        </div>

        <div shiro:hasRole="administrator">
            <a href="/administrator">administrator</a>
        </div>
    </div>
</body>
</html>


相关推荐

机器学习框架TensorFlow入门(tensorflow框架详解)

ensorFlow是一个广泛使用的开源机器学习框架,由GoogleBrain团队开发。它支持广泛的机器学习和深度学习任务,并且可以在CPU和GPU上运行。下面是一个使用TensorF...

合肥高新区企业本源发布量子机器学习框架VQNet 开辟量子机器学习的新领域

近日,高新区企业合肥本源量子计算科技有限责任公司通过研究混合实现变分量子算法和经典机器学习框架的可能性,全新开发了量子机器学习框架VQNet,可满足构建所有类型的量子机器学习算法,实现量子-经典混合任...

如何使用 TensorFlow 构建机器学习模型

在这篇文章中,我将逐步讲解如何使用TensorFlow创建一个简单的机器学习模型。TensorFlow是一个由谷歌开发的库,并在2015年开源,它能使构建和训练机器学习模型变得简单。我们接下...

机器学习框架底层揭秘:PyTorch、TensorFlow 如何高效“跑模型”

在使用PyTorch或TensorFlow时,你是否想过:这些深度学习框架底层到底是怎么运行的?为什么我们一行.backward()就能自动计算梯度?本篇将用最简单的语言,拆解几个关键概念...

2 个月的面试亲身经历告诉大家,如何进入 BAT 等大厂?

这篇文章主要是从项目来讲的,所以,从以下几个方面展开。怎么介绍项目?怎么介绍项目难点与亮点?你负责的模块?怎么让面试官满意?怎么介绍项目?我在刚刚开始面试的时候,也遇到了这个问题,也是我第一个思考的问...

基于SpringBoot 的CMS系统,拿去开发企业官网真香(附源码)

前言推荐这个项目是因为使用手册部署手册非常完善,项目也有开发教程视频对小白非常贴心,接私活可以直接拿去二开非常舒服开源说明系统100%开源模块化开发模式,铭飞所开发的模块都发布到了maven中央库。可...

【网络安全】关于Apache Shiro权限绕过高危漏洞的 预警通报

近日,国家信息安全漏洞共享平台(CNVD)公布了深信服终端检测平台(EDR)远程命令执行高危漏洞,攻击者利用该漏洞可远程执行系统命令,获得目标服务器的权限。一、漏洞情况ApacheShiro是一个强...

开发企业官网就用这个基于SpringBoot的CMS系统,真香

前言推荐这个项目是因为使用手册部署手册非常完善,项目也有开发教程视频对小白非常贴心,接私活可以直接拿去二开非常舒服。开源说明系统100%开源模块化开发模式,铭飞所开发的模块都发布到了maven中央库。...

这款基于SpringBoot 的CMS系统,开发企业官网确实香(附源码)

前言推荐这个项目是因为使用手册部署手册非常完善,项目也有开发教程视频对小白非常贴心,接私活可以直接拿去二开非常舒服开源说明系统100%开源模块化开发模式,铭飞所开发的模块都发布到了maven中央库。可...

【推荐】一款基于BPM和代码生成器的 AI 低代码开源平台

如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!项目介绍JeecgBoot是一款基于BPM和代码生成器的AI低代码平台,专为Java企业级Web应用而生。它采...

云安全日报200819:Apache发现重要漏洞 可窃取信息 控制系统 需要尽快升级

ApacheHTTPServer(简称Apache)是Apache软件基金会的一个开放源码的网页服务器,可以在大多数计算机操作系统中运行,由于其多平台和安全性被广泛使用,是最流行的Web服务器端软...

基于jeecgboot框架的cloud商城源码分享,兼容单体和微服务模式

3年时间里,随着关注java单商户商城系统的朋友越来越多,对cloud版本的商城呼声也越来越高。因此今年立项了cloud版本的开发,目前已发gitee开源,目前也基本测试完毕,欢迎大家体验以及提出宝贵...

SpringBoot + Mybatis + Shiro + mysql + redis智能平台源码分享

后端技术栈基于SpringBoot+Mybatis+Shiro+mysql+redis构建的智慧云智能教育平台基于数据驱动视图的理念封装element-ui,即使没有vue的使...

我敢保证,全网没有再比这更详细的Java知识点总结了,送你啊

接下来你看到的将是全网最详细的Java知识点总结,全文分为三大部分:Java基础、Java框架、Java+云数据小编将为大家仔细讲解每大部分里面的详细知识点,别眨眼,从小白到大佬、零基础到精通,你绝...

基于Spring+SpringMVC+Mybatis分布式敏捷开发系统架构(附源码)

前言zheng项目不仅仅是一个开发架构,而是努力打造一套从前端模板-基础框架-分布式架构-开源项目-持续集成-自动化部署-系统监测-无缝升级的全方位J2EE企业级开发解...

取消回复欢迎 发表评论: