Grails Spring Security最大并发会话

时间:2016-01-16 13:11:11

标签: grails spring-security grails-plugin

我有使用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/) 我的resources.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

谢谢..

1 个答案:

答案 0 :(得分:2)

首先,如果您决定继续我的解决方案,请告诉您。

  • SessionRegistryImpl 不可扩展。您需要根据扩展计划(例如数据网格)创建自己的可扩展实现。会话复制还不够。
  • 目前,默认注销处理程序未正确删除SessionRegistry。所以我创建了一个名为 CustomSessionLogoutHandler 的示例Logout处理程序。
  • 您必须覆盖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
    }
}