Spring Boot + Vue 前后端分离项目,如何踢掉已登录用户

本文探讨了SpringSecurity在前后端分离项目中的应用,重点讲述了如何处理多端登录问题,以及如何通过SpringDataJpa存储用户数据。作者还提到了自定义认证逻辑和查看用户信息的方法,强调了会话管理和安全性的重要性。
摘要由CSDN通过智能技术生成
  1. Spring Security 做前后端分离,咱就别做页面跳转了!统统 JSON 交互

  2. Spring Security 中的授权操作原来这么简单

  3. Spring Security 如何将用户数据存入数据库?

  4. Spring Security+Spring Data Jpa 强强联手,安全管理只有更简单!

  5. Spring Boot + Spring Security 实现自动登录功能

  6. Spring Boot 自动登录,安全风险要怎么控制?

  7. 在微服务项目中,Spring Security 比 Shiro 强在哪?

  8. SpringSecurity 自定义认证逻辑的两种方式(高级玩法)

  9. Spring Security 中如何快速查看登录用户 IP 地址等信息?

  10. Spring Security 自动踢掉前一个登录用户,一个配置搞定!

本文的案例将基于Spring Security+Spring Data Jpa 强强联手,安全管理只有更简单!一文来构建,所以重复的代码我就不写了,小伙伴们要是不熟悉可以参考该篇文章。

1.环境准备


首先,我们打开Spring Security+Spring Data Jpa 强强联手,安全管理只有更简单!一文中的案例,这个案例结合 Spring Data Jpa 将用户数据存储到数据库中去了。

然后我们将上篇文章中涉及到的登录页面拷贝到项目中(文末可以下载完整案例):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7XB0viq6-1588898082940)(http://img.itboyhub.com/2020/04/20200506204420.png)]

并在 SecurityConfig 中对登录页面稍作配置:

@Override

public void configure(WebSecurity web) throws Exception {

web.ignoring().antMatchers(“/js/", "/css/”, “/images/**”);

}

@Override

protected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests()

.and()

.formLogin()

.loginPage(“/login.html”)

.loginProcessingUrl(“/doLogin”)

.and()

.sessionManagement()

.maximumSessions(1);

}

这里都是常规配置,我就不再多说。注意最后面我们将 session 数量设置为 1。

好了,配置完成后,我们启动项目,并行性多端登录测试。

打开多个浏览器,分别进行多端登录测试,我们惊讶的发现,每个浏览器都能登录成功,每次登录成功也不会踢掉已经登录的用户!

这是怎么回事?

2.问题分析


要搞清楚这个问题,我们就要先搞明白 Spring Security 是怎么保存用户对象和 session 的。

Spring Security 中通过 SessionRegistryImpl 类来实现对会话信息的统一管理,我们来看下这个类的源码(部分):

public class SessionRegistryImpl implements SessionRegistry,

ApplicationListener {

/** principal:Object,SessionIdSet */

private final ConcurrentMap<Object, Set> principals;

/** sessionId:Object,SessionInformation */

private final Map<String, SessionInformation> sessionIds;

public void registerNewSession(String sessionId, Object principal) {

if (getSessionInformation(sessionId) != null) {

removeSessionInformation(sessionId);

}

sessionIds.put(sessionId,

new SessionInformation(principal, sessionId, new Date()));

principals.compute(principal, (key, sessionsUsedByPrincipal) -> {

if (sessionsUsedByPrincipal == null) {

sessionsUsedByPrincipal = new CopyOnWriteArraySet<>();

}

sessionsUsedByPrincipal.add(sessionId);

return sessionsUsedByPrincipal;

});

}

public void removeSessionInformation(String sessionId) {

SessionInformation info = getSessionInformation(sessionId);

if (info == null) {

return;

}

sessionIds.remove(sessionId);

principals.computeIfPresent(info.getPrincipal(), (key, sessionsUsedByPrincipal) -> {

sessionsUsedByPrincipal.remove(sessionId);

if (sessionsUsedByPrincipal.isEmpty()) {

sessionsUsedByPrincipal = null;

}

return sessionsUsedByPrincipal;

});

}

}

这个类的源码还是比较长,我这里提取出来一些比较关键的部分:

  1. 首先大家看到,一上来声明了一个 principals 对象,这是一个支持并发访问的 map 集合,集合的 key 就是用户的主体(principal),正常来说,用户的 principal 其实就是用户对象,松哥在之前的文章中也和大家讲过 principal 是怎么样存入到 Authentication 中的(参见:松哥手把手带你捋一遍 Spring Security 登录流程),而集合的 value 则是一个 set 集合,这个 set 集合中保存了这个用户对应的 sessionid。

  2. 如有新的 session 需要添加,就在 registerNewSession 方法中进行添加,具体是调用 principals.compute 方法进行添加,key 就是 principal。

  3. 如果用户注销登录,sessionid 需要移除,相关操作在 removeSessionInformation 方法中完成,具体也是调用 principals.computeIfPresent 方法,这些关于集合的基本操作我就不再赘述了。

看到这里,大家发现一个问题,ConcurrentMap 集合的 key 是 principal 对象,用对象做 key,一定要重写 equals 方法和 hashCode 方法,否则第一次存完数据,下次就找不到了,这是 JavaSE 方面的知识,我就不用多说了。

如果我们使用了基于内存的用户,我们来看下 Spring Security 中的定义:

public class User implements UserDetails, CredentialsContainer {

private String password;

private final String username;

private final Set authorities;

private final boolean accountNonExpired;

private final boolean accountNonLocked;

private final boolean credentialsNonExpired;

private final boolean enabled;

@Override

public boolean equals(Object rhs) {

if (rhs instanceof User) {

return username.equals(((User) rhs).username);

}

return false;

}

@Override

public int hashCode() {

return username.hashCode();

}

}

可以看到,他自己实际上是重写了 equals 和 hashCode 方法了。

所以我们使用基于内存的用户时没有问题,而我们使用自定义的用户就有问题了。

找到了问题所在,那么解决问题就很容易了,重写 User 类的 equals 方法和 hashCode 方法即可:

@Entity(name = “t_user”)

public class User implements UserDetails {

@Id

@GeneratedValue(strategy = GenerationType.IDENTITY)

private Long id;

private String username;

private String password;

private boolean accountNonExpired;

private boolean accountNonLocked;

private boolean credentialsNonExpired;

private boolean enabled;

@ManyToMany(fetch = FetchType.EAGER,cascade = CascadeType.PERSIST)

private List roles;

@Override

public boolean equals(Object o) {

if (this == o) return true;

if (o == null || getClass() != o.getClass()) return false;

User user = (User) o;

return Objects.equals(username, user.username);

}

@Override

public int hashCode() {

return Objects.hash(username);

}

小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级前端工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Web前端开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img
img
img
img

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频

如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注:前端)
img

总结

  • 框架原理真的深入某一部分具体的代码和实现方式时,要多注意到细节,不要只能写出一个框架。

  • 算法方面很薄弱的,最好多刷一刷,不然影响你的工资和成功率😯

  • 在投递简历之前,最好通过各种渠道找到公司内部的人,先提前了解业务,也可以帮助后期优秀 offer 的决策。

  • 要勇于说不,对于某些 offer 待遇不满意、业务不喜欢,应该相信自己,不要因为当下没有更好的 offer 而投降,一份工作短则一年长则 N 年,为了幸福生活要慎重选择!!!

    开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

一个框架。

  • 算法方面很薄弱的,最好多刷一刷,不然影响你的工资和成功率😯

  • 在投递简历之前,最好通过各种渠道找到公司内部的人,先提前了解业务,也可以帮助后期优秀 offer 的决策。

  • 要勇于说不,对于某些 offer 待遇不满意、业务不喜欢,应该相信自己,不要因为当下没有更好的 offer 而投降,一份工作短则一年长则 N 年,为了幸福生活要慎重选择!!!

    开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

喜欢这篇文章文章的小伙伴们点赞+转发支持,你们的支持是我最大的动力!

  • 10
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值