Grails Spring 安全最大并发会话
Grails Spring Security max concurrent session
我有带有 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
谢谢..
首先,如果您决定继续使用我的解决方案,请让我警告您。
- 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
}
}
我有带有 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
谢谢..
首先,如果您决定继续使用我的解决方案,请让我警告您。
- 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
}
}