Spring Boot整合Shiro,两种方式实战总结(含源码)

简介: Spring Boot整合Shiro,两种方式实战总结(含源码)

前言

Shiro是历史悠久的权限管理框,简单易用,易用集成,同时权限管理也是每个项目必不可少的功能。Spring Boot是Java领域炙手可热的脚手架框架。今天这篇文章就带大家将这两个框架进行整合。

通常Spring Boot中整合Shiro,有两种方案:第一,基于原生API进行整合;第二,基于Shiro官方Starter整合。

整体而言,官方Starter整合并没有方便很多,因此,本文主要以原则API进行整合,

下面就来看看具体的整合方式。

创建Spring Boot项目

创建Spring Boot项目通常有两种方式,一种是通过官网创建之后导入到IDE中。一种是通过Idea集成的Spring Boot进行创建。

这里以Idea创建为例,在IDEA中通过Spring Initializr创建项目,在引入依赖时引入Web依赖即可:image.png一路Next,项目创建完成。

引入Shiro依赖

项目创建完成之后,在pom.xml中加入Shiro相关的依赖。关于Shiro的版本信息大家需要留意,尽量使用1.6.0及以上版本。小于1.6.0的版本存在"绕过授权高危漏洞"Bug。

核心的pom依赖内容如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>${shiro.version}</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>${shiro.version}</version>
</dependency>

模拟用户查询功能

我们知道在Shiro中可以在Realm实现授权和认证等功能,我们这里采用从数据中查询用户信息,比对密码。所以需要提供一个UserService来实现该功能。

当然,这里只是示例,不建议大家直接在数据库中存储密码。

先创建实体类:

public class User {
    private String username;
    private String password;
    // 省略getter/setter
}

对应的Service:

@Service
public class UserService {
    public User getUserByUserName(String username) {
        // 模拟返回,生产中不建议直接返回明文密码
        User user = new User();
        user.setUsername("secbro");
        user.setPassword("123456");
        return user;
    }
}

自定义Realm并实现功能

这里我们自定义一个Realm,在其中进行认证,关于授权部分默认返回null,也就是不校验权限。具体的权限校验,可根据业务需求,角色权限等设计进行校验。

@Component("loginRealm")
public class LoginRealm extends AuthorizingRealm {
    @Resource
    private UserService userService;
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 这里不做授权校验
        return null;
    }
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 用户登录时传入的用户名称
        String username = (String) token.getPrincipal();
        String password = new String((char[]) token.getCredentials());
        // 根据用户名查询用户信息
        User user = userService.getUserByUserName(username);
        if (user == null) {
            throw new UnknownAccountException("账户不存在!");
        }
        if (!password.equals(user.getPassword())) {
            throw new UnknownAccountException("密码错误!");
        }
        return new SimpleAuthenticationInfo(token.getPrincipal(), password, getName());
    }
}

这里直接将LoginRealm通过@Component进行实例化,在doGetAuthenticationInfo方法中进行用户名和密码的认证。

其中核心业务逻辑就是根据用户登录时传入的AuthenticationToken,获得传入的用户名和密码。

然后根据用户名查询数据库存储的用户信息,如果不存在或密码不一致则抛出异常。

最后,返回一个SimpleAuthenticationInfo对象,其中携带用户名、密码信息。

这里我们可以看出来,AuthenticationToken对象中的Principal相当于用户身份(唯一ID),而Credentials相当于密码,用来验证身份的。

集成Shiro配置

创建ShiroConfig类,使用@Configuration注解标注:

@Configuration
public class ShiroConfig {
    /**
     * 此处参数内的Realm已经通过类似的@Compenent进行初始化了
     */
    @Bean
    SecurityManager securityManager(Realm loginRealm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(loginRealm);
        return manager;
    }
    @Bean
    ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
        bean.setSecurityManager(securityManager);
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/index");
        bean.setUnauthorizedUrl("/unauthorizedurl");
        Map<String, String> map = new LinkedHashMap<>();
        map.put("/doLogin", "anon");
        map.put("/**", "authc");
        bean.setFilterChainDefinitionMap(map);
        return bean;
    }
}

其中初始化SecurityManager时需要用到Realm,这里通过方法参数将前面实例的LoginRealm注入,创建SecurityManager时设置对应的Realm。

因为还需要对具体的Web请求进行拦截,因此还需要配置一个ShiroFilterFactoryBean,通过该类指定路径拦截规则,比如设置需要拦截的、不需要拦截的链接等。

其中,Map中配置了路径拦截规则,注意,要有序。

经过上面的配置,Shiro的集成已经完成。

测试案例

下面来写一个简单的Controller来验证一下Shiro的拦截功能。

@RestController
public class HelloController {
    @RequestMapping("/hello")
    public String hello() {
        System.out.println("Hello Shiro!");
        return "Hello Shiro!";
    }
    @RequestMapping("/login")
    public String toLogin() {
        return "please login!";
    }
    @RequestMapping("/doLogin")
    public void doLogin(String username, String password) {
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(new UsernamePasswordToken(username, password));
            System.out.println("用户:" + username + ",登录成功!");
        } catch (Exception e) {
            System.out.println("登录异常" + e.getMessage());
        }
    }
}

首先,直接访问/hello接口,由于未登录,直接跳转到/login当中,返回:

please login!

紧接着,访问/doLogin接口,传输用户名和密码,控制台打印:

用户:secbro,登录成功!

再次访问/hello接口,打印并返回:

Hello Shiro!

说明认证成功,并记录下认证成功的状态,后续可继续使用对应的功能了。

基于官方Starter整合

最后再提一下基于官方Starter的整合,其实整体来说只是把ShiroFilterFactoryBean的初始化从配置类移到了application.properties文件中。

首先,将shiro的依赖替换为starter的依赖:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-web-starter</artifactId>
</dependency>

然后,去掉ShiroConfig中关于ShiroFilterFactoryBean的实例化方法,改为ShiroFilterChainDefinition:

@Bean
ShiroFilterChainDefinition shiroFilterChainDefinition(){
    DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
    definition.addPathDefinition("/doLogin","anon");
    definition.addPathDefinition("/**","authc");
    return definition;
}

在application.properties文件中添加如下配置:

# 是否允许将sessionId放到cookie中
shiro.sessionManager.sessionIdCookieEnabled=true
# 是否允许将sessionId放到Url地址拦中
shiro.sessionManager.sessionIdUrlRewritingEnabled=true
# 访问未获授权的页面时,默认的跳转路径
shiro.unauthorizedUrl=/unauthorizedurl
# 开启shiro
shiro.web.enabled=true
# 登录成功的跳转页面
shiro.successUrl=/index
# 登录页面
shiro.loginUrl=/login

其他的配置和操作与上面的示例一样,不再赘述。

项目源码:https://github.com/secbr/shiro/tree/main/springboot-shiro

小结

学习Shiro的关键点其实在于理解Realm的使用场景和原理。而关于Shiro在Spring Boot中的集成,如果有不太理解的地方,可回顾一下单纯使用Shiro的示例。Spring Boot的集成只不过是将之前new创建配置项纳入了Spring容器的管理而已。

目录
相关文章
|
2月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
87 2
|
2月前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
12天前
|
Java 数据库连接 Maven
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
自动装配是现在面试中常考的一道面试题。本文基于最新的 SpringBoot 3.3.3 版本的源码来分析自动装配的原理,并在文未说明了SpringBoot2和SpringBoot3的自动装配源码中区别,以及面试回答的拿分核心话术。
最新版 | 深入剖析SpringBoot3源码——分析自动装配原理(面试常考)
|
22天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
107 13
|
30天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
1月前
|
存储 缓存 Java
Spring面试必问:手写Spring IoC 循环依赖底层源码剖析
在Spring框架中,IoC(Inversion of Control,控制反转)是一个核心概念,它允许容器管理对象的生命周期和依赖关系。然而,在实际应用中,我们可能会遇到对象间的循环依赖问题。本文将深入探讨Spring如何解决IoC中的循环依赖问题,并通过手写源码的方式,让你对其底层原理有一个全新的认识。
54 2
|
2月前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
71 9
|
2月前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
52 2
|
2月前
|
JavaScript Java 项目管理
Java毕设学习 基于SpringBoot + Vue 的医院管理系统 持续给大家寻找Java毕设学习项目(附源码)
基于SpringBoot + Vue的医院管理系统,涵盖医院、患者、挂号、药物、检查、病床、排班管理和数据分析等功能。开发工具为IDEA和HBuilder X,环境需配置jdk8、Node.js14、MySQL8。文末提供源码下载链接。
|
3月前
|
自然语言处理 Java API
Spring Boot 接入大模型实战:通义千问赋能智能应用快速构建
【10月更文挑战第23天】在人工智能(AI)技术飞速发展的今天,大模型如通义千问(阿里云推出的生成式对话引擎)等已成为推动智能应用创新的重要力量。然而,对于许多开发者而言,如何高效、便捷地接入这些大模型并构建出功能丰富的智能应用仍是一个挑战。
343 6