执行内省后未加载OAuth2AuthenticatedPrincipal

问题描述 投票:0回答:1

我创建了一个 Spring Security 6 项目,在资源服务器中实现了不透明令牌配置。我已经将授权服务器部署到测试/生产环境中。 我的内省器是从下面的自定义类调用的:

@Slf4j
public class CustomAuthoritiesOpaqueTokenIntrospector implements OpaqueTokenIntrospector {
    private OpaqueTokenIntrospector delegate;

    public CustomAuthoritiesOpaqueTokenIntrospector(String oauthServerUrl, String clientId, String clientSecret) {
        this.delegate = new NimbusOpaqueTokenIntrospector(oauthServerUrl, clientId, clientSecret);
    }

    public OAuth2AuthenticatedPrincipal introspect(String token) {
        OAuth2AuthenticatedPrincipal principal = this.delegate.introspect(token);
        return new DefaultOAuth2AuthenticatedPrincipal(
                principal.getName(), principal.getAttributes(), extractAuthorities(principal));
    }

    private Collection<GrantedAuthority> extractAuthorities(OAuth2AuthenticatedPrincipal principal) {
        List<String> scopes = principal.getAttribute("scope");
        return scopes.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }
}

我从本文档

复制的

这是我的资源服务器配置:

@Configuration
@EnableWebSecurity
public class ResourceServerConfiguration {
    @Value("${spring.profiles.active}")
    private String activeProfile;

    @Value("${root.url.osb.basic.auth}")
    private String osbBasicAuth;

    @Value("${auth.server.url}")
    private String oauthServerUrl;

    @Value("${auth.server.clientId}")
    private String clientId;

    @Value("${auth.server.clientsecret}")
    private String clientSecret;

    public static final String BEARER_PREFIX = "Bearer ";
    public static final String HEADER_NAME = "Authorization";
    private final UserService userService;

    public ResourceServerConfiguration(UserService userService) {
        this.userService = userService;
    }

    @Bean
    public SecurityFilterChain configure(HttpSecurity http) throws Exception {
        http.addFilterAfter(new OncePerRequestFilter() {
            @Override
            protected void doFilterInternal(HttpServletRequest request,
                                            HttpServletResponse response,
                                            FilterChain filterChain)
                    throws ServletException, IOException {
                // We don't want to allow access to a resource with no token so clear
                // the security context in case it is actually an OAuth2Authentication
                var authHeader = request.getHeader(HEADER_NAME);
                if (StringUtils.isEmpty(authHeader) || !StringUtils.startsWith(authHeader, BEARER_PREFIX)) {
                    SecurityContextHolder.clearContext();
                }
                filterChain.doFilter(request, response);
            }
        }, AbstractPreAuthenticatedProcessingFilter.class);
//        http.csrf().disable();
        http.authorizeHttpRequests(authorize -> authorize.requestMatchers("/", "/v1/open/**").permitAll());

        http.authorizeHttpRequests(authorize -> authorize.requestMatchers("/pension/**").authenticated())
                .httpBasic(Customizer.withDefaults());

        if (StringUtils.equalsIgnoreCase(activeProfile, "dev")) {
            http.authorizeHttpRequests(authorize ->
                authorize.requestMatchers("/webjars/**", "/resources/**", "/swagger-ui.html", "/swagger-resources/**", "/v2/api-docs", "index.html")
                .permitAll());
        }

        http.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated())
                .oauth2ResourceServer(oauth2 -> oauth2
                        .opaqueToken(Customizer.withDefaults())
                );
        return http.build();
    }

    @Bean
    public UserDetailsService userDetailsService() {
        String[] authParts = osbBasicAuth.split(":");
        String username = authParts[0];
        String password = authParts[1];

        UserDetails userDetails = User.builder()
                .username(username)
                .password(new BCryptPasswordEncoder().encode(password)).roles()
                .build();
        return new InMemoryUserDetailsManager(userDetails);
    }

    @Bean
    public OpaqueTokenIntrospector introspector() {
        return new CustomAuthoritiesOpaqueTokenIntrospector(oauthServerUrl + "check_token", clientId, clientSecret);
    }
}

在此过程中,通过链接 http://localhost:9696/oauth/token 从授权服务器获取令牌 这是单独调用的,在我的例子中是通过 Postman 调用的。获得令牌后,我通过将此令牌粘贴到授权标头来调用另一个服务。

例如,我调用此端点:http://localhost:9090/v1/lead-management/get-details/18410,此端点是私有的,仅使用令牌执行。 执行它后,发生的第一件事是通过调用 http://localhost:9696/oauth/http://localhost:9696/oauth/check_token 触发我的内省以验证我的令牌,然后它调用最终的端点。令牌验证成功,我获得了证明我的用户已通过身份验证所需的所有详细信息:此处

但是,最终端点会抛出 500 错误。在这里我不知道问题是什么,在我看来,校长没有正确映射或加载。

我已经处理这个烂摊子大约1个月了,但似乎仍然找不到解决方案。我希望你能帮助解决这个问题。谢谢!

access-token spring-security-oauth2 spring-security-6
1个回答
0
投票

有几件事需要改变。

一般注意事项

正如 @j-asgarov 的评论所指出的,你不应该报告 500 并忽略日志。

弥补内省效率低下的唯一有效案例是,当用户“会话”(状态)被卸载到前端(单页面或移动应用程序)时,对用户“会话”(状态)保持一定的控制。但由于现在不鼓励这样做而支持OAuth2 BFF模式,因此没有充分的理由选择内省而不是 JWT 解码。

安全配置被破坏

每个授权机制应该有一个

SecurityFilterChain
bean。在这里,您似乎希望某些请求通过
Basic
身份验证进行授权,而其他请求则通过
Bearer
身份验证进行授权。您需要不同的过滤器链(都带有
@Order
以及除最后一个之外的所有过滤器链,按顺序带有
SecurityMatcher
)。

您不需要特定的过滤器来禁止匿名请求。每个过滤器链中的

authenticated()
就足够了。

当您表达应禁止匿名请求的要求(甚至为此创建一个过滤器)时,

permitAll()
的意义何在?

下一步做什么

除非消耗资源服务器的内容是在服务器端渲染的(Thymeleaf、JSF 等),否则请研究 OAuth2 BFF 模式

将资源服务器从内省切换到 JWT 解码(如果尚未配置授权服务器以提供 JWT 访问令牌)。

删除您的自定义过滤器(该功能包含在 Spring 的请求授权中,您很可能需要匿名访问某些资源)。

Basic
Bearer
授权创建不同的安全过滤器链 bean。

© www.soinside.com 2019 - 2024. All rights reserved.