为什么有些网址 "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>
我有一个 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>