Grails Spring Security最大并发会话 [英] Grails Spring Security max concurrent session

查看:110
本文介绍了Grails Spring Security最大并发会话的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我使用Spring安全插件(2.0-RC5)的Grails 2.5.1应用程序。我想阻止每个用户的当前会话数量。我已经阅读了一些博客,但它不起作用。( http://www.block-consult.com/blog/2012/01/20/restricting-concurrent-user-sessions-in-grails -2)使用spring-security-core-plugin /
my 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 

谢谢..

解决方案

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




  • SessionRegistryImpl 不可扩展。您需要根据您的扩​​展计划(例如数据网格)创建自己的可扩展实现。会话复制不够。

  • 目前,默认注销处理程序没有正确删除SessionRegistry。所以我创建了一个名为 CustomSessionLogoutHandler 的示例Logout处理程序。你必须覆盖grails的spring-security-core登录控制器来处理 SessionAuthenticationException

  • 您可以更改可以登录maximumSessions = 1的用户数量为无限制会话的-1。



首先在resources.groovy中

pre $ 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


//将您的Spring DSL代码放在这里
beans = {

sessionRegistry(SessionRegistryImpl)

customSessionLogoutHandler(CustomSessionLogoutHandler,ref('sessionRegistry'))

concurrentSessionControlAuthenticationStrategy(ConcurrentSessionControlAuthenticationStrategy,ref('sessionRegistry')){
exceptionIfMaximumExceeded = true
maximumSessions = 1
}

sessionFixationProtectionStrategy(SessionFixationProtectionStrategy){
migrateSessionAttributes = true
alwaysCreateSession = true
}
registerSessionAuthenticationStrategy(RegisterSessionAuthenticationStrategy,ref(' sessionSchema('sessionRegistry'))
$ b $ sessionAuthenticationStrategy(CompositeSessionAuthenticationStrategy,[ref('concurrentSessionControlAuthenticationStrategy'),ref('sessionFixationProtectionStrategy'),ref('registerSessionAuthenticationStrategy')])

}

在config.groovy中确保customSessionLogoutHandler在securityContextLogoutHandler之前是第一个:

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

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

  ConcurrentSessionControlAuthenticationStrategy.exceededAllowed =超过此主体的最大会话数。 {0} 

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

  / * 
* Copyright 2002-2013原作者或作者。
*
*根据Apache许可证2.0版许可(许可证);
*除遵守许可证外,您不得使用此文件。
*您可以在
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*获得许可证副本根据适用法律要求或书面同意,根据许可分配的软件
*以原样基础,
*分发,不附有任何明示或暗示的担保或条件。
*请参阅许可证以了解许可证下特定语言的管理权限和
*限制。
* /
package com.basic;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

导入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}负责在注销时删除{@link SessionRegistry}。接下来的请求将由框架生成
* new {@link SessionRegistry}。
*
* @author Mohd Qusyairi
* @since 0.1
* /
public final class CustomSessionLogoutHandler实现LogoutHandler {
private final SessionRegistry sessionRegistry;
$ b $ / **
*创建一个新实例
* @param sessionRegistry {@link SessionRegistry}使用
* /
public CustomSessionLogoutHandler(SessionRegistry sessionRegistry){
Assert.notNull(sessionRegistry,sessionRegistry不能为null);
this.sessionRegistry = sessionRegistry;
}

/ **
*清除{@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请求,HttpServletResponse响应,
身份验证身份验证){
this.sessionRegistry.removeSessionInformation(request.getSession()。getId());


我的示例登录控制器(我从源复制)如果你也需要它。只需将其保存为项目中的常规控制器即可,因为它将覆盖默认值。当我处理 SessionAuthenticationException 时,请参阅下面的第115行:

  / * Copyright 2013-2016 Original author或作者。 
*
*根据Apache许可证2.0版许可(许可证);
*除遵守许可证外,您不得使用此文件。
*您可以在
*
* http://www.apache.org/licenses/LICENSE-2.0
*
*获得许可证副本根据适用法律要求或书面同意,根据许可分配的软件
*以原样基础,
*分发,不附有任何明示或暗示的担保或条件。
*请参阅许可证以了解许可证下特定语言的管理权限和
*限制。
* /
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
导入org.springframework.security.web.authentication.session.SessionAuthenticationException
导入javax.servlet.http.HttpServletResponse
导入grails.plugin.springsecurity.SpringSecurityUtils

@Secured('permitAll')
类LoginContro ller {

/ ** authenticationTrustResolver的依赖注入。 * /
AuthenticationTrustResolver authenticationTrustResolver

/ ** springSecurityService的依赖注入。 * /
def springSecurityService

/ **默认操作;如果已登录,则重定向到defaultTargetUrl,否则重定向到/ login / auth。 * /
def index(){
if(springSecurityService.isLoggedIn()){
redirect uri:conf.successHandler.defaultTargetUrl
}
else {
重定向动作:'auth',params:params
}
}

/ **显示登录页面。 * /
def auth(){

def conf = getConf()

if(springSecurityService.isLoggedIn()){
redirect uri:conf .successHandler.defaultTargetUrl
返回
}

字符串postUrl = request.contextPath + conf.apf.filterProcessesUrl
渲染视图:'auth',model:[postUrl: postUrl,
rememberMeParameter:conf.rememberMe.parameter,
usernameParameter:conf.apf.usernameParameter,
passwordParameter:conf.apf.passwordParameter,
gspLayout:conf.gsp.layoutAuth]
}

/ ** Ajax请求的重定向操作。 * /
def authAjax(){
response.setHeader'Location',conf.auth.ajaxLoginFormUrl
render(status:HttpServletResponse.SC_UNAUTHORIZED,text:'Unauthorized')
}

/ **显示拒绝页面。 * /
def denied(){
if(springSecurityService.isLoggedIn()&&; amp;& authenticationTrustResolver.isRememberMe(authentication)){
//有cookie,但页面被IS_AUTHENTICATED_FULLY或等效表达式)
重定向动作:'full',params:params
return
}

[gspLayout:conf.gsp.layoutDenied]
}

/ **具有remember-me Cookie但访问IS_AUTHENTICATED_FULLY页面的用户的登录页面。 * /
def full(){
def conf = getConf()
渲染视图:'auth',params:params,
模型:[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]
}

/ **登录失败后回调。使用警告消息重定向到授权页面。 * /
def authfail(){

String msg =''
def exception = session [WebAttributes.AUTHENTICATION_EXCEPTION]
if(exception){
if(异常instanceof AccountExpiredException){
msg = message(code:'springSecurity.errors.login.expired')
}
else if(异常instanceof CredentialsExpiredException){
msg =消息(代码:'springSecurity.errors.login.passwordExpired')
}
else if(exception instanceof DisabledException){
msg = message(code:'springSecurity.errors.login.disabled' )

else if(异常instanceof LockedException){
msg = message(code:'springSecurity.errors.login.locked')
}
else if(异常instanceof SessionAuthenticationException){
msg = exception.getMess年龄()
}
其他{
msg =消息(代码:'springSecurity.errors.login.fail')
}
}

if(springSecurityService.isAjax(request)){
render([error:msg] as JSON)
}
else {
flash.message = msg
重定向操作:'auth',params:params
}
}

/ ** Ajax成功重定向url。 * /
ajaxSuccess(){
render([success:true,username:authentication.name] as JSON)
}

/ ** Ajax被拒绝重定向网址。 * /
def ajaxDenied(){
render([error:'access denied'] as JSON)
}

protected getAuthentication(){
SecurityContextHolder.context?.authentication
}

保护ConfigObject getConf(){
SpringSecurityUtils.securityConfig
}
}


I have grails 2.5.1 app with spring security plugin(2.0-RC5). I would like to block the number of current session per user. I have read some blog and it doesn't work.(http://www.block-consult.com/blog/2012/01/20/restricting-concurrent-user-sessions-in-grails-2-using-spring-security-core-plugin/) my resources.groovy

beans = {
  sessionRegistry(SessionRegistryImpl)

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

    }
}

In my boostrap.groovy

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

and my config.groovy I have added this:

grails.plugin.springsecurity.useHttpSessionEventPublisher = true

Thanks..

解决方案

To start with, let me warn you if you decided to continue with my solution.

  • SessionRegistryImpl is not scalable. You need to create your own scalable implementation based on your scaling plan(e.g. data grid). Session Replication just not enough.
  • Currently, default logout handlers did not remove SessionRegistry properly. So i have created a sample Logout handler called CustomSessionLogoutHandler.
  • You have to override grails spring-security-core Login Controller to handle SessionAuthenticationException.
  • You can change number of users that can login maximumSessions = 1 to -1 for unlimited sessions.

first, in 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')])

}

in config.groovy make sure customSessionLogoutHandler is first before securityContextLogoutHandler:

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

ConcurrentSessionControlAuthenticationStrategy uses this i18n. So you can have it in your language:

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

This is my sample CustomSessionLogoutHandler you can save it in 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());
    }
}

My Sample Login Controller (I copied from the source) if you need it too. Just save as normal controller in your project as it will override the default. See line 115 below as i handle the SessionAuthenticationException:

/* 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
    }
}

这篇关于Grails Spring Security最大并发会话的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆