为什么在 Spring 应用程序中使用 @AuthenticationPrincipal 时我的用户身份为 null?

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

我正在使用 Spring 连接到 KeyCloak 在我的应用程序中提供身份验证,但遇到了问题。这是我的代码的一部分:

@GetMapping("/api/user/name")
public ResponseEntity<?> getUserName(@AuthenticationPrincipal OidcUser user) {
    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
    System.out.println("Authentication: " + authentication);

    if (user != null) {
        return ResponseEntity.ok(user);
    } else {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("User not authenticated");
    }
}

调用此方法时,我将 user 变量始终设置为 null。我希望获得有关经过身份验证的用户的信息。这是我的 Spring 应用程序的配置:

@SpringBootApplication
public class TestOauth2Application {

    public static void main(String[] args) {
        SpringApplication.run(TestOauth2Application.class, args);
    }


    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()));
        http.oauth2Login(Customizer.withDefaults());

        http.logout(logout -> logout
                .logoutSuccessUrl("/")
                .permitAll());

        return http
                .authorizeHttpRequests(c -> c
                        .requestMatchers("/error").permitAll()
                        .requestMatchers("/manager.html").hasRole("USER")
                        .requestMatchers("/test/test").hasRole("USER")
                        .anyRequest().authenticated())
                .build();
    }


    @Bean
    public JwtAuthenticationConverter jwtAuthenticationConverter() {
        var converter = new JwtAuthenticationConverter();
        var jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
        converter.setPrincipalClaimName("sub");
        converter.setJwtGrantedAuthoritiesConverter(jwt -> {
            var authorities = jwtGrantedAuthoritiesConverter.convert(jwt);
            var roles = jwt.getClaimAsStringList("spring_sec_roles");

            return Stream.concat(authorities.stream(),
                            roles.stream()
                                    .filter(role -> role.startsWith("ROLE_"))
                                    .map(SimpleGrantedAuthority::new)
                                    .map(GrantedAuthority.class::cast))
                    .toList();
        });

        return converter;
    }


    @Bean
    public OAuth2UserService<OidcUserRequest, OidcUser> oAuth2UserService() {
        var oidcUserService = new OidcUserService();
        return userRequest -> {
            var oidcUser = oidcUserService.loadUser(userRequest);
            var roles = oidcUser.getClaimAsStringList("spring_sec_roles");
            var authorities = Stream.concat(oidcUser.getAuthorities().stream(),
                            roles.stream()
                                    .filter(role -> role.startsWith("ROLE_"))
                                    .map(SimpleGrantedAuthority::new)
                                    .map(GrantedAuthority.class::cast))
                    .toList();

            var nameAttribute = userRequest.getClientRegistration()
                    .getProviderDetails()
                    .getUserInfoEndpoint()
                    .getUserNameAttributeName();
            var name = oidcUser.getAttribute(nameAttribute);
            oidcUser.getAttributes().put("name", name);

            return new DefaultOidcUser(authorities, oidcUser.getIdToken(), oidcUser.getUserInfo());
        };
    }
}

我正在发送下一个令牌:

Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJ6eTdPbVN1b3NTVGJCUlhEYnRzZFBtOUtDV3FBQWZhQy1iMzh6RHBVOTlVIn0.eyJleHAiOjE3MDM3MjM2NDEsImlhdCI6MTcwMzcyMjc0MSwiYXV0aF90aW1lIjoxNzAzNzIyNzQxLCJqdGkiOiI3YmE0YmQ2NC1mMDQ5LTRjMjgtYjBkMS1jODQ3NWEyNWIwYTUiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwOTAvcmVhbG1zL2thc2lhIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjJhYTYyM2Y4LTI3NTEtNDA1Mi05ZDBkLTNmMWQwNzhiNmU5MCIsInR5cCI6IkJlYXJlciIsImF6cCI6Imthc2lhLXNlY3VyaXR5Iiwibm9uY2UiOiJiYzUxYWE5MC0zMTA0LTQ5ZjEtOGNmYy01ZDM4N2NlOTE4NGQiLCJzZXNzaW9uX3N0YXRlIjoiNjIxMThmMTUtNDE1OC00YzlmLWEyZDItZTNmYzRiMGIwMThhIiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyJodHRwOi8vbG9jYWxob3N0OjgwODAiLCJodHRwOi8vbG9jYWxob3N0OjMwMDAvIiwiaHR0dHA6Ly9sb2NhbGhvc3Q6MzAwMCJdLCJyZWFsbV9hY2Nlc3MiOnsicm9sZXMiOlsiZGVmYXVsdC1yb2xlcy1rYXNpYSIsIlJPTEVfVVNFUiIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iLCJVU0VSIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtbWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUgIiwic2lkIjoiNjIxMThmMTUtNDE1OC00YzlmLWEyZDItZTNmYzRiMGIwMThhIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiQXJ0ZW0gRHViZW5rbyIsInNwcmluZ19zZWNyZXRfaWQiOiI2MjExOGYxNS00MTU4LTRjOWYtYTJkMi1lM2ZjNGIwYjAxOGEiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImNvbnRhY3Qtb3JpZ2lucyI6W3sicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfV0sInNjb3BlIjoiTm9ybWFsIEFwcCIsInNpZCI6IjYyMTE4ZjE1LTQxNTgtNGM5Zi1hMmQyLWUzZmM0YjBiMDE4YSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwibmFtZSI6IkFydGVtIER1YmVua28iLCJzcHJpbmdfc2VjX3JvbGVzIjpbImRlZmF1bHQtcm9sZXMta2FzaWEiLCJST0xFX1VTRVIiLCJvZmZsaW5lX2FjY2VzcyIsInVtYV9hdXRob3JpemF0aW9uIiwiVVNFUiJdLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJhcnRlbS5kdWJlbmtvMDMwOEBnbWFpbC5jb20iLCJnaXZlbl9uYW1lI

UPD。 但与此同时,

Authentication authentication = SecurityContextHolder.getContext().getAuthentication()
对我有用。

也许我不完全明白一些事情?

spring spring-security spring-security-oauth2
1个回答
0
投票

您使用

Authentication authentication = SecurityContextHolder.getContext().getAuthentication()
获得的实际类型将根据您授权请求的方式(具有登录名的浏览器会话或来自 REST 客户端的请求中的不记名令牌)而变化。

OidcUser
是 OIDC 环境中使用
OAuth2AuthenticationToken
的 OAuth2 客户端上经过身份验证的用户的
oauth2Login
的主体(Keycloak 就是这种情况)。

对带有

oauth2Login
的 OAuth2 客户端的请求是使用会话 cookie 进行授权的,而不是使用
Bearer
标头中的
Authorization
访问令牌进行授权。

Bearer
标头中的
Authorization
访问令牌授权对资源服务器的请求。资源服务器中的默认身份验证是
JwtAuthenticationToken
,其主体类型为
Jwt

在同一个安全过滤器链中混合配置

oauth2Login
oauth2ResourceServer
是一个非常糟糕的主意,安全要求太不同了:

  • oauth2Login
    :基于会话的安全性,需要针对 CSRF 进行保护,并且通常期望对受保护资源的未经授权的请求被重定向到登录(
    302
    状态代码)
  • oauth2ResourceServer
    :基于访问令牌的安全性,可以(应该?)是无状态的(无会话),对 CSRF 不敏感,并且期望对受保护资源的未经授权的请求得到答复
    401
    (登录在资源服务器上没有意义)

我没有看到在单个端点上拥有两种安全性的有效用例。

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