Spring安全服务让当前用户返回错误的用户

时间:2016-01-29 01:26:02

标签: grails spring-security spring-security-rest

用户A 登录我的应用程序,然后注销,然后用户B 登录,springSecurityService.getCurrentUser()返回用户A 域对象。

我的前端使用spring-security rest插件附带的api end point进行身份验证并获取访问令牌。然后向访问令牌提供对我的api的所有请求。

据我所知,此错误仅影响一个终点。将带有请求的相同access_token传递给有问题的终点并且正常工作会导致他们各自调用getCurrentUser()来回复两个不同的用户。

我没有使用静态或非静态的任何类成员。

我在我的grails(2.4.4)应用程序中使用spring-security-rest(1.5.3)和spring-security-core(2.0.0)进行身份验证。

应用程序在Tomcat 7(Java 7)服务器上远程运行。当应用程序在我的Mac上本地运行时,不会发生该错误,我认为这是该问题的线索。

以下是我的Spring安全配置来自Config.groovy

grails.plugin.springsecurity.interceptUrlMap = [
    '/assets/**':                     ['permitAll'],
    '/**/js/**':                      ['permitAll'],
    '/**/css/**':                     ['permitAll'],
    '/**/images/**':                  ['permitAll'],
    '/**/favicon.ico':                ['permitAll'],
    '/login/**':                      ['permitAll'],
    '/blank/**':                      ['permitAll'],
    '/register/**':                   ['permitAll'],
    '/api/v1/signup':                 ['permitAll'],
    '/api/v1/register':               ['permitAll'],
    '/api/v1/approved_emails':        ['permitAll'],
    '/api/v1/request_password_reset': ['permitAll'],
    '/api/v1/reset_password':         ['permitAll'],
    '/oauth/**':                      ['permitAll'],
    '/':                              ['isAuthenticated()'],
    '/index':                         ['isAuthenticated()'],
    '/index.gsp':                     ['isAuthenticated()'],
    '/**':                            ['isAuthenticated()']
]
grails.plugin.springsecurity.auth.loginFormUrl = "/pages/index"
grails.plugin.springsecurity.failureHandler.defaultFailureUrl = "/pages/index"
grails.plugin.springsecurity.defaultTargetUrl = "/"
grails.plugin.springsecurity.sch.strategyName = org.springframework.security.core.context.SecurityContextHolder.MODE_INHERITABLETHREADLOCAL
grails.plugin.springsecurity.rest.login.endpointUrl = '/api/v1/login'

grails.plugin.springsecurity.filterChain.chainMap = [
        '/api/**': 'JOINED_FILTERS,-anonymousAuthenticationFilter,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter,-rememberMeAuthenticationFilter',  // Stateless chain
        '/oauth/**': 'JOINED_FILTERS,-anonymousAuthenticationFilter,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter,-rememberMeAuthenticationFilter',  // Stateless chain
        '/**': 'JOINED_FILTERS,-restTokenValidationFilter,-restExceptionTranslationFilter'                                                                          // Traditional chain
]

以下是来自UrlMappings.groovy

的网址映射
class UrlMappings {

static mappings = {
    "/$controller/$action?/$id?(.$format)?"{
        constraints {
            // apply constraints here
        }
    }
    "/api/v1/document"(resources: "documentRest", includes:['show','update']){
        "/history"(resources: "documentHistoryRest", includes: ['index'])
    }

    "/api/v1/response"(resources: "responseRest", includes:['save', 'show', 'update']) {
        "/candidate_answers"(resources: "messagesRest", includes: ['show']) {
            "/source_document"(resources: "postRest", includes: ['index'])
            "/feedback"(resources: "candidateFeedbackRest", includes: ['save'])
        }
    }

    "/api/v1/result"(resources: "resultRest", includes:['index','update', 'save'])

    "/api/v1/user"(resources: "userRest", includes:['index']) {
        "/memo"(resources: "userMemoRest", includes: ['index','save', 'show','update'])
        "/folders"(resources: "userfoldersRest", includes:['index','save', 'show','update','delete']){
            "/items"(resources: "userfolderItemsRest", includes: ['index','show','save','delete'])
        }
    }

    '/api/v1/location_search'(resources: 'locationSearchRest', includes: ['index'])

    "/api/v1/request_password_reset"(resources: "requestPasswordResetRest", includes: ['save'])

    "/api/v1/reset_password"(resources: "resetPasswordRest", includes: ['save'])

    "/api/v1/register"(resources: "registerRest", includes:['save'])

    "/api/v1/approved_emails"(resources: "approvedEmailsRest", includes: ['index'])

    "/api/v1/autocomplete"(resources: "autoCompleteRest", includes:['index'])

    "500"(view:'/error')
}
}

以下是来自的debug信息:

'/api/v1/response'; against '/api/**'
2016-01-29 18:03:28,583 [http-bio-8080-exec-5] DEBUG web.FilterChainProxy  - /api/v1/response at position 1 of 7 in additional filter chain; firing Filter: 'SecurityRequestHolderFilter'
2016-01-29 18:03:28,583 [http-bio-8080-exec-5] DEBUG web.FilterChainProxy  - /api/v1/response at position 2 of 7 in additional filter chain; firing Filter: 'MutableLogoutFilter'
2016-01-29 18:03:28,583 [http-bio-8080-exec-5] DEBUG web.FilterChainProxy  - /api/v1/response at position 3 of 7 in additional filter chain; firing Filter: 'RestAuthenticationFilter'
2016-01-29 18:03:28,583 [http-bio-8080-exec-5] DEBUG rest.RestAuthenticationFilter  - Actual URI is /api/v1/response; endpoint URL is /api/v1/login
2016-01-29 18:03:28,583 [http-bio-8080-exec-5] DEBUG web.FilterChainProxy  - /api/v1/response at position 4 of 7 in additional filter chain; firing Filter: 'SecurityContextHolderAwareRequestFilter'
2016-01-29 18:03:28,583 [http-bio-8080-exec-5] DEBUG web.FilterChainProxy  - /api/v1/response at position 5 of 7 in additional filter chain; firing Filter: 'RestTokenValidationFilter'
2016-01-29 18:03:28,583 [http-bio-8080-exec-5] DEBUG bearer.BearerTokenReader  - Looking for bearer token in Authorization header, query string or Form-Encoded body parameter
2016-01-29 18:03:28,584 [http-bio-8080-exec-5] DEBUG bearer.BearerTokenReader  - Found bearer token in Authorization header
2016-01-29 18:03:28,584 [http-bio-8080-exec-5] DEBUG rest.RestTokenValidationFilter  - Token found: xxxxxxxxxxxxxxxxxxxxxx
2016-01-29 18:03:28,584 [http-bio-8080-exec-5] DEBUG rest.RestTokenValidationFilter  - Trying to authenticate the token
2016-01-29 18:03:28,584 [http-bio-8080-exec-5] DEBUG rest.RestAuthenticationProvider  - Trying to validate token xxxxxxxxxxxxxxxxxxxxxx
2016-01-29 18:03:28,584 [http-bio-8080-exec-5] DEBUG rest.JwtService  - Parsed an HMAC signed JWT
2016-01-29 18:03:28,585 [http-bio-8080-exec-5] DEBUG jwt.JwtTokenStorageService  - Successfully verified JWT
2016-01-29 18:03:28,585 [http-bio-8080-exec-5] DEBUG jwt.JwtTokenStorageService  - Trying to deserialize the principal object
2016-01-29 18:03:28,587 [http-bio-8080-exec-5] DEBUG jwt.JwtTokenStorageService  - UserDetails deserialized: grails.plugin.springsecurity.userdetails.GrailsUser@ff0d1268: Username: userb@email.com; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_NO_ROLES
2016-01-29 18:03:28,587 [http-bio-8080-exec-5] DEBUG rest.JwtService  - Parsed an HMAC signed JWT
2016-01-29 18:03:28,588 [http-bio-8080-exec-5] DEBUG rest.RestAuthenticationProvider  - Now is Fri Jan 29 18:03:28 UTC 2016 and token expires at Fri Jan 29 19:01:47 UTC 2016
2016-01-29 18:03:28,588 [http-bio-8080-exec-5] DEBUG rest.RestAuthenticationProvider  - Expiration: 3498
2016-01-29 18:03:28,588 [http-bio-8080-exec-5] DEBUG rest.RestAuthenticationProvider  - Authentication result: grails.plugin.springsecurity.rest.token.AccessToken(accessToken:xxxxxxxxxxxxxxxxxxxxxx, expiration:3498, refreshToken:null, principal:grails.plugin.springsecurity.userdetails.GrailsUser@ff0d1268: Username: userb@email.com; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_NO_ROLES, super:grails.plugin.springsecurity.rest.token.AccessToken@77be21e4: Principal: grails.plugin.springsecurity.userdetails.GrailsUser@ff0d1268: Username: userb@email.com; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_NO_ROLES; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_NO_ROLES)
2016-01-29 18:03:28,589 [http-bio-8080-exec-5] DEBUG rest.RestTokenValidationFilter  - Token authenticated. Storing the authentication result in the security context
2016-01-29 18:03:28,589 [http-bio-8080-exec-5] DEBUG rest.RestTokenValidationFilter  - Authentication result: grails.plugin.springsecurity.rest.token.AccessToken(accessToken:xxxxxxxxxxxxxxxxxxxxxx, expiration:3498, refreshToken:null, principal:grails.plugin.springsecurity.userdetails.GrailsUser@ff0d1268: Username: userb@email.com; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_NO_ROLES, super:grails.plugin.springsecurity.rest.token.AccessToken@77be21e4: Principal: grails.plugin.springsecurity.userdetails.GrailsUser@ff0d1268: Username: userb@email.com; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_NO_ROLES; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_NO_ROLES)
2016-01-29 18:03:28,589 [http-bio-8080-exec-5] DEBUG rest.RestTokenValidationFilter  - Continuing the filter chain
2016-01-29 18:03:28,589 [http-bio-8080-exec-5] DEBUG web.FilterChainProxy  - /api/v1/response at position 6 of 7 in additional filter chain; firing Filter: 'ExceptionTranslationFilter'
2016-01-29 18:03:28,589 [http-bio-8080-exec-5] DEBUG web.FilterChainProxy  - /api/v1/response at position 7 of 7 in additional filter chain; firing Filter: 'FilterSecurityInterceptor'
2016-01-29 18:03:28,590 [http-bio-8080-exec-5] DEBUG intercept.FilterSecurityInterceptor  - Secure object: FilterInvocation: URL: /api/v1/response; Attributes: [isAuthenticated()]
2016-01-29 18:03:28,590 [http-bio-8080-exec-5] DEBUG intercept.FilterSecurityInterceptor  - Previously Authenticated: grails.plugin.springsecurity.rest.token.AccessToken(accessToken:xxxxxxxxxxxxxxxxxxxxxx, expiration:3498, refreshToken:null, principal:grails.plugin.springsecurity.userdetails.GrailsUser@ff0d1268: Username: userb@email.com; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_NO_ROLES, super:grails.plugin.springsecurity.rest.token.AccessToken@77be21e4: Principal: grails.plugin.springsecurity.userdetails.GrailsUser@ff0d1268: Username: userb@email.com; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_NO_ROLES; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_NO_ROLES)
2016-01-29 18:03:28,590 [http-bio-8080-exec-5] DEBUG hierarchicalroles.RoleHierarchyImpl  - getReachableGrantedAuthorities() - From the roles [ROLE_NO_ROLES] one can reach [ROLE_NO_ROLES] in zero or more steps.
2016-01-29 18:03:28,590 [http-bio-8080-exec-5] DEBUG intercept.FilterSecurityInterceptor  - Authorization successful
2016-01-29 18:03:28,590 [http-bio-8080-exec-5] DEBUG intercept.FilterSecurityInterceptor  - RunAsManager did not change Authentication object
2016-01-29 18:03:28,590 [http-bio-8080-exec-5] DEBUG web.FilterChainProxy  - /api/v1/response reached end of additional filter chain; proceeding with original chain
2016-01-29 18:03:35,334 [http-bio-8080-exec-5] DEBUG access.ExceptionTranslationFilter  - Chain processed normally

提前感谢您的帮助。

1 个答案:

答案 0 :(得分:1)

我的预感是Spring Security正在使用来自HttpSession的缓存SecurityContext。

所以我删除了HttpSessionContextIntegrationFilter,它在我的Config.groovy文件中的过滤器链中,在Web请求之间的HttpSession中存储了SecurityContext,它解决了问题。

grails.plugin.springsecurity.filterChain.chainMap = [
        '/api/**': 'JOINED_FILTERS,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter,-rememberMeAuthenticationFilter, -httpSessionContextIntegrationFilter',  // Stateless chain
        '/oauth/**': 'JOINED_FILTERS,-exceptionTranslationFilter,-authenticationProcessingFilter,-securityContextPersistenceFilter,-rememberMeAuthenticationFilter',  // Stateless chain
        '/**': 'JOINED_FILTERS,-restTokenValidationFilter,-restExceptionTranslationFilter'                                                                          // Traditional chain
]