Grails Spring安全性:使用目标URL登录会跳过认证后工作流

Grails Spring安全性:使用目标URL登录会跳过认证后工作流,grails,spring-security,Grails,Spring Security,在我的grails应用程序中,我通过编写自定义的身份验证成功处理程序(在resources.groovy中)定制了授权后工作流,如下所示 authenticationSuccessHandler (MyAuthSuccessHandler) { def conf = SpringSecurityUtils.securityConfig requestCache = ref('requestCache') defaultTargetUrl = conf.successHan

在我的grails应用程序中,我通过编写自定义的身份验证成功处理程序(在resources.groovy中)定制了授权后工作流,如下所示

authenticationSuccessHandler (MyAuthSuccessHandler) {
    def conf = SpringSecurityUtils.securityConfig
    requestCache = ref('requestCache')
    defaultTargetUrl = conf.successHandler.defaultTargetUrl
    alwaysUseDefaultTargetUrl = conf.successHandler.alwaysUseDefault
    targetUrlParameter = conf.successHandler.targetUrlParameter
    useReferer = conf.successHandler.useReferer
    redirectStrategy = ref('redirectStrategy')
    superAdminUrl = "/admin/processSuperAdminLogin"
    adminUrl = "/admin/processAdminLogin"
    userUrl = "/admin/processUserLogin"
}
从上面的结束语的最后三行可以看出,根据授予登录用户的角色,我正在将她重定向到AdminController中的单独操作,其中在会话中创建并存储了自定义UserSessionBean

它适用于常规登录情况,在我的应用程序中如下所示:

  • 用户通过
    http://localhost:8080/my-app/
    http://localhost:8080/my-app/login/auth
  • 她输入有效的登录id和密码并继续
  • 应用程序在内部访问MyAuthSuccessHandler,考虑到授予此用户的角色,它将重定向到AdminController
  • UserSessionBean被创建并存储在会话中
  • 用户被带到应用程序主页
  • 我还通过扩展在上述流程中正确访问的
    GormUserDetailsService
    编写了一个自定义
    MyUserDetailsService

    问题场景:
    考虑一个用户直接访问受保护资源(在这种情况下,控制器在应用程序中以<代码> @安全< /代码>注释)进行保护。

  • 用户点击
    http://localhost:8080/my-应用程序/收件箱/索引
  • 应用程序将她重定向到
    http://localhost:8080/my-app/login/auth
  • 用户输入其有效的登录id和密码
  • 用户被带到
    http://localhost:8080/my-应用程序/收件箱/索引
  • 在这个过程中,
    MyAuthSuccessHandler
    被完全跳过,因此我的UserSessionBean不会被创建,从而导致在访问UserSessionBean的地方进一步使用时出错

    问题:

  • 在问题场景中,应用程序是否会跳过
    MyAuthSuccessHandler
    ,因为它有一个登录时要重定向到的目标URL
  • 即使目标URL存在,我们是否可以强制进程始终通过
    MyAuthSuccessHandler
  • 如果2的答案是否定的,那么是否有其他方法可以创建UserSessionBean
  • 1) 是的,因为它是“按需”登录

    2) 是的,您可以将其设置为“始终使用默认值”。spring安全插件有一个设置“”将其更改为true,默认为false

    此外,如果您需要更多详细信息,请查看spring中的设置默认登录后目的地部分


    3) 如果您仍然希望创建用户会话bean,然后重定向到原始URL,那么您有两个选项:在前面的过滤器中创建bean,或者通过自定义的方法公开所需的数据。就我个人而言,我会选择自定义详细信息服务。

    您可以实现自定义的eventListener来处理登录后的过程,而不会中断原始用户请求的url

    在config.groovy中,插入一个配置项:

    grails.plugins.springsecurity.useSecurityEventListener = true
    
    在resources.groovy中,添加如下bean:

    import com.yourapp.auth.LoginEventListener
    beans = {
        loginEventListener(LoginEventListener)
    }
    
    package com.yourapp.auth
    import org.springframework.context.ApplicationListener;
    import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent
    import org.springframework.web.context.request.RequestContextHolder as RCH
    
    class LoginEventListener implements
        ApplicationListener<InteractiveAuthenticationSuccessEvent> {    
    
        //deal with successful login    
        void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
            User.withTransaction { 
                def user = User.findByUsername(event.authentication.principal.username)
                        def adminRole = Role.findByAuthority('ROLE_ADMIN')
                        def userRole = Role.findByAuthority('ROLE_USER')
                        def session = RCH.currentRequestAttributes().session      //get httpSession
                        session.user = user
                        if(user.authorities.contains(adminRole)){
                            processAdminLogin()
                        }
                        else if(user.authorities.contains(userRole)){
                            processUserLogin()
                        }
            }
        }
    
        private void processAdminLogin(){    //move admin/processAdminLogin here
              .....
        }
    
        private void processUserLogin(){    //move admin/processUserLogin here
              .....
        }
    }
    
    并在src/groovy中创建一个eventListener,如下所示:

    import com.yourapp.auth.LoginEventListener
    beans = {
        loginEventListener(LoginEventListener)
    }
    
    package com.yourapp.auth
    import org.springframework.context.ApplicationListener;
    import org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent
    import org.springframework.web.context.request.RequestContextHolder as RCH
    
    class LoginEventListener implements
        ApplicationListener<InteractiveAuthenticationSuccessEvent> {    
    
        //deal with successful login    
        void onApplicationEvent(InteractiveAuthenticationSuccessEvent event) {
            User.withTransaction { 
                def user = User.findByUsername(event.authentication.principal.username)
                        def adminRole = Role.findByAuthority('ROLE_ADMIN')
                        def userRole = Role.findByAuthority('ROLE_USER')
                        def session = RCH.currentRequestAttributes().session      //get httpSession
                        session.user = user
                        if(user.authorities.contains(adminRole)){
                            processAdminLogin()
                        }
                        else if(user.authorities.contains(userRole)){
                            processUserLogin()
                        }
            }
        }
    
        private void processAdminLogin(){    //move admin/processAdminLogin here
              .....
        }
    
        private void processUserLogin(){    //move admin/processUserLogin here
              .....
        }
    }
    
    package com.yourapp.auth
    导入org.springframework.context.ApplicationListener;
    导入org.springframework.security.authentication.event.InteractiveAuthenticationSuccessEvent
    将org.springframework.web.context.request.RequestContextHolder作为RCH导入
    类LoginEventListener实现
    应用程序侦听器{
    //处理成功登录
    Application event上的无效(InteractiveAuthenticationSuccessEvent事件){
    User.withTransaction{
    def user=user.findByUsername(event.authentication.principal.username)
    def adminRole=Role.findByAuthority('Role\u ADMIN')
    def userRole=Role.findByAuthority('Role\u USER')
    def session=RCH.currentRequestAttributes().session//get-httpSession
    session.user=用户
    if(user.authorities.contains(adminRole)){
    processAdminLogin()
    }
    else if(user.authorities.contains(userRole)){
    processUserLogin()
    }
    }
    }
    私有void processAdminLogin(){//move admin/processAdminLogin此处
    .....
    }
    私有void processUserLogin(){//move admin/processUserLogin此处
    .....
    }
    }
    

    完成。

    conf.successHandler.AlwaysSuseDefault
    设置为
    true
    ?我没有更改它。它处于默认状态。感谢您的快速回答。我不知道alwaysUseDefault配置值,现在已经相应地设置了它。虽然现在它将始终重定向到默认URL(在本例中为应用程序主页)。在alwaysUseDefault设置为true的情况下,是否有办法将用户重定向到请求的URL?我编辑了答案并为选项3添加了答案,这可能更符合您的要求。@JeffBeck在我的场景中,自定义UserDetailsService也以与SuccessHandler相同的方式被绕过。这似乎是一个更好的选项,除了在这个类中有没有访问HttpSession的方法?如前所述,我必须在其中存储一个UserSessionBeanabove@SagarV当然,您可以像
    def session=RequestContextHolder.currentRequestAttributes().session
    那样访问它。我更新了我的示例以显示该情况。