skills/xu-cell/ai-engineering-init/leniu-security-guard

leniu-security-guard

SKILL.md

leniu-security-guard

认证注解

所有注解包路径:net.xnzn.framework.secure.filter.annotation.*

注解 用途
@RequiresAuthentication 需要登录认证(最常用,类/方法级)
@RequiresGuest 允许游客访问(公开接口)
@RequiresPermissions 需要指定权限码
@RequiresRoles 需要指定角色
@RequiresUser 需要用户登录
@RequiresHeader 需要指定请求头

Controller 认证示例

import net.xnzn.framework.secure.filter.annotation.RequiresAuthentication;
import net.xnzn.framework.secure.filter.annotation.RequiresGuest;
import net.xnzn.framework.secure.filter.annotation.RequiresPermissions;
import net.xnzn.framework.secure.filter.annotation.RequiresRoles;

// 类级别:整个 Controller 需要登录
@RestController
@RequestMapping("/api/v2/web/xxx")
@RequiresAuthentication
public class XxxWebController { }

// 方法级别:允许游客访问
@GetMapping("/public/info")
@RequiresGuest
public LeResponse<String> getPublicInfo() { }

// 需要特定权限
@GetMapping("/admin/users")
@RequiresPermissions("system:user:list")
public LeResponse<List<User>> listUsers() { }

// AND 逻辑(默认):需要所有权限
@RequiresPermissions(value = {"system:user:add", "system:user:edit"}, logical = Logical.AND)

// OR 逻辑:需要任一权限
@RequiresPermissions(value = {"system:user:add", "system:user:edit"}, logical = Logical.OR)

// 需要指定角色
@RequiresRoles("admin")

// 需要所有角色
@RequiresRoles(value = {"admin", "manager"}, logical = Logical.AND)

注意@RequiresPermissions 不指定 value 时,自动使用 @RequestMapping 路径作为权限码。


TokenManager API

import net.xnzn.framework.secure.token.TokenManager;

用户信息

// 登录状态
TokenManager.isLogin();

// 用户 ID
Long userId = TokenManager.getSubjectId().orElse(null);

// 用户名
String userName = TokenManager.getSubjectName().orElse(null);

// 附加数据
Map<String, String> userData = TokenManager.getSubjectData();
String orgId = userData.get("orgId");

权限/角色校验

// 单个权限
TokenManager.hasPermission("system:user:add");

// 多个权限(AND)
TokenManager.hasPermission("system:user:add", "system:user:edit");

// 任一权限(OR)
TokenManager.hasAnyPermission("system:user:add", "system:user:edit");

// 角色
TokenManager.hasRole("admin");
TokenManager.hasAnyRole("admin", "manager");

附加数据管理

// 添加
TokenManager.attachData("orgId", "12345");

// 批量添加
TokenManager.attachData(Map.of("orgId", "12345", "deptId", "67890"));

// 获取
String orgId = TokenManager.getSubjectData().get("orgId");

// 移除
TokenManager.removeData("orgId", "deptId");

登出与缓存

// 登出
TokenManager.logout();

// 强制下线
TokenManager.revokeAuthenticate();

// 撤销旧 Token(保留最近 N 个)
TokenManager.revokeAuthenticate(userId, 3);

// 清除权限/角色缓存
TokenManager.clearPermission(userId);
TokenManager.clearRole(userId);
TokenManager.clearRoleAndPermission(userId);
TokenManager.clearAllRoleAndPermission();

WebContext

import net.xnzn.framework.secure.WebContext;

HttpServletRequest request = WebContext.get().getRequest().orElse(null);
HttpServletResponse response = WebContext.get().getResponse().orElse(null);
Optional<AccessToken> token = WebContext.get().getAccessToken();

// 属性存取
WebContext.get().setAttribute("key", "value");
Object value = WebContext.get().getAttribute("key");
WebContext.reset();

数据权限校验(防越权)

@Transactional(rollbackFor = Exception.class)
public void delete(Long id) {
    Order order = orderMapper.selectById(id);
    Assert.notNull(order, () -> new LeException("订单不存在"));

    // 校验数据归属
    Long currentUserId = TokenManager.getSubjectId()
        .orElseThrow(() -> new LeException("用户未登录"));
    if (!order.getUserId().equals(currentUserId)) {
        throw new LeException("无权操作该订单");
    }

    orderMapper.deleteById(id);
}

最小权限原则

@RequiresAuthentication
@RequiresPermissions("order:view")
public PageVO<OrderVO> pageList(OrderPageParam param) {
    Long userId = TokenManager.getSubjectId()
        .orElseThrow(() -> new LeException("用户未登录"));
    param.setUserId(userId);  // 限制只查自己的数据
    return orderService.pageList(param);
}

SQL 注入防护

MyBatis XML 必须用 #{}

<!-- 正确:参数化查询 -->
<select id="selectByName" resultType="User">
    SELECT id, name, mobile FROM user WHERE name = #{name}
</select>

<!-- 错误:${} 有注入风险 -->
<select id="selectByName" resultType="User">
    SELECT * FROM user WHERE name = '${name}'
</select>

禁止 SELECT *

<!-- 错误 -->
SELECT * FROM user WHERE status = #{status}

<!-- 正确:明确指定字段 -->
SELECT id, name, mobile, status FROM user WHERE status = #{status}

VO 敏感字段处理

@Data
public class UserVO {
    @JsonIgnore
    private String password;  // 密码绝不返回

    @JsonSerialize(using = MobileSerializer.class)
    private String mobile;    // 手机号脱敏

    @JsonSerialize(using = IdCardSerializer.class)
    private String idCard;    // 身份证脱敏
}

日志脱敏

// 错误:记录敏感信息
log.info("用户登录, username:{}, password:{}", username, password);

// 正确:不记录密码
log.info("用户登录, username:{}", username);

输入验证

@Data
public class UserParam {
    @NotBlank(message = "用户名不能为空")
    @Pattern(regexp = "^[a-zA-Z0-9_]{4,20}$", message = "用户名格式不正确")
    private String username;

    @NotBlank(message = "密码不能为空")
    @Size(min = 6, max = 20, message = "密码长度必须在6-20之间")
    private String password;

    @NotBlank(message = "手机号不能为空")
    @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确")
    private String mobile;

    @Email(message = "邮箱格式不正确")
    private String email;
}

限流防护

// Redis 计数器限流
public void checkRateLimit(Long userId, String api, int limit, int seconds) {
    String key = String.format("rate:%d:%s", userId, api);
    Integer count = RedisUtil.incr(key, (long) seconds);
    if (count > limit) {
        throw new LeException("请求过于频繁,请稍后重试");
    }
}

注意事项

  1. 权限和角色数据自动缓存到 Redis,通过 clearPermission/clearRole 手动清除
  2. TokenManager 方法需在已登录上下文中使用
  3. @RequiresGuest 用于公开接口,不需要登录
  4. MyBatis XML 中必须使用 #{} 而非 ${}
  5. VO 中密码字段必须 @JsonIgnore,手机号等用序列化脱敏
  6. 限流 key 格式:rate:{userId}:{api}
Weekly Installs
3
GitHub Stars
8
First Seen
7 days ago
Installed on
github-copilot3
codex3
kimi-cli3
gemini-cli3
cursor3
amp3