首页 > 解决方案 > 为什么有些 URL 被“禁止”,而有些却不在我的网络应用程序中

问题描述

我有一个遇到奇怪行为的 Web 应用程序。当您尝试启动应用程序时,它会要求您按预期登录,并将您带到欢迎页面 ( /),然后您可以选择个人资料 ( /profile) 页面或搜索页面 ( /search)。如果您尝试在不登录的情况下访问这些页面中的任何一个,它会按预期将您重定向到登录页面。但是,当您尝试提交搜索条件或提交密码更改时,将返回 403 Forbidden。

<security:http use-expressions="true">
    <security:intercept-url pattern="/resources/css/*" access="permitAll"  />
    <security:intercept-url pattern="/resources/images/*" access="permitAll"  />
    <security:intercept-url pattern="/login" access="permitAll"  />
    <security:intercept-url pattern="/logout" access="permitAll"  />
    <security:intercept-url pattern="/accessdenied" access="permitAll"  />
    <security:intercept-url pattern="/**" access="hasRole('ROLE_USER')"  />
    <security:form-login 
        login-page="/login" 
        default-target-url="/" 
        authentication-success-handler-ref="loginSuccessHandler" 
        authentication-failure-url="/accessdenied"
    />
    <security:logout 
        logout-success-url="/" 
        logout-url="/perform_logout"
        delete-cookies="JSESSIONID"
    />
</security:http>

网址:

/                       (Welcome Page [GET])
/search                 (Search Page [GET])
/search/data            (Search Query [POST])
/profile                (Profile Page [GET])
/profile/updatePassword (Profile Update [POST])

配置文件控制器

@Controller
@RequestMapping({ "/profile" }) 
public class ProfileController {
    @Autowired
    UserService userService = null;
    @Autowired
    ProfileService profileService = null;

    @RequestMapping(value = { "/", "" }, method = RequestMethod.GET)
    public String getProfile(Model model) {
        Profile profile = profileService.getProfile();
        model.addAttribute("profile", profile);
        return "profile";
    }

    @RequestMapping(value = { "/updatePassword" }, method = RequestMethod.POST)
    public @ResponseBody AjaxResponse updatePassword(@RequestBody Profile profile) {
        // do stuff
        return new AjaxResponse(response, null, errors);
    }
}

搜索控制器

@Controller
@RequestMapping({ "/search" }) 
public class StockKeepingUnitController {
    @Autowired(required = true)
    private SkuService skuService;
    @Autowired(required = true)
    private UserService userService;

    @RequestMapping(value = {"", "/"}, method = RequestMethod.GET)
    public String search() {
        return "search";
    }

    @RequestMapping(value = "/data", method = RequestMethod.POST)
    public @ResponseBody AjaxResponse data(@RequestBody SearchCriteria searchCriteria) {
        List<StockKeepingUnit> skus = null;
        try {
            String criteria = searchCriteria.getCriteria();
            skus = skuService.listSkusBySearch(criteria);
        } catch (Exception ex) {
            ex.printStackTrace();
            List<String> errors = new ArrayList<>();
            errors.add("Error saving ALOT.");
            return new AjaxResponse("ERROR", null, errors);
        }
        return new AjaxResponse("OK", skus, null);
    }
}

搜索 ajax

    $.ajax({url: "${pageContext.request.contextPath}/search/data"
        , method: "POST"
        , contentType: "application/json; charset=utf-8"
        , dataType: "json"
        , data: JSON.stringify(searchCriteria)
        , success: function(ajaxResponse) { /* ... */ }
        , error: function(xhr, status, error) { /* ... */ }
    });

配置文件 ajax

$.ajax({
    url: "${pageContext.request.contextPath}/profile/updatePassword",
    , method: "POST"
    , contentType: "application/json; charset=utf-8"
    , dataType: "json"
    , data: JSON.stringify(profile)
    , success : function(ajaxResponse) { /* ... */ }
    , error : function(xhr, status, error) { /* ... */ }
});

---编辑--- csrf的jQuery

$(function() {
    var token = $("meta[name='_csrf']").attr("content");
    var header = $("meta[name='_csrf_header']").attr("content");
    $(document).ajaxSend(function(e, xhr, options) {
        xhr.setRequestHeader(header, token);
    });
});

另外我刚刚发现,如果我重新加载每个页面,POST 提交就可以工作。每个页面上的 CSRF 令牌是否有某种变化?我正在使用 jQuery Mobile 顺便说一句。

标签: jquery-mobilespring-securityspring-web

解决方案


问题是因为 jQuery Mobile 通常不会在每个页面请求上加载标头信息,这是存储 CSRF 令牌的位置。因此,当导航到新页面时,它在执行 POST 时使用了陈旧的 CSRF 令牌,这会导致 403 Forbidden。为了克服这个问题,我强迫 JQM 在没有 ajax 的情况下进行data-ajax="false"链接,方法是在每个指向页面的链接中包含。例如:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<form action="<c:url value="/perform_logout" />" method="POST" name="logoutform">
    <input type="hidden" name="${_csrf.parameterName}" value = "${_csrf.token}" />          
</form>
<ul data-role="listview" data-theme="a" data-divider-theme="a" style="margin-top: -16px;" class="nav-search">
    <li data-icon="delete" style="background-color: #111;"><a href="#" data-rel="close">Close menu</a></li>
    <li><a href="${pageContext.request.contextPath}/search" data-ajax="false">Search</a></li>
    <li><a href="${pageContext.request.contextPath}/profile" data-ajax="false">Profile</a></li>
    <li><a href="#" onclick="document.logoutform.submit();">Logout</a></li>
</ul>

推荐阅读