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

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

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);

}

}

配置完成后,重启项目,再去进行多端登录测试,发现就可以成功踢掉已经登录的用户了。

如果你使用了 MyBatis 而不是 Jpa,也是一样的处理方案,只需要重写登录用户的 equals 方法和 hashCode 方法即可。

3.微人事应用


3.1 存在的问题

由于微人事目前是采用了 JSON 格式登录,所以如果项目控制 session 并发数,就会有一些额外的问题要处理。

最大的问题在于我们用自定义的过滤器代替了 UsernamePasswordAuthenticationFilter,进而导致前面所讲的关于 session 的配置,统统失效。所有相关的配置我们都要在新的过滤器 LoginFilter 中进行配置 ,包括 SessionAuthenticationStrategy 也需要我们自己手动配置了。

这虽然带来了一些工作量,但是做完之后,相信大家对于 Spring Security 的理解又会更上一层楼。

3.2 具体应用

我们来看下具体怎么实现,我这里主要列出来一些关键代码,完整代码大家可以从 GitHub 上下载:https://github.com/lenve/vhr。

首先第一步,我们重写 Hr 类的 equals 和 hashCode 方法,如下:

public class Hr implements UserDetails {

@Override

public boolean equals(Object o) {

if (this == o) return true;

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

Hr hr = (Hr) o;

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

}

@Override

public int hashCode() {

return Objects.hash(username);

}

}

接下来在 SecurityConfig 中进行配置。

这里我们要自己提供 SessionAuthenticationStrategy,而前面处理 session 并发的是 ConcurrentSessionControlAuthenticationStrategy,也就是说,我们需要自己提供一个 ConcurrentSessionControlAuthenticationStrategy 的实例,然后配置给 LoginFilter,但是在创建 ConcurrentSessionControlAuthenticationStrategy 实例的过程中,还需要有一个 SessionRegistryImpl 对象。

前面我们说过,SessionRegistryImpl 对象是用来维护会话信息的,现在这个东西也要我们自己来提供,SessionRegistryImpl 实例很好创建,如下:

@Bean

SessionRegistryImpl sessionRegistry() {

return new SessionRegistryImpl();

}

然后在 LoginFilter 中配置 SessionAuthenticationStrategy,如下:

@Bean

LoginFilter loginFilter() throws Exception {

LoginFilter loginFilter = new LoginFilter();

loginFilter.setAuthenticationSuccessHandler((request, response, authentication) -> {

//省略

}

);

loginFilter.setAuthenticationFailureHandler((request, response, exception) -> {

//省略

}

);

loginFilter.setAuthenticationManager(authenticationManagerBean());

loginFilter.setFilterProcessesUrl(“/doLogin”);

ConcurrentSessionControlAuthenticationStrategy sessionStrategy = new ConcurrentSessionControlAuthenticationStrategy(sessionRegistry());

sessionStrategy.setMaximumSessions(1);

loginFilter.setSessionAuthenticationStrategy(sessionStrategy);

return loginFilter;

}

我们在这里自己手动构建 ConcurrentSessionControlAuthenticationStrategy 实例,构建时传递 SessionRegistryImpl 参数,然后设置 session 的并发数为 1,最后再将 sessionStrategy 配置给 LoginFilter。

其实上篇文章中,我们的配置方案,最终也是像上面这样,只不过现在我们自己把这个写出来了而已。

这就配置完了吗?没有!session 处理还有一个关键的过滤器叫做 ConcurrentSessionFilter,本来这个过滤器是不需要我们管的,但是这个过滤器中也用到了 SessionRegistryImpl,而 SessionRegistryImpl 现在是由我们自己来定义的,所以,该过滤器我们也要重新配置一下,如下:

@Override

protected void configure(HttpSecurity http) throws Exception {

http.authorizeRequests()

http.addFilterAt(new ConcurrentSessionFilter(sessionRegistry(), event -> {

HttpServletResponse resp = event.getResponse();

resp.setContentType(“application/json;charset=utf-8”);

resp.setStatus(401);

PrintWriter out = resp.getWriter();

out.write(new ObjectMapper().writeValueAsString(RespBean.error(“您已在另一台设备登录,本次登录已下线!”)));

out.flush();

out.close();

}), ConcurrentSessionFilter.class);

http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);

}

在这里,我们重新创建一个 ConcurrentSessionFilter 的实例,代替系统默认的即可。在创建新的 ConcurrentSessionFilter 实例时,需要两个参数:

  1. sessionRegistry 就是我们前面提供的 SessionRegistryImpl 实例。

  2. 第二个参数,是一个处理 session 过期后的回调函数,也就是说,当用户被另外一个登录踢下线之后,你要给什么样的下线提示,就在这里来完成。

最后,我们还需要在处理完登录数据之后,手动向 SessionRegistryImpl 中添加一条记录:

public class LoginFilter extends UsernamePasswordAuthenticationFilter {

@Autowired

SessionRegistry sessionRegistry;

总结

阿里伤透我心,疯狂复习刷题,终于喜提offer 哈哈~好啦,不闲扯了

image

1、JAVA面试核心知识整理(PDF):包含JVMJAVA集合JAVA多线程并发,JAVA基础,Spring原理微服务,Netty与RPC,网络,日志,ZookeeperKafkaRabbitMQ,Hbase,MongoDB,Cassandra,设计模式负载均衡数据库一致性哈希JAVA算法数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。

image

2、Redis学习笔记及学习思维脑图

image

3、数据面试必备20题+数据库性能优化的21个最佳实践

image

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

30个章节。

[外链图片转存中…(img-UAGzhKjS-1713648241667)]

2、Redis学习笔记及学习思维脑图

[外链图片转存中…(img-vbM8Xkfa-1713648241668)]

3、数据面试必备20题+数据库性能优化的21个最佳实践

[外链图片转存中…(img-okUdz0O0-1713648241668)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-tFBASGlA-1713648241669)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值