具有并发会话的 grails 3 [英] grails 3 with concurrent session

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

问题描述

我正在尝试从 2.1.1 升级我的项目.到 3.1.1

iam trying to upgrade my project from 2.1.1. to 3.1.1

我有一些并发会话的问题例如..

i have some problem with concurrent session for example..

我在浏览器chrome"上使用用户名AAA"登录然后其他用户在其他浏览器上使用用户名AAA"再次登录,然后用户名AAA"将自动在浏览器chrome"上注销

i am login with username "AAA" on browser "chrome" then other user is login again with username "AAA" on other browser then username "AAA" will automatically logout on browser "chrome"

...这是我的代码

登录控制器

package accounter

import com.vastpalaso.security.User
import grails.converters.JSON
import grails.plugin.springsecurity.SpringSecurityUtils
import org.slf4j.Logger
import org.slf4j.LoggerFactory
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 grails.plugin.springsecurity.SpringSecurityUtils
import org.springframework.security.web.authentication.session.SessionAuthenticationException

import javax.servlet.http.HttpServletResponse

@Secured('permitAll')
class LoginController {

    /** Dependency injection for the authenticationTrustResolver. */
    AuthenticationTrustResolver authenticationTrustResolver

    /** Dependency injection for the springSecurityService. */
    def springSecurityService
    def cifService

    private static final Logger logger = LoggerFactory.getLogger(this)

    /** 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() {
        def username = session['SPRING_SECURITY_LAST_USERNAME']
        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()
                println "test"
            }
            else {
                msg = message(code: 'springSecurity.errors.login.fail')
            }
        }

        try {
            boolean block = false;

            block = cifService.addTryLogin(username)
            if(session){
                render([error: msg, block: block, reload: false] as JSON)
            }
            else{
                render([error: msg, block: block, reload: true] as JSON)
            }
        }
        //catch unknown RuntimeException, redirect to Error 500 server Error page
        catch (RuntimeException e) {
            logger.error(e.getMessage(), e)
            redirect(controller: "error", action: "serverError")
            return
        }

        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() {
        def user = com.vastpalaso.security.User.findByUsername(springSecurityService.authentication.name)
        def userDetails = com.vastpalaso.security.UserDetails.findByUser(user)
        def cifUsergetCif

        try{
            cifUsergetCif = com.vastpalaso.app.cif.CifUser.findByUserDetails(userDetails)
            session.setAttribute("company",cifUsergetCif.cif.corpName)
        }
        catch (Exception e){
            println "e = "+e
            println "You are loginning as admin!"
        }

        try {

            println "params = "+params
            def ipAddress = request.getHeader("Client-IP")
            if (!ipAddress) {
                ipAddress = request.getHeader("X-Forwarded-For")
            }
            if (!ipAddress) {
                ipAddress = request.getRemoteAddr()
            }

            try{

                cifService.resetTryLoginAddInfo(userDetails, ipAddress, session.id)
            }catch (Exception e){
                println "e = "+e
            }

            session.setAttribute("alias", userDetails.userAlias)
            session.setAttribute("fullName", userDetails.firstName + " " + userDetails.lastName)
            session.setAttribute("change", userDetails.forceChangePassword)
            session.setAttribute("userType", userDetails.userType)


            if(userDetails.language != null){
                session[org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME] = new Locale(userDetails.language)
            }
            else{
                session[org.springframework.web.servlet.i18n.SessionLocaleResolver.LOCALE_SESSION_ATTRIBUTE_NAME] = new Locale("id")
            }
            buildMenuList()
            println "test = "
            if (params.callback) {
                render"${params.callback} (${[success: true,id: userDetails.id ,change: userDetails.forceChangePassword, username: springSecurityService.authentication.name, fullName: (userDetails.firstName + " " + userDetails.lastName)] as JSON})"
            }
            else {
                render([success: true,id: userDetails.id, change: userDetails.forceChangePassword, username: springSecurityService.authentication.name, fullName: (userDetails.firstName + " " + userDetails.lastName)] as JSON)
            }
        }
        //catch unknown RuntimeException, redirect to Error 500 server Error page
        catch (RuntimeException e) {
            logger.error(e.getMessage(), e)
            redirect(controller: "error", action: "serverError")
            return
        }

        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
    }

    def concurrentSession = {

        def msg = "Your account is logged in from another browser or location."

        if (springSecurityService.isAjax(request)) {
            render([error: msg] as JSON)
        }
        else {
            flash.message = msg
            redirect action: 'auth', params: params
        }

    }
}

这是 resouces.groovy

this is resouces.groovy

// Place your Spring DSL code here
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.vastpalaso.helper.CustomSessionLogoutHandler
import org.springframework.security.web.session.ConcurrentSessionFilter
beans = {

    sessionRegistry(SessionRegistryImpl)

    customSessionLogoutHandler(CustomSessionLogoutHandler,ref('sessionRegistry'))

    concurrencyFilter(ConcurrentSessionFilter) {
        sessionRegistry = sessionRegistry
        logoutHandlers = [ref("rememberMeServices"), ref("securityContextLogoutHandler")]
        expiredUrl='/login/concurrentSession'
    }

    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')])


    jmsConnectionFactory(org.apache.activemq.ActiveMQConnectionFactory) { brokerURL = "tcp://localhost:61616" }
}

这是我从这里复制的代码 页面

and this is my code where i copy from this page

package com.vastpalaso.helper;

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());
    }
}

推荐答案

这将使当前登录的用户(相同的用户名和密码)过期.新用户可以继续登录,没有任何问题.

this will expire currently logged in user (same username and password). The new user can continue to login without any issue.

通过将其添加到 src 文件夹来创建 SessionAuthenticationStrategy 的新实现.在 grails 3 中,grails (2.x src/groovy) 中的 (src/main/groovy).我根据您要实现的目标将其称为自定义名称,另存为 ConcurrentSingleSessionAuthenticationStrategy.groovy:

Create new implementation of SessionAuthenticationStrategy by adding this to src folder. In grails 3, (src/main/groovy) in grails (2.x src/groovy). I call this a custom name based on what you want to achieve save this as ConcurrentSingleSessionAuthenticationStrategy.groovy:

    package com.myapp.test;

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

    import org.springframework.security.core.Authentication;
    import org.springframework.security.web.session.HttpSessionEventPublisher;
    import org.springframework.util.Assert;
    import org.springframework.security.core.session.SessionRegistry;
    import grails.plugin.springsecurity.SpringSecurityUtils;
    import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;

    /**
     * Strategy used to register a user with the {@link SessionRegistry} after successful
     * {@link Authentication}.
     *
     * <p>
     * {@link RegisterSessionAuthenticationStrategy} is typically used in combination with
     * {@link CompositeSessionAuthenticationStrategy} and
     * {@link ConcurrentSessionControlAuthenticationStrategy}, but can be used on its own if
     * tracking of sessions is desired but no need to control concurrency.
     *
     * <p>
     * NOTE: When using a {@link SessionRegistry} it is important that all sessions (including
     * timed out sessions) are removed. This is typically done by adding
     * {@link HttpSessionEventPublisher}.
     *
     * @see CompositeSessionAuthenticationStrategy
     *
     * @author Luke Taylor
     * @author Rob Winch
     * @since 3.2
     */
    public class ConcurrentSingleSessionAuthenticationStrategy implements
            SessionAuthenticationStrategy {
        private SessionRegistry sessionRegistry;

        /**
         * @param sessionRegistry the session registry which should be updated when the
         * authenticated session is changed.
         */
        public ConcurrentSingleSessionAuthenticationStrategy(SessionRegistry sessionRegistry) {
            Assert.notNull(sessionRegistry, "SessionRegistry cannot be null");
            this.sessionRegistry = sessionRegistry;
        }
        /**
         * In addition to the steps from the superclass, the sessionRegistry will be removing
         * with the new session information.
         */
        public void onAuthentication(Authentication authentication,
                HttpServletRequest request, HttpServletResponse response) {

            def sessions = sessionRegistry.getAllSessions(
                    authentication.getPrincipal(), false);

            def principals = sessionRegistry.getAllPrincipals()
            sessions.each{
                if(it.principal == authentication.getPrincipal()){
                    it.expireNow()
                }
            }


        }
    }

resources.groovy中:

    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 org.springframework.security.core.session.SessionRegistryImpl;
    import com.myapp.test.ConcurrentSingleSessionAuthenticationStrategy;
    import org.springframework.security.web.session.ConcurrentSessionFilter
    // Place your Spring DSL code here
    beans = {
            sessionRegistry(SessionRegistryImpl)
            //I see you did not have this. Very dangerous!
            sessionFixationProtectionStrategy(SessionFixationProtectionStrategy){
                migrateSessionAttributes = true
                alwaysCreateSession = true
            }
            //Initiate the bean
            concurrentSingleSessionAuthenticationStrategy(ConcurrentSingleSessionAuthenticationStrategy,ref('sessionRegistry'))
            registerSessionAuthenticationStrategy(RegisterSessionAuthenticationStrategy,ref('sessionRegistry'))
            sessionAuthenticationStrategy(CompositeSessionAuthenticationStrategy,[ref('concurrentSingleSessionAuthenticationStrategy'),ref('sessionFixationProtectionStrategy'),ref('registerSessionAuthenticationStrategy')])
            concurrentSessionFilter(ConcurrentSessionFilter,ref('sessionRegistry'))
    }

在配置中,最后添加这一行:

In config, finally add this line:

grails.plugin.springsecurity.filterChain.filterNames = [ 'securityContextPersistenceFilter', 'logoutFilter', 'concurrentSessionFilter', 'rememberMeAuthenticationFilter', 'anonymousAuthenticationFilter', 'exceptionTranslationFilter', 'filterInvocationInterceptor' ]

希望这会有所帮助.

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

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