我想从谷歌用户那里获得邮箱地址.为此,我使用以下依赖项:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
    <version>3.0.4</version>
</dependency>

登录后,用户被重定向至此控制器:

@Controller
public class OAuthController {
    @GetMapping("/google")
    public ResponseEntity<String> google() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (authentication instanceof OAuth2AuthenticationToken) {
            OAuth2User user = (OAuth2User) authentication.getPrincipal();

            String email = user.getAttribute("email");
            System.out.println(email);
        } else {
            System.out.println("No Email");
        }

        return ResponseEntity.ok("Hello");
    }

然而,我总是进入Else-语句.这是authentication的回归:

enter image description here

这是我的安全配置:

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.csrf().disable();

    http.sessionManagement().sessionAuthenticationStrategy(sessionAuthenticationStrategy());

    http.authorizeHttpRequests(auth ->
            auth
                    .requestMatchers("/api/welcome").authenticated()
                    .anyRequest().permitAll()
    );

    http.httpBasic();
    http.logout().permitAll();
    http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
    http.oauth2Login(Customizer.withDefaults());
    return http.build();
}

在Google Cloud的OAuth consent screen中,我添加了我的邮箱地址作为测试用户,并添加了作用域.../auth/userinfo.email.../auth/userinfo.profile.我的应用程序.yml如下所示:

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: <my-client-id>
            client-secret: <my-client-secret>
            redirect-uri: http://localhost:8080/google
          scope:
            - email
            - profile

在登录时,谷歌会突出显示邮箱地址将被共享.请参见:

enter image description here

那么为什么我看不到邮箱地址呢?

推荐答案

修复应用程序属性

显然,openid望远镜不见了.

spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: <my-client-id>
            client-secret: <my-client-secret>
            redirect-uri: http://localhost:8080/google
          scope:
            - email
            - profile
            - openid

修复您的控制器代码

如果openid作用域在那里,并且如果用户成功地使用oauth2Login(authorization_code流)进行了身份验证,则OAuth2User实际上应该是OidcUser,这扩展了OAuth2UserIdTokenClaimAccessor.这意味着您应该能够执行以下操作:

@Controller
public class OAuthController {

    @GetMapping("/google")
    public ResponseEntity<String> google() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

        if (    authentication instanceof OAuth2AuthenticationToken oauth &&
                oauth.getPrincipal() instanceof OidcUser oidcUser) {
            System.out.println(oidcUser.getEmail());
        } else {
            System.out.println("Not an OIDC user");
        }

        return ResponseEntity.ok("Hello");
    }
}

例如,您还应该能够使用oidcUser.getIdToken().getEmail()显式访问ID令牌声明.

修复您的Java配置

根据您在 comments 中链接的日志(log),该请求未经授权(这是一个匿名请求).你的安全过滤器链是混乱的,至少有3种不同的请求授权机制(基本、JWT承载和带登录的OAuth2客户端).

您应该 for each SecurityFilterChain个Bean只使用一个请求授权机制(当您定义多个Bean时,@Order个这些Bean,并在每个Bean中加上securityMatcher,但优先级最低的Bean除外).对于您的实验,我建议您从只使用OAuth2客户端登录开始:

@Bean
public SecurityFilterChain oauth2ClientnWithLoginFilterChain(HttpSecurity http) throws Exception {
    // Don't  ever disable CSRF protection when sessions are enabled (OAuth2 clients with login need sessions)
    http.csrf(Customizer.withDefaults());
    http.oauth2Login(Customizer.withDefaults());

    http.authorizeHttpRequests(auth ->
            auth
                    .requestMatchers("/", "/login/**", "/oauth2/**").permitAll()
                    .anyRequest().authenticated()
    );

    return http.build();
}

我们没有你的jwtFilter的代码,也没有使用它的请求的来源,但它很可能是最糟糕的做法的产物.如果您打算为SPA(Angular 、react 、VUE等)或移动应用程序提供JWT来授权请求,这绝对是最糟糕的做法:此类前端应该使用您的OAuth2客户端oauth2Login过滤器链来授权会话Cookie(和CSRF保护).OAuth2 tokens should remain on servers you trust.在任何情况下,令牌都应该由授权服务器传递给客户端.此客户端使用令牌来授权其发送到资源服务器的请求.即使您将这3个参与者中的几个合并到单个应用程序中,每个参与者也应该有自己的安全筛选器链.

如果您使用的是OAuth2,那么在没有用户上下文的服务器上运行的客户端可能应该使用client_credentialsFlow而不是基本身份验证.

我建议你从阅读我的教程的第OAuth2 essentials部分开始.您应该能够更好地定义您想要的请求授权机制.然后参考正确的教程来配置您的应用程序(S):重组SecurityFilterChain(S)并在一个应用程序中配置多个应用程序(如果需要).

Java相关问答推荐

如何从片段请求数据到活动?在主要活动中单击按钮请求数据?

RDX触发ChoiceBox转换器(并按字符串值排序)

Spring boot:Bean和动态扩展器

屏蔽字母数字代码的Java正则表达式

为什么在枚举中分支预测比函数调用快?

对某一Hyroby控制器禁用@cacheable

编译多个.Java文件并运行一个依赖于用户参数的文件

通过移动一个类解决了潜在的StubbingProblem.它怎麽工作?

无法在Java中处理PayPal支付响应

如何在Microronaut中将 map 读取为 map

用OSQL创建索引

Spring Boot中的应用程序.properties文件中未使用的属性

从12小时开始的日期模式

如何在Record Java中使用isRecord()和RecordComponent[]?

Java集合:NPE,即使没有添加空值

如何使用Java ZoneID的区域设置?

如何用Micrometer&;斯普肯

当我将鼠标悬停在javafxTextArea上时,如何更改鼠标光标?

SonarQube在合并升级到java17后对旧代码提出错误

如何在java中从以百分比表示的经过时间和结束日期中找到开始日期