引入
最近学习了Servlet、Mybatis、Vue,想手搓一个用户登录界面+数据展示后台,但是在记住用户登录 设置cookie的时候遇到的问题。问题是:使用 HttpServletResponse
的 addCookie()
方法后,开发者工具提示 某些 Cookie 滥用推荐的"sameSite"属性 由于 Cookie 的"sameSite"属性设置为"none",但缺少"secure"属性,此 Cookie 未来将被拒绝。
解法1 使用Filter过滤器 推荐
创建一个Filter类,重写doFilter方法
package ski.mashiro.web.filter;
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
// "/*"代表过滤根路径下的所有访问,即给每个cookie添加SameSite属性
@WebFilter("/*")
public class SameSiteFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
chain.doFilter(request, response);
// add SameSite=strict cookie attribute
addSameSiteAttribute((HttpServletResponse) response);
}
private void addSameSiteAttribute(HttpServletResponse response) {
Collection<String> headers = response.getHeaders("Set-Cookie");
boolean firstHeader = true;
for (String header : headers) {
if (firstHeader) {
response.setHeader("Set-Cookie", String.format("%s; %s", header, "SameSite=Strict"));
firstHeader = false;
continue;
}
response.addHeader("Set-Cookie", String.format("%s; %s", header, "SameSite=Strict"));
}
}
@Override
public void destroy() {
}
}
解法2 继承Cookie类,添加SameSite属性 不推荐
package xxx.xxx.common.controller;
import javax.servlet.http.Cookie;
/**
* @author himans 2020-6-5
*/
public class NewCookie extends Cookie {
private String sameSite;
public final static String LAX = "Lax";
public final static String NONE = "None";
public final static String STRICT = "Strict";
public final static String SET_COOKIE = "Set-Cookie";
public NewCookie(String name, String value) {
super(name, value);
}
public String getSameSite() {
return sameSite;
}
public NewCookie setSameSite(String sameSite) {
this.sameSite = sameSite;
if (NONE.equals(sameSite)) {
setSecure(true);
}
return this;
}
@Override
public String toString() {
// 参照Controller中的doSetCookie代码
StringBuilder sb = new StringBuilder(getName()).append("=").append(getValue());
sb.append(";Path=").append(getPath()==null?"/":getPath());
if (getDomain() != null) {
sb.append(";Domain=").append(getDomain());
}
if (isHttpOnly()) {
sb.append(";HttpOnly");
}
sb.append(";Max-Age=").append(getMaxAge());
// 默认Lax
sb.append(";SameSite=").append(getSameSite()==null?"Lax":getSameSite());
if (getSecure()) {
sb.append(";Secure");
}
if (getComment() != null) {
sb.append(";Comment=").append(getComment());
}
return sb.toString();
}
}
修改LoginController中的doLogin代码:
/**
* 登录
*/
@Before(LoginValidator.class)
public void doLogin() {
boolean keepLogin = getParaToBoolean("keepLogin", false);
String loginIp = IpKit.getRealIp(getRequest());
Ret ret = srv.login(getPara("userName"), getPara("password"), keepLogin, loginIp);
if (ret.isOk()) {
String sessionId = ret.getStr(LoginService.COOKIE_SESSION_NAME);
int maxAgeInSeconds = ret.getInt("maxAgeInSeconds");
// setCookie(LoginService.COOKIE_SESSION_NAME, sessionId, maxAgeInSeconds, true);
// 核心代码
NewCookie cookie = new NewCookie(LoginService.COOKIE_SESSION_NAME, sessionId);
cookie.setMaxAge(maxAgeInSeconds);
cookie.setHttpOnly(true);
getResponse().addHeader(NewCookie.SET_COOKIE, cookie.toString());
setAttr(LoginService.ACCT_ATTR, ret.get(LoginService.ACCT_ATTR));
// 如果 returnUrl 存在则跳过去,否则跳去首页
ret.set("returnUrl", getPara("returnUrl", "/"));
}
renderJson(ret);
}
// 注意18行,前提是不能有重复的cookie,若有Controller.setCookie相同的key(如第12行),该代码(12行)要去掉。
设置SameSite其它属性:
cookie.setSameSite(NewCookie.STRICT);
或
cookie.setSameSite(NewCookie.NONE).setSecure(true);
参考1:[How to set the SameSite attribute in Java Web applications](http://www.mastertheboss.com/web/jboss-web-server/how-to-set-the-samesite-attribute-in-java-web-applications/) 注:参考1提供了更多解法,包括前端、WildFly及SpringBoot,本文只介绍Servlet相关,请自行阅读原文 参考2:[应对浏览器Cookie新属性SameSite的临门一脚](https://jfinal.com/share/2136)