You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring OAuth2 OIDC:如何获取用户信息与AD组信息用于授权

解决方案

1. 创建Spring Security配置类

由于需要自定义授权逻辑和用户信息获取,必须创建SecurityConfig类替代默认自动配置:

import org.springframework.beans.factory.annotation.Value;
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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.client.RestTemplate;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Value("${spring.security.oauth2.resourceserver.user-info-uri}")
    private String userInfoEndpoint;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                // 基础规则:所有请求需认证
                .anyRequest().authenticated()
            )
            .oauth2ResourceServer(oauth2 -> oauth2
                .jwt(jwt -> jwt
                    .jwtAuthenticationConverter(jwtAuthenticationConverter(restTemplate()))
                )
            );
        return http.build();
    }

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }

    // 自定义JWT转换器,整合用户信息端点数据
    private JwtAuthenticationConverter jwtAuthenticationConverter(RestTemplate restTemplate) {
        CustomJwtAuthenticationConverter converter = new CustomJwtAuthenticationConverter(restTemplate, userInfoEndpoint);
        
        // 配置JWT内置角色解析规则(按需调整)
        JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
        grantedAuthoritiesConverter.setAuthoritiesClaimName("groups");
        
        converter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
        return converter;
    }
}

同时在application.properties中添加用户信息端点配置:

spring.security.oauth2.resourceserver.user-info-uri=https://<org-auth-url>/userinfo

2. 自定义JWT认证转换器,获取用户与AD组信息

创建自定义转换器,在JWT验证通过后主动调用用户信息端点,将返回的用户详情和AD组存入认证对象:

import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.web.client.RestTemplate;
import org.springframework.core.ParameterizedTypeReference;

import java.util.List;
import java.util.Map;

public class CustomJwtAuthenticationConverter extends JwtAuthenticationConverter {

    private final RestTemplate restTemplate;
    private final String userInfoEndpoint;

    public CustomJwtAuthenticationConverter(RestTemplate restTemplate, String userInfoEndpoint) {
        this.restTemplate = restTemplate;
        this.userInfoEndpoint = userInfoEndpoint;
    }

    @Override
    protected Authentication convert(Jwt jwt) {
        Authentication authentication = super.convert(jwt);
        String token = jwt.getTokenValue();

        // 构造请求头,携带Bearer Token调用用户信息端点
        HttpHeaders headers = new HttpHeaders();
        headers.setBearerAuth(token);
        HttpEntity<Void> requestEntity = new HttpEntity<>(headers);
        
        ResponseEntity<Map<String, Object>> responseEntity = restTemplate.exchange(
                userInfoEndpoint,
                HttpMethod.GET,
                requestEntity,
                new ParameterizedTypeReference<>() {}
        );
        Map<String, Object> userInfo = responseEntity.getBody();

        // 将用户信息和AD组封装到自定义UserDetails中
        if (authentication instanceof JwtAuthenticationToken tokenAuth) {
            CustomUserDetails userDetails = new CustomUserDetails(
                    tokenAuth.getName(),
                    (String) userInfo.get("username"),
                    (List<String>) userInfo.get("adGroups")
            );
            return new JwtAuthenticationToken(
                    jwt,
                    tokenAuth.getAuthorities(),
                    tokenAuth.getName(),
                    userDetails
            );
        }
        return authentication;
    }
}

3. 自定义UserDetails存储用户与AD组数据

创建CustomUserDetails类,统一封装用户基础信息和AD组,同时实现Spring Security的UserDetails接口用于授权:

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

public class CustomUserDetails implements UserDetails {

    private final String userId;
    private final String username;
    private final List<String> adGroups;

    public CustomUserDetails(String userId, String username, List<String> adGroups) {
        this.userId = userId;
        this.username = username;
        this.adGroups = adGroups;
    }

    // 将AD组转换为Spring Security可识别的权限对象
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return adGroups.stream()
                .map(group -> (GrantedAuthority) () -> "GROUP_" + group)
                .collect(Collectors.toList());
    }

    @Override
    public String getPassword() {
        return null; // JWT认证无需密码
    }

    @Override
    public String getUsername() {
        return username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    // 自定义方法,用于获取AD组列表
    public List<String> getAdGroups() {
        return adGroups;
    }

    public String getUserId() {
        return userId;
    }
}

4. 配置基于AD组的授权规则

修改SecurityConfig中的securityFilterChain方法,添加基于AD组的路径授权规则:

http
    .authorizeHttpRequests(auth -> auth
        // 允许AD组"ADMIN_GROUP"访问/admin/**路径
        .antMatchers("/admin/**").hasAuthority("GROUP_ADMIN_GROUP")
        // 允许AD组"USER_GROUP"访问/user/**路径
        .antMatchers("/user/**").hasAuthority("GROUP_USER_GROUP")
        .anyRequest().authenticated()
    )

5. 在控制器中获取用户详情

通过@AuthenticationPrincipal注解直接获取当前认证用户的自定义信息:

import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @GetMapping("/user/me")
    public CustomUserDetails getCurrentUser(@AuthenticationPrincipal JwtAuthenticationToken authentication) {
        return (CustomUserDetails) authentication.getDetails();
    }
}

注意事项

  • 若用户信息端点返回的字段结构与示例不同,需调整CustomJwtAuthenticationConverter中解析userInfo的逻辑
  • 可添加Spring Cache缓存用户信息(按Token或用户ID缓存),避免重复调用用户信息端点
  • 确保用户信息端点支持Bearer Token认证,请求头格式为Authorization: Bearer {token}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 05:46:06