Spring security 对于返回签名JWT的userinfo终结点,Spring失败

Spring security 对于返回签名JWT的userinfo终结点,Spring失败,spring-security,oauth-2.0,jwt,openid-connect,spring-security-oauth2,Spring Security,Oauth 2.0,Jwt,Openid Connect,Spring Security Oauth2,我们正在开发一个Spring引导应用程序,它是一个OIDC客户端。身份提供者(IdP)是一项第三方服务,完全符合OpenID Connect和OAuth 2.0(据我们所知)。由于它是在考虑高安全性的情况下构建的,它的UserInfo端点返回一个签名的JWT(而不是常规的JWT) Spring Security似乎不支持它。身份验证流以错误消息结束(显示在Spring应用程序生成的HTML页面中): [无效的\u用户\u信息\u响应]尝试删除时出错 检索UserInfo资源:无法提取响应:否 为

我们正在开发一个Spring引导应用程序,它是一个OIDC客户端。身份提供者(IdP)是一项第三方服务,完全符合OpenID Connect和OAuth 2.0(据我们所知)。由于它是在考虑高安全性的情况下构建的,它的UserInfo端点返回一个签名的JWT(而不是常规的JWT)

Spring Security似乎不支持它。身份验证流以错误消息结束(显示在Spring应用程序生成的HTML页面中):

[无效的\u用户\u信息\u响应]尝试删除时出错 检索UserInfo资源:无法提取响应:否 为响应类型找到合适的HttpMessageConverter [java.util.Map]和内容类型 [应用程序/jwt;字符集=UTF-8]

我的问题是:

  • Spring当前不支持返回签名JWT的UserInfo端点,这是否正确
  • 如果是这样,我们如何添加对签名JWT的支持(包括签名的验证)
我们的分析表明,
DefaultOAuth2UserService
请求(
Accept:application/json
),并期望IdP做出json响应。但是,由于配置了高安全性,IdP返回内容类型为
application/JWT
的签名JWT。响应与上的示例类似。由于
restemplate
没有能够处理内容类型
application/jwt
的消息转换器,因此身份验证失败

我们的示例应用程序非常简单:

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.2.4.RELEASE'
    id 'io.spring.dependency-management' version '1.0.9.RELEASE'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
}
DemoApplication.java

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
package demo;


import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {
    @GetMapping("/")
    String hello() { return "hello"; }
}
application.yml

server:
  port: 8081

spring:
  security:
    oauth2:
      client:
        registration:
          demo:
            client-id: our-client-id
            client-secret: our-client-secret
            clientAuthenticationMethod: post
            provider: our-idp
            scope:
              - profile
              - email
        provider:
          our-idp:
            issuer-uri: https://login.idp.com:443/idp/oauth2
HomeController.java

package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}
package demo;


import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HomeController {
    @GetMapping("/")
    String hello() { return "hello"; }
}

经过进一步分析,Spring Boot似乎不支持返回签名JWT的UserInfo端点。这显然是一个不寻常的设置(但仍在OAuth 2.0/OIDC规范中)。到目前为止,我没有提到的是JWT是用客户机机密签名的

虽然Spring Boot不支持它,但可以添加它。解决方案包括:

  • 支持签名JWT的用户服务(作为
    DefaultOAuth2UserService
    的替代)
  • 支持JWTs的
    HttpMessageConverter
    (用于用户服务的
    RestTemplate
  • 使用客户端密码的
    JwtDecoder
  • 将各个部分组合在一起的安全配置
请注意,我们同时从OAuth 2.0更改为OIDC,因此我们的
应用程序.yml
现在包括
openid
范围

spring:
  security:
    oauth2:
      client:
        registration:
          demo:
            client-id: our-client-id
            client-secret: our-client-secret
            clientAuthenticationMethod: post
            provider: our-idp
            scope:
              - profile
              - email
        provider:
          our-idp:
            issuer-uri: https://login.idp.com:443/idp/oauth2
安全配置为:

package-demoapp;
导入org.springframework.context.annotation.Bean;
导入org.springframework.context.annotation.Configuration;
导入org.springframework.security.config.annotation.web.builders.HttpSecurity;
导入org.springframework.security.config.annotation.web.configuration.websecurityConfigureAdapter;
导入org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
导入org.springframework.security.oauth2.client.registration.ClientRegistration;
导入org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
导入org.springframework.security.oauth2.jwt.JwtDecoder;
导入org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
导入javax.crypto.spec.SecretKeySpec;
导入java.nio.charset.StandardCharset;
@配置
公共类SecurityConfig扩展了WebSecurity配置适配器{
专用最终ClientRegistrationRepository ClientRegistrationRepository;
公共安全配置(ClientRegistrationRepository ClientRegistrationRepository){
this.clientRegistrationRepository=clientRegistrationRepository;
}
@凌驾
受保护的无效配置(HttpSecurity HttpSecurity)引发异常{
httpSecurity
.授权请求()
.anyRequest().authenticated()
.及()
.oauth2Login()
.userInfoEndpoint()
.oidcUserService(OIDCUSERVICE());
}
@豆子
OidcUserService OIDCUSERVICE(){
OidcUserService userService=新的OIDCUSERVICE();
setOauth2UserService(新的ValidatingAuth2UserService(jwtDecoderUsingClientSecret(“演示”));
返回用户服务;
}
JwtDecoder jwtDecoderUsingClientSecret(字符串注册ID){
ClientRegistration registration=clientRegistrationRepository.FindByrRegistrationID(registrationId);
SecretKeySpec key=new SecretKeySpec(registration.getClientSecret().getBytes(StandardCharsets.UTF_8),“HS256”);
返回NimbusJwtDecoder.with secretkey(key.build();
}
}
如果您使用的是OAuth 2.0而不是OIDC(即,您不使用作用域“openid”),则配置更简单:

软件包演示;
导入org.springframework.context.annotation.Bean;
导入org.springframework.context.annotation.Configuration;
导入org.springframework.security.config.annotation.web.builders.HttpSecurity;
导入org.springframework.security.config.annotation.web.configuration.websecurityConfigureAdapter;
导入org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
导入org.springframework.security.oauth2.client.registration.ClientRegistration;
导入org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
导入org.springframework.security.oauth2.jwt.JwtDecoder;
导入org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
导入javax.crypto.spec.SecretKeySpec;
导入java.nio.charset.StandardCharset;
@配置
公共类SecurityConfig扩展了WebSecurity配置适配器{
专用最终ClientRegistrationRepository ClientRegistrationRepository;
公共安全配置(ClientRegistrationRepository ClientRegistrationRepository){
this.clientRegistrationRepository=clientRegistrationRepository;
}
@凌驾