You need to enable JavaScript to run this app. 如何正确扩展Azure AD UserPrincipal适配Spring Security
优惠活动
大模型
产品
解决方案
定价
更多

如何正确扩展Azure AD UserPrincipal适配Spring Security

可落地的自定义用户模型实现方案

针对你描述的场景,完全不需要硬耦合SDK中的UserPrincipal类,也不用重写整套JWT认证逻辑,以下两种都是生产环境中常用的适配方案:

方案一:替换JWT认证的主体解析逻辑

核心逻辑是在JWT校验通过后的解析节点,直接生成你的自定义用户类型,替换SDK默认创建的UserPrincipal,让Security Context直接返回自定义类型的对象。

  • 首先定义你的自定义用户类,直接实现Spring Security原生的AuthenticatedPrincipal接口,不需要继承SDK的UserPrincipal:
import org.springframework.security.core.AuthenticatedPrincipal;
import java.util.Map;

public class MyCustomUser implements AuthenticatedPrincipal {
    // 存储从JWT中解析出的全量声明
    private final Map<String, Object> claims;

    public MyCustomUser(Map<String, Object> claims) {
        this.claims = claims;
    }

    // 接口要求的主体名称方法,直接取JWT里的用户UPN即可
    @Override
    public String getName() {
        return (String) claims.get("upn");
    }

    // 自定义业务方法,直接从声明里取员工ID
    public String getEmployeeId() {
        return String.valueOf(claims.get("emplid"));
    }

    // 保留全量声明的获取能力,兼容原有业务的声明读取逻辑
    public Map<String, Object> getClaims() {
        return claims;
    }
}
  • 然后实现自定义的JWT转换器,替代SDK默认把JWT转成UserPrincipal的逻辑:
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Component;
import java.util.Collection;

@Component
public class CustomJwtAuthenticationConverter implements Converter<Jwt, AbstractAuthenticationToken> {
    @Override
    public AbstractAuthenticationToken convert(Jwt jwt) {
        // 直接基于JWT的全量声明构造自定义用户,不依赖SDK的UserPrincipal
        MyCustomUser principal = new MyCustomUser(jwt.getClaims());
        // 权限解析逻辑和SDK保持一致,从JWT的roles声明中提取权限
        Collection<GrantedAuthority> authorities = new JwtGrantedAuthoritiesConverter().convert(jwt);
        return new JwtAuthenticationToken(principal, authorities);
    }
}
  • 最后在Spring Security配置里注入这个自定义转换器,替换默认的解析逻辑:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
public class SecurityConfig {
    @Autowired
    private CustomJwtAuthenticationConverter customJwtConverter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth.anyRequest().authenticated())
            .oauth2ResourceServer()
            .jwt()
            .jwtAuthenticationConverter(customJwtConverter);
        return http.build();
    }
}

配置完成后,SecurityContextHolder中存储的principal直接就是MyCustomUser类型,不需要原来的外观包装类做二次转换,甚至可以直接通过@AuthenticationPrincipal MyCustomUser currentUser注解在接口、服务层方法参数中直接注入当前用户。

方案二:不修改默认认证流程的轻量适配

如果不想改动SDK默认的JWT校验、权限解析逻辑,可以在认证对象生成完成后,拦截替换其中存储的principal类型,全程不侵入SDK的默认流程:

  • 实现Spring的对象后处理器,拦截JWT认证令牌的创建过程,替换里面的principal:
import com.azure.spring.autoconfigure.aad.UserPrincipal;
import org.springframework.security.config.ObjectPostProcessor;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.stereotype.Component;

@Component
public class CustomPrincipalPostProcessor implements ObjectPostProcessor<JwtAuthenticationToken> {
    @Override
    public <O extends JwtAuthenticationToken> O postProcess(O object) {
        // 拿到SDK默认创建的UserPrincipal,提取全量声明
        UserPrincipal originalPrincipal = (UserPrincipal) object.getPrincipal();
        MyCustomUser customUser = new MyCustomUser(originalPrincipal.getClaims());
        // 用自定义用户重新构造认证令牌,替换原有的principal
        return (O) new JwtAuthenticationToken(customUser, object.getAuthorities(), customUser.getName());
    }
}

这种方式的改动量极小,完全兼容SDK的原有行为,SDK版本升级时也不会因为构造逻辑变更出现兼容问题。

实现注意事项

  • 不要硬继承SDK的UserPrincipal:从源码可见该类的构造方法强依赖JWT内部解析对象,没有对外暴露通用构造入口,硬继承会把你的代码和SDK内部实现强绑定,后续SDK版本迭代很容易出现不兼容问题。
  • 不需要额外实现UserDetails接口:你的场景是纯基于JWT声明的无状态校验,没有本地用户库查询的需求,实现AuthenticatedPrincipal接口就足够满足Spring Security的要求,硬套UserDetails只会引入不必要的权限判断冗余。
  • 原有的MyAppSecurityContext工具类可以简化为直接从SecurityContext读取强转后的principal,不需要再持有UserPrincipal的成员引用。

内容的提问来源于stack exchange,提问作者Josh

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.11.12 21:45:03