0

我创建了一个新的 grails v3 应用程序,并将以下依赖项添加到 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"

我使用 Grails Security Quickstart 创建了一个用户角色组配置,并在 bootstrap.groovy 中为用户设置了一个用户、一些角色和一些帖子。我查看了 /dbconsole 并保存了记录。<y 基本的集成测试似乎有效。

我创建了一个这样的休息控制器 - 我现在没有保护任何东西:

import grails.rest.RestfulController

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

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

经过大量阅读,我最终在我的 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']
]

我的静态规则有 /api/** 允许所有。我已经根据 Spring Security REST 文档更新了 /api/** 的链图

我更新了我的 UrlMappings 如下

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')
    }
}

当我在 Grails 控制台中运行 url-mappings-report 时,我的 URL 看起来正确公开。

我运行应用程序 - 一切正常。

然后我使用 Chrome 发布到 api/login。当我使用 Chrome REST 客户端连接时 - 这给了我 200 和 access_token:

API登录

我尝试使用返回的密钥进行帖子验证,并获得看起来不错的 HTML 200。

最后我尝试调用我的 /api/posts 服务。我剪切并粘贴我的密钥并添加到 GET 标头并提交,我得到 403/forbidden:

在此处输入图像描述

我看不出我哪里出错了。我在与最后一个 /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

最后 - 我启用了 fii.rejectPublicInvocations 并再次尝试,当我访问 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)

注意 - 请参阅下面的评论 - 我像这样对 PostRestController 进行了“破解” - 并将结果强制为“真”。这现在允许我使用登录密钥访问 api/guest/posts(匿名)和 /api/posts:

@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)
    }
}

我也启用了调试过滤器,它在我访问 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
]

我从闭包写到控制台的跟踪给了我这个:

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
4

0 回答 0