如果使用新版浏览器,在登录时,控制台会打印一个新的警告(之前没有遇到过):
文本为:
某些 Cookie 滥用推荐的“sameSite“属性 由于 Cookie “_jfinal_captcha”的“sameSite”属性设置为“none”,但缺少“secure”属性,此 Cookie 未来将被拒绝。 若要了解“sameSite“的更多信息,请参阅:https://developer.mozilla.org/docs/Web/HTTP/Cookies |
除了对验证码的警告,对doLogin中的cookie(jfinal-club里面key=jfinalId)也有一个警告。
该警告暂时不影响项目的进行。但是本着理科男负责任的态度和较真的精(mao)神(bing),作了反馈,并抽空断断续续研究了几天。
反馈地址:https://jfinal.com/feedback/7348
1、首先,名词解释
1.1 先搜一下
搜了一下,是Chrome 51 开始,浏览器的 Cookie 新增加了一个SameSite属性,用来防止 CSRF 攻击和用户追踪。
以前,如果SameSite属性没有设置,或者没有得到运行浏览器的支持,那么它的行为等同于None,Cookies会被包含在任何请求中——包括跨站请求。 但是,在新版本的浏览器中,SameSite的默认属性是SameSite=Lax。换句话说,当Cookie没有设置SameSite属性时,将会视作SameSite属性被设置为Lax——这意味着Cookies将会在当前用户使用时被自动发送。如果想要指定Cookies在同站、跨站请求都被发送,那么需要明确指定SameSite为None
若要了解“sameSite“的更多信息,请参阅:https://developer.mozilla.org/docs/Web/HTTP/Cookies
简而言之,如果没有为Cookie设置sameSite属性,那么默认是None。但是浏览规定,None的情况下,你必须设置Secure属性为真。
而目前最适合的设置为Lax。
1.2、后台语言的支持程度
目前还没有哪个后台语言的 API 支持了 SameSite 属性,比如 php 里的 setcookie 函数,或者 java 里的 java.net.HttpCookie 类,如果你想使用 SameSite,需要使用更底层的 API 直接修改 Set-Cookie 响应头。Node.js 本来就没有专门设置 cookie 的 API,只有通用的 setHeader 方法,不过 Node.js 的框架 Express 已经支持了 SameSite。
2、踩过的坑
坑1、不能用HttpServletResponse的addCookie方式增加sameSite属性。
1.2里面已经说过了,javax.servlet.http.Cookie里面没这个属性,你设置不进去。好吧,继承一个,增加属性,说干就干。
但是增加的属性,用HttpServletResponse的addCookie方法是加不进去的,里面是安全拷贝。
坑2、无法用全局Handler获取response里面的cookie来改造
原因有2:
HttpServletResponse不允许获取cookie,更无法修改cookie内容
HttpServletResponse中,getHeaders只对setHeader(和addHeader)的内容起效,无法获取Set-Cookie内容。因此无法全局判断修改。
坑3、直接setCookie("myCookie", "myValue; saveSite=Lax")这种方式无效
具体看反馈内容。
3、别整没用的,直接上代码
3.1、没错,继承一个Cookie,命名为NewCookie,增加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(); } }
3.2、修改LoginController中的doLogin代码:
核心代码,13-18行:
/** * 登录 */ @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行)要去掉。
3.3、设置SameSite其它属性:
cookie.setSameSite(NewCookie.STRICT);
或
cookie.setSameSite(NewCookie.NONE).setSecure(true);
4、其它
这里仅展示了为doLogin()中cookie增加SameSite属性的代码。
验证码生成里面的Cookie,只要从Jfianl继承一个新的CaptchaRender来进行render即可。此处不再赘述。
本着理科男负责任的态度和较真的精(mao)神(bing),反馈下,楼主把反馈地址后面那连接贴错了,写了两个HTTP,只能点击 “反馈地址”