改造黑马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 的工作流程
- 用户请求进入系统
- Shiro 的 Filter 拦截请求
- Subject 代表用户提交认证
- SecurityManager 负责管理认证过程
- Realm 负责获取安全数据
- 认证/授权成功后允许访问
- 失败则拒绝访问
五、Shiro 的优势
-
简单性
- API 简单易用
- 学习曲线平缓
-
灵活性
- 可以自定义认证和授权逻辑
- 支持多种数据源
-
全面性
- 提供完整的安全解决方案
- 不仅限于 Web 应用
-
可扩展性
- 支持插件机制
- 可以自定义各种组件
六、与 Spring Security 的对比
-
复杂度
- Shiro 较为简单
- Spring Security 功能更丰富但较复杂
-
依赖性
- Shiro 可独立使用
- Spring Security 与 Spring 框架绑定
-
使用场景
- Shiro 适合小型项目
- Spring Security 适合大型企业级应用
-
学习成本
- 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()); }
二、验证流程
- 请求进入时,首先经过 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; } }
- 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; } }
- 认证信息传递给 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); } }
- 认证通过后进行授权检查:
▼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 (最基础) (角色级别) (最细粒度)
四、使用建议
- 普通用户功能使用 @RequiresAuthentication
- 管理功能使用 @RequiresRoles
- 特定操作使用 @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 权限配置
- 开放接口配置
通过这样的权限体系,可以实现:
- 基于 token 的用户认证
- 基于角色的权限控制
- 细粒度的操作权限管理
- 统一的异常处理
评论
问答助学
相关内容
0个评论
全部评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
作者分享
hutools被黑产收购了,请问有什么工具可以替代hutools吗?
2
想问一下,旧项目springboot2.2.4,jdk8升级到springboot3.x,jdk21。
主要问题是不太明白,只需要更换版本号,修改报错地方,比如javax换成Jakarta,其他报错语法,更换成正常运行即可。
然后成功运行,就算升级成功了吗?
主要是我不太了解升级框架是不是这个意思,如果不是能不能解释一下啥叫作项目升级,主要需求就是希望换成jdk21+springboot3+
2
如何反制网站的开发者工具限制 - disable-devtool 反制指南
6
现在的网站都使用disable-devtool来阻止打开开发者模式,如何阻止disable-devtool运行呢?
1
友友们,请问第一次入职有经验帖子吗?需要准备什么?提前学习什么知识?需要知道什么知识点呢?(Java开发岗)
2
