【问题标题】:Grails Spring Security max concurrent sessionGrails Spring Security 最大并发会话
【发布时间】:2016-04-22 00:12:14
【问题描述】:

我有带有 spring 安全插件 (2.0-RC5) 的 grails 2.5.1 应用程序。我想阻止每个用户的当前会话数。我读了一些博客,但它不起作用。(http://www.block-consult.com/blog/2012/01/20/restricting-concurrent-user-sessions-in-grails-2-using-spring-security-core-plugin/) 我的资源.groovy

beans = {
  sessionRegistry(SessionRegistryImpl)

    concurrencyFilter(ConcurrentSessionFilter,sessionRegistry,'/main/index'){
        logoutHandlers = [ref("rememberMeServices"), ref("securityContextLogoutHandler")]
    }
    concurrentSessionControlStrategy(ConcurrentSessionControlAuthenticationStrategy, sessionRegistry) {
        exceptionIfMaximumExceeded = true
        maximumSessions = 1

    }
}

在我的 boostrap.groovy 中

 def init = { servletContext ->
    SpringSecurityUtils.clientRegisterFilter('concurrencyFilter', SecurityFilterPosition.CONCURRENT_SESSION_FILTER)
  }

我的 config.groovy 我已经添加了这个:

grails.plugin.springsecurity.useHttpSessionEventPublisher = true

谢谢..

【问题讨论】:

    标签: grails spring-security grails-plugin


    【解决方案1】:

    首先,如果您决定继续使用我的解决方案,请让我警告您。

    • SessionRegistryImpl 不可扩展。您需要根据您的扩​​展计划(例如数据网格)创建自己的可扩展实现。会话复制还不够。
    • 目前,默认注销处理程序没有正确删除 SessionRegistry。所以我创建了一个名为 CustomSessionLogoutHandler 的示例注销处理程序。
    • 您必须覆盖 grails spring-security-core 登录控制器来处理 SessionAuthenticationException
    • 您可以将可以登录 maximumSessions = 1 的用户数更改为 -1 以获得无限会话。

    首先,在 resources.groovy 中

    import org.springframework.security.core.session.SessionRegistryImpl;
    import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;
    import org.springframework.security.web.authentication.session.SessionFixationProtectionStrategy;
    import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy;
    import org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy;
    import com.basic.CustomSessionLogoutHandler
    
    
    // Place your Spring DSL code here
    beans = {
    
    sessionRegistry(SessionRegistryImpl)
    
    customSessionLogoutHandler(CustomSessionLogoutHandler,ref('sessionRegistry')    )
    
    concurrentSessionControlAuthenticationStrategy(ConcurrentSessionControlAuthenticationStrategy,ref('sessionRegistry')){
        exceptionIfMaximumExceeded = true
        maximumSessions = 1
    }
    
    sessionFixationProtectionStrategy(SessionFixationProtectionStrategy){
        migrateSessionAttributes = true
        alwaysCreateSession = true
    }
    registerSessionAuthenticationStrategy(RegisterSessionAuthenticationStrategy,ref('sessionRegistry'))
    
    sessionAuthenticationStrategy(CompositeSessionAuthenticationStrategy,[ref('concurrentSessionControlAuthenticationStrategy'),ref('sessionFixationProtectionStrategy'),ref('registerSessionAuthenticationStrategy')])
    
    }
    

    在 config.groovy 中确保 customSessionLogoutHandler 位于 securityContextLogoutHandler 之前:

    grails.plugin.springsecurity.logout.handlerNames = ['customSessionLogoutHandler','securityContextLogoutHandler'] 
    

    ConcurrentSessionControlAuthenticationStrategy 使用这个 i18n。因此,您可以使用您的语言:

    ConcurrentSessionControlAuthenticationStrategy.exceededAllowed = Maximum sessions for this principal exceeded. {0}
    

    这是我的示例CustomSessionLogoutHandler,您可以将其保存在 src/groovy/com/basic/CustomSessionLogoutHandler.groovy 中:

    /*
    * Copyright 2002-2013 the original author or authors.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package com.basic;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.security.core.Authentication;
    import org.springframework.security.web.authentication.logout.LogoutHandler;
    import org.springframework.util.Assert;
    import org.springframework.security.core.session.SessionRegistry;
    
    /**
     * {@link CustomSessionLogoutHandler} is in charge of removing the {@link SessionRegistry} upon logout. A
     * new {@link SessionRegistry} will then be generated by the framework upon the next request.
     *
     * @author Mohd Qusyairi
     * @since 0.1
     */
    public final class CustomSessionLogoutHandler implements LogoutHandler {
        private final SessionRegistry sessionRegistry;
    
        /**
         * Creates a new instance
         * @param sessionRegistry the {@link SessionRegistry} to use
         */
        public CustomSessionLogoutHandler(SessionRegistry sessionRegistry) {
            Assert.notNull(sessionRegistry, "sessionRegistry cannot be null");
            this.sessionRegistry = sessionRegistry;
        }
    
        /**
         * Clears the {@link SessionRegistry}
         *
         * @see org.springframework.security.web.authentication.logout.LogoutHandler#logout(javax.servlet.http.HttpServletRequest,
         * javax.servlet.http.HttpServletResponse,
         * org.springframework.security.core.Authentication)
         */
        public void logout(HttpServletRequest request, HttpServletResponse response,
                Authentication authentication) {
            this.sessionRegistry.removeSessionInformation(request.getSession().getId());
        }
    }
    

    如果您也需要,我的示例登录控制器(我从源代码复制)。只需在项目中另存为普通控制器,因为它将覆盖默认值。当我处理 SessionAuthenticationException 时,请参见下面的第 115 行:

    /* Copyright 2013-2016 the original author or authors.
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *      http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */
    package com.basic
    
    import grails.converters.JSON
    import org.springframework.security.access.annotation.Secured
    import org.springframework.security.authentication.AccountExpiredException
    import org.springframework.security.authentication.AuthenticationTrustResolver
    import org.springframework.security.authentication.CredentialsExpiredException
    import org.springframework.security.authentication.DisabledException
    import org.springframework.security.authentication.LockedException
    import org.springframework.security.core.Authentication
    import org.springframework.security.core.context.SecurityContextHolder
    import org.springframework.security.web.WebAttributes
    import org.springframework.security.web.authentication.session.SessionAuthenticationException
    import javax.servlet.http.HttpServletResponse
    import grails.plugin.springsecurity.SpringSecurityUtils
    
    @Secured('permitAll')
    class LoginController {
    
        /** Dependency injection for the authenticationTrustResolver. */
        AuthenticationTrustResolver authenticationTrustResolver
    
        /** Dependency injection for the springSecurityService. */
        def springSecurityService
    
        /** Default action; redirects to 'defaultTargetUrl' if logged in, /login/auth otherwise. */
        def index() {
            if (springSecurityService.isLoggedIn()) {
                redirect uri: conf.successHandler.defaultTargetUrl
            }
            else {
                redirect action: 'auth', params: params
            }
        }
    
        /** Show the login page. */
        def auth() {
    
            def conf = getConf()
    
            if (springSecurityService.isLoggedIn()) {
                redirect uri: conf.successHandler.defaultTargetUrl
                return
            }
    
            String postUrl = request.contextPath + conf.apf.filterProcessesUrl
            render view: 'auth', model: [postUrl: postUrl,
                                         rememberMeParameter: conf.rememberMe.parameter,
                                         usernameParameter: conf.apf.usernameParameter,
                                         passwordParameter: conf.apf.passwordParameter,
                                         gspLayout: conf.gsp.layoutAuth]
        }
    
        /** The redirect action for Ajax requests. */
        def authAjax() {
            response.setHeader 'Location', conf.auth.ajaxLoginFormUrl
            render(status: HttpServletResponse.SC_UNAUTHORIZED, text: 'Unauthorized')
        }
    
        /** Show denied page. */
        def denied() {
            if (springSecurityService.isLoggedIn() && authenticationTrustResolver.isRememberMe(authentication)) {
                // have cookie but the page is guarded with IS_AUTHENTICATED_FULLY (or the equivalent expression)
                redirect action: 'full', params: params
                return
            }
    
            [gspLayout: conf.gsp.layoutDenied]
        }
    
        /** Login page for users with a remember-me cookie but accessing a IS_AUTHENTICATED_FULLY page. */
        def full() {
            def conf = getConf()
            render view: 'auth', params: params,
                   model: [hasCookie: authenticationTrustResolver.isRememberMe(authentication),
                           postUrl: request.contextPath + conf.apf.filterProcessesUrl,
                           rememberMeParameter: conf.rememberMe.parameter,
                           usernameParameter: conf.apf.usernameParameter,
                           passwordParameter: conf.apf.passwordParameter,
                           gspLayout: conf.gsp.layoutAuth]
        }
    
        /** Callback after a failed login. Redirects to the auth page with a warning message. */
        def authfail() {
    
            String msg = ''
            def exception = session[WebAttributes.AUTHENTICATION_EXCEPTION]
            if (exception) {
                if (exception instanceof AccountExpiredException) {
                    msg = message(code: 'springSecurity.errors.login.expired')
                }
                else if (exception instanceof CredentialsExpiredException) {
                    msg = message(code: 'springSecurity.errors.login.passwordExpired')
                }
                else if (exception instanceof DisabledException) {
                    msg = message(code: 'springSecurity.errors.login.disabled')
                }
                else if (exception instanceof LockedException) {
                    msg = message(code: 'springSecurity.errors.login.locked')
                }
                else if (exception instanceof SessionAuthenticationException){
                    msg = exception.getMessage()
                }
                else {
                    msg = message(code: 'springSecurity.errors.login.fail')
                }
            }
    
            if (springSecurityService.isAjax(request)) {
                render([error: msg] as JSON)
            }
            else {
                flash.message = msg
                redirect action: 'auth', params: params
            }
        }
    
        /** The Ajax success redirect url. */
        def ajaxSuccess() {
            render([success: true, username: authentication.name] as JSON)
        }
    
        /** The Ajax denied redirect url. */
        def ajaxDenied() {
            render([error: 'access denied'] as JSON)
        }
    
        protected Authentication getAuthentication() {
            SecurityContextHolder.context?.authentication
        }
    
        protected ConfigObject getConf() {
            SpringSecurityUtils.securityConfig
        }
    }
    

    【讨论】:

    • 这个springSecurityService是哪里来的?我有一个不包含它的 Spring Boot/Security 应用程序。
    • @Quchie 在此用于禁用使用相同用户名的另一个用户登录?禁用多重登录怎么样?
    • @akiong 是的,这将使用用户名阻止同一用户登录。
    • @sonoerin 抱歉,这是针对 grails 2.x 而不是 spring boot。
    • @akiong 嗨,我认为你不再需要 concurrencyFilter 了。如果删除任何 concurrentSessioncontrolAuthenticationStrategy,则可以忽略 expiredUrl。您的目标应该是删除 sessionRegistry 中的相同用户,以确保只有 1 个具有相同用户名的用户可以登录。
    猜你喜欢
    • 2014-09-22
    • 2011-06-02
    • 1970-01-01
    • 2017-05-16
    • 2012-04-05
    • 2018-04-26
    • 1970-01-01
    • 2011-04-09
    • 1970-01-01
    相关资源
    最近更新 更多