spring-security-oauth1-provider

Spring Security OAuth1 Provider

The OAuth1 plugin adds OAuth 1.0 support to a Grails application that uses Spring Security. It depends on Spring Security Core plugin.

This documentation specifies a few specific steps you will have to take in order to ensure proper integration with the underlying library.

This plugin provides support for Grails domain classes necessary for providing OAuth 1.0 authorization. Access to protected resources is controlled by a combination of Spring Security Core’s methods, i.e. request maps, annotations, intercept maps and careful configuration of the Spring Security filter chains.

Getting Started

The following assumes that the Spring Security Core plugin has been installed and its required domain class created.

1. Install Plugin

Install the OAuth1 plugin by adding a dependency in grails-app/conf/BuildConfig.groovy

plugins {
  compile ":spring-security-oauth1-provider:1.0"
}

Note :- It’s not verified yet by Grails community, hence need to pull this plugin from here directly and compile it locally into your project.

2. Create Domain Classes

There is following two domain classes required which needs to be create manually for now

2.1 OAuthConsumer Class

packaage com.security.oauth

class OAuthConsumer {
  String consumerKey 
  String consumerName 
  String consumerSecret
  static hasMany = [ authorities: String ]

  static constraints = { 
    consumerKey blank: false, unique: true 
    consumerSecret nullable: true 
    consumerName nullable: true 
    authorities nullable: true 
  }
}

2.2 OAuthAccessProviderTokenWrapper Class

packaage com.security.oauth

class OAuthAccessProviderTokenWrapper implements OAuthAccessProviderToken {

  transient userAuthenticationtransient userAuthentication

  String value 
  String callbackUrl 
  String verifier 
  String secret 
  String consumerKey 
  boolean accessToken 
  long timestamp = System.currentTimeMillis()
 
  def user
  
  static transients = ['userAuthentication']

  static constraints = {
     consumerKey blank: false value nullable: true 
     callbackUrl nullable: true 
     verifier nullable: true 
     secret nullable: true 
     timestamp nullable: true 
     user nullable:true 
     userAuthentication nullable:true 
  }
 
  public void setUserAuthentication(Authentication userAuthentication) { 
    this.userAuthentication = userAuthentication; 
  }

  @Override 
  public Authentication getUserAuthentication() { 
    return this.userAuthentication 
  }
}

Now provided these domains information after adding it in grails-app/conf/Config.groovy:

grails.plugin.springsecurity.oauthProvider.consumerLookup.className = 'com.security.oauth.OAuthConsumer'
grails.plugin.springsecurity.oauthProvider.tokenLookup.className = 'com.security.oauth.OAuthAccessProviderTokenWrapper'

3. Secure Authorization and Token Endpoints

Update the Core plugin’s rules for the authorization and token endpoints so they are protected by Spring Security. If you’re using the Core plugin’s staticRules, you’ll want to add the following in grails-app/conf/Config.groovy:

grails.plugin.springsecurity.controllerAnnotations.staticRules = [
    '/oauth_request_token'     : ["(request.getMethod().equals('GET') or request.getMethod().equals('POST'))"],
    '/oauth_authenticate_token': ["(request.getMethod().equals('GET') or request.getMethod().equals('POST'))"],
    ... The endpoints are standard Spring MVC controllers in the underlying Spring Security OAuth1 implementations.

4. Exclude consumer_secret From Logs

Update the params exclusion list in grails-app/conf/Config.groovy so client secrets are not logged in the clear:

grails.exceptionresolver.params.exclude = ['password', 'consumer_secret']

5. Consumer Registration

At this point your application is a proper OAuth 1.0 provider. You can now register consumers in what ever method is appropriate for your application. For example, you can register a consumer in grails-app/conf/Bootstrap.groovy as follows:

def init = { servletContext ->
        new OAuthConsumer
          (consumerKey: 'my-consumer', 
           consumerName : 'Consumer Name', 
           consumerSecret : 'my-secret', 
           authorities : ['ROLE_CLIENT']).save(flush: true, failOnError: true)
 }

6. Controlling Access to Resources

Access to resources is controlled by the Spring Security Core plugin’s access control mechanisms. Additionally, the plugin has full support for the OAuth 1.0 extensions provided by the underlying Spring library.

class ConsumerController {

 @Secured('ROLE_CLIENT') 
 def getConsumerInfo() { 
    if(params.oauth_consumer_key) { 
     render "It's oauth1 request" 
    } else { 
     render "it's original login request" 
    } 
  }
}

The filter chains must be configured to ensure stateless access to the token endpoint and any OAuth 1.0 resources:

grails.plugin.springsecurity.filterChain.chainMap = [ 
 '/oauth_request_token'      : 'oauthRequestTokenFilter', 
 '/oauth_authenticate_token' : 'securityRequestHolderFilter,securityContextPersistenceFilter,securityContextHolderAwareRequestFilter,rememberMeAuthenticationFilter,exceptionTranslationFilter,filterInvocationInterceptor,oauthAuthenticateTokenFilter',
 '/oauth_access_token'       : 'securityRequestHolderFilter,securityContextPersistenceFilter,oauthAccessTokenFilter', 
 '/consumer/**'              : 'securityRequestHolderFilter,securityContextPersistenceFilter,oauthProtectedResourceFilter,rememberMeAuthenticationFilter,anonymousAuthenticationFilter,exceptionTranslationFilter,filterInvocationInterceptor',
 '/**'                       : 'JOINED_FILTERS'
]

Domain Class Properties

No default class name is assumed for the required domain classes. They must be specified in grails-app/conf/Config.groovy as follows :

grails.plugin.springsecurity.oauthProvider.consumerLookup.className = 'com.security.oauth.OAuthConsumer'
grails.plugin.springsecurity.oauthProvider.tokenLookup.className = 'com.security.oauth.OAuthAccessProviderTokenWrapper'

The following properties exist in the grails.plugin.springsecurity.oauthProvider namespace.

1. OAuthConsumer Class Properties

Property
Default Value
Meaning
consumerLookup.classNamenullConsumer class name.
consumerLookup.consumerKeyPropertyNameconsumerKeyConsumer class consumer key field.
consumerLookup.consumerNamePropertyNameconsumerNameConsumer class consumer name field.
consumerLookup.consumerSecretPropertyNameconsumerSecret

Consumer class consumer secret field.

consumerLookup.authoritiesPropertyNameauthoritiesConsumer class authorities field.

2. OAuthAccessProviderTokenWrapper Class Properties

Property
Default Value
Meaning
tokenLookup.callbackUrlPropertyNamecallbackUrl

AccessProviderToken class call back URL field.

tokenLookup.classNamenullAccessProviderToken class name.
tokenLookup.consumerKeyPropertyNameconsumerKeyAccessProviderToken class consumer key field.
tokenLookup.isAccessTokenPropertyNameaccessTokenAccessProviderToken class to check accesstoken field
tokenLookup.secretPropertyNamesecretAccessProviderToken class secret field.
tokenLookup.timestampPropertyNametimestampAccessProviderToken class timestamp field.
tokenLookup.userAuthenticationPropertyNameuserAuthenticationAccessProviderToken class user authentication field.
tokenLookup.userPropertyNameuserAccessProviderToken class user field.
tokenLookup.valuePropertyNamevalueAccessProviderToken class value field.
tokenLookup.verifierPropertyNameverifierAccessProviderToken class verifier field.

Other Configuration

The plugin is pessimistic by default, locking down as much as possible to guard against accidental security breaches. However, these constraints can be modified if so desired in grails-app/conf/Config.groovy. The properties below exist in the grails.plugin.springsecurity.oauthProvider namespace.

The following properties exist in the grails.plugin.springsecurity.oauthProvider namespace :

1. Enable Oauth1.0 Properties

Property
Default Value
Meaning
require10atrueTo enable oauth1.0.

2. RequestTokenFilter Properties

Property
Default Value
Meaning
requestTokenFilter.filterProcessesUrl/oauth_request_tokenEnd point url for request token.
requestTokenFilter.ignoreMissingCredentialsfalseIgnore missing credential for request token flag.
requestTokenFilter.allowedMethods['GET', 'POST']Methods to support for request.
requestTokenFilter.responseContentTypetext/plain;charset=utf-8Response content type to parse.

3. Endpoint Properties

Property
Default Value
Meaning
entryPoint.realmNameGrails OAuth ProviderResposne end point handler realm.

4. Nonce Properties

Property
Default Value
Meaning
nonce.validityWindowSeconds60 * 60 * 12Validitiy period of nonce.

5. Oauth1.0 Provider Properties

Property
Default Value
Meaning
provider.baseUrlnullProvider base URL.

6. Signature Properties

Property
Default Value
Meaning
signature.supportPlainTextfalseSupport plain text.
signature.supportHMAC_SHA1trueSupport HMAC_SHA1 signature method.
signature.supportRSA_SHA1trueSupport RSA_SHA1 signature method.

7. TokenServices Properties

Property
Default Value
Meaning
tokenServices.accessTokenValiditySeconds60 * 60 * 12Acess token validity period.
tokenServices.requestTokenValiditySeconds60 * 10Request token validity period.
tokenServices.tokenSecretLengthBytes80Token secret length.

8. AuthTokenFilter Properties

Property
Default Value
Meaning
authTokenFilter.filterProcessesUrl/oauth_authenticate_tokenEnd point URL to authorize token.
authTokenFilter.tokenIdParameterNamerequestTokenParameter name to get request token for authorization.

9. Requiest token verifier Properties

Property
Default Value
Meaning
verifier.lengthBytes6Length of verifier request token.

10. SuccessHandler Properties

Property
Default Value
Meaning
successHandler.tokenIdParameterNamerequestTokenResponse token parameter name.
successHandler.callbackParameterNamecallbackURLResponse callback URL parameter name.

11. AccessTokenFilter Properties

Property
Default Value
Meaning
accessTokenFilter.filterProcessesUrl/oauth_access_tokenEnd point URL to obtain acesstoken.
accessTokenFilter.ignoreMissingCredentialsfalseIgnore missing credetial to obtain accesstoken flag.
accessTokenFilter.allowedMethods['GET', 'POST']Methods to support forobtain accesstoken.

12. ProtectedResourceFilter Properties

Property
Default Value
Meaning
protectedResourceFilter.allowAllMethodstrueAllow all methods flag.
protectedResourceFilter.ignoreMissingCredentialstrueIgnore missing credetial to acess resources flag.

Example Flows

The key to understanding how OAuth1 works is understanding the authorization flow. This is the process clients go through to link to a site.

The flow with the OAuth1 plugin is called the three-legged flow, thanks to the three primary steps involved:

Temporary Credentials Acquisition: The client gets a set of temporary credentials from the server. Authorization: The user “authorizes” the request token to access their account. Token Exchange: The client exchanges the short-lived temporary credentials for a long-lived token.

1. Temporary Credentials Acquisition

The first step to authorization is acquiring temporary credentials (also known as a Request Token). These credentials are short-lived (typically 24 hours), and are used purely for the initial authorization process. They don’t grant any access to data on the server, and cannot be used for anything except the authorization flow.

These credentials are acquired by an initial HTTP request to the server. The client starts by sending a POST request to the temporary credential URL, typically /oauth_request_token with the plugin. (This URL should be autodiscovered from the API, as individual sites may move this route, or delegate the process to another server.) This looks something like:

This request includes the client key (oauth_consumer_key), the authorization callback (oauth_callback), and the request signature (oauth_signature and oauth_signature_method). This looks something like:

POST /oauth_request_token HTTP/1.1
Host: http://localhost:8080
Authorization: OAuth realm="Example",
           oauth_consumer_key="my-consumer",
           oauth_signature_method="HMAC-SHA1",
           oauth_timestamp="1513678344",
           oauth_nonce="Dc85Pk",
           oauth_version=1.0,
           oauth_callback="http://localhost:8080/token",
           oauth_signature="zCVppwQZ0Y7T68gxPLFzaMObhks="

Note : You can directlly hit the following URL on browser for getting the same :

http://localhost:8080/oauth_request_token?oauth_consumer_key=my-consumer&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1513678344&oauth_nonce=Dc85Pk&oauth_version=1.0&oauth_signature=zCVppwQZ0Y7T68gxPLFzaMObhks=&oauth_callback=http://localhost:8080/token

The server checks the key and signature to ensure the client is valid. It also checks the callback to ensure it’s valid for the client.

Once the checks are complete, the server creates a new set of Temporary Credentials (oauth_token and oauth_token_secret) and returns them in the HTTP response (URL encoded). This looks something like:

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=hdk48Djdsa&oauth_token_secret=xyz4992k83j47x0b&oauth_callback_confirmed=true

These credentials are then used as the oauth_token and oauth_token_secret parameters for the Authorization and Token Exchange steps.

Note : The oauth_callback_confirmed=true will always be returned, and indicates that the protocol is OAuth 1.0.

2 Authorization

The next step in the flow is the authorization process. This is a user-facing step, and the one that most users will be familiar with.

Using the authorization URL supplied by the site (typically /oauth_authenticate_token), the client appends the temporary credential key (requestToken from above) to the URL as a query parameter (again as requestToken ). The client then directs the user to this URL. Typically, this is done via a redirect for in-browser clients, or opening a browser for native clients.

The user then logs in if they aren’t already, and authorizes the client. They can also choose to cancel the authorization process if they don’t want to link the client.

If the user authorizes the client, the site then marks the token as authorized, and redirects the user back to the callback URL. The callback URL includes two extra query parameters: oauth_token (the same temporary credential token) and oauth_verifier, a CSRF token that needs to be passed in the next step.

For eg. http://localhost:8080/token?oauth_token=b9c91204-e4c3-42b3-a479-b85dff86427d&oauth_verifier=uVA2W3 where http://localhost:8080/token is redirect URL

3. Token Exchange

The final step in authorization is to exchange the temporary credentials (request token) for long-lived credentials (also known as an Access Token). This request also destroys the temporary credentials.

The temporary credentials are converted to long-lived credentials by sending a POST request to the token request endpoint (typically /oauth_authenticate_token). This request must be signed by the temporary credentials, and must include the oauth_verifier token from the authorization step. The request looks something like:

POST /oauth_authenticate_token HTTP/1.1
Host: http://localhost:8080 
Authorization: OAuth realm="Example",
     oauth_consumer_key="my-consumer",
     oauth_token="hdk48Djdsa",
     oauth_signature_method="HMAC-SHA1",
     oauth_timestamp="1513668573",
     oauth_nonce="8JpkZv", oauth_verifier="uVA2W3",
     oauth_version=1.0,
     oauth_signature="/133b9TEIp3f9l3W5hZVgEOyTYA="

Note : You can directly hit the following URL on browser for getting the same :

http://localhost:8080/oauth_access_token?oauth_consumer_key=my-consumer&oauth_token=b9c91204-e4c3-42b3-a479-b85dff86427d&oauth_signature_method=HMAC-SHA1&oauth_timestamp=1513668573&oauth_nonce=8JpkZv&oauth_version=1.0&oauth_signature=/133b9TEIp3f9l3W5hZVgEOyTYA=&oauth_verifier=uVA2W3

The server again checks the key and signature, as well as also checking the verifier token to avoid CSRF attacks.

Assuming these checks all pass, the server will respond with the final set of credentials in the HTTP response body (form data, URL-encoded):

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=j49ddk933skd9dks&oauth_token_secret=ll399dj47dskfjdk

At this point, you can now discard the temporary credentials (as they are now useless), as well as the verifier token.