springsecurity spring session redis
项目使用springsecurity进行认证和鉴权
因为项目每次部署登陆都会失败(session 认证机制)
故集成spring session redis将session保存在redis当中
集成完成后,发现认证成功后返回session-id登录后携带session-id不同导致认证成功
问题在于集成spring session redis 返回响应头 set-cookie上
此时SameSite的值为Lax,cookie部门场景不允许跨域
SameSite 属性
Cookie 的SameSite
该属性用于限制第三方 Cookie,从而降低安全风险。
它可以设置三个值。
- Strict
- Lax
- None
1 Strict
Strict
第三方完全禁止最严格的 Cookie,任何情况下任何情况下都不会发送 Cookie。换句话说,只有当前的网页 URL 只有与请求目标一致,才会带上 Cookie。
Set-Cookie: CookieName=CookieValue; SameSite=Strict;
这一规则过于严格,可能会导致非常糟糕的用户体验。例如,当前的网页有一个 GitHub 链接,用户点击跳转 GitHub 的 Cookie,跳转过去总是没有登。
2 Lax
Lax
规则略有放宽,大多数情况下不发送第三方 Cookie,但导航到目标网站 Get 请求除外。
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
导航到目标网站 GET 只有三种情况:链接,预加载请求,GET 表单。详见下表。
请求类型 | 示例 | 正常情况 | Lax |
---|---|---|---|
链接 | <a href="..."></a> |
发送 Cookie | 发送 Cookie |
预加载 | <link rel="prerender" href="..."/> |
发送 Cookie | 发送 Cookie |
GET 表单 | <form method="GET" action="..."> |
发送 Cookie | 发送 Cookie |
POST 表单 | <form method="POST" action="..."> |
发送 Cookie | 不发送 |
iframe | <iframe src="..."></iframe> |
发送 Cookie | 不发送 |
AJAX | $.get("...") |
发送 Cookie | 不发送 |
Image | <img src="..."> |
发送 Cookie | 不发送 |
设置了Strict
或Lax
以后基本杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。
3 None
Chrome 计划将Lax
变成默认设置。此时,网站可以选择显式关闭。SameSite
属性,设置为None
。但前提是必须同时设置Secure
属性(Cookie 只能通过 HTTPS 协议发送),否则无效。
参考博文:Cookie 的 SameSite 属性
问题的原因及解决方案
1.当我们介绍它时spring session redis的依赖包spring-session-core包括时间session自动配置
@Bean @Conditional({SessionAutoConfiguration.DefaultCookieSerializerCondition.class}) DefaultCookieSerializer cookieSerializer(ServerProperties serverProperties, ObjectProvider<DefaultCookieSerializerCustomizer> cookieSerializerCustomizers) { Cookie cookie = serverProperties.getServlet().getSession().getCookie(); DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer(); PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull(); cookie.getClass(); map.from(cookie::getName).to(cookieSerializer::setCookieName); cookie.getClass(); map.from(cookie::getDomain).to(cookieSerializer::setDomainName); cookie.getClass(); map.from(cookie::getPath).to(cookieSerializer::setCookiePath); cookie.getClass(); map.from(cookie::getHttpOnly).to(cookieSerializer::setUseHttpOnlyCookie); cookie.getClass(); map.from(cookie::getSecure).to(cookieSerializer::setUseSecureCookie); cookie.getClass(); map.from(cookie::getMaxAge).to((maxAge) -> { cookieSerializer.setCookieMaxAge((int)maxAge.getSeconds()); }); cookieSerializerCustomizers.orderedStream().forEach((customizer) -> { customizer.customize(cookieSerializer); }); return cookieSerializer; }
上述代码从配置文件中读取cookie的用户自定义配置(一般是在application.yml中定义的) 然后新建了cookie序列化配置类 用户定义的参数通过PropertyMapper工具设置到cookie序列化配置类(有兴趣可以阅读)PropertyMapper源码,还是比较容易读懂的)
注入遍历mvc环境的cookieSerializerCustomizers调用接口法 void customize(DefaultCookieSerializer cookieSerializer)
注入DefaultCookieSerializer
2 注解分析 @Conditional({SessionAutoConfiguration.DefaultCookieSerializerCondition.class})
上述注释是条件注入的注释,DefaultCookieSerializerConditin继承了AnyNestedCondition,当DefaultCookieSerializerCondition中分段注解任一生效时,bean就会注入
DefaultCookieSerializerCondition具体代码如下:
static class DefaultCookieSerializerCondition extends AnyNestedCondition {
DefaultCookieSerializerCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnBean({CookieHttpSessionIdResolver.class})
@ConditionalOnMissingBean({CookieSerializer.class})
static class CookieHttpSessionIdResolverAvailable {
CookieHttpSessionIdResolverAvailable() {
}
}
@ConditionalOnMissingBean({HttpSessionIdResolver.class, CookieSerializer.class})
static class NoComponentsAvailable {
NoComponentsAvailable() {
}
}
}
3.再溯源到DefaultCookieSerializer类中,找到罪魁元首
4.解决方案
手写配置注入CookieSerializer
@Configuration
public class SpringSessionConfig {
@Bean
DefaultCookieSerializer cookieSerializer(ServerProperties serverProperties, ObjectProvider<DefaultCookieSerializerCustomizer> cookieSerializerCustomizers) {
Session.Cookie cookie = serverProperties.getServlet().getSession().getCookie();
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setSameSite(null);
PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
map.from(cookie::getName).to(cookieSerializer::setCookieName);
map.from(cookie::getDomain).to(cookieSerializer::setDomainName);
map.from(cookie::getPath).to(cookieSerializer::setCookiePath);
map.from(cookie::getHttpOnly).to(cookieSerializer::setUseHttpOnlyCookie);
map.from(cookie::getSecure).to(cookieSerializer::setUseSecureCookie);
map.from(cookie::getMaxAge).to((maxAge) -> {
cookieSerializer.setCookieMaxAge((int)maxAge.getSeconds());
});
cookieSerializerCustomizers.orderedStream().forEach((customizer) -> {
customizer.customize(cookieSerializer);
});
return cookieSerializer;
}
}
问题解决: