改造黑马Springboot3Vue3课程项目BigEvent-集成Shiro权限控制-轻松实现权限控制

BigEvent-集成Shiro权限控制-轻松实现权限控制

gitee地址:仓库地址

技术栈

后端技术

  • Spring Boot 3.2.1: 作为主要开发框架
  • Java 21: 开发语言
  • Apache Shiro: 安全框架,用于身份认证与授权
  • MyBatis-Plus 3.5.9: ORM框架,简化数据库操作
  • PostgreSQL: 主要数据库

安全认证

  • JWT (java-jwt 4.4.0): 用于生成和验证令牌

API 文档

  • Knife4j 4.3.0: API文档生成工具,基于Swagger

工具库

  • Hutool 5.8.10: Java工具类库

代码生成

  • MyBatis-Plus Generator: 用于生成基础代码
  • Freemarker: 模板引擎

开发环境要求

  • JDK 21+
  • Maven 3.6+
  • PostgreSQL 12+
  • IDE 推荐: IntelliJ IDEA

项目结构

text
复制代码
src/main/java/com/code/springboot3/ ├── config/ // 配置类 ├── controller/ // 控制器 ├── service/ // 服务层 ├── model/ // 数据模型 ├── common/ // 公共组件 ├── utils/ // 工具类 └── mapper/ // 数据访问层

Shiro 介绍

一、什么是 Shiro?

Apache Shiro 是一个功能强大且灵活的开源安全框架,可以清晰地处理身份验证(Authentication)、授权(Authorization)、会话管理(Session Management)和加密(Cryptography)。

二、Shiro 的核心功能

1. 身份验证 (Authentication)

  • 验证用户身份
  • 支持多种验证方式(用户名密码、JWT token等)
  • 在本项目中使用 JWT token 进行身份验证
java
复制代码
@Override protected boolean executeLogin(ServletRequest request, ServletResponse response) { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String token = httpServletRequest.getHeader("Authorization"); JwtToken jwtToken = new JwtToken(token); // 执行登录验证 getSubject(request, response).login(jwtToken); return true; }

2. 授权 (Authorization)

  • 基于角色的访问控制
  • 基于权限的访问控制
  • 支持注解式权限控制
java
复制代码
// 角色验证 @RequiresRoles("admin") public void adminMethod() { ... } // 权限验证 @RequiresPermissions("user:create") public void createUser() { ... }

3. 会话管理 (Session Management)

  • 创建和管理用户会话数据
  • 支持集群环境下的会话共享
  • 会话超时和失效处理

4. 加密 (Cryptography)

  • 提供加密和解密功能
  • 散列算法支持
  • 密码加密和验证

三、Shiro 的核心组件

1. Subject

  • 当前操作用户
  • 可以是人,也可以是第三方服务
  • 所有 Subject 都绑定到 SecurityManager

2. SecurityManager

  • Shiro 的核心组件
  • 管理所有 Subject
  • 负责进行认证和授权
java
复制代码
@Bean public DefaultWebSecurityManager securityManager(JwtRealm jwtRealm) { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(jwtRealm); return securityManager; }

3. Realm

  • 连接 Shiro 和实际安全数据的桥梁
  • 负责从数据源获取数据
  • 可以定制认证和授权逻辑
java
复制代码
public class JwtRealm extends AuthorizingRealm { // 认证 protected AuthenticationInfo doGetAuthenticationInfo(...) { // 验证token,返回认证信息 } // 授权 protected AuthorizationInfo doGetAuthorizationInfo(...) { // 获取用户角色和权限信息 } }

四、Shiro 的工作流程

  1. 用户请求进入系统
  2. Shiro 的 Filter 拦截请求
  3. Subject 代表用户提交认证
  4. SecurityManager 负责管理认证过程
  5. Realm 负责获取安全数据
  6. 认证/授权成功后允许访问
  7. 失败则拒绝访问

五、Shiro 的优势

  1. 简单性

    • API 简单易用
    • 学习曲线平缓
  2. 灵活性

    • 可以自定义认证和授权逻辑
    • 支持多种数据源
  3. 全面性

    • 提供完整的安全解决方案
    • 不仅限于 Web 应用
  4. 可扩展性

    • 支持插件机制
    • 可以自定义各种组件

六、与 Spring Security 的对比

  1. 复杂度

    • Shiro 较为简单
    • Spring Security 功能更丰富但较复杂
  2. 依赖性

    • Shiro 可独立使用
    • Spring Security 与 Spring 框架绑定
  3. 使用场景

    • Shiro 适合小型项目
    • Spring Security 适合大型企业级应用
  4. 学习成本

    • Shiro 学习曲线较平缓
    • Spring Security 学习成本较高

项目认证流程

一、权限验证注解

Shiro 提供了三种主要的验证注解:

1. @RequiresAuthentication

  • 最基础的认证注解
  • 只检查用户是否已登录
  • 不关心用户角色和权限
  • 适用于个人信息查看等基础功能

示例:

java
复制代码
@RequiresAuthentication @PostMapping("/userInfo") public Result<UserVo> getUserInfo(HttpServletRequest request) { String token = request.getHeader("Authorization"); return bigUserService.getUserInfo(token); }

2. @RequiresRoles

  • 检查用户是否具有特定角色
  • 可以要求单个或多个角色
  • 支持角色之间的逻辑关系(AND/OR)
  • 适用于角色级别的权限控制

示例:

java
复制代码
// 单个角色 @RequiresRoles("admin") @PostMapping("/assignRole") public Result<Void> assignRole(@RequestBody RoleAssignDTO dto) { bigUserService.assignRole(dto); return Result.success(); } // 多个角色(AND关系) @RequiresRoles(value={"admin", "editor"}, logical=Logical.AND) @PostMapping("/someApi") public Result<Void> someMethod() { return Result.success(); }

3. @RequiresPermissions

  • 最细粒度的权限控制
  • 可以控制具体的操作权限
  • 同样支持多个权限和逻辑关系
  • 适用于具体功能的权限控制

示例:

java
复制代码
@RequiresPermissions("user:manage") @GetMapping("/list") public Result<List<UserVo>> getUserList() { return Result.success(bigUserService.getUserList()); }

二、验证流程

  1. 请求进入时,首先经过 JwtFilter:
java
复制代码
@Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { try { if (isLoginAttempt(request, response)) { System.out.println("=== Access Check Start ==="); System.out.println("Request URI: " + ((HttpServletRequest) request).getRequestURI()); boolean result = executeLogin(request, response); System.out.println("Login result: " + result); System.out.println("=== Access Check End ==="); return result; } return false; } catch (Exception e) { System.out.println("Access check failed: " + e.getMessage()); return false; } }
  1. JwtFilter 验证 token 并创建认证对象:
java
复制代码
@Override protected boolean executeLogin(ServletRequest request, ServletResponse response) { try { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String token = httpServletRequest.getHeader("Authorization"); System.out.println("Executing login with token: " + token); JwtToken jwtToken = new JwtToken(token); getSubject(request, response).login(jwtToken); return true; } catch (Exception e) { System.out.println("Login failed: " + e.getMessage()); return false; } }
  1. 认证信息传递给 JwtRealm 进行认证:
java
复制代码
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String jwtToken = (String) token.getPrincipal(); try { System.out.println("Authenticating token in realm"); Map<String, Object> claims = JwtUtil.verifyToken(jwtToken); Integer userId = (Integer) claims.get("id"); Long tokenPwdVersion = JwtUtil.getPasswordVersion(jwtToken); BigUser user = bigUserMapper.selectById(userId); System.out.println("User found: " + (user != null)); if (user == null || !user.getPwdVersion().equals(tokenPwdVersion)) { throw new AuthenticationException("Token invalid or expired"); } return new SimpleAuthenticationInfo(user, jwtToken, getName()); } catch (Exception e) { System.out.println("Authentication failed: " + e.getMessage()); throw new AuthenticationException("Token verification failed", e); } }
  1. 认证通过后进行授权检查:
java
复制代码
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { BigUser user = (BigUser) principals.getPrimaryPrincipal(); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); System.out.println("=== Authorization Process Start ==="); System.out.println("User ID: " + user.getId()); System.out.println("Username: " + user.getUsername()); System.out.println("Required roles: admin, abcd"); System.out.println("User actual roles: " + Arrays.toString(user.getBigRole())); if (user.getBigRole() != null) { Set<String> roles = new HashSet<>(Arrays.asList(user.getBigRole())); info.setRoles(roles); System.out.println("Roles set in AuthorizationInfo: " + roles); } System.out.println("=== Authorization Process End ==="); return info; }

三、权限等级关系

text
复制代码
@RequiresAuthentication < @RequiresRoles < @RequiresPermissions (最基础) (角色级别) (最细粒度)

四、使用建议

  1. 普通用户功能使用 @RequiresAuthentication
  2. 管理功能使用 @RequiresRoles
  3. 特定操作使用 @RequiresPermissions

五、异常处理

项目使用全局异常处理器处理权限相关异常:

72:82:src/main/java/com/code/springboot3/common/GlobalExceptionHandler.java
复制代码
@ExceptionHandler(UnauthorizedException.class) @ResponseStatus(HttpStatus.FORBIDDEN) public Result<Void> handleUnauthorizedException(UnauthorizedException e) { return Result.failure("没有相关权限"); } @ExceptionHandler(UnauthenticatedException.class) @ResponseStatus(HttpStatus.UNAUTHORIZED) public Result<Void> handleUnauthenticatedException(UnauthenticatedException e) { return Result.failure("请先登录"); }

这样可以统一处理:

  • 未登录异常(UnauthenticatedException)
  • 未授权异常(UnauthorizedException)
  • 返回统一的错误信息格式

六、配置说明

Shiro 的核心配置在 ShiroConfig 类中:

java
复制代码
@Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean(); filterFactoryBean.setSecurityManager(securityManager); // 添加自定义过滤器 Map<String, Filter> filters = new LinkedHashMap<>(); filters.put("jwt", new JwtFilter()); filterFactoryBean.setFilters(filters); // 使用LinkedHashMap保证顺序 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 公共接口 filterChainDefinitionMap.put("/user/login", "anon"); filterChainDefinitionMap.put("/user/register", "anon"); filterChainDefinitionMap.put("/doc.html", "anon"); filterChainDefinitionMap.put("/webjars/**", "anon"); filterChainDefinitionMap.put("/v3/**", "anon"); filterChainDefinitionMap.put("/swagger-resources/**", "anon"); // 其他所有接口需要认证 filterChainDefinitionMap.put("/**", "jwt"); filterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return filterFactoryBean; }

主要配置了:

  • JWT 过滤器
  • 安全管理器
  • URL 权限配置
  • 开放接口配置

通过这样的权限体系,可以实现:

  1. 基于 token 的用户认证
  2. 基于角色的权限控制
  3. 细粒度的操作权限管理
  4. 统一的异常处理
0个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
安逸
下载 APP