복붙노트

[SPRING] 동시 세션이있는 grails 3

SPRING

동시 세션이있는 grails 3

iam이 내 프로젝트를 2.1.1에서 업그레이드하려고합니다. ~ 3.1.1

동시 세션에 몇 가지 문제가 있습니다. 예를 들면 ..

나는 브라우저 "chrome"에 사용자 이름 "AAA"로 로그인하고있다. 다른 사용자는 다른 브라우저에서 사용자 이름 "AAA"로 다시 로그인 한 다음 사용자 이름 "AAA"가 브라우저 "크롬"에서 자동으로 로그 아웃합니다

.. 내 코드

로그인 컨트롤러

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
        }

    }
}

이것은 resources.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" }
}

이 코드는 내 페이지에서 복사합니다.

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

해결법

  1. ==============================

    1.이것은 현재 로그인 한 사용자 (동일한 사용자 이름과 암호)에 만료됩니다. 새 사용자는 아무런 문제없이 계속 로그인 할 수 있습니다.

    이것은 현재 로그인 한 사용자 (동일한 사용자 이름과 암호)에 만료됩니다. 새 사용자는 아무런 문제없이 계속 로그인 할 수 있습니다.

    이것을 src 폴더에 추가하여 SessionAuthenticationStrategy의 새로운 구현을 생성하십시오. grails 3 (src / main / groovy) in grails (2.x src / groovy). 이 이름을 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'))
        }
    

    config에서 마지막으로 다음 행을 추가하십시오.

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

    희망이 도움이됩니다.

  2. from https://stackoverflow.com/questions/39741426/grails-3-with-concurrent-session by cc-by-sa and MIT license