Search code examples
grailsspring-securityspring-security-rest

Grails 2.5.4 / Spring Security Rest 1.5.4 - Validating token


What I'm trying to do is login a user and get a token back (this part works). Then I want to validate this token every time I access an API path. I'm obviously doing something wrong, maybe I don't fully understand what the Spring Security Rest plugin is actually supposed to do but whenever I call an API path and send the token all I get back is the Spring Security login page's html. I'm using Boomerang Soap and Rest Client. Here's what I'm sending.

Login Request (path: http://localhost:7070/backend3/api/login):

{
    "username": "test@user.com",
    "password": "1234"
}

Login Response:

{
    "username": "test@user.com",
    "roles": [
        "ROLE_USER"
    ],
    "token_type": "Bearer",
    "access_token": "eyJhbGciOiJIU.", // shortened token to conserve space
    "expires_in": 3600,
    "refresh_token": "eyJhbGciOiJIU." // shortened token to conserve space
}

So clearly this part works fine. Then I try to use the token I received from the above's response to access my API. Keep in mind that in my config under Spring Security's rules I have '/external/**':['ROLE_USER']. If I change that to ['permitAll'] then it I gain access no problem but then spring security is effectively useless.

API Request (path: http://localhost:7070/backend3/external/user/info)

{
    "access_token": "eyJhbGciOiJIU" // shortened token to conserve space
}

And this then return's the standard login page's html. I've tried sending all sorts of combinations of stuff like access_token, username, token_type, roles. I have even tried a different format of the access_token; "Authorization":"Bearer eyJhbGciOiJIU" which I found on the docs but nothing works.

Can someone please explain to me how to go about this because the docs assume a lot of prior knowledge which I clearly don't have. I'll post (the relevant parts of) my Config and UrlMappings files below for further clarification. Please let me know if I need to supply more info, I just really need to get this working as I've been stuck on it for 2 weeks. Thanks

Config:

// Added by the Spring Security Core plugin:
grails.plugin.springsecurity.userLookup.userDomainClassName = 'com.crastino.domain.User'
grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'com.crastino.domain.UserRole'
grails.plugin.springsecurity.authority.className = 'com.crastino.domain.Role'
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
    '/':                ['permitAll'],
    '/index':           ['permitAll'],
    '/index.gsp':       ['permitAll'],
    '/assets/**':       ['permitAll'],
    '/**/js/**':        ['permitAll'],
    '/**/css/**':       ['permitAll'],
    '/**/images/**':    ['permitAll'],
    '/**/favicon.ico':  ['permitAll'],
    '/external/**':     ['ROLE_USER']
]
grails.plugin.springsecurity.filterChain.chainMap = [
    '/auth/**': 'JOINED_FILTERS,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter,-rememberMeAuthenticationFilter', // Stateless chain
    '/api/**': 'JOINED_FILTERS,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter', // Stateless chain
    '/**': 'JOINED_FILTERS,-restTokenValidationFilter,-restExceptionTranslationFilter'
]

grails.plugin.springsecurity.rest.login.active=true
grails.plugin.springsecurity.rest.login.endpointUrl='/api/login'
grails.plugin.springsecurity.rest.login.failureStatusCode=401
grails.plugin.springsecurity.rest.login.useJsonCredentials=true
grails.plugin.springsecurity.rest.login.usernamePropertyName='username'
grails.plugin.springsecurity.rest.login.passwordPropertyName='password'
grails.plugin.springsecurity.rest.logout.endpointUrl='/auth/logout'
grails.plugin.springsecurity.rest.token.generation.useSecureRandom=true
grails.plugin.springsecurity.rest.token.generation.useUUID=false
grails.plugin.springsecurity.rest.token.validation.active=true
grails.plugin.springsecurity.rest.token.validation.endpointUrl='/auth/validate'

Ps. I have also tried adding the path I'm trying to access to this part grails.plugin.springsecurity.filterChain.chainMap by adding '/external/**': 'JOINED_FILTERS,-restTokenValidationFilter,-restExceptionTranslationFilter' to the end of the array

UrlMappings:

group("/external") {
    "/user/info" (controller: 'external', action: 'Service_UserInfo')
}

Solution

  • My boss managed to get it working for me. The main issue seems to have been the controller I was trying to access. The controller now looks like this:

    // package statement
    // import statements
    import grails.rest.RestfulController
    
    class UserController extends RestfulController<User> {
    
        def springSecurityService
    
        static responseFormats = ['json', 'xml']
    
        UserController() {
            super(User)
        }
    
        @Secured(['permitAll'])
        def index() {
            // use this method to test the api without authenticating
            // if you are authenticated though, it'll let you know.
            if(springSecurityService.isLoggedIn()) {
                render(text:[loggedIn:'true'] as JSON)
            } else {
                render(text:[loggedIn:'false'] as JSON)
            }
        }
    
        @Secured(['ROLE_USER'])
        def listContacts() {
            // needs authentication to access so if you call this method
            // through the api and it returns users you're golden
            def result = User.findAll()
            respond result, [excludes: ['class']]
        }
    }
    

    So the key differences seem to be importing and extending the RestfulController, the responseFormats and the constructor. I don't actually know what any of that means or how it works so if someone wants to edit an explanation into this answer be my guest.

    Another key mistake I made was in the rest call. I would call /api/login with my username/password as json parameters which would return the access_token no problem. But then when I wanted to access, for example, /user/listContacts I would send the access_token as a parameter with key access_token, instead of sending it as a header with key X-Auth-Token. Doing that seems to have been part of the fix.

    My boss also removed most of the crap (related to Spring Security and Rest) that was in my config file, so I'll just provide these changes too. Note that the controller, config and API call was the only necessary changes so everything else in your project can be kept standard (aside from including the plugins in your BuildConfig).

    //Config for Spring Security REST plugin
    //login
    grails.plugin.springsecurity.rest.login.useJsonCredentials = true
    grails.plugin.springsecurity.rest.token.storage.useGorm = true
    grails.plugin.springsecurity.rest.token.storage.gorm.tokenDomainClassName = 'com.crastino.domain.AuthenticationToken'
    

    And that was it. Also, here's a link to an example project that helped a lot.

    And that's it.