为什么有些网址 "forbidden" 而有些不在我的网络应用程序中

Why are some URLs "forbidden" and some not in my web app

我有一个 Web 应用程序出现异常行为。当您尝试启动该应用程序时,它会要求您按预期登录,然后将您带到欢迎页面 (/),然后您可以 select 配置文件 (/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) { /* ... */ }
});

---编辑--- jQuery 用于 csrf

$(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 手机顺便说一句。

问题是因为 jQuery Mobile 通常不会在每个页面请求上加载 header 信息,这是存储 CSRF 令牌的地方。因此,当导航到新页面时,它在执行 POST 时使用陈旧的 CSRF 令牌,这会导致 403 Forbidden。为了克服这个问题,我强制 JQM 在没有 ajax 的情况下 link,方法是在页面的每个 link 中包含 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>