Răsfoiți Sursa

feat(auth): 升级认证逻辑以支持权限组

- 修改 AuthInterceptor 以使用 AuthPrincipal 对象传递用户 ID 和角色
- 新增 AuthPrincipal 类用于封装认证主体信息
- 更新 TokenService 接口及实现,返回 AuthPrincipal 而非仅 userId
- 在 TokenServiceImpl 中查询并绑定用户权限组信息
- 添加 SecurityUtils 工具方法获取当前用户角色
- 注入 UserInfoMapper 以支持查询用户权限组
- 扩展日志记录内容,包含 userId 和 role 信息
mcbaiyun 2 luni în urmă
părinte
comite
46cdf7d531

+ 12 - 6
src/main/java/work/baiyun/chronicdiseaseapp/config/AuthInterceptor.java

@@ -9,6 +9,8 @@ import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Component;
 import org.springframework.web.servlet.HandlerInterceptor;
 import work.baiyun.chronicdiseaseapp.service.TokenService;
+import work.baiyun.chronicdiseaseapp.model.vo.AuthPrincipal;
+import work.baiyun.chronicdiseaseapp.enums.PermissionGroup;
 
 
 @Component
@@ -69,16 +71,20 @@ public class AuthInterceptor implements HandlerInterceptor {
             return false;
         }
 
-        Long userId = tokenService.validateToken(token);
-        if (userId == null) {
-            logger.info("preHandle: validateToken returned null for token starting with {}... - rejecting request", token.length() > 8 ? token.substring(0,8) : token);
+        AuthPrincipal principal = tokenService.validateToken(token);
+        if (principal == null || principal.getUserId() == null) {
+            logger.info("preHandle: validateToken returned null/invalid principal for token starting with {}... - rejecting request", token.length() > 8 ? token.substring(0,8) : token);
             response.setStatus(HttpStatus.UNAUTHORIZED.value());
             return false;
         }
 
-        // 将 userId 放入 request attribute,后续 controller 可用
-        request.setAttribute("currentUserId", userId);
-        logger.debug("preHandle: token validated, userId={}", userId);
+        // 将 userId 和 权限组 放入 request attribute,后续 controller 可用
+        request.setAttribute("currentUserId", principal.getUserId());
+        PermissionGroup role = principal.getRole();
+        if (role != null) {
+            request.setAttribute("currentUserRole", role);
+        }
+        logger.debug("preHandle: token validated, userId={}, role={}", principal.getUserId(), role);
         return true;
     }
 }

+ 34 - 0
src/main/java/work/baiyun/chronicdiseaseapp/model/vo/AuthPrincipal.java

@@ -0,0 +1,34 @@
+package work.baiyun.chronicdiseaseapp.model.vo;
+
+import work.baiyun.chronicdiseaseapp.enums.PermissionGroup;
+
+/**
+ * 简单的认证主体对象:包含 userId 和 权限组
+ */
+public class AuthPrincipal {
+    private Long userId;
+    private PermissionGroup role;
+
+    public AuthPrincipal() {}
+
+    public AuthPrincipal(Long userId, PermissionGroup role) {
+        this.userId = userId;
+        this.role = role;
+    }
+
+    public Long getUserId() {
+        return userId;
+    }
+
+    public void setUserId(Long userId) {
+        this.userId = userId;
+    }
+
+    public PermissionGroup getRole() {
+        return role;
+    }
+
+    public void setRole(PermissionGroup role) {
+        this.role = role;
+    }
+}

+ 4 - 2
src/main/java/work/baiyun/chronicdiseaseapp/service/TokenService.java

@@ -1,5 +1,7 @@
 package work.baiyun.chronicdiseaseapp.service;
 
+import work.baiyun.chronicdiseaseapp.model.vo.AuthPrincipal;
+
 public interface TokenService {
     /**
      * 为用户生成新的 token,并保存(覆盖旧的 token)。返回生成的 token 字符串。
@@ -7,10 +9,10 @@ public interface TokenService {
     String createToken(Long userId);
 
     /**
-     * 根据 token 校验并返回 userId;若无效或过期返回 null。
+    * 根据 token 校验并返回认证主体(userId + 权限组);若无效或过期返回 null。
      * 在校验过程中会处理临近过期(小于24小时)时延长有效期的逻辑。
      */
-    Long validateToken(String token);
+    AuthPrincipal validateToken(String token);
 
     /**
      * 撤销 token(例如登出)

+ 18 - 3
src/main/java/work/baiyun/chronicdiseaseapp/service/impl/TokenServiceImpl.java

@@ -7,6 +7,10 @@ import work.baiyun.chronicdiseaseapp.mapper.UserTokenMapper;
 import work.baiyun.chronicdiseaseapp.model.po.UserToken;
 import work.baiyun.chronicdiseaseapp.service.TokenService;
 import work.baiyun.chronicdiseaseapp.util.TokenUtil;
+import work.baiyun.chronicdiseaseapp.model.vo.AuthPrincipal;
+import work.baiyun.chronicdiseaseapp.mapper.UserInfoMapper;
+import work.baiyun.chronicdiseaseapp.model.po.UserInfo;
+import work.baiyun.chronicdiseaseapp.enums.PermissionGroup;
 
 import java.time.LocalDateTime;
 
@@ -14,12 +18,14 @@ import java.time.LocalDateTime;
 public class TokenServiceImpl implements TokenService {
 
     private final UserTokenMapper userTokenMapper;
+    private final UserInfoMapper userInfoMapper;
 
     private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(TokenServiceImpl.class);
 
     @Autowired
-    public TokenServiceImpl(UserTokenMapper userTokenMapper) {
+    public TokenServiceImpl(UserTokenMapper userTokenMapper, UserInfoMapper userInfoMapper) {
         this.userTokenMapper = userTokenMapper;
+        this.userInfoMapper = userInfoMapper;
     }
 
     @Override
@@ -45,7 +51,7 @@ public class TokenServiceImpl implements TokenService {
     }
 
     @Override
-    public Long validateToken(String token) {
+    public AuthPrincipal validateToken(String token) {
         if (token == null || token.isEmpty()) return null;
         UserToken ut = userTokenMapper.selectOne(new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<UserToken>().eq("token", token));
         logger.debug("validateToken: token={}, userToken={}", token, ut);
@@ -66,7 +72,16 @@ public class TokenServiceImpl implements TokenService {
             userTokenMapper.updateById(ut);
         }
 
-        return ut.getUserId();
+        Long userId = ut.getUserId();
+
+        // 查询用户的 role(权限组)
+        UserInfo ui = userInfoMapper.selectById(userId);
+        PermissionGroup role = null;
+        if (ui != null) {
+            role = ui.getRole();
+        }
+
+        return new AuthPrincipal(userId, role);
     }
 
     @Override

+ 32 - 0
src/main/java/work/baiyun/chronicdiseaseapp/util/SecurityUtils.java

@@ -29,4 +29,36 @@ public class SecurityUtils {
         }
         return userId;
     }
+
+    /**
+     * 从当前请求上下文读取拦截器设置的 currentUserRole(PermissionGroup)
+     * 支持 PermissionGroup、Integer/Long code、或 String(枚举名或 code)三种类型;读取失败抛出 CustomException
+     */
+    public static work.baiyun.chronicdiseaseapp.enums.PermissionGroup getCurrentUserRole() {
+        org.springframework.web.context.request.RequestAttributes attrs = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();
+        if (attrs == null || !(attrs instanceof org.springframework.web.context.request.ServletRequestAttributes)) {
+            throw new work.baiyun.chronicdiseaseapp.exception.CustomException("No valid user role");
+        }
+        jakarta.servlet.http.HttpServletRequest request = ((org.springframework.web.context.request.ServletRequestAttributes) attrs).getRequest();
+        Object attr = request.getAttribute("currentUserRole");
+        work.baiyun.chronicdiseaseapp.enums.PermissionGroup role = null;
+        if (attr instanceof work.baiyun.chronicdiseaseapp.enums.PermissionGroup) {
+            role = (work.baiyun.chronicdiseaseapp.enums.PermissionGroup) attr;
+        } else if (attr instanceof Integer) {
+            try { role = work.baiyun.chronicdiseaseapp.enums.PermissionGroup.fromCode((Integer) attr); } catch (Exception ignored) {}
+        } else if (attr instanceof Long) {
+            try { role = work.baiyun.chronicdiseaseapp.enums.PermissionGroup.fromCode(((Long) attr).intValue()); } catch (Exception ignored) {}
+        } else if (attr instanceof String) {
+            String s = (String) attr;
+            try {
+                role = work.baiyun.chronicdiseaseapp.enums.PermissionGroup.valueOf(s);
+            } catch (Exception e) {
+                try { role = work.baiyun.chronicdiseaseapp.enums.PermissionGroup.fromCode(Integer.parseInt(s)); } catch (Exception ignored) {}
+            }
+        }
+        if (role == null) {
+            throw new work.baiyun.chronicdiseaseapp.exception.CustomException("No valid user role");
+        }
+        return role;
+    }
 }