【问题标题】:Spring security service's get current user returns incorrect userSpring 安全服务获取当前用户返回不正确的用户
【发布时间】:2016-05-06 15:30:52
【问题描述】:

用户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 上本地运行时,该错误不会发生,我认为这是问题的线索。

下面是我来自 Config.groovy 的 spring 安全配置

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 的 URL 映射

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

提前感谢您的帮助。

【问题讨论】:

  • 用什么代码记录它们?
  • 我的前端使用 spring-security rest 插件附带的 api 端点来进行身份验证并获取访问令牌。然后将访问令牌与对我的 api 的所有请求一起提供。据我所知,这个错误只影响一个端点。将相同的 access_token 与请求一起传递给有问题的端点和一个有效的端点会导致它们各自对 getCurrentUser() 的调用返回两个不同的用户。
  • 可能没用,但你应该更新到核心插件的2.0.0版本,RC4已经很老了。
  • getCurrentUser 通过存储域类实例的 id 来工作,该实例用于创建 UserDetails 实例,该实例用作 Authentication 中的 principal。它纯粹是一种实用/方便的方法,与 Spring Security 几乎没有关系。它检索 User/Person 域类实例,以便您可以访问 Spring Security 未使用的配置文件信息。
  • 如果您只需要用户名、授予的角色或其他与安全相关的数据,您应该使用springSecurityService.getPrincipal(),因为它已缓存并且不需要数据库访问;如果您需要未缓存在主体/身份验证中的数据库数据,请仅使用 getCurrentUser

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


【解决方案1】:

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

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

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
]

【讨论】:

    猜你喜欢
    • 2019-01-12
    • 1970-01-01
    • 2015-09-12
    • 1970-01-01
    • 2018-10-25
    • 2019-02-02
    • 1970-01-01
    • 2018-06-16
    • 2014-08-08
    相关资源
    最近更新 更多