0

I created a new grails v3 app, and added following dependencies to build.gradle:

compile group: 'org.springframework.security', name: 'spring-security-core', version: '4.2.1.RELEASE'
compile 'org.grails.plugins:spring-security-core:3.1.1'
compile "org.grails.plugins:spring-security-rest:2.0.0.M2"

I created a User Role Group config using Grails Security Quickstart, and set up a user, some roles and some posts for a users in bootstrap.groovy. I looked in /dbconsole and records are saved. <y basic integration tests seem to work.

I created a rest controller like this - I'm not securing anything at this point:

import grails.rest.RestfulController

class PostRestController extends RestfulController {
    static  responseFormats = ["json", "xml"]

    //constructor - tells rest controller which domain class to scaffold
    PostRestController() {
        super (Post)
    }
}

After much reading, I have ended up with this in my application.groovy:

//added to avoid login screens for dbconsole.  if you add /** at end it works
grails.plugin.springsecurity.rejectIfNoRule = true//false//
grails.plugin.springsecurity.fii.rejectPublicInvocations = false
grails.plugin.springsecurity.securityConfigType = "Annotation"
grails.plugin.springsecurity.useSecurityEventListener = true    //enable security events

//Rest
grails.plugin.springsecurity.useBasicAuth = true
grails.plugin.springsecurity.basic.realName = "coffeeShopApp"
grails.plugin.springsecurity.rest.login.useJsonCredentials = true //default
grails.plugin.springsecurity.rest.login.active  = true //default
grails.plugin.springsecurity.rest.login.endpointUrl = '/api/login'
grails.plugin.springsecurity.rest.login.failureStatusCode = 401
grails.plugin.springsecurity.rest.token.validation.enableAnonymousAccess = true

// Added by the Spring Security Core plugin:
grails.plugin.springsecurity.userLookup.userDomainClassName = 'org.softwood.security.User'
grails.plugin.springsecurity.userLookup.authorityJoinClassName = 'org.softwood.security.UserToUserGroup'
grails.plugin.springsecurity.authority.className = 'org.softwood.security.Role'
grails.plugin.springsecurity.authority.groupAuthorityNameField = 'authorities'
grails.plugin.springsecurity.useRoleGroups = true
grails.plugin.springsecurity.controllerAnnotations.staticRules = [
    [pattern: '/',               access: ['permitAll']],
    [pattern: '/error',          access: ['permitAll']],
    [pattern: '/index',          access: ['permitAll']],
    [pattern: '/index.gsp',      access: ['permitAll']],
    [pattern: '/shutdown',       access: ['permitAll']],
    [pattern: '/dbconsole/**',   access: ['permitAll']],    //added
    [pattern: '/console/**',     access: ['permitAll']],    //added
        [pattern: '/secureTest/secure',         access: ['ROLE_ADMIN']],
        [pattern: '/secureTest/index',          access: ['permitAll']],
        [pattern: '/secureTest/willsPage',      access: ["authentication.name == 'will'"]],
        [pattern: '/api/**',     access: ['permitAll']],
    [pattern: '/assets/**',      access: ['permitAll']],
    [pattern: '/**/js/**',       access: ['permitAll']],
    [pattern: '/**/css/**',      access: ['permitAll']],
    [pattern: '/**/images/**',   access: ['permitAll']],
    [pattern: '/**/favicon.ico', access: ['permitAll']]
]

grails.plugin.springsecurity.filterChain.chainMap = [
    [pattern: '/assets/**',      filters: 'none'],
    [pattern: '/**/js/**',       filters: 'none'],
    [pattern: '/**/css/**',      filters: 'none'],
    [pattern: '/**/images/**',   filters: 'none'],
    [pattern: '/**/favicon.ico', filters: 'none'],
    [pattern: '/api/guest/**',         filters: 'anonymousAuthenticationFilter,' +
            'restTokenValidationFilter,' +
            'restExceptionTranslationFilter,' +
            'filterInvocationInterceptor'],
    [pattern: '/api/**',         filters: 'JOINED_FILTERS,' +
            '-anonymousAuthenticationFilter,' +
            '-exceptionTranslationFilter,' +
            '-authenticationProcessingFilter,' +
            '-securityContextPersistenceFilter,' +
            '-rememberMeAuthenticationFilter'],

    [pattern: '/**',             filters: 'JOINED_FILTERS,' +
            //'-basicAuthenticationFeature,' +
            //'-basicExceptionTranslationFilter,' +
            '-restTokenValidationFilter,' +
            '-restExceptionTranslationFilter']
]

My static rules have /api/** as permit all. I've updated the chain map for /api/** as per Spring Security REST documentation

I updated my UrlMappings as follows

class UrlMappings {

    static mappings = {
        //add alternate url mapping for rest based resources
        "/api/posts" (resources : "postRest")
        "/api/guest/posts" (resources : "postRest")

        "/$controller/$action?/$id?(.$format)?"{
            constraints {
                // apply constraints here
            }
        }

        "/"(view:"/index")
        "500"(view:'/error')
        "404"(view:'/notFound')
    }
}

When I run the url-mappings-report in the Grails console, my URLs look exposed correctly.

I run the app - it all comes OK.

I then use Chrome to post to api/login. When I use Chrome REST client to connect - this gives me a 200 and the access_token:

API login

I try and do a post validate with the key returned and get HTML 200 for that looks good.

Finally I try and call my /api/posts service. I cut and paste my key and add to the GET header and submit and I get 403/forbidden:

enter image description here

I can't see where I have gone wrong. I have this in the log trace relating to the last /api/guest/posts:

2017-03-06 22:32:05.239 DEBUG --- [nio-8080-exec-5] o.s.s.w.a.i.FilterSecurityInterceptor    : Secure object: FilterInvocation: URL: /api/guest/posts; Attributes: [permitAll]
2017-03-06 22:32:05.239 DEBUG --- [nio-8080-exec-5] o.s.s.w.a.i.FilterSecurityInterceptor    : Previously Authenticated: grails.plugin.springsecurity.authentication.GrailsAnonymousAuthenticationToken@f23b6234: Principal: org.springframework.security.core.userdetails.User@dc730200: Username: __grails.anonymous.user__; Password: [PROTECTED]; Enabled: false; AccountNonExpired: false; credentialsNonExpired: false; AccountNonLocked: false; Granted Authorities: ROLE_ANONYMOUS; Credentials: [PROTECTED]; Authenticated: true; Details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffffc434: RemoteIpAddress: 192.168.1.238; SessionId: null; Granted Authorities: ROLE_ANONYMOUS
2017-03-06 22:32:05.239 DEBUG --- [nio-8080-exec-5] o.s.s.a.h.RoleHierarchyImpl              : getReachableGrantedAuthorities() - From the roles [ROLE_ANONYMOUS] one can reach [ROLE_ANONYMOUS] in zero or more steps.
2017-03-06 22:32:05.250 DEBUG --- [nio-8080-exec-5] o.s.s.w.a.i.FilterSecurityInterceptor    : Authorization successful
2017-03-06 22:32:05.250 DEBUG --- [nio-8080-exec-5] o.s.s.w.a.i.FilterSecurityInterceptor    : RunAsManager did not change Authentication object
2017-03-06 22:32:05.250 DEBUG --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : /api/guest/posts reached end of additional filter chain; proceeding with original chain
2017-03-06 22:32:05.277 DEBUG --- [nio-8080-exec-5] o.s.s.w.a.ExceptionTranslationFilter     : Chain processed normally
2017-03-06 22:32:05.288 DEBUG --- [nio-8080-exec-5] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/error'; against '/assets/**'
2017-03-06 22:32:05.288 DEBUG --- [nio-8080-exec-5] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/error'; against '/**/js/**'
2017-03-06 22:32:05.288 DEBUG --- [nio-8080-exec-5] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/error'; against '/**/css/**'
2017-03-06 22:32:05.288 DEBUG --- [nio-8080-exec-5] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/error'; against '/**/images/**'
2017-03-06 22:32:05.288 DEBUG --- [nio-8080-exec-5] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/error'; against '/**/favicon.ico'
2017-03-06 22:32:05.288 DEBUG --- [nio-8080-exec-5] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/error'; against '/api/guest/**'
2017-03-06 22:32:05.288 DEBUG --- [nio-8080-exec-5] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/error'; against '/api/**'
2017-03-06 22:32:05.288 DEBUG --- [nio-8080-exec-5] o.s.s.w.u.matcher.AntPathRequestMatcher  : Request '/error' matched by universal pattern '/**'
2017-03-06 22:32:05.288 DEBUG --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : /error at position 1 of 12 in additional filter chain; firing Filter: 'SecurityRequestHolderFilter'
2017-03-06 22:32:05.288 DEBUG --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : /error at position 2 of 12 in additional filter chain; firing Filter: 'SecurityContextPersistenceFilter'
2017-03-06 22:32:05.289 DEBUG --- [nio-8080-exec-5] w.c.HttpSessionSecurityContextRepository : No HttpSession currently exists
2017-03-06 22:32:05.289 DEBUG --- [nio-8080-exec-5] w.c.HttpSessionSecurityContextRepository : No SecurityContext was available from the HttpSession: null. A new one will be created.
2017-03-06 22:32:05.289 DEBUG --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : /error at position 3 of 12 in additional filter chain; firing Filter: 'MutableLogoutFilter'
2017-03-06 22:32:05.289 DEBUG --- [nio-8080-exec-5] o.s.s.w.u.matcher.AntPathRequestMatcher  : Checking match of request : '/error'; against '/logoff'
2017-03-06 22:32:05.289 DEBUG --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : /error at position 4 of 12 in additional filter chain; firing Filter: 'GrailsUsernamePasswordAuthenticationFilter'
2017-03-06 22:32:05.289 DEBUG --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : /error at position 5 of 12 in additional filter chain; firing Filter: 'RestAuthenticationFilter'
2017-03-06 22:32:05.289 DEBUG --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : /error at position 6 of 12 in additional filter chain; firing Filter: 'BasicAuthenticationFilter'
2017-03-06 22:32:05.289 DEBUG --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : /error at position 7 of 12 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2017-03-06 22:32:05.289 DEBUG --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : /error at position 8 of 12 in additional filter chain; firing Filter: 'GrailsRememberMeAuthenticationFilter'
2017-03-06 22:32:05.289 DEBUG --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : /error at position 9 of 12 in additional filter chain; firing Filter: 'GrailsAnonymousAuthenticationFilter'
2017-03-06 22:32:05.289 DEBUG --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : /error at position 10 of 12 in additional filter chain; firing Filter: 'UpdateRequestContextHolderExceptionTranslationFilter'
2017-03-06 22:32:05.289 DEBUG --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : /error at position 11 of 12 in additional filter chain; firing Filter: 'UpdateRequestContextHolderExceptionTranslationFilter'
2017-03-06 22:32:05.289 DEBUG --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : /error at position 12 of 12 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2017-03-06 22:32:05.293 DEBUG --- [nio-8080-exec-5] o.s.security.web.FilterChainProxy        : /error reached end of additional filter chain; proceeding with original chain
2017-03-06 22:32:07.314 DEBUG --- [nio-8080-exec-5] w.c.HttpSessionSecurityContextRepository : SecurityContext is empty or contents are anonymous - context will not be stored in HttpSession.
2017-03-06 22:32:07.315 DEBUG --- [nio-8080-exec-5] s.s.w.c.SecurityContextPersistenceFilter : SecurityContextHolder now cleared, as request processing completed

lastly - i enabled the fii.rejectPublicInvocations and tried again and i get this stacktrace when i access the URL

java.lang.IllegalArgumentException: Secure object invocation FilterInvocation: URL: /api/posts was denied as public invocations are not allowed via this interceptor. This indicates a configuration error because the rejectPublicInvocations property is set to 'true'
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:201)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:124)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:91)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:115)
    at grails.plugin.springsecurity.web.UpdateRequestContextHolderExceptionTranslationFilter.doFilter(UpdateRequestContextHolderExceptionTranslationFilter.groovy:64)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:115)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:331)

NB - see comment below - I did a 'hack' on the PostRestController like this - and forced the result to 'true'. This now allows me access the api/guest/posts (anonymously) and /api/posts with login key:

@Secured (closure = {
    assert request
    assert ctx
    println """details passed to rest controller >  
authentication name : ${authentication.name} 
is authenticed : ${isAuthenticated()} 
has any role from admin or user: ${hasAnyRole('ROLE_ADMIN','ROLE_USER')} 
has role admin : ${hasRole('ROLE_ADMIN')}
principal : $principal
"""
    true
} )

class PostRestController extends RestfulController {
    static  responseFormats = ["json", "xml"]

    //constructor - tells rest controller which domain class to scaffold
    PostRestController() {
        super (Post)
    }
}

I've enabled the debug filter as well which outputs this when I access the api/posts:

Request received for '/api/posts':

SecurityContextHolderAwareRequestWrapper[ FirewalledRequest[ org.apache.catalina.connector.RequestFacade@3085690a]]

servletPath:/api/posts
pathInfo:null

Security filter chain: [
  SecurityRequestHolderFilter
  MutableLogoutFilter
  RestAuthenticationFilter
  SecurityContextHolderAwareRequestFilter
  RestTokenValidationFilter
  ExceptionTranslationFilter
  FilterSecurityInterceptor
]

and my trace write to console from the closure gave me this:

details passed to rest controller >  
authentication name : will 
is authenticed : true 
has any role from admin or user: true 
has role admin : true
principal : grails.plugin.springsecurity.userdetails.GrailsUser@37afd2: Username: will; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN,ROLE_USER
halfer
  • 19,824
  • 17
  • 99
  • 186
WILLIAM WOODMAN
  • 1,185
  • 5
  • 19
  • 36
  • still doesnt work but i had missed the /api/guest/posts (resources:"postRest") - when i added this i now get a 401 unathorised when i try a get on the resource. i ran the url-mappings-report and it reports my urls as being exposed with all the expected mappings – WILLIAM WOODMAN Mar 07 '17 at 08:20
  • my entries in url mappings looks like this `static mappings = { //add alternate url mapping for rest based resources "/api/posts" (resources : "postRest") "/api/guest/posts" (resources : "postRest")` – WILLIAM WOODMAN Mar 07 '17 at 08:22
  • i just set grails.plugin.springsecurity.rejectIfNoRule = false in application.groovy, (stop checking) and re ran the GET on api/guest/posts and api/posts - they both succeed and render with expected result. - so the controller is teher and working as are the urls. so its something in the securing that i cant have enabled correctly – WILLIAM WOODMAN Mar 07 '17 at 08:40
  • ok - got a partial success of sorts - had to apply a 'hack'. which was in the PostRestController i added a @Secured (closure= {...true}) to the class. i put some debug trace in there to. Now when i access via rest client it works both as guest, and if i login and get token by calling /api/posts. howevver surely this can't be right? i have a 'permitAll' on my url mappings whats going on ? – WILLIAM WOODMAN Mar 07 '17 at 11:21

0 Answers0