当浏览器调用登录接口登录成功后,服务端会和浏览器之间建立一个会话(Session)浏览器在每次发送请求时都会携带一个Sessionld,服务端则根据这个Sessionld来判断用户身份。当浏览器关闭后,服务端的Session并不会自动销毁,需要开发者手动在服务端调用Session销毁方法,或者等Session过期时间到了自动销毁。在Spring Security中,与HttpSession相关的功能由SessionManagementFiter和SessionAutheaticationStrateey接口来处理,SessionManagomentFilter过滤器将Session相关操作委托给SessionAuthenticationStrategy接口去完成。
会话并发管理就是指在当前系统中,同一个用户可以同时创建多少个会话,如果一合设备对应一个会话,那么也可以简单理解为同一个用户可以同时在多少台设备上进行登录。默认情况下,同一用户在多少台设备上登录并没有限制,不过开发者可以在Spring Security中对此进行配置。
默认情况下我们可以在同一时刻在多台设备上进行同时操作
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).disable()
.build();
}
http.sessionManagement().maximumSessions(1)
当我们多个浏览器访问的时候(多会话)下就会报这个错误
使用.expiredSessionStrategy
进行自定义处理
http.sessionManagement()
.maximumSessions(1)
.expiredSessionStrategy(
event ->
{
HttpServletResponse response = event.getResponse();
HashMap<String, String> data = new HashMap<>();
data.put("code", String.valueOf(403));
data.put("msg", "会话过期");
response.setContentType("application/json; charset=UTF-8");
String s = new ObjectMapper().writeValueAsString(data);
response.getWriter().println(s);
response.getWriter().flush();
response.getWriter().close();
}
);
如此我们就自定义了一段json的返回作为会话处理
默认的效果是一种被“挤下线”的效果,后面登录的用户会把前面登录的用户“挤下线”。还有一种是禁止后来者登录,即一旦当前用户登录成功,后来者无法再次使用相同的用户登录,直到当前用户主动注销登录,配置如下:
即:在一个设备上登录后不允许在其他的设备上进行登录
加如下配置即可
.maxSessionsPreventsLogin(true)
若其他设备进行登录时你就会看到如下提示
利用redis进行集群的管理,当多端登录之前在redis上进行确认,redis上存在这里呢我只提供思路,先不写实现,后续在进阶实战系列中会给大家写完整的实例
FindByIndexNameSessionRepository
.sessionRegistry()
new SpringSessionBackedSessionRegistry(FindByIndexNameSessionRepository)
.sessionRegistry()
中CSRF (Cross-Site Request Forgery跨站请求伪造),也可称为一键式攻击(one-click-attack) ,通常缩写为CSRF或者XSRF。
CSRF攻击是一种挟持用户在当前已登录的河览器上发送恶意请求的攻击方法。相对于XSS利用用户对指定网站的信任,CSRF则是利用网站对用户网页浏览器的信任。简单来说,CSRF是致击者通过一些技术手段欺骗用户的浏览器,去访问一个用户曾经认证过的网站并执行恶意请求,例如发送邮件、发消息、甚至财产操作(如转账和购买商品)。由于客户端(浏览器)已经在该网站上认证过,所以该网站会认为是真正用户在操作而执行请求(实际上这个并非用户的本意)。
CSRF攻击的根源在于浏览器默认的身份验证机制(自动携带当前网站的Cookie信息),这种机制虽然可以保证请求是来自用户的某个浏览器,但是无法确保这请求是用户授权发送。攻击者和用户发送的请求一模一样,这意味着我们没有办法去直接拒绝这里的某一个请求。如果能在合法清求中额外携带一个攻击者无法获取的参数,就可以成功区分出两种不同的请求,进而直接拒绝掉恶意请求。在 SpringSecurity 中就提供了这种机制来防御CSRF 攻击,这种机制我们称之为令牌同步模式。
这是目前主流的CSRF 攻击防御方案。具体的操作方式就是在每一个HTTP请求中,除了默认自动携带的Cookie参数之外,再提供一个安全的、随机生成的宇符串,我们称之为CSRF令牌。这个CSRF令牌由服务端生成,生成后在HtpSession中保存一份。当前端请求到达后,将请求携带的CSRF令牌信息和服务端中保存的令牌进行对比,如果两者不相等,则拒绝掉该HTTP请求。
默认来说我们的CSRF是开启的
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf().disable().build;
}
在传统web开发中实现是很简单的只需要开启防护即可
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf()
.build;
}
这样每次发请求是就会含有令牌了,当然在页面上是不可见的
当然开启后你的Cookie中就会有XSRF-TOKEN了
为了从正文中读取 CSRF 令牌,在 Spring Security 过滤器之前指定了 MultipartFilter。 在 Spring Security 过滤器之前指定 MultipartFilter 意味着没有调用 MultipartFilter 的授权,这意味着任何人都可以在您的服务器上放置临时文件。 但是,只有授权用户才能提交由您的应用程序处理的文件。 一般来说,这是推荐的方法,因为临时文件上传对大多数服务器的影响可以忽略不计。
为了确保 MultipartFilter 在 Spring Security 过滤器之前指定 java 配置,用户可以覆盖 beforeSpringSecurityFilterChain ,如下所示:
public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
@Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
insertFilters(servletContext, new MultipartFilter());
}
}
将CSRF放入到cookie中,在清求时获取cookie中的CSRF令牌一并提交即可,其实最直接的实现方式是设置到header中即可,这样是否方便,反正每次令牌都会变化即使你的请求被获取也不会有这样的问题
自然我们也可以将CSRF放置到本地存储中(localStorage)代替cookie也是一个很好的选择
当然还有其他策略
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
);
.build;
}
该示例明确设置 cookieHttpOnly=false。 这是允许 JavaScript(即 AngularJS)读取它所必需的。 如果您不需要使用 JavaScript 直接读取 cookie 的能力,建议省略 cookieHttpOnly=false (通过使用 new CookieCsrfTokenRepository() 代替)以提高安全性。
当你只是刚刚配置完上面的进行测试
此时你进行常规登录会发现无法正常认证,这是因为此时的登录请求也需要带有CSRF的令牌!
并且你会看到如下信息
{exceptionMsg=Could not verify the provided CSRF token because no token was found to compare., code=403}
这里要知道我使用前后端分离的时候登录的请求是匿名的!因此甚至你无法请求到CSRF!而且我们的请求头中一定要包含:X-CSRF-TOKEN,导致我们会发现我们之前有一个东西和CSRF的token很像,就是我们的jwt的token!所以我得出一个结论前后端分离的项目的CSRF可以不需要进行处理!
虽然前面我们说可以不需要设置,但是我们也是可以通过这种思想强化我们的程序的: