Spring security 在Spring Security中配置Microsoft Graph OAuth2身份验证-错误AADSTS90014

Spring security 在Spring Security中配置Microsoft Graph OAuth2身份验证-错误AADSTS90014,spring-security,oauth-2.0,microsoft-graph-api,Spring Security,Oauth 2.0,Microsoft Graph Api,我正在为MS Graph API Azure AD v2端点编写一个SSO提供程序,该端点利用Spring OAuth2 我正在进行实现和不断的测试,但我偶然发现了AAD返回的一个错误,这让我感到困惑。毕竟,这应该是普通的标准OAuth 2流 我在MS dev portal上成功地配置了我的应用程序,提供了一个本地主机重定向URL(记录在案,它是唯一支持httpscheme.Kudos to MS的)。所以当我调用http://localhost/myapp/auth/office365Spri

我正在为MS Graph API Azure AD v2端点编写一个SSO提供程序,该端点利用Spring OAuth2

我正在进行实现和不断的测试,但我偶然发现了AAD返回的一个错误,这让我感到困惑。毕竟,这应该是普通的标准OAuth 2流

我在MS dev portal上成功地配置了我的应用程序,提供了一个本地主机重定向URL(记录在案,它是唯一支持
http
scheme.Kudos to MS的)。所以当我调用
http://localhost/myapp/auth/office365
Spring security成功拦截了调用,提供了一个正确的重定向到my browser(客户端ID为
https://login.microsoftonline.com/common/oauth2/v2.0/authorize
具有预期参数

Microsoft向我显示了一个同意屏幕,之后我通过HTTP get重定向回我的Spring安全应用程序,并带有预期的授权码参数

问题是,当应用程序尝试协商承载令牌的给定授权码时,将无法启动。Spring Security调用到
https://login.microsoftonline.com/common/oauth2/v2.0/token
但以401错误结束

这是堆栈跟踪

error="invalid_request", error_description="AADSTS90014: The request body must contain the following parameter: 'client_id'.
Trace ID: 9acd2a10-1cfb-443f-9c57-78d608c00c00
Correlation ID: bf063914-8926-4e8f-b102-7522d0e3b0af
Timestamp: 2017-10-09 15:51:44Z", correlation_id="bf063914-8926-4e8f-b102-7522d0e3b0af", error_codes="[90014]", timestamp="2017-10-09 15:51:44Z", trace_id="9acd2a10-1cfb-443f-9c57-78d608c00c00"
    at org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionJackson2Deserializer.deserialize(OAuth2ExceptionJackson2Deserializer.java:100)
    at org.springframework.security.oauth2.common.exceptions.OAuth2ExceptionJackson2Deserializer.deserialize(OAuth2ExceptionJackson2Deserializer.java:33)
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3072)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:235)
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readInternal(AbstractJackson2HttpMessageConverter.java:215)
    at org.springframework.http.converter.AbstractHttpMessageConverter.read(AbstractHttpMessageConverter.java:193)
    at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport$AccessTokenErrorHandler.handleError(OAuth2AccessTokenSupport.java:235)
    at org.springframework.web.client.RestTemplate.handleResponse(RestTemplate.java:700)
    at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:653)
    at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:621)
    at org.springframework.security.oauth2.client.token.OAuth2AccessTokenSupport.retrieveToken(OAuth2AccessTokenSupport.java:137)
    at org.springframework.security.oauth2.client.token.grant.code.AuthorizationCodeAccessTokenProvider.obtainAccessToken(AuthorizationCodeAccessTokenProvider.java:209)
    at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainNewAccessTokenInternal(AccessTokenProviderChain.java:148)
    at org.springframework.security.oauth2.client.token.AccessTokenProviderChain.obtainAccessToken(AccessTokenProviderChain.java:121)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.acquireAccessToken(OAuth2RestTemplate.java:221)
    at org.springframework.security.oauth2.client.OAuth2RestTemplate.getAccessToken(OAuth2RestTemplate.java:173)
    at org.springframework.security.oauth2.client.filter.OAuth2ClientAuthenticationProcessingFilter.attemptAuthentication(OAuth2ClientAuthenticationProcessingFilter.java:105)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
    at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:200)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
我已经研究了Spring安全性实现以找到原因

错误消息
error=“invalid_request”,error_description=“AADSTS90014:请求正文必须包含以下参数:“client_id”。
是不言自明的:MS Graph需要客户端id(仍然由基本身份验证标头提供)在请求正文中。暂停片刻。我想使用普通的旧Spring安全性,而不是第三方特定的jar,以免污染我的类路径

查看Spring OAuth 2的Java源代码,问题非常清楚。Spring仅在中使用客户端ID,用于生成重定向URL。当涉及到客户端ID时,表单中没有指定


问题:谁在这里?我如何告诉Spring,在获得授权码后,MS想要令牌请求中的客户端id?

只是澄清一下,您并不是在使用或使用Microsoft Graph进行身份验证。您实际上是在使用Azure Active Directory进行身份验证。Microsoft Graph API accep这是您最终将获得的承载令牌,但它本身并不发出访问令牌

目前还不清楚您使用哪个端点作为授权代码流,AAD有两个端点:和。主要区别在于v2使用集中注册,可以对工作/学校和个人帐户进行身份验证

无论端点是什么,当您请求访问令牌时,确实需要在请求正文中提供
clientid
。正文中实际上需要提供几个值。还要注意,这些值需要作为
application/x-www-form-urlencoded
提供

对于您提供的v1端点(换行符仅用于可读性):

v2端点几乎相同,但使用
范围
而不是
资源

grant_type=authorization_code
&client_id={client-id}
&code={authoization-code}
&redirect_uri={redirect-uri}
&client_secret={client-secret}
&scope={scopes}

OP的编辑

现在,回到Spring安全性。Spring默认情况下对Azure AD使用HTTP基本身份验证方案。在该方案中,客户端ID和密码被编码到HTTP
Authorization
头中,然后表单只包含授权代码和状态参数,所以这里是为什么我(OP,ndr)对AAD拒绝授权的原因感到困惑

为了将客户端ID和密码传递到表单中,我们可以告诉Spring Security使用不同的受支持的身份验证方案。
表单
身份验证方案将客户端ID和密码推送到表单中。 下面的代码工作并检索访问令牌

<oauth2:resource
  id="msAdAuthenticationSource"
  client-id="${oauth.appId}"
  client-secret="${oauth.appSecret}"
  type="authorization_code"
  authentication-scheme="form"
  client-authentication-scheme="form"
  use-current-uri="true"
  user-authorization-uri="${oauth.authorizationUri}"
  access-token-uri="${oauth.accessTokenUri}"
  scope="${oauth.scopes}"
  pre-established-redirect-uri="${oauth.redirectUri}" />

问题解决了,还有很多问题要解决!

我在试用Spring后得出了相同的结论。Spring安全配置中有一部分需要修改,以便通过表单传递客户端ID。我将编辑您的答案以添加我的partMarc,在编辑我的q和uestion(显示V2端点)和您的答案提供了使Spring安全性能够工作的配置。
<oauth2:resource
  id="msAdAuthenticationSource"
  client-id="${oauth.appId}"
  client-secret="${oauth.appSecret}"
  type="authorization_code"
  authentication-scheme="form"
  client-authentication-scheme="form"
  use-current-uri="true"
  user-authorization-uri="${oauth.authorizationUri}"
  access-token-uri="${oauth.accessTokenUri}"
  scope="${oauth.scopes}"
  pre-established-redirect-uri="${oauth.redirectUri}" />
authentication-scheme="form"
client-authentication-scheme="form"