Spring Security and Google OpenID Connect migration
Asked Answered
L

1

14

Questions:

1) What's the best way to integrate OpenID Connect authentication into a webapp that uses Spring Security for authentication?

2) Is there any way - either from the MITREid side of things or the Google Accounts side of things - to get the MITREid OpenID Connect authentication filter to work with Google's OpenID Connect service?

I'm sure answers to these questions will be useful for any developer that uses the Spring Security OpenID module to authenticate with Google.

Detail:

My webapp uses Spring Security's OpenID module (<openid-login .../>) for authentication with Google Accounts as the Identity Provider. ie., users authenticate using their Google Apps or GMail email address.

Recently, whenever users authenticate, they receive this warning message from Google accounts:

Important notice: OpenID2 for Google accounts is going away on April 20, 2015.

So Google is dropping support for OpenID, will turn it off completely in April 2015, and states that you must switch to the OpenID Connect protocol if you want to authenticate with Google Accounts.

I was hoping Spring Security would have built-in support for OpenID Connect, just like it has built-in support for OpenID. e.g. something like an <openid-connect-login .../> element. But my searches have turned up no such support.

The best candidate I've found so far is MITREid Connect . It includes a Spring Security authentication filter named OIDCAuthenticationFilter for OpenID Connect. The problem is, it does not interoperate with Google's OpenID Connect implementation.

I tried cloning the MITREid simple-web-app and configured it to authenticate (using OpenID Connect) with Google Accounts. But it did not work because it depends on a nonce which Google's OpenID Connect implementation does not support. The error message from Google accounts was:

Parameter not allowed for this message type: nonce

Next I tried plugging my own implementation of MITREid's AuthRequestUrlBuilder interface into the MITREid configuration. The only difference between my implementation and MITREid's implementation was that I did not send the nonce.

Not sending the nonce made Google's OpenID Connect implementation happy but MITREid threw an exception when it couldn't find a nonce in the Google authentication response. The error message was:

Authentication Failed: ID token did not contain a nonce claim

I tracked the MITREid exception down to these lines in MITREID'S OIDCAuthenticationFilter:

// compare the nonce to our stored claim
String nonce = idClaims.getStringClaim("nonce");
if (Strings.isNullOrEmpty(nonce)) {

    logger.error("ID token did not contain a nonce claim.");

    throw new AuthenticationServiceException("ID token did not contain a nonce claim.");
}

But there is no way for me to extend MITREid's implementation to ignore the nonce. So close but yet so far! If Google Accounts would accept the nonce or MITREid could be configured to ignore the nonce then we'd have a solution.

Within the MITREid Connect issues list on github I've found others have run into these similar issues:

1) #726 - Documentation on using client with Google as authentication provider

2) #704 - Add a useNonce attribute into ServerConfiguration to indicate if the IdP accepts the nonce value into its requests.

So I am stuck. Come April 2015 Google will shutdown Open ID authentication.

Some relevant links:

1) https://support.google.com/accounts/answer/6135882

2) https://www.tbray.org/ongoing/When/201x/2014/03/01/OpenID-Connect

3) https://github.com/mitreid-connect

4) https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/blob/master/openid-connect-client/src/main/java/org/mitre/openid/connect/client/OIDCAuthenticationFilter.java

5) https://github.com/mitreid-connect/simple-web-app

6) https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/blob/master/openid-connect-client/src/main/java/org/mitre/openid/connect/client/service/impl/PlainAuthRequestUrlBuilder.java

7) https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/issues/726

8) https://github.com/mitreid-connect/OpenID-Connect-Java-Spring-Server/pull/704


2015-02-18 Update

Functionality has recently been added to the development branch of mitreid-connect for disabling the nonce - therefore making Google's OIDC server happy. Thankfully, mitreid-connect has also provided some guidance on interoperating with Google . Unfortunately the "nonceEnabled" change is not yet available in Maven central but hopefully that will change soon.

Leontineleontyne answered 10/12, 2014 at 18:49 Comment(6)
wrt. to 2): the "nonce" issue is indeed a problem with Google's OpenID Connect implementation in the sense that it is not conformant to the spec; you should be able to switch to a flow that that actually does work, e.g. the Implicit FlowPare
You are not the only one stuck. And it seems like the Spring Security folks do not really care. Any progress at you end?Dibru
Hans Z.: Thanks for confirming the nonce issue is a known interoperability problem with Google OIDC. I'm not familiar with how OAuth 2 flows work but might have to consider consider digging deeper. I tagged this question with "google-oauth" and "google-openid" hoping that somebody from Google could provide guidance.Leontineleontyne
Kees: I tagged this question with "Spring Security" with the hope that somebody from the Spring Security team would provide some guidance. I thought Stack Overflow replaced the old Spring Security forums and that the the Spring Security team monitored relevant Stack Overflow questions.Leontineleontyne
You could use the Spring-social-google to integrate spring security with google open connectid . It's use oauth 2.0 under the hood. Also take a look at #11849648 for more optionsMicra
Chuck Mah: Thanks for the pointer to How to implement Openid connect and Spring Security. During my initial investigation this question had only one answer from 2012 and it wasn't helpful to me. Since then there's been a couple answers that look promising. The code in the sample project given by Romain F. at github.com/fromi/spring-google-openidconnect looks remarkably simple and I intend to investigate it further.Leontineleontyne
L
3

AFAIK, there is no clean and easy Spring Security migration from OpenID to OpenID Connect authentication. Implementing OpenID authentication with Spring Security is straight-forward using the well documented <openid-login/> but there exists no analog for OpenID Connect.

The MITREid alternative is still on a development branch and unavailable at Maven Central and therefore not a candidate.

In the comments, Chuck Mah points to How to implement Openid connect and Spring Security where Romain F. provides the sample code.

Romain's sample code pointed me in the right direction. Given time is running out, I went with romain's approach, which was to write a custom Spring Security AuthenticationFilter that uses spring-security-oauth2 to query the oauth2 api userinfo endpoint (for Google that's https://www.googleapis.com/oauth2/v2/userinfo). The assumption is that if we are able to successfully query the userinfo endpoint then the user has successfully authenticated so we can trust the information returned - eg the user's email address.

When i first started learning about OpenID Connect the “id token” seemed to be the central concept. However, browsing the spring-security-oauth2 source code, it appears to be ignored. This leads to the question, what’s the point of the ID token if we can authenticate without it (by simply querying oauth2 userinfo endpoint)?

A minimalist solution - which i would prefer - would simply return a validated ID token. There would be no need to query the userinfo endpoint. But no such solution exists in the form of a Spring Security authentication filter.

My webapp was not a spring-boot app like romain's. spring-boot does alot of configuration behind the scenes. Here are some of the problems/solutions I encountered along the way:

  1. problem: HTTP Status 403 - Expected CSRF token not found. Has your session expired?

    • solution: java config: httpSecurity.csrf().disable()
  2. problem: HTTP Status 500 - Error creating bean with name 'scopedTarget.googleOAuth2RestTemplate': Scope 'session' is not active for the current thread;

    • solution: java config: OAuth2RestTemplate does not need to be session scoped (OAuth2ClientContext is already session scoped and that's all that's necessary)
  3. problem: HTTP Status 500 - Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'session' is not active for the current thread;

    • solution: web.xml: add RequestContextListener
    • explanation: because the oauth2ClientContext session-scoped bean is accessed outside the scope of the Spring MVC DispatcherServlet (it is being accessed from OpenIdConnectAuthenticationFilter, which is part of the Spring Security filter chain).
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>
    
  4. problem: org.springframework.security.oauth2.client.resource.UserRedirectRequiredException: A redirect is required to get the users approval.

    • solution: web.xml: Add filter definition immediately PRECEEDING springSecurityFilterChain
    <filter>
        <filter-name>oauth2ClientContextFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>oauth2ClientContextFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

Unfortunately, OpenID Connect does not allow us to request only email scope. When our users authenticated using OpenID they would see a consent screen like "webapp would like to view your email address" with which they were comfortable. Now we must request scopes openid email resulting in a consent screen asking the user to share their entire public profile with us ... which we really don't need or want ... and users are less comfortable with this consent screen.

Leontineleontyne answered 23/3, 2015 at 22:20 Comment(2)
With regards to authenticating just by the ID token, the ID Token should be a JWT that "MUST be signed using JWS" (encryption is optional). It's fairly straightforward to write a Filter that verifies the signature and populates the security context with the JWT's claims. I have not yet seen a Spring security anything that does this, just custom filters built on the e.g. the Auth0 jwk/jwt packages. You're right about the scopes as well. The OIDC providers I've worked with are either "not enough" or "way too much" info per scope.Oftentimes
In my case, I'm just implementing a UI client in Spring Boot over the Resource server (that I mentioned above, which verifies the JWT). In overcoming your problem 3 HTTP Status 500 - Error creating bean with name 'scopedTarget.oauth2ClientContext': Scope 'session' is not active for the current thread;, I had to change to a org.springframework.web.filter.RequestContextFilter instead of a RequestContextListener. Haven't figured out why yet.Oftentimes

© 2022 - 2024 — McMap. All rights reserved.