Grails Spring Security 最大并发会话
Posted
技术标签:
【中文标题】Grails Spring Security 最大并发会话【英文标题】:Grails Spring Security max concurrent session 【发布时间】: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
谢谢..
【问题讨论】:
【参考方案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 个具有相同用户名的用户可以登录。以上是关于Grails Spring Security 最大并发会话的主要内容,如果未能解决你的问题,请参考以下文章
Grails - grails-spring-security-rest - 无法从 application.yml 加载 jwt 机密
grails-spring-security-rest 插件和悲观锁定
Grails + spring-security-core:用户登录后如何分配角色?