(Quick Reference)
spring-security-oauth2-provider
Authors:
Version: 4.0.0-RC1
1 Introduction to the Spring Security OAuth2 Plugin
The OAuth2 plugin adds
OAuth 2.0 support to a Grails application that uses Spring Security. It depends on
Spring Security Core plugin.
Under the covers,
Spring Security OAuth is used by the plugin to provide OAuth 2.0 services. 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 2.0 authorization. The standard grant types described in
RFC 6749 are supported by the plugin. 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.
1.1 Change Log
- 4.0.0-RC1
- Upgrade to support Grails 4 (pull request #16)
- 3.2.1
- Fix logging (https://github.com/grails/grails-core/issues/10683)
- Upgrade spring-security-oauth2 to 2.0.18 for security fixes
- Moved to a new organization (grails-plugins) on GitHub, all PR references below are for the bluesliverx repo
- 3.2.0
- Bad release, ignore this version
- 3.1.0-RC1
- Upgrades needed for Grails 3.3.x (PR #141)
- Fixes for config loading (PR #139)
- 3.0.0-RC2
- Upgrade to Spring Security OAuth 2.0.9-RELEASE
- Fix issues with Grails 3.1.x (#114, PR #116)
- 3.0.0-RC1
- Upgrade to Grails 3.x
- Make `readAuthentication` in `GormTokenStoreService` null safe (pull request #109)
- Upgrade to Spring OAuth 2.0.8.RELEASE
- 2.0-RC5
- Upgrade to Spring OAuth 2.0.7.RELEASE for compatibility with Spring Security Core RC5 (issue #100)
- Resolve minor problems affecting stateless access of OAuth 2.0 resources
- Remove need to include `clientCredentialsAuthenticationProvider` in `grails.plugin.springsecurity.providerNames` list
- Document using plugin to create only authorization server only or only a resource server (issue #71)
- 2.0-RC4
- Fix for Grails 2.5.0 (issue #76)
- Add support for basic authentication (issue #80)
- Fix access token header format in the docs (issue #84)
- Throw exception on validation code save (issue #90)
- Fixes and enhancements for additional information (issue #87)
- Add support for unlimited refresh tokens (issue #75)
- 2.0-RC3
- Upgrade to Spring OAuth 2.0.6.RELEASE (issue #63)
- Fix problems with updating access tokens (issues #49, #50, and #68)
- Add TravisCI build
- Ensure Set-Cookie header is not set in response
- Fix handling of scope parameter (issue #64)
- 2.0-RC2
- Resolves session vulnerability (issue #42)
- Upgrade to Spring Security OAuth2 2.0.4.RELEASE
- Supports authorization auto-approval
- Minor tweaks to domain models
- 2.0-RC1
- Complete overhaul of the plugin
- Requires/supports Spring Security Core 2.0-RC4
- Uses Spring Security OAuth2 2.0.2.RELEASE
- 1.0.5.2
- Fix #13 - Make clientSecret optional in client configuration structure
- 1.0.5.1
- Merge pull request #21 (Burt's cleanup)
- Use log wrapper instead of log4j
- Depends on Grails 2.0 or greater (consistent with core plugin)
- 1.0.5
- Initial release of plugin compatible with spring security core 2.0-RC2
2 Getting Started
The following assumes that the Spring Security Core plugin has been installed and its required domain class created.
2.1 Install Plugin
Install the OAuth2 plugin by adding a dependency in
build.gradle
:
dependencies {
# For Grails 3.3+
compile 'org.grails.plugins:spring-security-oauth2-provider:3.2.1'
# For Grails 3 before 3.3
compile 'org.grails.plugins:spring-security-oauth2-provider:3.0.0-RC2'
}
This has a dependency on the Spring Security Core plugin, which will be installed if necessary.
2.2 Create Domain Classes
Run the
s2-init-oauth2-provider script to generate the required domain classes.
2.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/application.groovy
:
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
[pattern: '/oauth/authorize', access: "isFullyAuthenticated() and (request.getMethod().equals('GET') or request.getMethod().equals('POST'))"],
[pattern: '/oauth/token', access: "isFullyAuthenticated() and request.getMethod().equals('POST')"],
...
The endpoints are standard Spring MVC controllers in the underlying Spring Security OAuth2 implementations and the URLs must be mapped with
.dispatch
.
The additional restrictions on the allowed HTTP methods are to ensure compliance with the OAuth 2.0 spec as defined in
RFC 6749.
2.4 (Optional) Customize Error and Confirm Access Views
The plugin provides views for the error and confirm access pages. These can be overridden by providing your own
grails-app/views/oauth/error.gsp
and
grails-app/views/oauth/confirm_access.gsp
files, respectively.
2.5 Client Registration
At this point your application is a proper OAuth 2.0 provider. You can now register clients in what ever method is appropriate for your application. For example, you can register a client in
grails-app/init/Bootstrap.groovy
as follows:
def init = { servletContext ->
new Client(
clientId: 'my-client',
authorizedGrantTypes: ['authorization_code', 'refresh_token', 'implicit', 'password', 'client_credentials'],
authorities: ['ROLE_CLIENT'],
scopes: ['read', 'write'],
redirectUris: ['http://myredirect.com']
).save(flush: true)
}
2.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 2.0 SPeL extensions provided by the underlying Spring library. Refer to the methods in
OAuth2SecurityExpressionMethods for what is available in the plugin.
Using SPeL is the only tested and confirmed way to enforce OAuth 2.0 specific restrictions on resource access.
The following controller illustrates the use of OAuth 2.0 SPeL:
class SecuredOAuth2ResourcesController { @Secured(["#oauth2.clientHasRole('ROLE_CLIENT')"])
def clientRoleExpression() {
render "client role expression"
} @Secured(["ROLE_CLIENT"])
def clientRole() {
render "client role"
} @Secured(["#oauth2.clientHasAnyRole('ROLE_CLIENT', 'ROLE_TRUSTED_CLIENT')"])
def clientHasAnyRole() {
render "client has any role"
} @Secured(["#oauth2.isClient()"])
def client() {
render "is client"
} @Secured(["#oauth2.isUser()"])
def user() {
render "is user"
} @Secured(["#oauth2.denyOAuthClient()"])
def denyClient() {
render "no client can see"
} @Secured(["permitAll"])
def anyone() {
render "anyone can see"
} def nobody() {
render "nobody can see"
} @Secured(["#oauth2.clientHasRole('ROLE_TRUSTED_CLIENT') and #oauth2.isClient() and #oauth2.hasScope('trust')"])
def trustedClient() {
render "trusted client"
} @Secured(["hasRole('ROLE_USER') and #oauth2.isUser() and #oauth2.hasScope('trust')"])
def trustedUser() {
render "trusted user"
} @Secured(["hasRole('ROLE_USER') or #oauth2.hasScope('read')"])
def userRoleOrReadScope() {
render "user role or read scope"
}
}
The filter chains must be configured to ensure stateless access to the token endpoint and any OAuth 2.0 resources:
grails.plugin.springsecurity.filterChain.chainMap = [
[pattern: '/oauth/token', filters: 'JOINED_FILTERS,-oauth2ProviderFilter,-securityContextPersistenceFilter,-logoutFilter,-authenticationProcessingFilter,-rememberMeAuthenticationFilter,-exceptionTranslationFilter'],
[pattern: '/securedOAuth2Resources/**', filters: 'JOINED_FILTERS,-securityContextPersistenceFilter,-logoutFilter,-authenticationProcessingFilter,-rememberMeAuthenticationFilter,-oauth2BasicAuthenticationFilter,-exceptionTranslationFilter'],
[pattern: '/**', filters: 'JOINED_FILTERS,-statelessSecurityContextPersistenceFilter,-oauth2ProviderFilter,-clientCredentialsTokenEndpointFilter,-oauth2BasicAuthenticationFilter,-oauth2ExceptionTranslationFilter']
]
Please consult the section on
Filter Chain Configuration for more information.
2.7 Trouble Shooting
If an instance of one of the GORM backed classes that the plugin uses cannot be saved, an
OAuth2ValidationException
will be thrown. This is a subclass of the standard Grails
ValidationException
so the plugin consumer has the flexibility to determine how to deal with this type of error. The typical reason for this exception being thrown will likely be related to the max size allotted to the serialized
OAuth2Authentication
fields. The thrown exception can be inspected for further information about the
Errors
.
3 Example Flows
The following examples assume you have followed the steps outlined in the
Getting Started section for an application named
oauth2-test
and your
grails-app/init/BootStrap.groovy
contains the following:
def init = { servletContext -> Role roleUser = new Role(authority: 'ROLE_USER').save(flush: true) User user = new User(
username: 'my-user',
password: 'my-password',
enabled: true,
accountExpired: false,
accountLocked: false,
passwordExpired: false
).save(flush: true) UserRole.create(user, roleUser, true) new Client(
clientId: 'my-client',
authorizedGrantTypes: ['authorization_code', 'refresh_token', 'implicit', 'password', 'client_credentials'],
authorities: ['ROLE_CLIENT'],
scopes: ['read', 'write'],
redirectUris: ['http://myredirect.com']
).save(flush: true)
}
After retrieving an
access_token
via one of the flows, you must include this in the
Authorization
header when accessing protected resources.
For example, if you receive
7b9a989e-3702-4621-a631-fbd1a996fc94
as the
access_token
, you will include this in the
Authorization
header as
Bearer 7b9a989e-3702-4621-a631-fbd1a996fc94
when requesting a protected resource.
The examples below are given using
CURL tool to make the requests. The plugin is compliant with RFC 6749 when configured properly. Therefore token requests should be made using an HTTP POST and authorization requests should be initiated by the User-Agent with an HTTP GET.
3.1 Authorization Code Grant
The authorization code grant flow is initiated by directing your browser to the authorization endpoint:
http://localhost:8080/oauth2-test/oauth/authorize?response_type=code&client_id=my-client&scope=read
You will be redirected to the login page. After signing in, you will be prompted to confirm the request. Doing so will redirect your browser to the following URL:
http://myredirect.com/?code=139R59
The authorization code included in the query can be exchanged for an access token via the token endpoint:
curl -X POST \
-d "client_id=my-client" \
-d "grant_type=authorization_code" \
-d "code=139R59" http://localhost:8080/oauth2-test/oauth/token
Using HTTP Basic for client authentication:
curl -X POST -u my-client: \
-d "grant_type=authorization_code" \
-d "code=139R59" http://localhost:8080/oauth2-test/oauth/token
You'll receive the
access_token
in the response:
{
"access_token": "a1ce2915-8d79-4961-8abb-2c6f0fdb4aba",
"token_type": "bearer",
"refresh_token": "6540222d-0fb9-4b01-8d45-7be2bdfb68f9",
"expires_in": 43199,
"scope": "read"
}
3.2 Implicit Grant
The implicit grant is similar to the authorization code grant and can be initiated by directing your browser to the authorization endpoint:
http://localhost:8080/oauth2-test/oauth/authorize?response_type=token&client_id=my-client&scope=read
Upon confirmation, your browser will be redirected to the following URL:
http://myredirect.com/#access_token=4e22ad4f-08ae-49dc-befb-2c9821af04d1&token_type=bearer&expires_in=43199
The
access_token
can be extracted from the URL fragment.
3.3 Resource Owner Password Credentials Grant
The resource owner password grant is performed by requesting an access token from the token endpoint:
curl -X POST \
-d "client_id=my-client" \
-d "grant_type=password" \
-d "username=my-user" \
-d "password=my-password" \
-d "scope=read" http://localhost:8080/oauth2-test/oauth/token
Using HTTP Basic for client authentication:
curl -X POST -u my-client: \
-d "grant_type=password" \
-d "username=my-user" \
-d "password=my-password" \
-d "scope=read" http://localhost:8080/oauth2-test/oauth/token
The
access_token
is included in the response:
{
"access_token": "1d49fc35-2af6-477e-8fd4-ab0353a4a76f",
"token_type": "bearer",
"refresh_token": "4996ba33-be3f-4555-b3e3-0b094a4e60c0",
"expires_in": 43199,
"scope": "read"
}
3.4 Client Credentials Grant
The client credentials grant is performed by authenticating the client via the token endpoint:
curl -X POST \
-d "client_id=my-client" \
-d "grant_type=client_credentials" \
-d "scope=read" http://localhost:8080/oauth2-test/oauth/token
Using HTTP Basic for client authentication:
curl -X POST -u my-client: \
-d "grant_type=client_credentials" \
-d "scope=read" http://localhost:8080/oauth2-test/oauth/token
The
access_token
can be extracted from the response:
{
"access_token": "7b9a989e-3702-4621-a631-fbd1a996fc94",
"token_type": "bearer",
"expires_in": 43199,
"scope": "read"
}
3.5 Refresh Token Grant
The refresh token grant is performed by exchanging a refresh token received during a previous authorization request for an access token from the token endpoint:
curl -X POST \
-d "client_id=my-client" \
-d "grant_type=refresh_token" \
-d "refresh_token=269afd46-0b41-45c2-a920-7d5af8a38d56" \
-d "scope=read" http://localhost:8080/oauth2-test/oauth/token
Using HTTP Basic for client authentication:
curl -X POST -u my-client: \
-d "grant_type=refresh_token" \
-d "refresh_token=269afd46-0b41-45c2-a920-7d5af8a38d56" \
-d "scope=read" http://localhost:8080/oauth2-test/oauth/token
The above assumes that
269afd46-0b41-45c2-a920-7d5af8a38d56
is the value of the refresh token the client had obtained prior to this request.
The
access_token
is included in the response:
{
"access_token": "a3da52c7-4bd2-4d42-a58d-efa64b4de453",
"token_type": "bearer",
"refresh_token": "6396c283-47ff-41d2-b887-39bde6af5f1e",
"expires_in": 43199,
"scope": "read"
}
4 Required Domain Classes
The plugin uses regular Grails domain classes backed by GORM. There are four required domain classes representing clients, access tokens, refresh tokens and authorization codes that you'll need.
The
s2-init-oauth2-provider script will create the domain classes for you in a specified package and update
grails-app/conf/application.groovy
so the plugin recognizes them. You can customize the generated classes to fit your needs. If you change the default property names, you will need to update
grails-app/conf/application.groovy
so the plugin is aware of the changes. See the section on
domain class properties for more information.
The maxSize
constraints in the generated domain classes have been set to reasonable defaults. However, tweaking may
be required if you are using longer usernames (email addresses for example), or have many authorities attached to a
single user.
The below discussion assumes the
s2-init-oauth2-provider script has been run with
com.yourapp
specified as the package and
Client
,
AccessToken
,
RefreshToken
and
AuthorizationCode
as the names of your domain classes.
4.1 Client Class
Information from the Grails client domain class will be extracted to create a
ClientDetails
instance for the underlying Spring Security OAuth 2.0 implementation.
The generated class will look like this:
package com.yourappclass Client { private static final String NO_CLIENT_SECRET = '' transient springSecurityService String clientId
String clientSecret Integer accessTokenValiditySeconds
Integer refreshTokenValiditySeconds Map<String, Object> additionalInformation static hasMany = [
authorities: String,
authorizedGrantTypes: String,
resourceIds: String,
scopes: String,
autoApproveScopes: String,
redirectUris: String
] static transients = ['springSecurityService'] static constraints = {
clientId blank: false, unique: true
clientSecret nullable: true accessTokenValiditySeconds nullable: true
refreshTokenValiditySeconds nullable: true authorities nullable: true
authorizedGrantTypes nullable: true resourceIds nullable: true scopes nullable: true
autoApproveScopes nullable: true redirectUris nullable: true
additionalInformation nullable: true
} def beforeInsert() {
encodeClientSecret()
} def beforeUpdate() {
if(isDirty('clientSecret')) {
encodeClientSecret()
}
} protected void encodeClientSecret() {
clientSecret = clientSecret ?: NO_CLIENT_SECRET
clientSecret = springSecurityService?.passwordEncoder ? springSecurityService.encodePassword(clientSecret) : clientSecret
}
}
The client secret is encoded using the same strategy that is configured by the Core plugin for handling passwords. Optional client secrets are also supported out of the box.
4.2 Access Token Class
This class represents an access token than has been issued to a client on behalf of a user. The authentication object serialized is an instance of
OAuth2Authentication
from Spring Security OAuth 2.0.
package com.yourappclass AccessToken { String authenticationKey
byte[] authentication String username
String clientId String value
String tokenType Date expiration
Map<String, Object> additionalInformation static hasOne = [refreshToken: String]
static hasMany = [scope: String] static constraints = {
username nullable: true
clientId nullable: false, blank: false
value nullable: false, blank: false, unique: true
tokenType nullable: false, blank: false
expiration nullable: false
scope nullable: false
refreshToken nullable: true
authenticationKey nullable: false, blank: false, unique: true
authentication nullable: false, minSize: 1, maxSize: 1024 * 4
additionalInformation nullable: true
} static mapping = {
version false
scope lazy: false
}
}
4.3 Refresh Token Class
This class represents a refresh token issued as part of one of the grants that supports issuing a refresh token. The length of time the refresh token is valid is determined by the token services and can be configured. See
token services configuration for more. The authentication object serialized is an instance of
OAuth2Authentication
from Spring Security OAuth 2.0.
package com.yourappclass RefreshToken { String value
Date expiration
byte[] authentication static constraints = {
value nullable: false, blank: false, unique: true
expiration nullable: true
authentication nullable: false, minSize: 1, maxSize: 1024 * 4
} static mapping = {
version false
}
}
If a non-expiring refresh token is desired, the client issuing the refresh token should be configured to return a 0 or less for the refresh token validity length in accordance with the behavior of Spring Security OAuth beginning with 2.0.6.RELEASE. A non-expiring GORM refresh token will be stored with a
null
expiration. When reading a GORM refresh token, if the expiration field is
null
, an
ExpiringOAuth2RefreshToken
instance will be created and returned for processing by Spring Security OAuth. Otherwise an instance of
OAuth2RefreshToken
will be created and used.
4.4 Authorization Code Class
This class represents an authorization code that has been issued via the authorization endpoint as part of an authorization code grant. The authentication object serialized is an instance of
OAuth2Authentication
from Spring Security OAuth 2.0.
package com.yourappclass AuthorizationCode { byte[] authentication
String code static constraints = {
code nullable: false, blank: false, unique: true
authentication nullable: false, minSize: 1, maxSize: 1024 * 4
} static mapping = {
version false
}
}
5 Optional Domain Classes
The plugin provides support for using a GORM backed
ApprovalStore
with the
ApprovalStoreUserApprovalHandler
provided by the underlying Spring OAuth library. This class is only required if the consuming application is configured to use the
UserApprovalSupport.APPROVAL_STORE
method of auto-approval.
The
s2-init-oauth2-approval script will create the required domain class for you in a specified package and update
grails-app/conf/application.groovy
so the plugin recognizes it. You can customize the generated class to fit your needs. If you change the default property names, you will need to update
grails-app/conf/application.groovy
so the plugin is aware of the changes. See the section on
domain class properties for more information.
The below discussion assumes the
s2-init-oauth2-approval script has been run with
com.yourapp
specified as the package and
Approval
as the name of the domain class.
5.1 Approval Class
This class represents a prior scoped approval granted to a client by a user.
package com.yourappclass Approval { String username
String clientId String scope
boolean approved Date expiration
Date lastModified static constraints = {
username nullable: false, blank: false
clientId nullable: false, blank: false
scope nullable: false, blank: false
expiration nullable: false
lastModified nullable: false
}
}
6 Domain Class Properties
No default class name is assumed for the required domain classes. They must be specified in
grails-app/conf/application.groovy
. This is done automatically by the
s2-init-oauth2-provider script. The following properties exist in the
grails.plugin.springsecurity.oauthProvider
namespace.
6.1 Client Class Properties
Property | Default Value | Meaning |
---|
clientLookup.className | null | Client class name. |
clientLookup.clientIdPropertyName | 'clientId' | Client class client ID field. |
clientLookup.clientSecretPropertyName | 'clientSecret' | Client class client secret field. |
clientLookup.accessTokenValiditySecondsPropertyName | 'accessTokenValiditySeconds' | Client class access token validity length field. |
clientLookup.refreshTokenValiditySecondsPropertyName | 'refreshTokenValiditySeconds' | Client class refresh token validity length field. |
clientLookup.authoritiesPropertyName | 'authorities' | Client class authorities field. |
clientLookup.authorizedGrantTypesPropertyName | 'authorizedGrantTypes' | Client class authorized grant types field. |
clientLookup.resourceIdsPropertyName | 'resourceIds' | Client class allowed resource IDs field. |
clientLookup.scopesPropertyName | 'scopes' | Client class scopes field. |
clientLookup.autoApproveScopesPropertyName | 'autoApproveScopes' | Client class auto-approved scopes field. Including a value of true in the list will auto-approve all scopes for the configured client. |
clientLookup.redirectUrisPropertyName | 'redirectUris' | Client class redirect URIs field. |
clientLookup.additionalInformationPropertyName | 'additionalInformation' | Client class additional information field. |
6.2 Access Token Class Properties
Property | Default Value | Meaning |
---|
accessTokenLookup.className | null | Access token class name. |
accessTokenLookup.authenticationKeyPropertyName | 'authenticationKey' | Access token class serialized authentication key used to locate tokens via serialized authentication field. |
accessTokenLookup.authenticationPropertyName | 'authentication' | Access token class serialized authentication field. |
accessTokenLookup.usernamePropertyName | 'username' | Access token class username field. |
accessTokenLookup.clientIdPropertyName | 'clientId' | Access token class client ID field. |
accessTokenLookup.valuePropertyName | 'value' | Access token class value field. |
accessTokenLookup.tokenTypePropertyName | 'tokenType' | Access token class token type field. |
accessTokenLookup.expirationPropertyName | 'expiration' | Access token class expiration field. |
accessTokenLookup.refreshTokenPropertyName | 'refreshToken' | Access token class refresh token value field. |
accessTokenLookup.scopePropertyName | 'scope' | Access token class scope field. |
accessTokenLookup.additionalInformationPropertyName | 'additionalInformation' | Access token class additional information field. |
Currently only
'bearer'
tokens are supported.
6.3 Refresh Token Class Properties
Property | Default Value | Meaning |
---|
refreshTokenLookup.className | null | Refresh token class name. |
refreshTokenLookup.authenticationPropertyName | 'authentication' | Refresh token class serialized authentication field. |
refreshTokenLookup.valuePropertyName | 'value' | Refresh token class value field. |
refreshTokenLookup.expirationPropertyName | 'expiration' | Refresh |
6.4 Authorization Code Class Properties
Property | Default Value | Meaning |
---|
authorizationCodeLookup.className | null | Authorization code class name. |
authorizationCodeLookup.authenticationPropertyName | 'authentication' | Authorization code class serialized authentication field. |
authorizationCodeLookup.codePropertyName | 'code' | Authorization code class code field. |
7 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/application.groovy
. The properties below exist in the
grails.plugin.springsecurity.oauthProvider
namespace.
7.1 Plugin
The following properties define whether the plugin is active and where the required filters are registered in the Spring Security filter chain:
Property | Default Value | Meaning |
---|
active | true | Whether the plugin is enabled. |
filterStartPosition | SecurityFilterPosition.X509_FILTER.order | The position in the filter chain of the OAuth2AuthenticationProcessingFilter , which handles authentication for resource access by extracting an access token from the incoming request. |
clientFilterStartPosition | SecurityFilterPosition.DIGEST_AUTH_FILTER.order | The position in the filter chain of the ClientCredentialsTokenEndpointFilter , which handles client authentication. |
statelessFilterStartPosition | SecurityFilterPosition.SECURITY_CONTEXT_FILTER.order | The position in the filter chain of the StatelessSecurityContextPersistenceFilter , which is used to ensure access to OAuth 2.0 resources and the token endpoint are stateless. |
exceptionTranslationFilterStartPosition | SecurityFilterPosition.EXCEPTION_TRANSLATION_FILTER.order | The position in the filter chain of the ExceptionTranslationFilter configured with a NullRequestCache , which is used to ensure access to OAuth 2.0 resources and the token endpoint are stateless. |
basicAuthenticationFilterStartPosition | SecurityFilterPosition.BASIC_AUTH_FILTER.order | The position in the filter chain of the BasicAuthenticationFilter configured with a NullRememberMeServices , which is used to ensure access to OAuth 2.0 resources and the token endpoint are stateless. |
registerStatelessFilter | true | When this is true , the plugin will register the statelessSecurityContextPersistenceFilter in the filter chain after the securityContextPersistenceFilter provided by the Spring Security Core plugin. See below for additional configuration of the filter chain(s) required to properly secure access to OAuth 2.0 resources. |
registerExceptionTranslationFilter | true | When this is true , the plugin will register the oauth2ExceptionTranslationFilter in the filter chain after the exceptionTranslationFilter provided by the Spring Security Core plugin. See below for additional configuration of the filter chain(s) required to properly secure access to OAuth 2.0 resources. |
registerBasicAuthenticationFilter | true | When this is true , the plugin will register the oauth2BasicAuthenticationFilter in the filter chain after the basicAuthenticationFilter provided by the Spring Security Core plugin. See below for additional configuration of the filter chain(s) required to properly secure access to OAuth 2.0 resources. |
realmName | Grails OAuth2 Realm | Realm name included in the WWW-Authenticate header in a challenge response. |
7.2 Endpoint URLs
The endpoint URLs used by the underlying Spring Security OAuth 2.0 implementation can be changed using the following properties:
Property | Default Value | Meaning |
---|
authorizationEndpointUrl | '/oauth/authorize' | Authorization endpoint URL. |
tokenEndpointUrl | '/oauth/token' | Token endpoint URL. |
userApprovalEndpointUrl | '/oauth/confirm_access' | URL of the view to display for confirming access to protected resources. |
userApprovalParameter | 'user_oauth_approval' | The name of the parameter submitted in the confirmation request. The value of this parameter is used to determine whether a user has confirmed (true ) or denied (false ) access. |
errorEndpointUrl | '/oauth/error' | URL of the view to display if an error occurs while interacting with the authorization endpoint and the error cannot be returned as part of the query or fragment of the client's redirect URI. This is usually the case when there is a problem with the requesting client's redirect URI. |
When changing the URL for the
authorizationEndpointUrl
or
tokenEndpointUrl
, you
must update Spring Security Core's configuration. Using the
staticRules
configuration and the default configuration as an example, your
grails-app/conf/Config.groovy
will look like this:
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
[pattern: '/oauth/authorize', access: "isFullyAuthenticated() and (request.getMethod().equals('GET') or request.getMethod().equals('POST'))"],
[pattern: '/oauth/token', access: "isFullyAuthenticated() and request.getMethod().equals('POST')"],
...
To change the
authorizationEndpointUrl
to
/authorize
, you will need to make the following changes in
grails-app/conf/application.groovy
:
grails.plugin.springsecurity.oauthProvider.authorizationEndpointUrl = '/authorize'grails.plugin.springsecurity.controllerAnnotations.staticRules = [
[pattern: '/authorize', access: "isFullyAuthenticated() and (request.getMethod().equals('GET') or request.getMethod().equals('POST'))"],
[pattern: '/oauth/token', access: "isFullyAuthenticated() and request.getMethod().equals('POST')"],
...
7.3 Token Services
The following properties apply to how tokens are issued and how long they are valid. If a client has defined a specific token validity length, the client's length will take priority over the values configured for the token services.
Property | Default Value | Meaning |
---|
tokenServices.registerTokenEnhancers | true | Whether registered TokenEnhancer instances should be used. |
tokenServices.accessTokenValiditySeconds | 60 * 60 * 12 | The length of time that an access token will be valid after it has been issued. |
tokenServices.refreshTokenValiditySeconds | 60 * 60 * 24 * 30 | The length of time that a refresh token will be valid after it has been issued. |
tokenServices.reuseRefreshToken | false | Whether a new refresh token should be generated if one is currently available. |
tokenServices.supportRefreshToken | true | Whether a refresh token can be issued upon request. |
7.4 Token Enhancers Configuration
By default, the plugin will register a
TokenEnhancerChain
with an empty list of
TokenEnhancer
delegates. When the
tokenServices.registerTokenEnhancers
option is
true
, the plugin will detect and use all registered Spring beans implementing the
TokenEnhancer
interface.
If more control over the ordering of the enhancers in the chain is desired, set the
tokenServices.registerTokenEnhancers
option to
false
. The
TokenEnhancerChain
bean is registered under the name
tokenEnhancerChain
, so the plugin consumer can get a handle to it for more fine grained configuration.
This bean is aliased under the name
tokenEnhancer
. This is the bean that is registered with the
tokenServices
bean. This is done to allow customization of the registered enhancer with minimal effort. Just override the
tokenEnhancer
bean.
7.5 Supported Grant Types
The following properties determine which of the standard grant types the application can support. Individual clients
must declare which of the enabled grant types they support.
Property | Default Value | Meaning |
---|
grantTypes.authorizationCode | true | Whether the Authorization Code Grant is supported. |
grantTypes.implicit | true | Whether the Implicit Grant is supported. |
grantTypes.clientCredentials | true | Whether the Client Credentials Grant is supported. |
grantTypes.password | true | Whether the Resource Owner Password Credentials is supported. |
grantTypes.refreshToken | true | Whether Refresh Token Grant is supported. |
7.6 Additional Authorization Constraints
The plugin enforces the following restrictions on authorization request params:
Property | Default Value | Meaning |
---|
authorization.requireRegisteredRedirectUri | true | Whether a client is required to have registered a redirect URI before performing an authorization request. This addresses RFC 6749 Section 10.6: Authorization Code Redirection URI Manipulation and RFC 6749 Section 10.15: Open Redirectors . |
authorization.requireScope | true | Whether the scope for each access token requested is required. |
7.7 User Approval Configuration
The plugin provides support for the three
UserApprovalHandler
implementations provided by the underlying Spring OAuth library. This only applies to the authorization endpoint and allows you to configure the method of auto-approval used by the application. The following properties determine which method of auto-approval to use and how it is configured:
Property | Default Value | Meaning |
---|
auto | EXPLICIT | Determines which method of auto-approval to use. The value is determined by grails.plugin.springsecurity.oauthProvider.approval.UserApprovalSupport and must be EXPLICIT , TOKEN_STORE or APPROVAL_STORE . |
handleRevocationAsExpiry | false | When configured to use an approval store for auto-approval, this determines if approval revocation should expire the corresponding approval instance (true ) or delete the approval (false ) outright. |
approvalValiditySeconds | 60 * 60 * 24 * 30 | When configured to use an approval store for auto-approval, the length of time that an approval will be valid after it has been granted. |
scopePrefix | 'scope.' | When configured to use an approval store for auto-approval, the prefix added to approved scopes as part of the auto-approval process. |
The
auto
property determines which of the three
UserApprovalHandler
provided by Spring OAuth will be used.
The default option is to require explicit approval for every authorization and is equivalent to setting
auto
to
EXPLICIT
:
grails.plugin.springsecurity.oauthprovider.approval.auto = UserApproval.EXPLICIT
Auto-approval based on previously issued access tokens is supported via the
TokenStoreUserApprovalHandler
provided by Spring OAuth and can be configured by setting
auto
to
TOKEN_STORE
:
grails.plugin.springsecurity.oauthprovider.approval.auto = UserApproval.TOKEN_STORE
Auto-approval based on prior approvals is supported via the
ApprovalStoreUserApprovalHandler
provided by Spring OAuth and can be configured by setting
auto
to
APPROVAL_STORE
:
grails.plugin.springsecurity.oauthprovider.approval.auto = UserApproval.APPROVAL_STORE
The plugin will configure the
TokenStoreUserApprovalHandler
and
ApprovalStoreUserApprovalHandler
to use the GORM backed
TokenStore
and
ApprovalStore
respectively.
Please consult Spring OAuth directly for more information on the usage of the
TokenStore
and
ApprovalStore
methods of auto-approval.
7.8 Default Client Configuration
An application can use the following properties to define the default values that will be used when creating a
ClientDetails
instance if a client has not specified a value. The default configuration will not allow a client to retrieve an access token unless they have explicitly registered support for the requested grant type.
Property | Default Value | Meaning |
---|
defaultClientConfig.resourceIds | [] | Resources the client is authorized to access. This is currently unused as access to resources is controlled by Spring Security Core's rules. |
defaultClientConfig.authorizedGrantTypes | [] | Grant types the client supports. |
defaultClientConfig.scope | [] | Scope to use for each access token request. |
defaultClientConfig.autoApproveScopes | [] | Scopes to auto-approve for authorization requests. Including a value of true in the list will auto-approve all scopes for clients using the default configuration. |
defaultClientConfig.registeredRedirectUri | null | URI to redirect the user-agent to during an authorization code or implicit grant. |
defaultClientConfig.authorities | [] | Roles and authorities granted to the client. |
defaultClientConfig.accessTokenValiditySeconds | null | The length of time that an access token will be valid after it has been issued. This is used instead of the length configured for token services if available. |
defaultClientConfig.refreshTokenValiditySeconds | null | The length of time that a refresh token will be valid after it has been issued. This is used instead of the length configured for token services if available. |
defaultClientConfig.additionalInformation | [:] | Additional information about the client. This is not required by OAuth 2.0 but is exposed in the underlying Spring library. |
7.9 Filter Chain Configuration
Spring Security Core plugin's
securityContextPersistenceFilter
stores state in the HTTP session. Access to the token endpoint and OAuth 2.0 resources must be stateless.
By default, the OAuth2 plugin will register the
statelessSecurityContextPersistenceFilter
in the filter chain after the
securityContextPersistenceFilter
provided by the Spring Security Core plugin. This is provided as a convenience for the plugin consumer, so they can remove one filter or the other to easily achieve stateful or stateless request handling. See the example below. This automatic filter registration can be disabled by setting the
registerStatelessFilter
configuration option to
false
.
The plugin registers an
OAuth2AuthenticationProcessingFilter
under the bean name
oauth2ProviderFilter
. This filter provides token authentication using the underlying token store for resource access.
The plugin registers a
ClientCredentialsTokenEndpointFilter
under the bean name
clientCredentialsTokenEndpointFilter
. This filter is responsible for authenticating the client specified in any OAuth 2.0 requests. The plugin also registers a
BasicAuthenticationFilter
under the bean name
oauth2BasicAuthenticationFilter
. This filter is responsible for authenticating the client via HTTP Basic authentication, which is the recommended method in the OAuth 2.0 specification.
Finally, the plugin registers an
ExceptionTranslationFilter
under the bean name
oauth2ExceptionTranslationFilter
. This filter is created with a
NullRequestCache
instance rather than the
HttpSessionRequestCache
instance that the Spring Security Core plugin provided
ExceptionTranslationFilter
is created with. Similar to the
statelessSecurityContextPersistenceFilter
, this filter is registered automatically by the plugin but can be disabled by setting the
registerExceptionTranslationFilter
configuration option to
false
.
The following filter chain configuration is recommended:
grails.plugin.springsecurity.filterChain.chainMap = [
[pattern: '/oauth/token', filters: 'JOINED_FILTERS,-oauth2ProviderFilter,-securityContextPersistenceFilter,-logoutFilter,-authenticationProcessingFilter,-rememberMeAuthenticationFilter,-exceptionTranslationFilter'],
[pattern: '/securedOAuth2Resources/**', filters: 'JOINED_FILTERS,-securityContextPersistenceFilter,-logoutFilter,-authenticationProcessingFilter,-rememberMeAuthenticationFilter,-oauth2BasicAuthenticationFilter,-exceptionTranslationFilter'],
[pattern: '/**', filters: 'JOINED_FILTERS,-statelessSecurityContextPersistenceFilter,-oauth2ProviderFilter,-clientCredentialsTokenEndpointFilter,-oauth2BasicAuthenticationFilter,-oauth2ExceptionTranslationFilter']
]
The
oauth2ProviderFilter
and stateful
securityContextPersistenceFilter
and
exceptionTranslationFilter
are removed from the token endpoint's filter chain. With the
securityContextPersistenceFilter
removed, the
statelessSecurityContextPersistenceFilter
will be used to ensure access token requests are stateless. Similarly, removing the
exceptionTranslationFilter
will allow the
oauth2ExceptionTranslationFilter
to take its place in the filter chain.
The
securityContextPersistenceFilter
and
exceptionTranslationFilter
are also removed from the filter chain for OAuth 2.0 resources. However, the
oauth2ProviderFilter
must not be removed, as this filter is responsible for authenticating the OAuth 2.0 access token included in the request.
It is recommend that filter chain(s) for non-OAuth 2.0 resources have all OAuth 2.0 specific filters removed. These include:
statelessSecurityContextPersistenceFilter
,
oauth2ProviderFilter
,
clientCredentialsTokenEndpointFilter
,
basicAuthenticationFilter
and
oauth2ExceptionTranslationFilter
. It is also recommended that any unnecessary filters such as the
rememberMeAuthenticationFilter
and
authenticationProcessingFilter
are removed from the filter chains for the token endpoint and OAuth 2.0 resources.
7.10 Domain Class Custom Serialization Configuration
The default behavior of the plugin is to serialize the
additionalInformation
and
scope
properties of the
Client
and
AccessToken
classes as a
Map<String, Object>
and
Set<String>
respectively. This is how the
s2-init-oauth2-provider script will generate the domain classes. However, this might not be ideal for all plugin consumers who want more control over the serialization of these fields.
To accommodate these users, it is possible to override the default serialization method on a case-by-case basis. The plugin provides two interfaces to accomplish.
For the
additionalInformation
fields:
package grails.plugin.springsecurity.oauthprovider.serialization;import java.util.Map;public interface OAuth2AdditionalInformationSerializer { Object serialize(Map<String, Object> additionalInformation); Map<String, Object> deserialize(Object additionalInformation);
}
For the
scope
field:
package grails.plugin.springsecurity.oauthprovider.serialization;import java.util.Set;public interface OAuth2ScopeSerializer { Object serialize(Set<String> scopes); Set<String> deserialize(Object scopes);
}
By default, the plugin registers implementations that do little more than return the argument provided to each method. The following table shows which plugin provided Spring beans implement these interfaces and how they're used:
Bean Name | Interface Implemented | Description |
---|
clientAdditionalInformationSerializer | OAuth2AdditionalInformationSerializer | Handles deserialization of the additionalInformation property of the Client class into a Map<String, Object> . Only the deserialize method is used by the plugin at this time. |
accessTokenAdditionalInformationSerializer | OAuth2AdditionalInformationSerializer | Handles serialization and deserialization of the additionalInformation property of the AccessToken class into a Map<String, Object> . |
accessTokenScopeSerializer | OAuth2ScopeSerializer | Handles serialization and deserialization of the scope property of the AccessToken class into a Set<String> . |
Overriding these beans in
resources.groovy
will allow the plugin consumer to customize how these fields are serialized. However, this will require the affected domain class to be modified to accommodate the change. For example, let's change the
AccessToken
to serialized its
additionalInformation
as JSON
String
and its
scope
as white space delimited
String
.
First, modify the
AccessToken
class to reflect the change in the storage of these fields:
package test.oauth2class AccessToken { String authenticationKey
byte[] authentication String username
String clientId String value
String tokenType Date expiration
String additionalInformation String scope static hasOne = [refreshToken: String] static constraints = {
username nullable: true
clientId nullable: false, blank: false
value nullable: false, blank: false, unique: true
tokenType nullable: false, blank: false
expiration nullable: false
scope nullable: false
refreshToken nullable: true
authenticationKey nullable: false, blank: false, unique: true
authentication nullable: false, minSize: 1, maxSize: 1024 * 4
additionalInformation nullable: true
} static mapping = {
version false
scope lazy: false
}
}
Next, implement the earlier described interfaces:
package testimport grails.plugin.springsecurity.oauthprovider.serialization.OAuth2ScopeSerializer
import org.springframework.security.oauth2.common.util.OAuth2Utilsclass WhiteSpaceDelimitedStringScopeSerializer implements OAuth2ScopeSerializer { @Override
Object serialize(Set<String> scopes) {
return OAuth2Utils.formatParameterList(scopes)
} @Override
Set<String> deserialize(Object scopes) {
return OAuth2Utils.parseParameterList(scopes)
}
}
And:
package testimport grails.plugin.springsecurity.oauthprovider.serialization.OAuth2AdditionalInformationSerializer
import groovy.json.JsonOutput
import groovy.json.JsonSlurperclass JsonAdditionalInformationSerializer implements OAuth2AdditionalInformationSerializer { @Override
Object serialize(Map<String, Object> additionalInformation) {
JsonOutput.toJson(additionalInformation)
} @Override
Map<String, Object> deserialize(Object additionalInformation) {
new JsonSlurper().parseText(additionalInformation as String)
}
}
The serialize
methods are guaranteed to receive a non-null argument, although they may be provided an empty collection. The deserialize
methods are expected to return a non-null value.
Finally, in
resources.groovy
, override the appropriate beans:
import test.JsonAdditionalInformationSerializer
import test.WhiteSpaceDelimitedStringScopeSerializerbeans = {
// Other beans here accessTokenAdditionalInformationSerializer(JsonAdditionalInformationSerializer)
accessTokenScopeSerializer(WhiteSpaceDelimitedStringScopeSerializer)
}
8 Standalone Resource Server or Authorization Server
By default, the plugin is configured to assume the role of both the Authorization Server and the Resource Server as defined by RFC 6749. However, it is possible to configure an application to fulfill only one role.
The plugin registers an instance of the Spring OAuth provided
OAuth2AuthenticationProcessingFilter
under the bean name
oauth2ProviderFilter
. This filter is responsible for extracting the
Bearer
token from the
Authorization
header and confirming its authenticity.
8.1 Authorization Server
To create an application that is only an Authorization Server, it is as simple as configuring the authorization and token endpoints as shown in the
Getting Started and
Filter Chain Configuration sections and excluding the
oauth2ProviderFilter
.
So instead of the following filter chain:
grails.plugin.springsecurity.filterChain.chainMap = [
[pattern: '/oauth/token', filters: 'JOINED_FILTERS,-oauth2ProviderFilter,-securityContextPersistenceFilter,-logoutFilter,-authenticationProcessingFilter,-rememberMeAuthenticationFilter,-exceptionTranslationFilter'],
[pattern: '/securedOAuth2Resources/**', filters: 'JOINED_FILTERS,-securityContextPersistenceFilter,-logoutFilter,-authenticationProcessingFilter,-rememberMeAuthenticationFilter,-oauth2BasicAuthenticationFilter,-exceptionTranslationFilter'],
[pattern: '/**', filters: 'JOINED_FILTERS,-statelessSecurityContextPersistenceFilter,-oauth2ProviderFilter,-clientCredentialsTokenEndpointFilter,-oauth2BasicAuthenticationFilter,-oauth2ExceptionTranslationFilter']
]
You would have something like this:
grails.plugin.springsecurity.filterChain.chainMap = [
[pattern: '/oauth/token', filters: 'JOINED_FILTERS,-oauth2ProviderFilter,-securityContextPersistenceFilter,-logoutFilter,-rememberMeAuthenticationFilter,-authenticationProcessingFilter,-exceptionTranslationFilter'],
[pattern: '/**', filters: 'JOINED_FILTERS,-statelessSecurityContextPersistenceFilter,-oauth2ProviderFilter,-clientCredentialsTokenEndpointFilter,-basicAuthenticationFilter,-oauth2ExceptionTranslationFilter']
]
Where
/**
is any Authorization Server specific functionality.
8.2 Resource Server
To create an application that is only a Resource Server is slightly more involved. The plugin uses an implementation of the Spring provided
ResourceServerTokenServices
interface that uses the currently configured
TokenStore
to authenticate the presented
Bearer
token. If the Authorization Server and Resource Server are distinct applications, it is very likely that the Resource Server will need some means to validate the provided
Bearer
token that depends on your use case. To do this, simply implement the aforementioned
ResourceServerTokenServices
interface and override the
resourceServerTokenServices
bean in your
resources.groovy
.
Next you will need to disable access to the authorization and token endpoints. This can be accomplished by changing access to the appropriate URL. For example, when using static rules to secure your endpoints, you might have the following when the Authorization and Resource Servers are the same application:
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
[pattern: '/oauth/authorize', access: "isFullyAuthenticated() and (request.getMethod().equals('GET') or request.getMethod().equals('POST'))"],
[pattern: '/oauth/token', access: "isFullyAuthenticated() and request.getMethod().equals('POST')"],
[pattern: '/', access: 'permitAll'],
[pattern: '/index', access: 'permitAll'],
[pattern: '/index.gsp', access: 'permitAll'],
[pattern: '/**/js/**', access: 'permitAll'],
[pattern: '/**/css/**', access: 'permitAll'],
[pattern: '/**/images/**', access: 'permitAll'],
[pattern: '/**/favicon.ico', access: 'permitAll'],
[pattern: '/assets/**', access: 'permitAll']
]
Splitting out the authorization parts will result in something like this:
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
[pattern: '/', access: 'permitAll'],
[pattern: '/index', access: 'permitAll'],
[pattern: '/index.gsp', access: 'permitAll'],
[pattern: '/**/js/**', access: 'permitAll'],
[pattern: '/**/css/**', access: 'permitAll'],
[pattern: '/**/images/**', access: 'permitAll'],
[pattern: '/**/favicon.ico', access: 'permitAll'],
[pattern: '/assets/**', access: 'permitAll']
]
Any requests to the authorization or token endpoints will be greeted with a 403 response. You should also remove any filter chain configurations in place for these endpoints as well. Our earlier filter chain would become something like the following:
grails.plugin.springsecurity.filterChain.chainMap = [
[pattern: '/securedOAuth2Resources/**', filters: 'JOINED_FILTERS,-securityContextPersistenceFilter,-logoutFilter,-rememberMeAuthenticationFilter,-authenticationProcessingFilter,-basicAuthenticationFilter,-exceptionTranslationFilter'],
[pattern: '/**', filters: 'JOINED_FILTERS,-statelessSecurityContextPersistenceFilter,-oauth2ProviderFilter,-clientCredentialsTokenEndpointFilter,-basicAuthenticationFilter,-oauth2ExceptionTranslationFilter']
]
Where
/**
is any Resource Server specific functionality.