From 02e40a667b8b533c32645886526a21817dde4bc0 Mon Sep 17 00:00:00 2001 From: suyiiyii Date: Sat, 24 Aug 2024 22:53:25 +0800 Subject: [PATCH] =?UTF-8?q?feat(auth):=20=E5=AE=9E=E7=8E=B0RBAC=EF=BC=8C?= =?UTF-8?q?=E8=B0=83=E6=95=B4JWT=E9=AA=8C=E8=AF=81=EF=BC=8C=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E7=94=A8=E6=88=B7=E6=9C=8D=E5=8A=A1=E5=92=8C=E6=8E=A7?= =?UTF-8?q?=E5=88=B6=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sims/common/InterceptorConfig.java | 42 ++---------- .../suyiiyii/sims/common/JwtInterceptor.java | 60 +++++------------ .../suyiiyii/sims/common/RbacInterceptor.java | 64 +++++++++++++++++++ .../sims/controller/UserController.java | 2 + .../java/top/suyiiyii/sims/entity/Role.java | 3 + .../top/suyiiyii/sims/mapper/UserMapper.java | 12 ++-- .../suyiiyii/sims/service/RbacService.java | 14 ++++ .../suyiiyii/sims/service/UserService.java | 15 +++-- src/main/resources/application.yaml | 5 +- .../sims/service/RbacServiceTest.java | 33 ++++++++++ 10 files changed, 155 insertions(+), 95 deletions(-) create mode 100644 src/main/java/top/suyiiyii/sims/common/RbacInterceptor.java create mode 100644 src/test/java/top/suyiiyii/sims/service/RbacServiceTest.java diff --git a/src/main/java/top/suyiiyii/sims/common/InterceptorConfig.java b/src/main/java/top/suyiiyii/sims/common/InterceptorConfig.java index 0332375..ee3e9ac 100644 --- a/src/main/java/top/suyiiyii/sims/common/InterceptorConfig.java +++ b/src/main/java/top/suyiiyii/sims/common/InterceptorConfig.java @@ -1,14 +1,10 @@ package top.suyiiyii.sims.common; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; -import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport; import top.suyiiyii.sims.service.RoleService; -import top.suyiiyii.sims.utils.JwtUtils; /** * @Author tortoise @@ -26,50 +22,20 @@ public class InterceptorConfig extends WebMvcConfigurationSupport { @Autowired private JwtInterceptor jwtInterceptor; + @Autowired + private RbacInterceptor rbacInterceptor; @Override protected void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(jwtInterceptor) .addPathPatterns("/**") .excludePathPatterns("/v3/api-docs/**"); + registry.addInterceptor(rbacInterceptor) + .excludePathPatterns("/v3/api-docs/**");; - // 注册AdminInterceptor,只拦截以admin/开头的路径 - registry.addInterceptor(new AdminInterceptor()) - .addPathPatterns("/admin/**"); super.addInterceptors(registry); } - // AdminInterceptor的实现 - public class AdminInterceptor implements HandlerInterceptor { - @Override - public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { - String path = request.getRequestURI(); - if (path.startsWith("/admin/") && !hasAdminPermission(request)) { - // 如果用户没有管理员权限,返回403 Forbidden - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - return false; - } - return true; - } - - private boolean hasAdminPermission(HttpServletRequest request) { - // 这里应该实现检查用户权限的逻辑 - // 例如,从session、token或者数据库中获取用户信息并判断权限 - // 以下仅为示例 - String token = (String) request.getAttribute("token"); - //非空 - if (token == null) { - return false; - } - try { - Integer userId = Integer.valueOf(JwtUtils.extractUserId(token)); - return roleService.isRoleNameAdmin(userId); - } catch (Exception e) { - // 处理令牌解析过程中可能出现的异常 - return false; - } - } - } } diff --git a/src/main/java/top/suyiiyii/sims/common/JwtInterceptor.java b/src/main/java/top/suyiiyii/sims/common/JwtInterceptor.java index 4a93347..bfcc7b0 100644 --- a/src/main/java/top/suyiiyii/sims/common/JwtInterceptor.java +++ b/src/main/java/top/suyiiyii/sims/common/JwtInterceptor.java @@ -1,19 +1,15 @@ package top.suyiiyii.sims.common; -import cn.hutool.core.util.StrUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.HandlerInterceptor; -import top.suyiiyii.sims.entity.User; import top.suyiiyii.sims.exception.ServiceException; import top.suyiiyii.sims.mapper.MpUserMapper; import top.suyiiyii.sims.utils.JwtUtils; -import java.util.List; - /** * @Author tortoise * @Date 2024/8/12 11:33 @@ -26,8 +22,8 @@ import java.util.List; @Component public class JwtInterceptor implements HandlerInterceptor { - @Autowired - MpUserMapper userMapper; + @Value("${jwt.secret}") + private String secret; @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { @@ -37,46 +33,22 @@ public class JwtInterceptor implements HandlerInterceptor { // 从 Authorization 头中获取 token String token = request.getHeader("Authorization"); if (token != null && token.startsWith("Bearer ")) { - token = token.substring(7); // 去除 "Bearer " 前缀 + token = token.substring(7); } else { - // 如果 Authorization 头中没有 token,则尝试从请求参数中获取 - token = request.getParameter("token"); + // 如果没有有效的token,设置userId为-1,表示未登录 + request.setAttribute("userId", -1); + return true; } - List allowRoles = null; - // 如果不是映射到方法直接通过 - if (handler instanceof HandlerMethod) { - AuthAccess annotation = ((HandlerMethod) handler).getMethodAnnotation(AuthAccess.class); - if (annotation != null) { - allowRoles = List.of(annotation.allowRoles()); - } + // 验证 token 的有效性 + if (!JwtUtils.verifyToken(token, secret)) { + throw new ServiceException("401", "登录已过期,请重新登录"); } -// // 执行认证 -// if (StrUtil.isBlank(token)) { -// //权限错误 -// throw new ServiceException("401", "请登录"); -// } -// // 获取 token 中的 user id -// String userId = JwtUtils.extractUserId(token); -// if (userId == null) { -// throw new ServiceException("401", "请登录"); -// } -// -// User user = userMapper.selectById(Integer.parseInt(userId)); -// if (user == null) { -// throw new ServiceException("401", "请登录"); -// } -// // 验证 token 的有效性 -// if (!JwtUtils.verifyToken(token, user.getPassword())) { -// throw new ServiceException("401", "请登录"); -// } - // 验证token后,如果一切正常,将token存储到request的属性中 - request.setAttribute("token", token); - if (allowRoles != null && !allowRoles.isEmpty()) { - if (allowRoles.contains("guest")) { - return true; - } - } - throw new ServiceException("403", "权限不足"); + + // 获取 token 中的 user id + String userId = JwtUtils.extractUserId(token); + + request.setAttribute("userId", userId); + return true; } } diff --git a/src/main/java/top/suyiiyii/sims/common/RbacInterceptor.java b/src/main/java/top/suyiiyii/sims/common/RbacInterceptor.java new file mode 100644 index 0000000..35ec723 --- /dev/null +++ b/src/main/java/top/suyiiyii/sims/common/RbacInterceptor.java @@ -0,0 +1,64 @@ +package top.suyiiyii.sims.common; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.method.HandlerMethod; +import org.springframework.web.servlet.HandlerInterceptor; +import top.suyiiyii.sims.entity.Role; +import top.suyiiyii.sims.exception.ServiceException; +import top.suyiiyii.sims.service.RbacService; + +import java.util.List; + +/** + * Rbac 拦截器 + * 从请求对象中获取用户信息,然后判断用户是否有权限访问当前路径 + */ +@Component +public class RbacInterceptor implements HandlerInterceptor { + + @Autowired + RbacService rbacService; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + if ("/error".equals(request.getRequestURI())) { + return true; + } + // 获取用户角色 + List roles = getUserRole(request).stream().map(Role::getRoleName).toList(); + + List allowRoles = null; + + // 获取当前请求的方法上的 AuthAccess 注解,从而获取允许访问的角色 + if (handler instanceof HandlerMethod) { + AuthAccess annotation = ((HandlerMethod) handler).getMethodAnnotation(AuthAccess.class); + if (annotation != null) { + allowRoles = List.of(annotation.allowRoles()); + } + } + + if (allowRoles != null && !allowRoles.isEmpty()) { + if (allowRoles.contains("guest")) { + return true; + } + for (String role : roles) { + if (allowRoles.contains(role)) { + return true; + } + } + } + throw new ServiceException("403", "权限不足"); + } + + private List getUserRole(HttpServletRequest request) { + Integer UserId = (Integer) request.getAttribute("userId"); + if (UserId == null || UserId == -1) { + return List.of(Role.guest()); + } + return rbacService.getRolesByUserId(UserId); + } + +} diff --git a/src/main/java/top/suyiiyii/sims/controller/UserController.java b/src/main/java/top/suyiiyii/sims/controller/UserController.java index 3f1a50d..d6b610a 100644 --- a/src/main/java/top/suyiiyii/sims/controller/UserController.java +++ b/src/main/java/top/suyiiyii/sims/controller/UserController.java @@ -78,6 +78,7 @@ public class UserController { User user = new User(); user.setUsername(request.getUsername()); user.setPassword(request.getPassword()); + user.setStudentId(request.getStudentId()); user.setEmail(request.getEmail()); user.setGrade(request.getGrade()); user.setUserGroup(request.getGroup()); @@ -121,6 +122,7 @@ public class UserController { public static class RegisterRequest { private String username; private String password; + private int studentId; private String email; private String grade; private String group; diff --git a/src/main/java/top/suyiiyii/sims/entity/Role.java b/src/main/java/top/suyiiyii/sims/entity/Role.java index 78897ed..69fe543 100644 --- a/src/main/java/top/suyiiyii/sims/entity/Role.java +++ b/src/main/java/top/suyiiyii/sims/entity/Role.java @@ -26,4 +26,7 @@ public class Role { //管理员,普通用户,组员,组长,队长 private String roleName; + public static Role guest() { + return new Role(-1, -1, "guest"); + } } diff --git a/src/main/java/top/suyiiyii/sims/mapper/UserMapper.java b/src/main/java/top/suyiiyii/sims/mapper/UserMapper.java index 1aef24f..7346c84 100644 --- a/src/main/java/top/suyiiyii/sims/mapper/UserMapper.java +++ b/src/main/java/top/suyiiyii/sims/mapper/UserMapper.java @@ -22,7 +22,7 @@ public interface UserMapper extends BaseMapper { * @param user 新用户对象 * @return 影响的行数 */ - @Insert("insert INTO user (id,student_id, username, password, name, email, userGroup) VALUES (#{id},#{studentId}, #{username}, #{password}, #{name}, #{email}, #{userGroup})") + @Insert("insert INTO user (id,student_id, username, password, username, email, user_group) VALUES (#{id},#{studentId}, #{username}, #{password}, #{name}, #{email}, #{userGroup})") int addUser(User user); /** @@ -41,10 +41,10 @@ public interface UserMapper extends BaseMapper { @Update("UPDATE user SET " + "student_id = #{userId}, " + "username = #{username}, " + - "name = #{name}, " + + "username = #{name}, " + "email = #{email}, " + "grade = #{grade}, " + - "userGroup = #{group} " + + "user_group = #{group} " + "WHERE id = #{id}") int updateUser(User user); @@ -53,7 +53,7 @@ public interface UserMapper extends BaseMapper { * @param * @return 用户对象 */ - @Select("SELECT id, student_id, username, password, name, email,grade,user_group from user WHERE student_id = #{id}") + @Select("SELECT id, student_id, username, password, username, email,grade,user_group from user WHERE student_id = #{id}") User selectByUserId(Integer id); /** @@ -61,13 +61,13 @@ public interface UserMapper extends BaseMapper { * @param * @return 用户对象 */ - @Select("SELECT id, student_id, username, password, name, email,grade, user_group from user WHERE id = #{id}") + @Select("SELECT id, student_id, username, password, username, email,grade, user_group from user WHERE id = #{id}") User selectById(Integer id); /** * 查询所有用户信息 * @return 用户列表 */ - @Select("SELECT id, student_id, username, password, name, email, grade, user_group FROM user") + @Select("SELECT id, student_id, username, password, username, email, grade, user_group FROM user") List selectAll(); @Select("select * from user where username = #{username}") diff --git a/src/main/java/top/suyiiyii/sims/service/RbacService.java b/src/main/java/top/suyiiyii/sims/service/RbacService.java index 8d56220..5cab939 100644 --- a/src/main/java/top/suyiiyii/sims/service/RbacService.java +++ b/src/main/java/top/suyiiyii/sims/service/RbacService.java @@ -38,4 +38,18 @@ public class RbacService { return roleMapper.selectBatchIds(userRoles.stream().map(UserRole::getRoleId).toList()); } + public boolean addRoleWithUserId(int userId, String roleName) { + Role role = roleMapper.selectOne(new QueryWrapper().eq("role_name", roleName)); + if (role == null) { + Role newRole = new Role(); + newRole.setRoleName(roleName); + roleMapper.insert(newRole); + role = roleMapper.selectOne(new QueryWrapper().eq("role_name", roleName)); + } + UserRole userRole = new UserRole(); + userRole.setUserId(userId); + userRole.setRoleId(role.getId()); + return userRoleMapper.insert(userRole) > 0; + } + } diff --git a/src/main/java/top/suyiiyii/sims/service/UserService.java b/src/main/java/top/suyiiyii/sims/service/UserService.java index f870e76..12a606b 100644 --- a/src/main/java/top/suyiiyii/sims/service/UserService.java +++ b/src/main/java/top/suyiiyii/sims/service/UserService.java @@ -3,15 +3,14 @@ package top.suyiiyii.sims.service; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import top.suyiiyii.sims.dto.UserDto; import top.suyiiyii.sims.entity.*; import top.suyiiyii.sims.exception.ServiceException; -import top.suyiiyii.sims.mapper.PermissionsMapper; -import top.suyiiyii.sims.mapper.RoleMapper; -import top.suyiiyii.sims.mapper.UserMapper; +import top.suyiiyii.sims.mapper.*; import top.suyiiyii.sims.utils.JwtUtils; import java.util.ArrayList; @@ -31,9 +30,13 @@ public class UserService { @Autowired UserMapper userMapper; @Autowired + MpUserMapper mpUserMapper; + @Autowired RoleMapper roleMapper; @Autowired PermissionsMapper permissionsMapper; + @Value("${jwt.secret}") + private String secret; public void addUser(User user) { userMapper.addUser(user); @@ -74,7 +77,7 @@ public class UserService { } } - String token = JwtUtils.createToken(dbUser.getId().toString(), dbUser.getPassword()); + String token = JwtUtils.createToken(dbUser.getId().toString(), secret); return token; @@ -93,7 +96,7 @@ public class UserService { throw new ServiceException("账号已经存在"); } if (user.getStudentId() == null || user.getStudentId().equals("")) { - throw new ServiceException("用户id不能为空"); + throw new ServiceException("学号不能为空"); } if( user.getPassword() == null || user.getPassword().equals("")) { throw new ServiceException("密码不能为空"); @@ -105,7 +108,7 @@ public class UserService { throw new ServiceException("组别不能为空"); } - userMapper.addUser(user); + mpUserMapper.insert(user); return user; } public User selectByUsername(String username) { diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 2b0fca7..4b7599a 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -11,4 +11,7 @@ spring: auto-table: enable: true - model-package: top.suyiiyii.sims.entity \ No newline at end of file + model-package: top.suyiiyii.sims.entity + +jwt: + secret: ${JWT_SECRET} \ No newline at end of file diff --git a/src/test/java/top/suyiiyii/sims/service/RbacServiceTest.java b/src/test/java/top/suyiiyii/sims/service/RbacServiceTest.java new file mode 100644 index 0000000..a5a6509 --- /dev/null +++ b/src/test/java/top/suyiiyii/sims/service/RbacServiceTest.java @@ -0,0 +1,33 @@ +package top.suyiiyii.sims.service; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import top.suyiiyii.sims.entity.Role; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.*; + +@SpringBootTest +class RbacServiceTest { + + @Autowired + private RbacService rbacService; + + @Test + void addRoleWithUserId() { + int userId = 1; // mock userId + String roleName = "ROLE"; // mock roleName + boolean result = rbacService.addRoleWithUserId(userId, roleName); + assertTrue(result); + } + @Test + void getRolesByUserId() { + int userId = 1; // mock userId + List roles = rbacService.getRolesByUserId(userId); + assertNotNull(roles); + assert roles.stream().map(Role::getRoleName).toList().contains("ROLE"); // mock roleName + } + +} \ No newline at end of file