CookieInterceptor
该拦截器的目的是把cookie的name/value对设置到stack/action中。
- 如果一个星号出现在cookiesName参数值中,它将会表明所有cookie名称将被注入到struts的action中,即使cookiesName是被逗号分隔,例如(cookie1,*,cookie2)。
- 如果cookieName值为空,它将会表明没有cookie将会被注入到Struts的action。
- 如果一个星号出现在cookiesValue参数中,它将会表明所有cookie名称,不管它的值,将会被注入到Struts的action中,只要cookie名称匹配这些在cookiesName参数中指定的值。
- 如果cookiesValue值为空,它将表明所有匹配cookieName参数的cookie将会被注入到Struts的action中。
为了具有一个被过滤cookies的Map表,action可以实现CookiesAware接口。
属性
- cookiesName(必须):将要被注入到action中的cookies名称。如果有多个,它将会使用逗号分隔。如果需要所有cookies名称 ,可以使用*号表示。当任何匹配逗号分隔的cookies名称的cookie都是合格的。
- cookiesValue(必须):如果cookies的名字匹配cookieName且它的值也匹配,该cookie将会被注入Struts的action中。如果有多个值,可以使用逗号分隔。如果为空,则表明任何值都匹配。如果多个值被指定(逗号分隔),它如果匹配任何一个值,则表明它匹配。
- acceptCookieNames(可选):用来检查cookie的名称是否匹配该指定模式。
方法
- populateCookieValueIntoStack:该方法将会决定是否该cookie值合格且被放入值栈中。
- injectIntoCookiesAwareAction:该方法将会注入选择cookies(即Map中的)到实现CookieAware接口action中。
示例
<!--该例子将注入名称为cookie1或者cookie2且值为cookie1value或者cookie2value的cookies到Struts的action中-->
<action ... >
<interceptor-ref name="cookie">
<param name="cookiesName">cookie1, cookie2</param>
<param name="cookiesValue">cookie1value, cookie2value</param>
</interceptor-ref>
....
</action>
<!--该例子将注入名称为cookie1或者cookie2且值为任何的cookies到Struts的action中-->
<action ... >
<interceptor-ref name="cookie">
<param name="cookiesName">cookie1, cookie2</param>
<param name="cookiesValue">*</param>
<interceptor-ref>
...
</action>
<!--该例子将注入名称为cookie1且值为cookie1value或者名称为cookie2且值为cookie2value的cookies到Struts的action中-->
<action ... >
<interceptor-ref name="cookie">
<param name="cookiesName">cookie1</param>
<param name="cookiesValue">cookie1value</param>
</interceptor-ref>
<interceptor-ref name="cookie">
<param name="cookiesName"<cookie2</param>
<param name="cookiesValue">cookie2value</param>
</interceptor-ref>
....
</action>
<!--该例子将会注入任何cookies不管它的值到Struts的action中-->
<action ... >
<interceptor-ref name="cookie">
<param name="cookiesName">*</param>
<param name="cookiesValue">*</param>
</interceptor-ref>
...
</action>
源码如下:
public class CookieInterceptor extends AbstractInterceptor {
private static final long serialVersionUID = 4153142432948747305L;
private static final Logger LOG = LoggerFactory.getLogger(CookieInterceptor.class);
private static final String ACCEPTED_PATTERN = "[a-zA-Z0-9\\.\\]\\[_'\\s]+";
private Set<String> cookiesNameSet = Collections.emptySet();
private Set<String> cookiesValueSet = Collections.emptySet();
private ExcludedPatternsChecker excludedPatternsChecker;
private AcceptedPatternsChecker acceptedPatternsChecker;
@Inject
public void setExcludedPatternsChecker(ExcludedPatternsChecker excludedPatternsChecker) {
this.excludedPatternsChecker = excludedPatternsChecker;
}
@Inject
public void setAcceptedPatternsChecker(AcceptedPatternsChecker acceptedPatternsChecker) {
this.acceptedPatternsChecker = acceptedPatternsChecker;
this.acceptedPatternsChecker.setAcceptedPatterns(ACCEPTED_PATTERN);
}
//设置cookiesName,如果匹配它将会允许cookies被注入到action中,可以使用逗号分隔字符串
public void setCookiesName(String cookiesName) {
if (cookiesName != null)
this.cookiesNameSet = TextParseUtil.commaDelimitedStringToSet(cookiesName);
}
//设置cookiesValue,如果匹配将会引起该cookie被注入到action中,可以使用逗号分隔字符串
public void setCookiesValue(String cookiesValue) {
if (cookiesValue != null)
this.cookiesValueSet = TextParseUtil.commaDelimitedStringToSet(cookiesValue);
}
//设置允许的cookies名称字符串模式来防止远程命名执行漏洞
public void setAcceptCookieNames(String commaDelimitedPattern) {
acceptedPatternsChecker.setAcceptedPatterns(commaDelimitedPattern);
}
public String intercept(ActionInvocation invocation) throws Exception {
if (LOG.isDebugEnabled()) {
LOG.debug("start interception");
}
// contains selected cookies
final Map<String, String> cookiesMap = new LinkedHashMap<String, String>();
Cookie[] cookies = ServletActionContext.getRequest().getCookies();
if (cookies != null) {
final ValueStack stack = ActionContext.getContext().getValueStack();
for (Cookie cookie : cookies) {
String name = cookie.getName();
String value = cookie.getValue();
if (isAcceptableName(name) && isAcceptableValue(value)) {
if (cookiesNameSet.contains("*")) {
if (LOG.isDebugEnabled()) {
LOG.debug("contains cookie name [*] in configured cookies name set, cookie with name [" + name + "] with value [" + value + "] will be injected");
}
populateCookieValueIntoStack(name, value, cookiesMap, stack);
} else if (cookiesNameSet.contains(cookie.getName())) {
populateCookieValueIntoStack(name, value, cookiesMap, stack);
}
} else {
LOG.warn("Cookie name [#0] with value [#1] was rejected!", name, value);
}
}
}
//即使我们没有任何cookies,注入cookieMap到action中
injectIntoCookiesAwareAction(invocation.getAction(), cookiesMap);
return invocation.invoke();
}
//检查Cookie的值不包含漏洞代码
protected boolean isAcceptableValue(String value) {
//如果该值没有被排除且该值可接受,则返回true
return !isExcluded(value) && isAccepted(value);
}
//检查Cookie的名称不包含漏洞代码
protected boolean isAcceptableName(String name) {
//如果该名称没有被排除且该名称可接受,则返回true
return !isExcluded(name) && isAccepted(name);
}
//检查cookie的name/value可接受
protected boolean isAccepted(String name) {
AcceptedPatternsChecker.IsAccepted accepted = acceptedPatternsChecker.isAccepted(name);
if (accepted.isAccepted()) {
if (LOG.isTraceEnabled()) {
LOG.trace("Cookie [#0] matches acceptedPattern [#1]", name, accepted.getAcceptedPattern());
}
return true;
}
if (LOG.isTraceEnabled()) {
LOG.trace("Cookie [#0] doesn't match acceptedPattern [#1]", name, accepted.getAcceptedPattern());
}
return false;
}
//检查cookie的name/value被排除
protected boolean isExcluded(String name) {
ExcludedPatternsChecker.IsExcluded excluded = excludedPatternsChecker.isExcluded(name);
if (excluded.isExcluded()) {
if (LOG.isTraceEnabled()) {
LOG.trace("Cookie [#0] matches excludedPattern [#1]", name, excluded.getExcludedPattern());
}
return true;
}
if (LOG.isTraceEnabled()) {
LOG.trace("Cookie [#0] doesn't match excludedPattern [#1]", name, excluded.getExcludedPattern());
}
return false;
}
//填充cookie值到值栈的钩子,如果满足了规则(即如果cookie值匹配这些配置)
protected void populateCookieValueIntoStack(String cookieName, String cookieValue, Map<String, String> cookiesMap, ValueStack stack) {
if (cookiesValueSet.isEmpty() || cookiesValueSet.contains("*")) {
//如果该拦截器被配置来接受任何cookie值或者没有cookiesValue被指定,只要cookie名称匹配,
//我们将注入它到Struts的action中。
if (LOG.isDebugEnabled()) {
if (cookiesValueSet.isEmpty())
LOG.debug("no cookie value is configured, cookie with name ["+cookieName+"] with value ["+cookieValue+"] will be injected");
else if (cookiesValueSet.contains("*"))
LOG.debug("interceptor is configured to accept any value, cookie with name ["+cookieName+"] with value ["+cookieValue+"] will be injected");
}
cookiesMap.put(cookieName, cookieValue); //放入Map中
stack.setValue(cookieName, cookieValue); //放入值栈中
}
else {
//如果cookieValues被指定,该cookie的值必须匹配,我们才注入它到Struts的action中
if (cookiesValueSet.contains(cookieValue)) {
if (LOG.isDebugEnabled()) {
LOG.debug("both configured cookie name and value matched, cookie ["+cookieName+"] with value ["+cookieValue+"] will be injected");
}
cookiesMap.put(cookieName, cookieValue);
stack.setValue(cookieName, cookieValue);
}
}
}
//设置cookiesMap到实现CookiesAware接口的action中的钩子
protected void injectIntoCookiesAwareAction(Object action, Map<String, String> cookiesMap) {
if (action instanceof CookiesAware) {
if (LOG.isDebugEnabled()) {
LOG.debug("action ["+action+"] implements CookiesAware, injecting cookies map ["+cookiesMap+"]");
}
((CookiesAware)action).setCookiesMap(cookiesMap);
}
}
}
使用演示
- cookie拦截器的声明
由于默认情况下不使用cookie拦截器(不在defaultStack中),因此在struts.xml配置文件中需要对其进行声明:
<action name="Index" class="com.lifelaf.blog.action.ExampleAction">
<!--注意该拦截器的顺序很重要,因为需要使用ModelDrivenInterceptor中的Model
这样才可以使用它的属性来存储username,password等属性-->
<interceptor-ref name="defaultStack" />
<interceptor-ref name="cookie">
<param name="cookiesName">exampleKeyName1, exampleKeyName2</param>
<param name="cookiesValue">*</param>
</interceptor-ref>
<result>/exampleResult.jsp</result>
</action>
仅仅声明使用cookie拦截器是不够的,我们还需要对该拦截器的cookiesName参数和cookiesValue参数进行设定。如果不设定cookiesName参数,action类将不会收到任何Cookie:
//CookieInterceptor.java
for (Cookie cookie : cookies) {
if (cookiesNameSet.contains("*")) {
populateCookieValueIntoStack(name, value, cookiesMap, stack);
} else if (cookiesNameSet.contains(cookie.getName())) {
populateCookieValueIntoStack(name, value, cookiesMap, stack);
}
}
在之前的struts.xml配置文件实例中,cookiesName参数设定为exampleKeyName1
和exampleKeyName2
,因此ExampleAction将会收到键为exampleKeyName1
和exampleKeyName2
的Cookie。有趣的是,只要cookiesName中出现*(比如:exampleKeyName1, *, exampleKeyName2),那么action类将会收到所有的Cookie。
而对于cookiesValue,我们可以用它来设定可接受的Cookie的值。如果cookiesValue未设定,或者cookiesValue中包含*,那么所有name属性符合cookiesName参数设定的Cookie都会被action收到。
- ValueStack声明
除了在struts.xml配置文件中声明cookie拦截器及其参数,cookie拦截器的使用还需要一个条件:ValueStack中必须包含cookiesName参数中所设定的那些属性;否则当截取Cookie的时候Struts2会抛异常(”No object in the CompoundRoot has a publicly accessible property named …”)。这是因为在截取Cookie的时候CookieInterceptor会尝试往ValueStack中写入cookie信息:
private String exampleKeyName1;
public String getExampleKeyName1() {
return exampleKeyName1;
}
public void setExampleKeyName1(String exampleKeyName1) {
this.exampleKeyName1 = exampleKeyName1;
}
private String exampleKeyName2;
public String getExampleKeyName2() {
return exampleKeyName2;
}
public void setExampleKeyName2(String exampleKeyName2) {
this.exampleKeyName2 = exampleKeyName2;
}
至此,action类可以通过访问这些bean来读取Cookie的信息。
- CookiesAware接口与cookiesMap
除了设定ValueStack外,action类还可以通过实现CookiesAware接口来获取目标Cookie键值对的Map对象(cookiesMap):
public class ExampleAction extends ActionSupport implements CookiesAware {
private Map<String, String> cookiesMap;
@Override
public void setCookiesMap(Map<String, String> cookiesMap) {
this.cookiesMap = cookiesMap;
}
}
对于使用cookie拦截器而言,实现CookiesAware接口不是必需的(如果不实现CookiesAware,那么action类中不会含有Cookie键值对的Map对象),而在ValueStack中添加目标Cookie键则是必需的。
- 结语
与SessionAware等其它拦截器相比,cookie拦截器的使用显得更为复杂 — 既要声明目标Cookie,又要在ValueStack中加入相关支持。个人对此做法的理解是:这么做可以让action通过访问ValueStack来直接读取感兴趣的Cookie的值,相应的代价则是编程复杂度的上升。
Struts2中的cookie拦截器只能用于读取Cookie,如果想向浏览器端发送Cookie则需要使用cookieProvider拦截器。