【问题标题】:Grails Spring Security AJAX responds with HTML of requested page, not JSON from ajaxSuccess()Grails Spring Security AJAX 响应请求页面的 HTML,而不是来自 ajaxSuccess() 的 JSON
【发布时间】:2013-11-30 07:49:23
【问题描述】:

所以我一直在玩 Grails 的 Spring Security,我玩得很开心,但我被困在关于通过 AJAX 登录的一件小事上。我有一个登录表单,通过 AJAX 发布到/j_spring_security_check 以便登录,它运行良好,但是当我直接进入登录页面 1)时,我对不同的响应感到困惑,2)当我被重定向时当我尝试访问安全页面时到登录页面。登录页面是/login/auth,我将auth.gsp 中的标准表单替换为AJAXified 表单。以下是两种情况:

  1. 我直接浏览到 /login/auth,当我这样做并使用 AJAX 登录时,我得到了标准 JSON 对象,也就是说,我得到了类似 {"success":true,"username":"charles"}{"error":"Sorry, we were not able to find a user with that username and password."} 的东西 - 没有意外 - 一切都很好。
  2. 由于/login/auth 是“该”登录页面,当我浏览到我的应用程序中的另一个控制器/操作(如/users/list)并且我未通过身份验证时,我会按预期重定向到/login/auth。因此,当我输入伪造的凭据时,我会按预期返回{"error":"Sorry, we were not able to find a user with that username and password."},但是当我使用正确的凭据登录时,我会在响应正文中返回最初请求页面的实际 HTML。理想情况下,我想取回{"success":true,"username":"charles", referer:"/users/list"} 之类的东西——这样我的登录表单就可以做很酷的动画内容,然后重定向到最初请求的 URL。我看到发生了什么事,在LoginController.groovy 中的第 1 种情况 ajaxSuccess() 被调用(返回 JSON) - 但在这种情况下它没有被调用,我无法找出调用了什么所以我可以破解它来做我想做的事。

有没有更好的方法来做到这一点?或者有人可以指出我正确的方向吗?我将非常感谢任何帮助!

非常感谢!

:)

这是我的 AJAX 调用,如果它有帮助的话,虽然它非常标准:

<script>
$(document).ready(function(){
    $("#submit").click(function(){
        $("#response").html("Attempting login...");
        var formdata = $('#loginForm').serialize();
        $.ajax({
            url: "/UserDemo/j_spring_security_check",
            type: 'post',
            data: formdata,
            success: function(r){
                if (r.success) {
                    $("#response").html("Success!");
                } else if (r.error) {
                    $("#response").html(r.error);
                }
            }
        });
    });
});

【问题讨论】:

  • 有趣的问题。我从来没有用spring security做过ajax登录。您使用的是什么版本(Grails 和插件)?尝试在您的配置中设置 successHandler.ajaxSuccessUrl,如下所述:grails-plugins.github.io/grails-spring-security-core/docs/…
  • 可能覆盖成功处理程序以返回 json 响应会有所帮助
  • @チャルス リヅ 你找到解决方案了吗。我有同样的问题。

标签: ajax grails spring-security


【解决方案1】:

对 user3391859 的贡献,targetUrl 不起作用。但是有了这个修复它对我有用:

class AjaxSuccessAwareRedirectStrategy extends DefaultRedirectStrategy {

private RequestCache requestCache = new HttpSessionRequestCache();
private String _ajaxSuccessUrl;

public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
    SavedRequest    savedRequest = this.requestCache.getRequest(request, response)

    String          targetUrl = savedRequest ? savedRequest.getRedirectUrl() : ""
    if (SpringSecurityUtils.isAjax(request)) {
        //if  (url != _ajaxSuccessUrl) {
        url = _ajaxSuccessUrl + "?targetUrl=" + URLEncoder.encode(targetUrl, 'UTF-8')
        //}
        super.sendRedirect(request, response, url)
    } else {
        super.sendRedirect(request, response, url)
    }
}

public void setAjaxSuccessUrl(final String ajaxSuccessUrl) {
    _ajaxSuccessUrl = ajaxSuccessUrl;
}

这也将 AjaxSuccessAwareRedirectStragegy 的命名修复为 AjaxSuccessAwareRedirectStrategy

【讨论】:

    【解决方案2】:

    我遇到了类似的问题。我的登录页面用于重定向到用于呈现一些 json 的页面(而不是 URLMappings 中定义的根页面)。在 ajax 请求中调用了我的控制器操作。

    我只是应用了一个 setTimeout 函数,以便在页面加载时不会立即触发 ajax 调用。 只要您的 ajax 调用仅呈现响应(并且没有重定向),此解决方案就可以工作。

    这里是sn-p的代码:

    function isRunning() {
    
                $.ajax({
                    type: "GET",
                    url: "${g.createLink(controller:'globalSchedule',action:'isJobRunning')}",
                    dataType:"json",
                    success: function(data,textStatus){
                        if (data.status) {
                            $("#refreshOrdersLoadingImage").show();
                        } else {
                            $("#refreshOrdersLoadingImage").hide();
                        }
                    }
                });
            }
    //Now Applying the setTimeout so that on page load, the ajax immediately does not fire
            $(function(){setTimeout(function () {
                setInterval(isRunning, 15000);
            },10000)});
    

    【讨论】:

      【解决方案3】:

      基本上问题在于,即使对于 ajax 请求,当来自另一个 url 时,Spring 的 SavedRequestAwareAuthenticationSuccessHandler 也会重定向到整个页面。 您可以在浏览器调试工具中看到这一点,但由于 ajax 请求的工作方式(透明重定向),您无法规避/阻止重定向。

      可能有更好的方法,但以下方法有效。 ajax 请求的重定向更改如下:

      把这个类放到source/groovy中

      class AjaxSuccessAwareRedirectStragegy extends DefaultRedirectStrategy {
      
          private String _ajaxSuccessUrl;
      
          public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url) throws IOException {
              if (SpringSecurityUtils.isAjax(request)) {
                  if  (url != _ajaxSuccessUrl) {
                      url = _ajaxSuccessUrl + "?targetUrl=" + URLEncoder.encode(url, 'UTF-8')
                  }
                  super.sendRedirect(request, response, url)
              } else {
                  super.sendRedirect(request, response, url)
              }
          }
      
          public void setAjaxSuccessUrl(final String ajaxSuccessUrl) {
              _ajaxSuccessUrl = ajaxSuccessUrl;
          }
      }
      

      然后将在 *AuthenticationSuccessHandler 上使用这种特殊的重定向策略。为了注册它,将此配置放入 resources.groovy(AjaxSuccessAwareRedirectStragegy 正是与此用例相关的配置)

      beans = {
      
          def conf = SpringSecurityUtils.securityConfig
      
          authenticationSuccessHandler(AjaxAwareAuthenticationSuccessHandler) {
              //borrowed from DefaultSecurityConfig.groovy
              requestCache = ref('requestCache')
              defaultTargetUrl = conf.successHandler.defaultTargetUrl // '/'
              alwaysUseDefaultTargetUrl = conf.successHandler.alwaysUseDefault // false
              targetUrlParameter = conf.successHandler.targetUrlParameter // 'spring-security-redirect'
              ajaxSuccessUrl = conf.successHandler.ajaxSuccessUrl // '/login/ajaxSuccess'
              useReferer = conf.successHandler.useReferer // false
              redirectStrategy = ref('ajaxSuccessAwareRedirectStrategy')
          }
      
          ajaxSuccessAwareRedirectStrategy(AjaxSuccessAwareRedirectStragegy) {
              contextRelative = conf.redirectStrategy.contextRelative
              ajaxSuccessUrl = conf.successHandler.ajaxSuccessUrl
          }
      
      }
      

      瞧,在您的 LoginController 中,您可以更改 ajaxSuccess 操作以使用 targetUrl,然后可以在您的登录 Javascript 中使用它

      def ajaxSuccess = {
          def redirect = params.targetUrl
          println "LoginControllerAjaxAuthParams " + params
          render([success: true, username: springSecurityService.authentication.name] << (redirect ? [redirect: redirect] : [:]) as JSON)
      }
      

      【讨论】:

        【解决方案4】:

        我相信我有一个可以帮助你的答案。我最近一直在玩弄这个,这是我想出的解决方案。我敢肯定它并不完美,但对我来说效果很好。

        首先,我必须弄清楚为什么 Grails Spring Security 插件没有像预期的那样重定向到我的 LoginController 中的 ajaxSuccess 操作(请参阅Spring Security Plugin Docs)。我发现插件正在寻找标头或查询参数(请参阅Spring Security Plugin source code on github。将我的凭据发布到此 URI,我能够获得重定向以转到控制器中的正确操作:/j_spring_security_check? ajax=true

        其次,我在我的 LoginController.groovy 中更改了 ajaxSuccess 操作的实现。你可以让这个返回任何你需要支持你自己的实现的东西。

        /**
         * The Ajax success redirect url.
         */
        def ajaxSuccess(){
            def currentUser = springSecurityService.principal
            [success: true, user: [id:currentUser.id, fullName:currentUser.fullName, confirmed:currentUser.confirmed, enabled:currentUser.enabled]]
        }
        

        第三,我想使用“Accept”HTTP 标头来确定何时返回 json,以及何时让控制器呈现 GSP 视图。我创建了一个 Grails 过滤器,它查找一些参数或标题以确定何时“jsonify”我的结果。总体思路是,我将从控制器获取模型返回并将其呈现为 json(或 jsonp)。我相信我从一个名为 jsonify 的插件或类似的插件中获取了这个概念。这是我在过滤器中使用的代码:

        jsonify(controller: '*', action: '*') {
                after = { Map model ->
                    String acceptHeader = request.getHeader('accept')
        
                    boolean isAcceptHeaderApplicationJson = "application/json".equals(acceptHeader)
                    boolean isJsonRequest = 'json'.equalsIgnoreCase(params.format?.trim()) || isAcceptHeaderApplicationJson
                    boolean isJsonpRequest = StringUtils.isNotBlank(params.callback)
        
                    if(isJsonRequest != true && isJsonpRequest != true){
                        return true
                    }
        
        
                    // check if we can unwrap the model (such in the case of a show)
                    def modelToRenderAsJson = model?.size() == 1 ? model.find { true }.value : model
                    if(isJsonpRequest){
                        response.setContentType('application/javascript;charset=utf-8')
                        render "${params.callback}(${modelToRenderAsJson as JSON})"
                    }else{
                        response.setContentType('application/json;charset=utf-8')
                        render modelToRenderAsJson as JSON
                    }
        
                    return false
                }
            }
        

        最后,我可以使用我正在运行的 Grails 应用程序和名为 Postman 的 Google Chrome 插件进行测试。我附上了我的 Postman 配置的屏幕截图,希望对您有所帮助。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-09-09
          • 1970-01-01
          • 2013-05-07
          • 1970-01-01
          • 2014-03-21
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多