Получение ошибки 403 Bearer error=insufficient_scope #3 с плагином Grail spring-security-rest

Я пытаюсь сделать базовый пример работы REST API с безопасностью Spring. Я использую подключаемый модуль Grails spring-security-rest:2.0.0.M2.

Я попытался следовать этому превосходному учебнику, и столкнулся с недостаточным объемом ошибка.

Я определяю одну роль, ADMIN_ROLE. После успешного входа я получаю access_token

Я добавил @Secured(['ROLE_ADMIN'])tag в свой класс ProjectController:

@Secured(['ROLE_ADMIN'])
class ProjectController extends RestfulController {
    static responseFormats = ['json', 'xml']
    ProjectController() {
        super(Project)
    }

    def active() {
        respond Project.findAllByActive(true), view: 'index'
    }
}

Мои URLMappings указывают на ProjectController: пакет dk.mathmagicians.demo

class UrlMappings {

    static mappings = {
        "/$controller/$action?/$id?(.$format)?"{
            constraints {
                // apply constraints here
            }
        }
        "/projects"(resources:"project")
        "/active"(controller: 'project', action: 'active')
        "/"(view: '/index')
        "500"(view: '/error')
        "404"(view: '/notFound')
    }
}

Я настроил свой файл application.groovy с фильтрами следующим образом.

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/**',         filters: 'JOINED_FILTERS,-anonymousAuthenticationFilter,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter,-rememberMeAuthenticationFilter']
]

Когда я пытаюсь вызвать свой API ($TOKEN — это переменная env, где токен хранится для целей тестирования)

curl -v -i -H "Authorization: Bearer $STOKEN" 0:8080/api/projects'

Я получаю ошибку 403 о недостаточном объеме:

*   Trying 0.0.0.0...
* Connected to 0 (127.0.0.1) port 8080 (#0)
> GET /api/projects HTTP/1.1
> Host: 0:8080
> User-Agent: curl/7.43.0
> Accept: */*
> Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJwcmluY2lwYWwiOiJINHNJQUFBQUFBQUFBSlZTUFdcL1RRQmgrSGRJUFVhbGZVcEU2bElXeUlVZUNNVk9cL1FGUW1RWVFzUmFLNjJHXC9kYTg5Mzd0MjVUWllxRXd3WldnR1JrUGdMK1NmdDBoOVF3Y0RhbVpYMzNLWk9XU3B1c3Q5N1wvSHk5SGx6Qm1OSHdJdGFNQytPbklvdTU5RTJxdVl3Tmhwbm10dU5uQm5XRU5rZTh5b0ZObXNEMThVcmdCVkRpa1lYNVlJOGRzb3BnTXE3VVczc1kybXBidzNPbDR4dkdIYzBTUEZKNjM3XC9sRHBYR093SUZ0WGRhZ29rdG1HTmhxREpwYTBwdXRGT3VNZHFDMldJV3FIRGZqUlpDdWtGcE9STm1GRHFCa3JVRVJnRk1zY3p1S2xMbGFDek1YSnZOTEJlVkJ0cHFBSk1wTTRiY1wvWk9rWVoxMWQrOXNTa3B3QU1kUWJxY2VIZXJ1cVlQNmpzZGZVMEpRYXE2a1dXN0tSRVY4aHp0eDR1OHVmYms0K2RGdGxnQ29rMmYzZjFQTUYxZWhlXC9ieHorTzhhQyswOEdqRWVnR3J0bE55TTFjd3Y5Zm9sQytcL3ZcLzNhdlwvcjg0UUVwTzhUTFwvOVwvSDhzcE5jNTAxbGFSTU02dEdka1MwUjJYM1RPU3I5NU1QdDlEeEd6eEpCZElmSlMxR3R4SUZNY1V0YXlXR2ZWdDQrSzRlYkd5dnJMOTVYWE92NCt0S01oR1I3SFNlMnEzTER4UXRxXC9mNzlQemt5VStpMklTeFF5WXlwTkpuQzFBdFMxcW9QdzM2UzFQZmZ2WHlDTVBmK1M5TElGejNFZ01BQUE9PSIsInN1YiI6IkRvbmFsZCIsInJvbGVzIjpbIlJPTEVfQURNSU4iXSwiZXhwIjoxNDc2MTQwNjk4LCJpYXQiOjE0NzYxMzcwOTh9.wSwQad4POS77y61ULiTeOWEjxGcSv7xuDRryfWpZa-Y
>
< HTTP/1.1 403 Forbidden
HTTP/1.1 403 Forbidden
< Server: Apache-Coyote/1.1
Server: Apache-Coyote/1.1
< WWW-Authenticate: Bearer error="insufficient_scope"
WWW-Authenticate: Bearer error="insufficient_scope"
< Content-Type: application/json;charset=UTF-8
Content-Type: application/json;charset=UTF-8
< Transfer-Encoding: chunked
Transfer-Encoding: chunked
< Date: Mon, 10 Oct 2016 22:17:11 GMT
Date: Mon, 10 Oct 2016 22:17:11 GMT

<
* Connection #0 to host 0 left intact
{"timestamp":1476137831338,"status":403,"error":"Forbidden","message":"Access is denied","path":"/api/projects"}

Я включил ведение журнала отладки, вот вывод из приложения Grails

DEBUG org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/api/projects'; against '/assets/**'
DEBUG org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/api/projects'; against '/**/js/**'
DEBUG org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/api/projects'; against '/**/css/**'
DEBUG org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/api/projects'; against '/**/images/**'
DEBUG org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/api/projects'; against '/**/favicon.ico'
DEBUG org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/api/projects'; against '/api/**'
DEBUG org.springframework.security.web.FilterChainProxy - /api/projects at position 1 of 7 in additional filter chain; firing Filter: 'SecurityRequestHolderFilter'
DEBUG org.springframework.security.web.FilterChainProxy - /api/projects at position 2 of 7 in additional filter chain; firing Filter: 'MutableLogoutFilter'
DEBUG org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/api/projects'; against '/logoff'
DEBUG org.springframework.security.web.FilterChainProxy - /api/projects at position 3 of 7 in additional filter chain; firing Filter: 'RestAuthenticationFilter'
DEBUG grails.plugin.springsecurity.rest.RestAuthenticationFilter - Actual URI is /api/projects; endpoint URL is /api/login
DEBUG org.springframework.security.web.FilterChainProxy - /api/projects at position 4 of 7 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
DEBUG org.springframework.security.web.FilterChainProxy - /api/projects at position 5 of 7 in additional filter chain; firing Filter: 'RestTokenValidationFilter'
DEBUG grails.plugin.springsecurity.rest.token.bearer.BearerTokenReader - Looking for bearer token in Authorization header, query string or Form-Encoded body parameter
DEBUG grails.plugin.springsecurity.rest.token.bearer.BearerTokenReader - Found bearer token in Authorization header
DEBUG grails.plugin.springsecurity.rest.token.bearer.BearerTokenReader - Token: eyJhbGciOiJIUzI1NiJ9.eyJwcmluY2lwYWwiOiJINHNJQUFBQUFBQUFBSlZTUFdcL1RRQmgrSGRJUFVhbGZVcEU2bElXeUlVZUNNVk9cL1FGUW1RWVFzUmFLNjJHXC9kYTg5Mzd0MjVUWllxRXd3WldnR1JrUGdMK1NmdDBoOVF3Y0RhbVpYMzNLWk9XU3B1c3Q5N1wvSHk5SGx6Qm1OSHdJdGFNQytPbklvdTU5RTJxdVl3Tmhwbm10dU5uQm5XRU5rZTh5b0ZObXNEMThVcmdCVkRpa1lYNVlJOGRzb3BnTXE3VVczc1kybXBidzNPbDR4dkdIYzBTUEZKNjM3XC9sRHBYR093SUZ0WGRhZ29rdG1HTmhxREpwYTBwdXRGT3VNZHFDMldJV3FIRGZqUlpDdWtGcE9STm1GRHFCa3JVRVJnRk1zY3p1S2xMbGFDek1YSnZOTEJlVkJ0cHFBSk1wTTRiY1wvWk9rWVoxMWQrOXNTa3B3QU1kUWJxY2VIZXJ1cVlQNmpzZGZVMEpRYXE2a1dXN0tSRVY4aHp0eDR1OHVmYms0K2RGdGxnQ29rMmYzZjFQTUYxZWhlXC9ieHorTzhhQyswOEdqRWVnR3J0bE55TTFjd3Y5Zm9sQytcL3ZcLzNhdlwvcjg0UUVwTzhUTFwvOVwvSDhzcE5jNTAxbGFSTU02dEdka1MwUjJYM1RPU3I5NU1QdDlEeEd6eEpCZElmSlMxR3R4SUZNY1V0YXlXR2ZWdDQrSzRlYkd5dnJMOTVYWE92NCt0S01oR1I3SFNlMnEzTER4UXRxXC9mNzlQemt5VStpMklTeFF5WXlwTkpuQzFBdFMxcW9QdzM2UzFQZmZ2WHlDTVBmK1M5TElGejNFZ01BQUE9PSIsInN1YiI6IkRvbmFsZCIsInJvbGVzIjpbIlJPTEVfQURNSU4iXSwiZXhwIjoxNDc2MTQwNjk4LCJpYXQiOjE0NzYxMzcwOTh9.wSwQad4POS77y61ULiTeOWEjxGcSv7xuDRryfWpZa-Y
DEBUG grails.plugin.springsecurity.rest.RestTokenValidationFilter - Token found: eyJhbGciOiJIUzI1NiJ9.eyJwcmluY2lwYWwiOiJINHNJQUFBQUFBQUFBSlZTUFdcL1RRQmgrSGRJUFVhbGZVcEU2bElXeUlVZUNNVk9cL1FGUW1RWVFzUmFLNjJHXC9kYTg5Mzd0MjVUWllxRXd3WldnR1JrUGdMK1NmdDBoOVF3Y0RhbVpYMzNLWk9XU3B1c3Q5N1wvSHk5SGx6Qm1OSHdJdGFNQytPbklvdTU5RTJxdVl3Tmhwbm10dU5uQm5XRU5rZTh5b0ZObXNEMThVcmdCVkRpa1lYNVlJOGRzb3BnTXE3VVczc1kybXBidzNPbDR4dkdIYzBTUEZKNjM3XC9sRHBYR093SUZ0WGRhZ29rdG1HTmhxREpwYTBwdXRGT3VNZHFDMldJV3FIRGZqUlpDdWtGcE9STm1GRHFCa3JVRVJnRk1zY3p1S2xMbGFDek1YSnZOTEJlVkJ0cHFBSk1wTTRiY1wvWk9rWVoxMWQrOXNTa3B3QU1kUWJxY2VIZXJ1cVlQNmpzZGZVMEpRYXE2a1dXN0tSRVY4aHp0eDR1OHVmYms0K2RGdGxnQ29rMmYzZjFQTUYxZWhlXC9ieHorTzhhQyswOEdqRWVnR3J0bE55TTFjd3Y5Zm9sQytcL3ZcLzNhdlwvcjg0UUVwTzhUTFwvOVwvSDhzcE5jNTAxbGFSTU02dEdka1MwUjJYM1RPU3I5NU1QdDlEeEd6eEpCZElmSlMxR3R4SUZNY1V0YXlXR2ZWdDQrSzRlYkd5dnJMOTVYWE92NCt0S01oR1I3SFNlMnEzTER4UXRxXC9mNzlQemt5VStpMklTeFF5WXlwTkpuQzFBdFMxcW9QdzM2UzFQZmZ2WHlDTVBmK1M5TElGejNFZ01BQUE9PSIsInN1YiI6IkRvbmFsZCIsInJvbGVzIjpbIlJPTEVfQURNSU4iXSwiZXhwIjoxNDc2MTQwNjk4LCJpYXQiOjE0NzYxMzcwOTh9.wSwQad4POS77y61ULiTeOWEjxGcSv7xuDRryfWpZa-Y
DEBUG grails.plugin.springsecurity.rest.RestTokenValidationFilter - Trying to authenticate the token
DEBUG grails.plugin.springsecurity.rest.RestAuthenticationProvider - Use JWT: true
DEBUG grails.plugin.springsecurity.rest.RestAuthenticationProvider - Trying to validate token eyJhbGciOiJIUzI1NiJ9.eyJwcmluY2lwYWwiOiJINHNJQUFBQUFBQUFBSlZTUFdcL1RRQmgrSGRJUFVhbGZVcEU2bElXeUlVZUNNVk9cL1FGUW1RWVFzUmFLNjJHXC9kYTg5Mzd0MjVUWllxRXd3WldnR1JrUGdMK1NmdDBoOVF3Y0RhbVpYMzNLWk9XU3B1c3Q5N1wvSHk5SGx6Qm1OSHdJdGFNQytPbklvdTU5RTJxdVl3Tmhwbm10dU5uQm5XRU5rZTh5b0ZObXNEMThVcmdCVkRpa1lYNVlJOGRzb3BnTXE3VVczc1kybXBidzNPbDR4dkdIYzBTUEZKNjM3XC9sRHBYR093SUZ0WGRhZ29rdG1HTmhxREpwYTBwdXRGT3VNZHFDMldJV3FIRGZqUlpDdWtGcE9STm1GRHFCa3JVRVJnRk1zY3p1S2xMbGFDek1YSnZOTEJlVkJ0cHFBSk1wTTRiY1wvWk9rWVoxMWQrOXNTa3B3QU1kUWJxY2VIZXJ1cVlQNmpzZGZVMEpRYXE2a1dXN0tSRVY4aHp0eDR1OHVmYms0K2RGdGxnQ29rMmYzZjFQTUYxZWhlXC9ieHorTzhhQyswOEdqRWVnR3J0bE55TTFjd3Y5Zm9sQytcL3ZcLzNhdlwvcjg0UUVwTzhUTFwvOVwvSDhzcE5jNTAxbGFSTU02dEdka1MwUjJYM1RPU3I5NU1QdDlEeEd6eEpCZElmSlMxR3R4SUZNY1V0YXlXR2ZWdDQrSzRlYkd5dnJMOTVYWE92NCt0S01oR1I3SFNlMnEzTER4UXRxXC9mNzlQemt5VStpMklTeFF5WXlwTkpuQzFBdFMxcW9QdzM2UzFQZmZ2WHlDTVBmK1M5TElGejNFZ01BQUE9PSIsInN1YiI6IkRvbmFsZCIsInJvbGVzIjpbIlJPTEVfQURNSU4iXSwiZXhwIjoxNDc2MTQwNjk4LCJpYXQiOjE0NzYxMzcwOTh9.wSwQad4POS77y61ULiTeOWEjxGcSv7xuDRryfWpZa-Y
DEBUG grails.plugin.springsecurity.rest.token.storage.jwt.JwtTokenStorageService - Successfully verified JWT
DEBUG grails.plugin.springsecurity.rest.token.storage.jwt.JwtTokenStorageService - Trying to deserialize the principal object
DEBUG grails.plugin.springsecurity.rest.token.storage.jwt.JwtTokenStorageService - UserDetails deserialized: grails.plugin.springsecurity.userdetails.GrailsUser@7a593596: Username: Donald; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN
DEBUG grails.plugin.springsecurity.rest.RestAuthenticationProvider - Now is Tue Oct 11 00:17:11 CEST 2016 and token expires at Tue Oct 11 01:04:58 CEST 2016
DEBUG grails.plugin.springsecurity.rest.RestAuthenticationProvider - Expiration: 2867
DEBUG grails.plugin.springsecurity.rest.RestAuthenticationProvider - Authentication result: grails.plugin.springsecurity.rest.token.AccessToken(accessToken:eyJhbGciOiJIUzI1NiJ9.eyJwcmluY2lwYWwiOiJINHNJQUFBQUFBQUFBSlZTUFdcL1RRQmgrSGRJUFVhbGZVcEU2bElXeUlVZUNNVk9cL1FGUW1RWVFzUmFLNjJHXC9kYTg5Mzd0MjVUWllxRXd3WldnR1JrUGdMK1NmdDBoOVF3Y0RhbVpYMzNLWk9XU3B1c3Q5N1wvSHk5SGx6Qm1OSHdJdGFNQytPbklvdTU5RTJxdVl3Tmhwbm10dU5uQm5XRU5rZTh5b0ZObXNEMThVcmdCVkRpa1lYNVlJOGRzb3BnTXE3VVczc1kybXBidzNPbDR4dkdIYzBTUEZKNjM3XC9sRHBYR093SUZ0WGRhZ29rdG1HTmhxREpwYTBwdXRGT3VNZHFDMldJV3FIRGZqUlpDdWtGcE9STm1GRHFCa3JVRVJnRk1zY3p1S2xMbGFDek1YSnZOTEJlVkJ0cHFBSk1wTTRiY1wvWk9rWVoxMWQrOXNTa3B3QU1kUWJxY2VIZXJ1cVlQNmpzZGZVMEpRYXE2a1dXN0tSRVY4aHp0eDR1OHVmYms0K2RGdGxnQ29rMmYzZjFQTUYxZWhlXC9ieHorTzhhQyswOEdqRWVnR3J0bE55TTFjd3Y5Zm9sQytcL3ZcLzNhdlwvcjg0UUVwTzhUTFwvOVwvSDhzcE5jNTAxbGFSTU02dEdka1MwUjJYM1RPU3I5NU1QdDlEeEd6eEpCZElmSlMxR3R4SUZNY1V0YXlXR2ZWdDQrSzRlYkd5dnJMOTVYWE92NCt0S01oR1I3SFNlMnEzTER4UXRxXC9mNzlQemt5VStpMklTeFF5WXlwTkpuQzFBdFMxcW9QdzM2UzFQZmZ2WHlDTVBmK1M5TElGejNFZ01BQUE9PSIsInN1YiI6IkRvbmFsZCIsInJvbGVzIjpbIlJPTEVfQURNSU4iXSwiZXhwIjoxNDc2MTQwNjk4LCJpYXQiOjE0NzYxMzcwOTh9.wSwQad4POS77y61ULiTeOWEjxGcSv7xuDRryfWpZa-Y, expiration:2867, refreshToken:null, principal:grails.plugin.springsecurity.userdetails.GrailsUser@7a593596: Username: Donald; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN, super:grails.plugin.springsecurity.rest.token.AccessToken@3afafc74: Principal: grails.plugin.springsecurity.userdetails.GrailsUser@7a593596: Username: Donald; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_ADMIN)
DEBUG grails.plugin.springsecurity.rest.RestTokenValidationFilter - Token authenticated. Storing the authentication result in the security context
DEBUG grails.plugin.springsecurity.rest.RestTokenValidationFilter - Authentication result: grails.plugin.springsecurity.rest.token.AccessToken(accessToken:eyJhbGciOiJIUzI1NiJ9.eyJwcmluY2lwYWwiOiJINHNJQUFBQUFBQUFBSlZTUFdcL1RRQmgrSGRJUFVhbGZVcEU2bElXeUlVZUNNVk9cL1FGUW1RWVFzUmFLNjJHXC9kYTg5Mzd0MjVUWllxRXd3WldnR1JrUGdMK1NmdDBoOVF3Y0RhbVpYMzNLWk9XU3B1c3Q5N1wvSHk5SGx6Qm1OSHdJdGFNQytPbklvdTU5RTJxdVl3Tmhwbm10dU5uQm5XRU5rZTh5b0ZObXNEMThVcmdCVkRpa1lYNVlJOGRzb3BnTXE3VVczc1kybXBidzNPbDR4dkdIYzBTUEZKNjM3XC9sRHBYR093SUZ0WGRhZ29rdG1HTmhxREpwYTBwdXRGT3VNZHFDMldJV3FIRGZqUlpDdWtGcE9STm1GRHFCa3JVRVJnRk1zY3p1S2xMbGFDek1YSnZOTEJlVkJ0cHFBSk1wTTRiY1wvWk9rWVoxMWQrOXNTa3B3QU1kUWJxY2VIZXJ1cVlQNmpzZGZVMEpRYXE2a1dXN0tSRVY4aHp0eDR1OHVmYms0K2RGdGxnQ29rMmYzZjFQTUYxZWhlXC9ieHorTzhhQyswOEdqRWVnR3J0bE55TTFjd3Y5Zm9sQytcL3ZcLzNhdlwvcjg0UUVwTzhUTFwvOVwvSDhzcE5jNTAxbGFSTU02dEdka1MwUjJYM1RPU3I5NU1QdDlEeEd6eEpCZElmSlMxR3R4SUZNY1V0YXlXR2ZWdDQrSzRlYkd5dnJMOTVYWE92NCt0S01oR1I3SFNlMnEzTER4UXRxXC9mNzlQemt5VStpMklTeFF5WXlwTkpuQzFBdFMxcW9QdzM2UzFQZmZ2WHlDTVBmK1M5TElGejNFZ01BQUE9PSIsInN1YiI6IkRvbmFsZCIsInJvbGVzIjpbIlJPTEVfQURNSU4iXSwiZXhwIjoxNDc2MTQwNjk4LCJpYXQiOjE0NzYxMzcwOTh9.wSwQad4POS77y61ULiTeOWEjxGcSv7xuDRryfWpZa-Y, expiration:2867, refreshToken:null, principal:grails.plugin.springsecurity.userdetails.GrailsUser@7a593596: Username: Donald; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN, super:grails.plugin.springsecurity.rest.token.AccessToken@3afafc74: Principal: grails.plugin.springsecurity.userdetails.GrailsUser@7a593596: Username: Donald; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_ADMIN)
DEBUG grails.plugin.springsecurity.rest.RestTokenValidationFilter - Continuing the filter chain
DEBUG org.springframework.security.web.FilterChainProxy - /api/projects at position 6 of 7 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
DEBUG org.springframework.security.web.FilterChainProxy - /api/projects at position 7 of 7 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
DEBUG org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Secure object: FilterInvocation: URL: /api/projects; Attributes: [_DENY_]
DEBUG org.springframework.security.web.access.intercept.FilterSecurityInterceptor - Previously Authenticated: grails.plugin.springsecurity.rest.token.AccessToken(accessToken:eyJhbGciOiJIUzI1NiJ9.eyJwcmluY2lwYWwiOiJINHNJQUFBQUFBQUFBSlZTUFdcL1RRQmgrSGRJUFVhbGZVcEU2bElXeUlVZUNNVk9cL1FGUW1RWVFzUmFLNjJHXC9kYTg5Mzd0MjVUWllxRXd3WldnR1JrUGdMK1NmdDBoOVF3Y0RhbVpYMzNLWk9XU3B1c3Q5N1wvSHk5SGx6Qm1OSHdJdGFNQytPbklvdTU5RTJxdVl3Tmhwbm10dU5uQm5XRU5rZTh5b0ZObXNEMThVcmdCVkRpa1lYNVlJOGRzb3BnTXE3VVczc1kybXBidzNPbDR4dkdIYzBTUEZKNjM3XC9sRHBYR093SUZ0WGRhZ29rdG1HTmhxREpwYTBwdXRGT3VNZHFDMldJV3FIRGZqUlpDdWtGcE9STm1GRHFCa3JVRVJnRk1zY3p1S2xMbGFDek1YSnZOTEJlVkJ0cHFBSk1wTTRiY1wvWk9rWVoxMWQrOXNTa3B3QU1kUWJxY2VIZXJ1cVlQNmpzZGZVMEpRYXE2a1dXN0tSRVY4aHp0eDR1OHVmYms0K2RGdGxnQ29rMmYzZjFQTUYxZWhlXC9ieHorTzhhQyswOEdqRWVnR3J0bE55TTFjd3Y5Zm9sQytcL3ZcLzNhdlwvcjg0UUVwTzhUTFwvOVwvSDhzcE5jNTAxbGFSTU02dEdka1MwUjJYM1RPU3I5NU1QdDlEeEd6eEpCZElmSlMxR3R4SUZNY1V0YXlXR2ZWdDQrSzRlYkd5dnJMOTVYWE92NCt0S01oR1I3SFNlMnEzTER4UXRxXC9mNzlQemt5VStpMklTeFF5WXlwTkpuQzFBdFMxcW9QdzM2UzFQZmZ2WHlDTVBmK1M5TElGejNFZ01BQUE9PSIsInN1YiI6IkRvbmFsZCIsInJvbGVzIjpbIlJPTEVfQURNSU4iXSwiZXhwIjoxNDc2MTQwNjk4LCJpYXQiOjE0NzYxMzcwOTh9.wSwQad4POS77y61ULiTeOWEjxGcSv7xuDRryfWpZa-Y, expiration:2867, refreshToken:null, principal:grails.plugin.springsecurity.userdetails.GrailsUser@7a593596: Username: Donald; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN, super:grails.plugin.springsecurity.rest.token.AccessToken@3afafc74: Principal: grails.plugin.springsecurity.userdetails.GrailsUser@7a593596: Username: Donald; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_ADMIN; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_ADMIN)
DEBUG org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl - getReachableGrantedAuthorities() - From the roles [ROLE_ADMIN] one can reach [ROLE_ADMIN] in zero or more steps.
DEBUG org.springframework.security.web.access.ExceptionTranslationFilter - Access is denied (user is not anonymous); delegating to AccessDeniedHandler
org.springframework.security.access.AccessDeniedException: Access is denied
    at org.springframework.security.access.vote.AbstractAccessDecisionManager.checkAllowIfAllAbstainDecisions(AbstractAccessDecisionManager.java:70)
    at grails.plugin.springsecurity.access.vote.AuthenticatedVetoableDecisionManager.decide(AuthenticatedVetoableDecisionManager.groovy:50)
    at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:232)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:123)
    at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:90)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:114)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at javax.servlet.FilterChain$doFilter.call(Unknown Source)
    at grails.plugin.springsecurity.rest.RestTokenValidationFilter.processFilterChain(RestTokenValidationFilter.groovy:118)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springsource.loaded.ri.ReflectiveInterceptor.jlrMethodInvoke(ReflectiveInterceptor.java:1426)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite$PogoCachedMethodSiteNoUnwrapNoCoerce.invoke(PogoMetaMethodSite.java:210)
    at org.codehaus.groovy.runtime.callsite.PogoMetaMethodSite.callCurrent(PogoMetaMethodSite.java:59)
    at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callCurrent(AbstractCallSite.java:190)
    at grails.plugin.springsecurity.rest.RestTokenValidationFilter.doFilter(RestTokenValidationFilter.groovy:84)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:169)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at javax.servlet.FilterChain$doFilter.call(Unknown Source)
    at grails.plugin.springsecurity.rest.RestAuthenticationFilter.doFilter(RestAuthenticationFilter.groovy:143)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at grails.plugin.springsecurity.web.authentication.logout.MutableLogoutFilter.doFilter(MutableLogoutFilter.groovy:62)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at grails.plugin.springsecurity.web.SecurityRequestHolderFilter.doFilter(SecurityRequestHolderFilter.groovy:58)
    at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:330)
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:213)
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:176)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
    at org.grails.web.servlet.mvc.GrailsWebRequestFilter.doFilterInternal(GrailsWebRequestFilter.java:75)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
    at org.grails.web.filters.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:67)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
    at org.springframework.boot.actuate.autoconfigure.MetricsFilter.doFilterInternal(MetricsFilter.java:103)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:240)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:207)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:528)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1099)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:670)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1520)
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1476)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)
DEBUG org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/error'; against '/assets/**'
DEBUG org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/error'; against '/**/js/**'
DEBUG org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/error'; against '/**/css/**'
DEBUG org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/error'; against '/**/images/**'
DEBUG org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/error'; against '/**/favicon.ico'
DEBUG org.springframework.security.web.util.matcher.AntPathRequestMatcher - Checking match of request : '/error'; against '/api/**'
DEBUG org.springframework.security.web.FilterChainProxy - /error has no matching filters

Любые идеи о том, что я делаю неправильно? Любые советы о том, как решить эту ошибку?

Лучшая Агата


Обновленный вопрос с ProjectController и UrlMapping


person themathmagician    schedule 12.10.2016    source источник
comment
Ваше сопоставление неверно: URL: /api/projects; Attributes: [_DENY_]. Либо ваши UrlMappings неверны, либо вы забыли аннотацию @Secured.   -  person Álvaro Sánchez-Mariscal    schedule 13.10.2016
comment
У меня есть аннотация @Secured: @Secured(['ROLE_ADMIN']) class ProjectController extends RestfulController . Что вы имеете в виду под сопоставлением URL? В классе Application у меня есть это в цепочке фильтров: pattern: '/api/**', filters: 'JOINED_FILTERS,-anonymousAuthenticationFilter,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter,-rememberMeAuthenticationFilter']. Из кода отладки я вижу, что ROLE_ADMIN проверяется на ROLE_ADMIN, и это приводит к Insufficient Scope?   -  person themathmagician    schedule 13.10.2016
comment
Учитывая сообщение журнала URL: /api/projects; Attributes: [_DENY_], отображение /api/projects не указывает на ProjectController. Обновите вопрос, указав содержимое вашего файла UrlMappings.   -  person Álvaro Sánchez-Mariscal    schedule 13.10.2016
comment
Я обновил ProjectController и UrlMappings. Незащищенный API был протестирован перед добавлением аннотации @Secured и изменением фильтров.   -  person themathmagician    schedule 13.10.2016
comment
О, мой! Вы были абсолютно правы, у меня нет /api в моем отображении, только /projects... здесь запутался автогенерируемый код! Спасибо, что пролили свет на это, теперь работает для меня.   -  person themathmagician    schedule 13.10.2016


Ответы (1)


Я решил проблему. Вывод состоял в том, что вы можете получить ошибку Insufficient_scope, если ресурс, к которому вы пытаетесь получить доступ, не существует.

Оказывается, команда s2-quickstart в Grails генерирует какой-то код в файле application.groovy. Я добавил фильтры в карту filterChain с шаблоном pattern: '/api/**',, в то время как мой API находился в шаблоне /.

Урок выучен:

  • Как включить журнал уровня отладки для классов безопасности Spring: добавьте в файл Logback.groovy следующее:

    logger("org.springframework.security", DEBUG, ['STDOUT'], false) logger("grails.plugin.springsecurity", DEBUG, ['STDOUT'], false) logger("org.pac4j", DEBUG, ['СТАНДАРТ'], ложь)

  • Вы можете получить ошибку Insufficient_scope, если ресурс, к которому вы пытаетесь получить доступ, не существует.

    • I found the error by turning OFF security, and discovered the /api/projects call gave an 404 error.
person themathmagician    schedule 13.10.2016