java - 使用 spring boot 的 LinkedIn 身份验证

标签 java spring spring-boot spring-security-oauth2 linkedin-api

我正在尝试在我的 spring boot 应用程序中使用 LinkedIn 身份验证,我收到以下错误

[invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: Error while extracting response for type [class org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse] and content type [application/json;charset=utf-8]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: An error occurred reading the OAuth 2.0 Access Token Response: tokenType cannot be null; nested exception is java.lang.IllegalArgumentException: tokenType cannot be null

Error Message On Screen

这是我的application.yml

spring:
  security:
    oauth2:
      client:
        registration:
          linkedin:
            clientId: CLIENTID
            clientSecret: SECRET
            client-authentication-method: post
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8080/login/oauth2/code/linkedin
            scope: r_liteprofile, r_emailaddress,w_member_social

            client-name: Linkedin

        provider:
          linkedin:          
            authorization-uri: https://www.linkedin.com/oauth/v2/authorization
            token-uri: https://www.linkedin.com/oauth/v2/accessToken
            user-info-uri: https://api.linkedin.com/v1/people/~?format=json
            user-name-attribute: id

关于如何解决这个问题或如何在 spring boot 中使用 linkedin 进行身份验证的任何想法

最佳答案

我有同样的错误。

An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: Error while extracting response for type [class org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse] and content type [application/json]; nested exception is org.springframework.http.converter.HttpMessageNotReadableException: An error occurred reading the OAuth 2.0 Access Token Response: tokenType cannot be null; nested exception is java.lang.IllegalArgumentException: tokenType cannot be null

在这里,当您请求 token 时,您会看到 linkedin OAuth2 提供商提示 tokenType cannot be null

Google 和某些其他第 3 方身份提供商对在 header 中发送到用户信息端点的 token 类型名称更加严格时,就会发生这种情况。默认值为“Bearer”,它适合大多数提供商并符合规范,但如果您需要更改它,您可以设置 security.oauth2.resource.token-type。 Reference

为了拥有这个配置,我们需要引入spring-security-oauth2-autoconfigure

解决方案1

依赖

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.0.0.RC2</version>
  </dependency>

配置

.properties配置中,可以添加

security.oauth2.resource.token-type=Bearer

但是上面的解决方案并没有解决问题,所以我看看下一个解决方案。

解决方案2

Reference

Reference

这是 Spring Security 5 的一个已知错误。LinkedIn 作为 OAuth2 授权提供者并不是一个非常受欢迎的选择。 Spring Security 与 Google 和其他提供商完美兼容并不奇怪,但它不符合 LinkedIn OAuth2 对客户的要求。

5.1. Successful Response The authorization server issues an access token and optional refresh token, and constructs the response by adding the following parameters to the entity-body of the HTTP response with a 200 (OK) status code:
token_type REQUIRED. The type of the token issued as described in Section 7.1. Value is case insensitive.

token_type 是必需参数,但 Spring Security 不会将其传递到请求中。

SecurityConfig 中,您可以通过设置 .tokenEndpoint().accessTokenResponseClient(authorizationCodeTokenResponseClient()) 来指定您希望如何处理 token 响应 完整的演示代码可在 https://github.com/jzheaux/messaging-app/blob/master/client-app/src/main/java/sample/config/SecurityConfig.java#L61 获得。

安全配置

                .authorizationEndpoint()
                .baseUri(...)
                .authorizationRequestRepository(...)

                .and()
                .tokenEndpoint()
                .accessTokenResponseClient(authorizationCodeTokenResponseClient())

                .and()
                .redirectionEndpoint()
                .baseUri(...)

authorizationCodeTokenResponseClient()私有(private)方法

    private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeTokenResponseClient() {
        OAuth2AccessTokenResponseHttpMessageConverter tokenResponseHttpMessageConverter =
                new OAuth2AccessTokenResponseHttpMessageConverter();
        tokenResponseHttpMessageConverter.setTokenResponseConverter(new CustomAccessTokenResponseConverter()); //https://github.com/jzheaux/messaging-app/blob/392a1eb724b7447928c750fb2e47c22ed26d144e/client-app/src/main/java/sample/web/CustomAccessTokenResponseConverter.java#L35

        RestTemplate restTemplate = new RestTemplate(Arrays.asList(
                new FormHttpMessageConverter(), tokenResponseHttpMessageConverter));
        restTemplate.setErrorHandler(new OAuth2ErrorResponseErrorHandler());

        DefaultAuthorizationCodeTokenResponseClient tokenResponseClient = new DefaultAuthorizationCodeTokenResponseClient();
        tokenResponseClient.setRestOperations(restTemplate);

        return tokenResponseClient;
    }

自定义转换器

package com.fermedu.resume.config;

import org.springframework.core.convert.converter.Converter;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class CustomAccessTokenResponseConverter implements Converter<Map<String, String>, OAuth2AccessTokenResponse> {
    private static final Set<String> TOKEN_RESPONSE_PARAMETER_NAMES = Stream.of(
            OAuth2ParameterNames.ACCESS_TOKEN,
            OAuth2ParameterNames.TOKEN_TYPE,
            OAuth2ParameterNames.EXPIRES_IN,
            OAuth2ParameterNames.REFRESH_TOKEN,
            OAuth2ParameterNames.SCOPE).collect(Collectors.toSet());

    @Override
    public OAuth2AccessTokenResponse convert(Map<String, String> tokenResponseParameters) {
        String accessToken = tokenResponseParameters.get(OAuth2ParameterNames.ACCESS_TOKEN);

        OAuth2AccessToken.TokenType accessTokenType = OAuth2AccessToken.TokenType.BEARER;

        long expiresIn = 0;
        if (tokenResponseParameters.containsKey(OAuth2ParameterNames.EXPIRES_IN)) {
            try {
                expiresIn = Long.valueOf(tokenResponseParameters.get(OAuth2ParameterNames.EXPIRES_IN));
            } catch (NumberFormatException ex) { }
        }

        Set<String> scopes = Collections.emptySet();
        if (tokenResponseParameters.containsKey(OAuth2ParameterNames.SCOPE)) {
            String scope = tokenResponseParameters.get(OAuth2ParameterNames.SCOPE);
            scopes = Arrays.stream(StringUtils.delimitedListToStringArray(scope, " ")).collect(Collectors.toSet());
        }

        Map<String, Object> additionalParameters = new LinkedHashMap<>();
        tokenResponseParameters.entrySet().stream()
                .filter(e -> !TOKEN_RESPONSE_PARAMETER_NAMES.contains(e.getKey()))
                .forEach(e -> additionalParameters.put(e.getKey(), e.getValue()));

        return OAuth2AccessTokenResponse.withToken(accessToken)
                .tokenType(accessTokenType)
                .expiresIn(expiresIn)
                .scopes(scopes)
                .additionalParameters(additionalParameters)
                .build();
    }
}


这个解决方案 2 适合我。

关于java - 使用 spring boot 的 LinkedIn 身份验证,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61251322/

相关文章:

java - Logback RollingFileAppender FileNotFoundException

hibernate - 使用鉴别器值查询数据库

Java堆内存持有大字节数组

java - AppEngine Java8 应用程序缺少 appengine-api-1.0

java - 我将如何打破程序中这一部分的循环?

不同端口上的spring boot 2.1.0管理服务器端口

java - 配置数据源属性

java - 如何获取所有 Controller 和请求映射方法

java - Android drawSelectorOnTop 与 GridView

java - 如何正确重用 Jackson ObjectMapper?