Click here to Skip to main content
15,867,453 members
Articles / Programming Languages / Java

How to integrate Spring-oAuth2 with Spring-SAML

Rate me:
Please Sign up or sign in to vote.
4.86/5 (6 votes)
5 Jun 2013CPOL9 min read 94.2K   9   23
This document describes how to integrate the Spring-Security-oAuth2 project with Spring-Security-SAML.

Introduction 

This document describes how to integrate the Spring-Security-oAuth2 project with Spring-Security-SAML.

I assume the reader is familiar with both oAuth and its components, and SAML and its components.

Motivation

Suppose you want your system to support oAuth2.  I would recommend using the Spring-Security-oAuth project. When you use Spring, you enjoy the many benefits of this open-source package: it is widely used, there is responsive support (in the forum), it is open source, and much more. This package allows the developer to write an oAuth-client, an oAuth resource server, or an oAuth authorization server.

Let us discuss SAML.  If you want to implement your own SAML SP (Service Provider), I recommend using Spring-Security-SAML, for the same reasons I recommended Spring-security-oAuth, above.

Now, consider an application that authenticates its users with oAuth, meaning the application is an "oAuth resource server", and its clients implement the oAuth protocol, meaning they are "oAuth clients".  I was asked to enable this application to connect SAML IdPs (identity providers) and authenticate users in front of them. This means the application must support not only oAuth, but SAML as well. Note, however, that if the application supports SAML, changes would have to be made in all clients, not only in the application itself. Currently the clients are "oAuth clients", (i.e., they fulfill the oAuth protocol). If the application supports SAML as well, the clients will also have to support it on their side. In SAML, the redirects are implemented differently, and the requests are different. So, the question is, how can we make this application support SAML without changing all clients?

The solution is to create an application ("the bridge") that will be a bridge between oAuth and SAML. When a non-authorized client tries to access the protected resource, it is redirected to the authorization server (this is how oAuth works). But here is the trick: from the client’s point of view– and from the application itself – this bridge functions as a valid "oAuth authorization server". Therefore, there is no need to change anything, not in the client code and not in the application code. On the other hand, instead of opening a popup dialog with username and password, this server functions as an SP and redirects the user to authenticate in front of a pre-configured IdP. 

Get to Business

How oAuth works

Let us start with a brief description of how oAuth works. OAuth supports several flows (called "Authorization Grants"); in this document I will discuss the grant called "authorization code". The other grants can be implemented similarly.

From the spec:

In the "authorization code" flow, the authorization code is obtained by using an authorization server    as an intermediary between the client and resource owner.  Instead of requesting authorization directly from the resource owner, the client directs the resource owner to an authorization server (via its user-agent = the browser), which in turn directs the resource owner back to the client with the authorization code.

Before directing the resource owner back to the client with the authorization code, the authorization server authenticates the resource owner and obtains authorization.  Because the resource owner only authenticates with the authorization server, the resource   owner's credentials are never shared with the client.

The authorization code provides a few important security benefits such as the ability to authenticate the client, and the transmission of the access token directly to the client without passing it through the resource owner's user-agent, potentially exposing it to others, including the resource owner. 

Image 1

How SAML works 

SAML diagram

When the user wants to get to a protected resource (=service provider, or SP) using a web browser, he navigates to that webpage. The SP will not ask for a username and password to log in, but instead redirect the browser to the IDP for authentication.

Embedded within this redirect message (as the SAMLRequest parameter) is a SAML authentication request message. As SAML is XML-based the complete authentication request message is compressed (to save space in the URL) and encoded (because many characters are not allowed in URLs).

When the IDP receives this message and decides to grant the SP’s request, it will authenticate the user by asking him to enter his credentials (unless he already did – for example when having logged in at another service earlier – in which case single sign-on is triggered by simply skipping authentication). After successful authentication, the user’s browser is sent back to the SP at the so called AssertionConsumerService URL. As before, a SAML protocol message is piggybacking  along – this time carrying a SAML authentication response message.

Spring configurations

As mentioned earlier, amongst Spring’s benefits are its flexibility and configurability. One can develop oAuth or SAML projects with minimal POJO classes; most of the work is done via the beans configuration. It is up to the developer to decide whether the basic implementation of Spring suits him, or whether he should configure the application differently, or even implement his own classes.

The Spring configurations (XML beans file) for the oAuth authorization server looks something like this (I’ve pasted only the relevant beans):

XML
<!-- Protect the /oauth/token url to allow only registered clients -->
<security:http pattern="/oauth/token" 
        authentication-manager-ref="clientAuthenticationManager">
    <security:intercept-url pattern="/oauth/token" 
      access="ROLE_CLIENT" requires-channel="https"/>
    <security:anonymous enabled="false" />
    <security:http-basic />
 
    <security:custom-filter 
      ref="clientCredentialsTokenEndpointFilter" 
      before="BASIC_AUTH_FILTER" />
</security:http>
 
 
<security:http auto-config="true"
              authentication-manager-ref="usersAuthManager">   
   <security:intercept-url pattern="/oauth/**"  access="ROLE_USER" />
   <security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/>
   <security:anonymous enabled="false"/>
</security:http>

<security:authentication-manager alias="usersAuthManager">
    <security:authentication-provider user-service-ref="userDetailsService"/>
</security:authentication-manager>
 
<security:user-service id="userDetailsService">
    <security:user name="demo1@company.com" 
      password="pass" authorities="ROLE_USER" />
    <security:user name="demo2@company.com" 
      password="demo" authorities="ROLE_USER" />
</security:user-service>

<bean id="clientCredentialsTokenEndpointFilter" 
      class="org.springframework.security.oauth2.provider.
             client.ClientCredentialsTokenEndpointFilter">
    <property name="authenticationManager" ref="clientAuthenticationManager" />
</bean>

<!-- OAuth2 Configuration -->
<oauth:authorization-server
       client-details-service-ref="clientDetails"
       token-services-ref="watchdoxAuthorizationServerTokenServices"
       user-approval-handler-ref="automaticUserApprovalHandler">
       <oauth:authorization-code />
       <oauth:implicit />
       <oauth:refresh-token />
       <oauth:client-credentials />
       <oauth:password />
</oauth:authorization-server>
 
<security:authentication-manager id="clientAuthenticationManager">
    <security:authentication-provider user-service-ref="clientDetailsUserService" />
</security:authentication-manager>
 
<bean id="clientDetailsUserService"
    class="org.springframework.security.oauth2...ClientDetailsUserDetailsService">
        <constructor-arg ref="clientDetails" />
</bean>
 
<bean id="clientDetails"
    class="org.springframework.security.oauth2...JdbcClientDetailsService">
      <constructor-arg ref="dataSource" />
</bean>

<bean id="dataSource" ...>
… DataSource configurations here
</bean>

The Spring configurations for SAML SP can be found in the Spring-Security-SAML-sample project, so I will not include it here.

Now, we have to understand how to integrate these two projects.

Integrating Spring’s oAuth and SAML

As mentioned earlier, our application is an oAuth authorization server. We would like to configure this application so it will also be a SAML SP. By doing this, every time a user is redirected to our application in order to authenticate, we will redirect him again to the SAML IdP. Technically, redirecting the user to the IdP means creating a SAML request. So, our application must have the capability to process SAML responses that come from that IdP.

Importing the saml-securityContext.xml

From the beans.xml file of our application, we have to add all the SAML beans that are declared in the saml-securityContext.xml:

XML
<import resource="classpath:saml-securityContext.xml"/>

SAML Entry point 

The first thing to note in SAML properties is that in the main http block there is a declaration of the entry-point:  

XML
entry-point-ref="samlEntryPoint"

Generally, it means that whenever a relevant http call tries to access this app, this is the entry-point that handles the request. In the SAML case, samlEntryPoint (class SAMLEntryPoint) checks if the IdP is known; if not, it starts a discovery process, otherwise it initializes the SSO process – this generates the SAML request to the IdP.

Obviously, we need this functionality. So we will declare our web application as a "protected resource", so that every time the user tries to authenticate, the SAML process will occur. We achieve this by changing  the http block that handles the  /oAuth/authorize call, and setting its endpoint filter to point to "samlEntryPoint".

XML
<security:http entry-point-ref="samlEntryPoint">  

User’s Authentication Manager

As you can see in the code above, the same http block that we discussed also points to an Authentication Manager:

XML
...authentication-manager-ref="usersAuthManager">

This Authentication Manager is the one that is responsible for the user’s authentication. Within it, there is a declaration of the provider, which holds the users and passwords. Of course, in real world implementations, this provider directs to a JDBC, where all users are stored with their encrypted passwords. The good news here is, we do not have to handle any users and passwords, since we delegate this to the IdP. So, we change the declaration of our server to point to the authentication manager of the SAML SP: this manager has a provider (SAMLAuthenticationProvider or some other class that extends it). This class implements the authenticate() method, that attempts to perform authentication of an Authentication object. 

MetadataGeneratorFilter 

This is a filter that automatically generates default SP metadata. To include it in our filter chain, it has to be declared as the first element in the chain of the http block we saw above:

XML
<security:custom-filter before="FIRST" ref="metadataGeneratorFilter"/>

SAML Filter Chain 

Working with Spring-SAML requires several filters to be added to the chain, such as logout filter, metadata display filter and a few more. Spring-SAML creates a dedicated chain for this, that should be run right after the BASIC_AUTH_FILTER. So, in the http block (yes, the same one…) we add the following:

XML
<security:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>

To Sum Up  

We end up with these changes only; all other beans remain unchanged:

XML
<import resource="classpath:saml-securityContext.xml"/>
<security:http authentication-manager-ref="authenticationManager"
           entry-point-ref="samlEntryPoint">   
    <security:intercept-url pattern="/oauth/**" access="ROLE_USER" />
    <security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY"/>
 
    <security:anonymous enabled="false"/>
 
    <security:custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
    <security:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>
</security:http>

The Token

Some of you may be asking – hey, what about the token?

Ah!

Until now, everything works great; Spring SAML makes sure we have a valid Authentication object in the SecurityContextHolder (actually, it is of type ExpiringUsernameAuthenticationToken); thanks to this, we are able to get the authorization code and then the access token. The user is happy, and we are all happy. But what happens when we want to get some information from the SAML token (e.g. the user’s name or email) and create an oAuth token that contains them? In this case (not a rare one, actually) we need to  become a bit more familiar with how SAMLAuthenticationProvider works.

SAMLAuthenticationProvider is autowired to the authentication manager thanks to the SAML-securityContext.xml (as discussed above). The authentication manager calls the SAMLAuthenticationProvider.authenticate() method and passes it the Authentication object, which is of type SAMLAuthenticationToken. This method validates the token, parses it and makes all necessary checks to make sure it corresponds to the SAMLRequest. Then – and this is the interesting part– it tries to load the user’s details from the SAML credentials (parsed earlier from the response). How does this happen? It checks if there is a SAMLUserDetailsService object available; if there is none, it returns null (as it did in our case till now) and this is fine, but it does not use the details from the SAML token. If, however, there is a SAMLUserDetailsService object, it calls its loadUserBySaml() method.

Now, we have to ensure that there is a SAMLUserDetailsService object attached. We do this by implementing one by ourselves, and annotating it as "Component" so it will be autowired to the SAMLAuthenticationProvider.

C#
/**
 * this class is autowired to the SamlProvider, so it tries to get the user's details from the token using this
 * class, o/w it returns null.
 * @author Ohad
 *
 */
@Component
public class SAMLUserDetailsServiceImpl implements SAMLUserDetailsService
{
       @Override
       public Object loadUserBySAML(SAMLCredential credential)
                     throws UsernameNotFoundException
       {
              List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
              String email = credential.getNameID().getValue();
              GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");
              authorities.add(authority);
             
              UserDetails userDetails = new User(
                           email, "password", true, true, true, true, authorities);
 
              return userDetails;
       }
}

The provider then takes the UserDetails object and sets it to the Authentication object that it creates. Now we have access to these details so we can call the SecurityContextHolder.getAuthentication() and get them. When we do this? Spring’s TokenEndpoint.getAccessToken() calls implicitly to the  tokenServices bean, to create the access token. Spring’s default token-services is DefaultTokenServices, which does not use the ‘name’ field of the given Authentication object. (Actually, it uses a random UUID). To overcome this, we create our own token-service that will do this work. Note that its bean name has to be attached properly in the XML file:

C#
<!-- OAuth2 Configuration -->
<oauth:authorization-server
	token-services-ref="myAuthorizationServerTokenServices"
       ...

Below is an example for such token service:

C#
@Component("myAuthorizationServerTokenServices")
public class OAuth2TokenServices implements AuthorizationServerTokenServices, InitializingBean
{
	@Override
	public OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException
	{
			String email = authentication.getName();
			String generatedToken = generate the Token with the 'email'
			DefaultOAuth2AccessToken result = new DefaultOAuth2AccessToken( generatedToken );
			result.setExpiration( expiration );
			result.setTokenType("Bearer");
			...
			return result;
	}
}

Summary  

We have seen how to integrate two different Spring projects, each handling a different authentication mechanism, and by integrating them have achieved a bridge that, from the clients’ side, remains an oAuth authentication server, but allows the application to connect and authenticate in front of SAML IdPs.

Many thanks to David Goldhar for his help with this document! 

References

  1. OAuth spec 
  2. SAML diagram and flow 
  3. oAuth diagram 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Israel Israel
working for Intel

My Linkedin Profile

Visit my photography gallery

Comments and Discussions

 
QuestionHow to get oauth2 token and return it to client with successRedirectHandler Pin
Surkov Vladimir12-Apr-21 7:17
Surkov Vladimir12-Apr-21 7:17 
AnswerRe: How to get oauth2 token and return it to client with successRedirectHandler Pin
Ohad Redlich20-Apr-21 5:49
Ohad Redlich20-Apr-21 5:49 
QuestionCombine SAML2.0+ Auth2.0 + Database authentication Pin
Member 1469781315-Apr-20 6:54
Member 1469781315-Apr-20 6:54 
AnswerRe: Combine SAML2.0+ Auth2.0 + Database authentication Pin
Ohad Redlich20-Apr-21 5:41
Ohad Redlich20-Apr-21 5:41 
Questionam i missing a web.xml? Pin
John DMZ21-Feb-18 13:13
John DMZ21-Feb-18 13:13 
Answercan't you find the web.xml in GitHub?! Pin
Ohad Redlich9-Oct-19 2:18
Ohad Redlich9-Oct-19 2:18 
QuestionCan you share a sample project? Pin
Sander Van Schoote12-Apr-15 15:50
Sander Van Schoote12-Apr-15 15:50 
AnswerRe: Can you share a sample project? Pin
Ohad Redlich12-Oct-15 19:35
Ohad Redlich12-Oct-15 19:35 
Questionrequired Spring xml file for Spring OAuth 2 Pin
Member 1124127110-Feb-15 5:07
Member 1124127110-Feb-15 5:07 
Questionrequires Spring-oAuth2 - source code Pin
Member 112412718-Feb-15 23:44
Member 112412718-Feb-15 23:44 
QuestionOauth token from SAML assertion in spring ? Pin
jamsheer citrus16-Dec-14 1:08
jamsheer citrus16-Dec-14 1:08 
AnswerRe: Oauth token from SAML assertion in spring ? Pin
Ohad Redlich16-Dec-14 1:48
Ohad Redlich16-Dec-14 1:48 
GeneralRe: Oauth token from SAML assertion in spring ? Pin
jamsheer citrus16-Dec-14 1:58
jamsheer citrus16-Dec-14 1:58 
Thank you for the replay ,If you update that will be great appreciable
GeneralRe: Oauth token from SAML assertion in spring ? Pin
Ohad Redlich30-Sep-15 23:23
Ohad Redlich30-Sep-15 23:23 
GeneralRe: Oauth token from SAML assertion in spring ? Pin
Member 112412719-Feb-15 21:21
Member 112412719-Feb-15 21:21 
GeneralRe: Oauth token from SAML assertion in spring ? Pin
Ohad Redlich30-Sep-15 23:24
Ohad Redlich30-Sep-15 23:24 
SuggestionNeeded full Configuration.xml file . Pin
Member 1130535311-Dec-14 22:17
Member 1130535311-Dec-14 22:17 
GeneralRe: Needed full Configuration.xml file . Pin
Ohad Redlich16-Dec-14 0:28
Ohad Redlich16-Dec-14 0:28 
GeneralRe: Needed full Configuration.xml file . Pin
Member 1130535316-Dec-14 1:58
Member 1130535316-Dec-14 1:58 
GeneralRe: Needed full Configuration.xml file . Pin
Ohad Redlich16-Dec-14 2:50
Ohad Redlich16-Dec-14 2:50 
QuestionGreat doc Pin
Member 1094055611-Jul-14 6:23
Member 1094055611-Jul-14 6:23 
QuestionSAML + OAuth - is this the way? Pin
Member 1012270123-Jun-13 15:55
Member 1012270123-Jun-13 15:55 
AnswerRe: SAML + OAuth - is this the way? Pin
Ohad Redlich24-Jun-13 2:18
Ohad Redlich24-Jun-13 2:18 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.