摘要:在当今互联网软件开发的大环境下,保障应用程序的安全性是至关重要的一环。对于众多使用 Spring Boot3 框架进行开发的项目而言,整合强大的安全框架 Apache Shiro 可以有效提升系统的安全性。其中,实现对未登录请求的拦截是保障系统资源不被非法访问
在当今互联网软件开发的大环境下,保障应用程序的安全性是至关重要的一环。对于众多使用 Spring Boot3 框架进行开发的项目而言,整合强大的安全框架 Apache Shiro 可以有效提升系统的安全性。其中,实现对未登录请求的拦截是保障系统资源不被非法访问的关键措施。本文将详细地为各位互联网软件开发人员讲解在 Spring Boot3 中整合 Apache Shiro 来拦截未登录请求的方法及相关原理。
Apache Shiro 是一个强大且易用的 java 安全框架,它提供了认证、授权、加密和会话管理等功能。在应用程序中,Shiro 主要通过以下三个核心组件协同工作来实现安全控制:
Subject:代表了当前用户的安全操作,它是应用程序与 Shiro 交互的主要接口。通过 Subject,应用代码可以进行认证和授权等操作。例如,当用户尝试登录时,我们通过 Subject 的 login 方法来执行登录操作。
SecurityManager:它是 Shiro 框架的核心,采用典型的 Facade 模式。Shiro 通过 SecurityManager 来管理内部组件实例,并提供安全管理的各种服务。在认证过程中,Subject 会委托给 SecurityManager 进行实际的认证逻辑处理。
Realm:Realm 充当了 Shiro 与应用安全数据间的 “桥梁” 或者 “连接器”。当对用户执行认证(登录)和授权(访问控制)验证时,Shiro 会从应用配置的 Realm 中查找用户及其权限信息。例如,我们可以在 Realm 中查询数据库,获取用户的正确密码用于登录校验,以及获取用户所拥有的角色和权限信息用于授权校验。
(一)添加依赖
在 Spring Boot3 项目中,我们首先需要在pom.xml文件中引入 Shiro 相关的依赖。如下是引入 Shiro Spring Boot Starter 的示例代码:
org.apache.shiroshiro-Spring-boot-starter1.12.0同时,根据项目的实际需求,可能还需要引入其他相关依赖,比如数据库驱动(如果需要从数据库获取用户信息和权限信息)、Spring Web 依赖等。
(二)创建 Shiro 配置类
接下来,我们需要创建一个 Shiro 的配置类,用于配置 Shiro 的核心组件和相关规则。在 Spring Boot 中,配置类通常使用@Configuration注解标识。以下是一个基本的 Shiro 配置类示例:
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;import org.apache.shiro.web.mgt.DefaultWebSecurityManager;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import java.util.LinkedHashMap;import java.util.Map;@Configurationpublic class ShiroConfig {// 配置核心安全事务管理器@Bean(name = "securityManager")public DefaultWebSecurityManager securityManager {return new DefaultWebSecurityManager;}// 配置Shiro过滤器链@Beanpublic ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {ShiroFilterFactoryBean factory = new ShiroFilterFactoryBean;factory.setSecurityManager(securityManager);// 设置登录页和未授权页factory.setLoginUrl("/login");factory.setUnauthorizedUrl("/403");// 定义过滤规则Map filterMap = new LinkedHashMap;filterMap.put("/static/**", "anon"); // 匿名访问静态资源filterMap.put("/login", "anon"); // 登录页无需认证filterMap.put("/admin/**", "roles(admin)"); // 需要admin角色才能访问filterMap.put("/**", "authc"); // 其他路径需要认证factory.setFilterChainDefinitionMap(filterMap);return factory;}}在上述配置类中,我们主要完成了以下几个关键配置:
创建SecurityManager:通过@Bean注解创建了一个DefaultWebSecurityManager实例,它将作为 Shiro 的安全管理器,负责管理整个应用的安全相关操作。
配置ShiroFilterFactoryBean:这是 Shiro 与 Web 应用集成的关键组件,它负责创建 Shiro 的过滤器链,拦截请求并执行相应的安全规则。
设置安全管理器:通过setSecurityManager方法将前面创建的DefaultWebSecurityManager实例设置给ShiroFilterFactoryBean。设置登录页和未授权页:使用setLoginUrl方法设置用户未登录时跳转到的登录页面路径,这里设置为/login;使用setUnauthorizedUrl方法设置用户没有权限访问资源时跳转到的未授权页面路径,这里设置为/403。定义过滤规则:通过FilterChainDefinitionMap来定义请求路径与过滤器之间的映射关系。例如,filterMap.put("/static/**", "anon")表示对/static/目录下的所有资源采用anon过滤器,即匿名用户可以直接访问,无需登录认证;filterMap.put("/admin/**", "roles(admin)")表示访问/admin/目录下的资源需要用户拥有admin角色;filterMap.put("/**", "authc")表示其他所有路径都需要用户进行认证才能访问。(一)常用拦截器介绍
Shiro 提供了一系列的拦截器,用于对请求进行不同类型的处理。以下是一些常用的拦截器及其功能说明:
anon:匿名拦截器,不需要登录即可访问的资源,常用于过滤静态资源,如图片、CSS、JavaScript 文件等。例如,配置/static/** = anon,则所有位于/static/目录下的资源都可以被匿名用户直接访问。authc:登录拦截器,需要用户进行认证登录才能访问的资源。当配置了/user/* = authc时,意味着访问/user/目录下的任何资源,用户都必须先登录。perms:权限授权拦截器,用于验证用户是否拥有特定的权限。比如filterMap.put("/user/update", "perms(user:update)"),表示用户必须拥有user:update权限才能访问/user/update路径。roles:角色授权拦截器,验证用户是否拥有指定的角色。例如filterMap.put("/admin/delete", "roles(admin)"),只有拥有admin角色的用户才能访问/admin/delete路径。logout:登出拦截器,主要用于处理用户的登出操作。可以通过设置redirectURL属性来指定用户登出后重定向的地址。(二)拦截器优先级
在配置多个拦截器时,了解它们的优先级是很重要的。不同的拦截器具有不同的优先级,例如anon拦截器优先级为 1,authc拦截器优先级为 2 等。当某个资源访问同时设置了多个拦截器时,Shiro 会按照优先级顺序依次执行拦截器。比如,对于一个请求路径同时配置了anon和authc拦截器,由于anon优先级更高,会先执行anon拦截器的逻辑,如果不满足anon的条件(即不是匿名可访问的资源),才会继续执行authc拦截器的逻辑。
(三)配置拦截器
在 Spring Boot 中,我们可以通过代码方式配置拦截器,就像前面的 Shiro 配置类中所展示的那样。在ShiroFilterFactoryBean中通过FilterChainDefinitionMap来设置路径与拦截器的对应关系。例如:
Map filterMap = new LinkedHashMap;filterMap.put("/logout", "logout");filterMap.put("/static/**", "anon");filterMap.put("/user/*", "authc");filterMap.put("/user/add", "perms(user:add)");filterMap.put("/admin/*", "roles(admin)");factory.setFilterChainDefinitionMap(filterMap);上述代码中,我们定义了不同路径对应的拦截器规则。同时,需要注意的是,在配置路径时可以使用通配符。其中,?匹配一个字符,*匹配零个或多个字符,**匹配零个或多个路径。例如,/user/*可以匹配/user/list、/user/detail等路径;/static/**可以匹配/static/css/style.css、/static/js/app.js等任何位于/static/目录及其子目录下的资源路径。
当用户发送一个请求到应用程序时,Shiro 对未登录请求的拦截流程如下:
请求进入 ShiroFilter:ShiroFilter 作为安全控制的入口点,会首先拦截所有进入应用的请求。匹配过滤规则:ShiroFilter 根据配置的FilterChainDefinitionMap,查找与当前请求路径匹配的过滤规则。例如,如果请求路径是/user/detail,ShiroFilter 会在FilterChainDefinitionMap中查找是否有以/user/detail或/user/*等类似模式匹配的规则。执行拦截器逻辑:假设匹配到的规则是authc,则会执行authc拦截器的逻辑。authc拦截器会检查当前用户是否已经登录。如果用户已经登录,则允许请求继续访问目标资源;如果用户未登录,则会根据配置跳转到登录页面。在前面的配置示例中,我们设置了factory.setLoginUrl("/login"),所以此时用户会被重定向到/login路径,提示用户进行登录操作。在实际项目中,我们通常需要从数据库或其他数据源获取用户的认证信息(如用户名和密码)以及授权信息(如角色和权限)。这就需要我们自定义 Realm 来实现 Shiro 的认证和授权逻辑。
(一)继承 AuthorizingRealm 类
我们创建一个自定义的 Realm 类,继承自AuthorizingRealm类。AuthorizingRealm类提供了认证和授权的抽象方法,我们需要重写这些方法来实现自己的业务逻辑。以下是一个简单的自定义 Realm 示例:
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.subject.PrincipalCollection;import org.springframework.beans.factory.annotation.Autowired;import java.util.Set;public class UserRealm extends AuthorizingRealm {@Autowiredprivate UserService userService;// 授权逻辑:获取用户权限信息@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String username = (String) principals.getPrimaryPrincipal;SimpleAuthorizationInfo info = new SimpleAuthorizationInfo;// 从数据库查询用户的角色和权限信息Set roles = userService.getUserRoles(username);Set permissions = userService.getUserPermissions(username);info.setRoles(roles);info.setStringPermissions(permissions);return info;}// 认证逻辑:验证用户身份@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String username = (String) token.getPrincipal;User user = userService.findByUsername(username);if (user == null) {throw new UnknownAccountException("用户不存在");}return new SimpleAuthenticationInfo(user.getUsername,user.getPassword,getName);}}在上述代码中:
授权方法doGetAuthorizationInfo:该方法在 Shiro 需要验证用户的权限时被调用。首先从PrincipalCollection中获取当前用户的用户名,然后通过调用UserService的方法从数据库中查询该用户所拥有的角色和权限信息。最后,将查询到的角色和权限信息设置到SimpleAuthorizationInfo对象中并返回。
认证方法doGetAuthenticationInfo:当用户尝试登录时,Shiro 会调用该方法进行认证。方法中首先从AuthenticationToken中获取用户名,然后通过UserService从数据库中查询该用户的信息。如果用户不存在,则抛出UnknownAccountException异常;如果用户存在,则创建一个SimpleAuthenticationInfo对象并返回,其中包含了用户的用户名、密码(从数据库中获取的正确密码)以及当前 Realm 的名称。
(二)将自定义 Realm 注入到 SecurityManager
完成自定义 Realm 的编写后,我们需要将其注入到SecurityManager中,以便 Shiro 在进行认证和授权时能够使用我们自定义的逻辑。在 Shiro 配置类中进行如下修改:
@Configurationpublic class ShiroConfig {// 创建自定义Realm@Beanpublic UserRealm userRealm {return new UserRealm;}// 配置核心安全事务管理器@Bean(name = "securityManager")public DefaultWebSecurityManager securityManager(UserRealm userRealm) {DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager;securityManager.setRealm(userRealm);return securityManager;}// 配置Shiro过滤器链@Beanpublic ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {// 省略其他代码,与前面相同}}通过上述配置,我们创建了UserRealm的 Bean 实例,并在securityManager的配置方法中,将UserRealm设置到DefaultWebSecurityManager中。这样,Shiro 在进行认证和授权操作时,就会使用我们自定义的UserRealm中的逻辑来处理用户的认证和授权请求。
通过以上步骤,我们详细地介绍了在 Spring Boot3 中整合 Apache Shiro 实现对未登录请求拦截的方法,包括 Shiro 框架的核心组件、整合的准备工作、拦截器的配置、拦截流程以及自定义 Realm 实现认证和授权等内容。在实际项目开发中,各位互联网软件开发人员可以根据项目的具体需求和业务场景,对 Shiro 的配置和自定义 Realm 进行灵活调整和扩展。
例如,在处理复杂的权限管理场景时,可以进一步优化自定义 Realm 中的授权逻辑,支持更细粒度的权限控制,如数据级别的权限控制。同时,还可以结合 Shiro 的缓存机制,提高系统的性能,减少对数据库的频繁查询。希望本文能够对大家在 Spring Boot3 项目中使用 Shiro 实现安全控制提供有力的帮助,让我们的应用程序在安全的道路上不断前行。
来源:从程序员到架构师一点号