SmartMVC
全部文档
指南
功能
认证授权
示例
参考
  • 简体中文
  • English
  • 日本語
  • Русский
GitHub
全部文档
指南
功能
认证授权
示例
参考
  • 简体中文
  • English
  • 日本語
  • Русский
GitHub
  • 认证授权

    • 认证授权概览
    • 自定义认证与当前用户

自定义认证与当前用户

这一节实现一个基于 Bearer Token 的认证过程,并说明角色、请求权限和 CurrentAuth 的生命周期。

1. 定义应用用户

public record AppUser(
        String id,
        String username,
        String displayName
) {
}

SmartMVC 不要求用户实现特定接口。用户类型可以是应用已有的实体或 DTO。

2. 实现 AuthInterceptor

@Component
public class TokenAuthInterceptor implements AuthInterceptor<AppUser> {

    private final TokenService tokenService;

    public TokenAuthInterceptor(TokenService tokenService) {
        this.tokenService = tokenService;
    }

    @Override
    public AuthPrincipal<AppUser> authenticate(
            String token,
            HttpServletRequest request) {
        AppUser user = tokenService.findUser(token);
        if (user == null) {
            return null;
        }

        return new AuthPrincipal<>(
                user.id(),
                user,
                Set.of("admin"),
                Set.of("user:read", "GET:/api/users/**"),
                Map.of("tenantId", "tenant-001")
        );
    }
}

返回 null 表示认证失败,SmartMVC 会抛出 UnauthorizedException 并返回 HTTP 401。

应用注册该 Bean 后,默认的 PermitAllAuthInterceptor 会自动退让。

3. 配置凭据位置

spring:
  smart:
    mvc:
      auth:
        authorization-header: Authorization
        token-prefix: Bearer

对于下面的请求:

Authorization: Bearer eyJhbGciOi...

authenticate 收到的是去掉前缀后的 Token。

如果凭据来自 Cookie、Session 或其他位置,可以覆写:

@Override
public String resolveToken(
        HttpServletRequest request,
        AuthConfig config) {
    Cookie[] cookies = request.getCookies();
    if (cookies == null) {
        return null;
    }
    return Arrays.stream(cookies)
            .filter(cookie -> "session_token".equals(cookie.getName()))
            .map(Cookie::getValue)
            .findFirst()
            .orElse(null);
}

AuthPrincipal<T>

认证成功后返回的身份包含:

内容用途
id稳定的用户标识,不能为空
user应用自己的用户对象,可以为空
roles角色集合
permissions命名权限和请求权限集合
attributes租户、认证方式等扩展属性

集合和 Map 会被防御性复制并变为只读,避免认证完成后身份被意外修改。

在业务代码中使用 CurrentAuth

@Service
public class ProfileService {

    private final CurrentAuth currentAuth;

    public ProfileService(CurrentAuth currentAuth) {
        this.currentAuth = currentAuth;
    }

    public ProfileView currentProfile() {
        AppUser user = currentAuth.getUser(AppUser.class);
        return ProfileView.from(user);
    }
}

常用方法:

currentAuth.isAuthenticated();
currentAuth.getPrincipal();
currentAuth.requirePrincipal();
currentAuth.getUserId();
currentAuth.getUser(AppUser.class);
currentAuth.getRoles();
currentAuth.getPermissions();
currentAuth.hasRole("admin");
currentAuth.hasPermission("GET", "/api/users/1001");

requirePrincipal() 在没有身份时抛出 IllegalStateException,适合只会在受保护请求中执行的代码。

并发请求是否安全

CurrentAuth 是一个 Spring 单例,但它不在实例字段中保存用户。所有查询都委托给基于 ThreadLocal 的 AuthContext:

  • 同步 Servlet 请求之间相互隔离;
  • 请求开始前会先清理旧上下文;
  • 请求结束后再次清理,避免线程池复用导致身份残留。

它不会自动传播到 @Async、自建线程、线程池任务或响应式链路。跨线程执行时,应显式传递需要的用户 ID、角色或不可变身份快照。

Spring MVC 异步处理

Callable、DeferredResult、WebAsyncTask 和 StreamingResponseBody 同样会跨越 Servlet 请求线程。当前认证拦截器只在同步完成回调 afterCompletion 中清理身份,没有在 afterConcurrentHandlingStarted 中处理异步切换。因此,前面的自动清理保证只适用于同步 Servlet 请求:异步任务不会自动获得 CurrentAuth,初始请求线程切换到异步处理时也不会在该时点自动清理上下文。

使用 Spring MVC 异步返回类型时,请在离开同步请求线程前复制最少且不可变的身份数据,并由应用安排异步生命周期中的清理;或者提供能够处理异步切换的自定义拦截器。不要在异步回调中直接依赖 CurrentAuth。

请求权限表达式

开启请求级权限校验:

spring.smart.mvc.auth.check-request-permission: true

权限格式为 METHOD:/path/pattern:

表达式匹配范围
GET:/api/users/**GET 请求及该路径下所有层级
POST:/api/users/*POST 请求及单层子路径
*:/api/public/**任意 HTTP 方法
*:*任意方法和路径

路径支持:

  • **:跨多个路径段;
  • *:单个路径段内的任意字符;
  • ?:单个字符。

HTTP 方法匹配不区分大小写。

自定义授权过程

需要从外部权限服务实时判断,或使用完全不同的授权模型时,可以覆写 authorize:

@Override
public boolean authorize(
        AuthPrincipal<AppUser> principal,
        Auth requirement,
        HttpServletRequest request,
        AuthConfig config) {
    return permissionService.isAllowed(
            principal.getId(),
            request.getMethod(),
            request.getRequestURI()
    );
}

还可以按需覆写 bind、clear 和 resolveRequestPath。通常只覆写真正需要改变的部分,其他生命周期逻辑继续使用默认实现。

推荐配置

spring:
  smart:
    mvc:
      auth:
        enabled: true
        mode: GLOBAL
        authorization-header: Authorization
        token-prefix: Bearer
        check-request-permission: true
        exclude-paths:
          - /actuator/health

完成自定义接入后,可以参考下一节的无数据库示例验证完整流程。

在 GitHub 上编辑此页
最后更新: 2026/8/7 08:33
上一页
认证授权概览
下一页
登录鉴权示例