文章目录

  • 一、自定义认证成功、失败处理
    • 1.1 CustomAuthenticationSuccessHandle
    • 1.2 CustomAuthenticationFailureHandler
    • 1.3 修改 WebSecurityConfig
    • 1.4 运行程序
  • 二、Session 超时
  • 三、限制最大登录数
  • 四、踢出用户
  • 五、退出登录
  • 六、Session 共享
    • 6.1 导入依赖
    • 6.3 运行程序

一、自定义认证成功、失败处理

有些时候我们想要在认证成功后做一些业务处理,例如添加积分;有些时候我们想要在认证失败后也做一些业务处理,例如记录日志。

在之前的文章中,关于认证成功、失败后的处理都是如下配置的:

http.authorizeRequests()// 如果有允许匿名的url,填在下面
//    .antMatchers().permitAll().anyRequest().authenticated().and()// 设置登陆页.formLogin().loginPage("/login").failureUrl("/login/error").defaultSuccessUrl("/").permitAll()...;

即 failureUrl() 指定认证失败后Url,defaultSuccessUrl() 指定认证成功后Url。我们可以通过设置 successHandler() 和 failureHandler() 来实现自定义认证成功、失败处理。

PS:当我们设置了这两个后,需要去除 failureUrl() 和 defaultSuccessUrl() 的设置,否则无法生效。这两套配置同时只能存在一套

1.1 CustomAuthenticationSuccessHandle

自定义 CustomAuthenticationSuccessHandler 类来实现 AuthenticationSuccessHandler 接口,用来处理认证成功后逻辑:

@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {private  Logger logger = LoggerFactory.getLogger(getClass());@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {logger.info("登录成功,{}", authentication);response.sendRedirect("/");}
}

onAuthenticationSuccess() 方法的第三个参数 Authentication 为认证后该用户的认证信息,这里打印日志后,重定向到了首页。

1.2 CustomAuthenticationFailureHandler

自定义 CustomAuthenticationFailureHandler 类来实现 AuthenticationFailureHandler 接口,用来处理认证失败后逻辑:

@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {private  Logger logger = LoggerFactory.getLogger(getClass());@Autowiredprivate ObjectMapper objectMapper;@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {logger.info("登录失败,{}");response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());response.setContentType("application/json;charset=UTF-8");response.getWriter().write(objectMapper.writeValueAsString(e.getMessage()));}
}

onAuthenticationFailure()方法的第三个参数 exception 为认证失败所产生的异常,这里也是简单的返回到前台。

1.3 修改 WebSecurityConfig

@Autowiredprivate CustomAuthenticationFailureHandler customAuthenticationFailureHandler;@Autowiredprivate CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;
@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 如果有允许匿名的url,填在下面//.antMatchers().permitAll().anyRequest().authenticated().and()// 设置登陆页.formLogin().loginPage("/login").successHandler(customAuthenticationSuccessHandler).failureHandler(customAuthenticationFailureHandler)
//                .failureUrl("/login/error")
//                .defaultSuccessUrl("/").permitAll();http.csrf().disable();}

1.首先将 customAuthenticationSuccessHandler 和 customAuthenticationFailureHandler注入进来
2.配置 successHandler() 和 failureHandler()
3.注释 failureUrl() 和 defaultSuccessUrl()

1.4 运行程序

运行程序,当我们成功登陆后,发现日志信息被打印出来,页面被重定向到了首页:

当我们认证失败后,发现日志中“登陆失败”被打印出来,页面展示了认证失败的异常消息:

二、Session 超时

当用户登录后,我们可以设置 session 的超时时间,当达到超时时间后,自动将用户退出登录。

Session 超时的配置是 SpringBoot 原生支持的,我们只需要在 application.properties 配置文件中配置:

# session 过期时间,单位:秒
server.servlet.session.timeout=60

Tip:
从用户最后一次操作开始计算过期时间。
过期时间最小值为 60 秒,如果你设置的值小于 60 秒,也会被更改为 60 秒。

我们可以在 Spring Security 中配置处理逻辑,在 session 过期退出时调用。修改 WebSecurityConfig 的 configure() 方法,添加:

.sessionManagement()// 以下二选一//.invalidSessionStrategy()//.invalidSessionUrl();

Spring Security 提供了两种处理配置,一个是 invalidSessionStrategy(),另外一个是 invalidSessionUrl()。

这两个的区别就是一个是前者是在一个类中进行处理,后者是直接跳转到一个 Url。简单起见,我就直接用 invalidSessionUrl()了,跳转到 /login/invalid,我们需要把该 Url 设置为免授权访问, 配置如下:

@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 如果有允许匿名的url,填在下面.antMatchers("/login/invalid").permitAll().anyRequest().authenticated().and()// 设置登陆页.formLogin().loginPage("/login").successHandler(customAuthenticationSuccessHandler).failureHandler(customAuthenticationFailureHandler).permitAll().and()
//                .failureUrl("/login/error")
//                .defaultSuccessUrl("/").sessionManagement().invalidSessionUrl("/login/invalid");http.csrf().disable();}

在 controller 中写一个接口进行处理:

@RequestMapping("/login/invalid")
@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ResponseBody
public String invalid() {return "Session 已过期,请重新登录";
}

运行程序,登陆成功后等待一分钟(或者重启服务器),刷新页面:

三、限制最大登录数

接下来实现限制最大登陆数,原理就是限制单个用户能够存在的最大 session 数。

在上一节的基础上,修改 configure() 为:

@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 如果有允许匿名的url,填在下面.antMatchers("/login/invalid").permitAll().anyRequest().authenticated().and()// 设置登陆页.formLogin().loginPage("/login").successHandler(customAuthenticationSuccessHandler).failureHandler(customAuthenticationFailureHandler).permitAll().and()
//                .failureUrl("/login/error")
//                .defaultSuccessUrl("/").sessionManagement().invalidSessionUrl("/login/invalid")//指定最大登录数.maximumSessions(1)//当达到最大值时,是否保留已经登录的用户.maxSessionsPreventsLogin(false)//当达到最大值时,旧用户被踢出后的操作.expiredSessionStrategy(customExpiredSessionStrategy);http.csrf().disable();}

增加了下面三行代码,其中:

maximumSessions(int):指定最大登录数
maxSessionsPreventsLogin(boolean):是否保留已经登录的用户;为true,新用户无法登录;为 false,旧用户被踢出
expiredSessionStrategy(SessionInformationExpiredStrategy):旧用户被踢出后处理方法

maxSessionsPreventsLogin()可能不太好理解,这里我们先设为 false,效果和 QQ 登录是一样的,登陆后之前登录的账户被踢出。

编写 CustomExpiredSessionStrategy 类,来处理旧用户登陆失败的逻辑:

@Component
public class CustomExpiredSessionStrategy implements SessionInformationExpiredStrategy {private ObjectMapper objectMapper = new ObjectMapper();@Overridepublic void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {Map<String,Object> map = new HashMap<>();map.put("code",0);map.put("msg","已经另一台机器登录,您被迫下线。" + event.getSessionInformation().getLastRequest());String s = objectMapper.writeValueAsString(map);event.getResponse().setContentType("application/json;charset=UTF-8");event.getResponse().getWriter().write(s);}
}

在 onExpiredSessionDetected() 方法中,处理相关逻辑,我这里只是简单的返回一句话。

执行程序,打开两个浏览器,登录同一个账户。因为我设置了 maximumSessions(1),也就是单个用户只能存在一个 session,因此当你刷新先登录的那个浏览器时,被提示踢出了。

下面我们来测试下 maxSessionsPreventsLogin(true) 时的情况,我们发现第一个浏览器登录后,第二个浏览器无法登录:

四、踢出用户

下面来看下如何主动踢出一个用户。

首先需要在容器中注入名为 SessionRegistry 的 Bean,这里我就简单的写在 WebSecurityConfig 中:

@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 如果有允许匿名的url,填在下面.antMatchers("/login/invalid").permitAll().anyRequest().authenticated().and()// 设置登陆页.formLogin().loginPage("/login").successHandler(customAuthenticationSuccessHandler).failureHandler(customAuthenticationFailureHandler).permitAll().and()
//                .failureUrl("/login/error")
//                .defaultSuccessUrl("/").sessionManagement().invalidSessionUrl("/login/invalid")//指定最大登录数.maximumSessions(1)//当达到最大值时,是否保留已经登录的用户.maxSessionsPreventsLogin(false)//当达到最大值时,旧用户被踢出后的操作.expiredSessionStrategy(customExpiredSessionStrategy).sessionRegistry(sessionRegistry());http.csrf().disable();}
@Bean
public SessionRegistry sessionRegistry() {return new SessionRegistryImpl();
}

编写一个接口用于测试踢出用户:

@GetMapping("/click")@ResponseBodypublic String removeUserSessionByUsername(@RequestParam String username) {int count = 0;List<Object> users = sessionRegistry.getAllPrincipals();for (Object user:users) {if (user instanceof User) {String principalName  = ((User) user).getUsername();if (principalName.equals(username)) {List<SessionInformation> allSessions = sessionRegistry.getAllSessions(user, false);if (allSessions != null && allSessions.size() > 0) {for (SessionInformation sessionInformation:allSessions) {sessionInformation.expireNow();count++;}}}}}return "操作成功,清理session共" + count + "个";}
  1. sessionRegistry.getAllPrincipals(); 获取所有 principal 信息
  2. 通过 principal.getUsername 是否等于输入值,获取到指定用户的 principal
  3. sessionRegistry.getAllSessions(principal, false)获取该 principal 上的所有 session
  4. 通过 sessionInformation.expireNow() 使得 session 过期

运行程序,分别使用 admin 和 zhao账户登录,admin 访问 /kick?username=zhao 来踢出用户 zhao,zhao 刷新页面,发现被踢出。

五、退出登录

补充一下退出登录的内容,在之前,我们直接在 WebSecurityConfig 的 configure() 方法中,配置了:

http.logout();

这就是 Spring Security 的默认退出配置,Spring Security 在退出时候做了这样几件事:

  1. 使当前的 session 失效
  2. 清除与当前用户有关的 remember-me 记录
  3. 清空当前的 SecurityContext
  4. 重定向到登录页
    Spring Security 默认的退出 Url 是 /logout,我们可以修改默认的退出 Url,例如修改为 /signout:
    WebSecurityConfig配置如下:
@Autowiredprivate CustomLogoutSuccessHandler customLogoutSuccessHandler;@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 如果有允许匿名的url,填在下面.antMatchers("/login/invalid").permitAll().anyRequest().authenticated().and()// 设置登陆页.formLogin().loginPage("/login").successHandler(customAuthenticationSuccessHandler).failureHandler(customAuthenticationFailureHandler).permitAll().and().logout().logoutUrl("/signout").deleteCookies("JSESSIONID").logoutSuccessHandler(customLogoutSuccessHandler).and()
//                .failureUrl("/login/error")
//                .defaultSuccessUrl("/").sessionManagement().invalidSessionUrl("/login/invalid")//指定最大登录数.maximumSessions(1)//当达到最大值时,是否保留已经登录的用户.maxSessionsPreventsLogin(false)//当达到最大值时,旧用户被踢出后的操作.expiredSessionStrategy(customExpiredSessionStrategy).sessionRegistry(sessionRegistry());http.csrf().disable();}
@Component
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {private final Logger log = LoggerFactory.getLogger(CustomLogoutSuccessHandler.class);@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {String username = ((User)authentication.getPrincipal()).getUsername();log.info("退出成功,用户名:{}", username);// 重定向到登录页response.sendRedirect("/login");}
}

六、Session 共享

在最后补充下关于 Session 共享的知识点,一般情况下,一个程序为了保证稳定至少要部署两个,构成集群。那么就牵扯到了 Session 共享的问题,不然用户在 8080 登录成功后,后续访问了 8060 服务器,结果又提示没有登录。

这里就简单实现下 Session 共享,采用 Redis 来存储。

6.1 导入依赖

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId>
</dependency>

在 application.xml 中新增配置指定 redis 地址以及 session 的存储方式

spring.redis.host=localhost
spring.redis.port=6379spring.session.store-type=redis

然后为主类添加 @EnableRedisHttpSession 注解。

@EnableRedisHttpSession
@SpringBootApplication
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

6.3 运行程序

分别启动两个端口8080,8086
先访问 localhost:8080,登录成功后,再访问 localhost:8060,发现无需登录。

然后我们进入 Redis 查看下 key:

最后再测试下之前配置的 session 设置是否还有效,使用其他浏览器登陆,登陆成功后发现原浏览器用户的确被踢出。

SpringBoot整合Spring Security——登录管理相关推荐

  1. springBoot整合spring security+JWT实现单点登录与权限管理前后端分离

    在前一篇文章当中,我们介绍了springBoot整合spring security单体应用版,在这篇文章当中,我将介绍springBoot整合spring secury+JWT实现单点登录与权限管理. ...

  2. springBoot整合spring security+JWT实现单点登录与权限管理前后端分离--筑基中期

    写在前面 在前一篇文章当中,我们介绍了springBoot整合spring security单体应用版,在这篇文章当中,我将介绍springBoot整合spring secury+JWT实现单点登录与 ...

  3. SpringBoot整合Spring Security【超详细教程】

    好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/Lee/DayDayUP,欢迎Star,更多文章请前往:目录导航 前言 Spring Security是一个 ...

  4. 八、springboot整合Spring Security

    springboot整合Spring Security 简介 Spring Security是一个功能强大且可高度自定义的身份验证和访问控制框架.它是保护基于Spring的应用程序的事实标准. Spr ...

  5. SpringBoot 整合 Spring Security 实现安全认证【SpringBoot系列9】

    SpringCloud 大型系列课程正在制作中,欢迎大家关注与提意见. 程序员每天的CV 与 板砖,也要知其所以然,本系列课程可以帮助初学者学习 SpringBooot 项目开发 与 SpringCl ...

  6. SpringBoot 整合Spring Security(简单版)

    1 写在前面 关于spring security的介绍,网上一大堆,这里就不介绍了,这里直接使用springboot开始整合 2 整个流程 spring security授权和认证的流程大致和shir ...

  7. SpringBoot整合Spring Security

    个人资源与分享网站:http://xiaocaoshare.com/ SpringSecurity核心功能: 认证(你是谁) 授权(你能干什么) 攻击防护(防止伪造身份) 1.pom.xml < ...

  8. SpringBoot整合Spring Security——第三章异常处理

    文章目录 一.常见异常 二.源码分析 三.处理异常 四.拓展spring security authenticationProvider用法及关闭不隐藏UserNotFoundException的解决 ...

  9. 31 | SpringBoot安全之整合Spring Security

    一.安全 应用程序的两个主要区域是"认证"和"授权"(或者访问控制),这两个主要区域是安全的两个目标. 身份验证意味着确认您自己的身份,而授权意味着授予对系统的 ...

最新文章

  1. 一次地址选择器的实践
  2. 在js中加html_在HTML文档中嵌入JavaScript的四种方法
  3. 饼图大小调整_别让这些细节毁了你的图表,饼图制作的三大准则和七大细节
  4. 【Google Play】IARC 年龄分级 ( IARC 国际年龄分级联盟 | Google Play 设置应用年龄分级 )
  5. CentOS下面service mysqld start出现[failed]情况
  6. 小森生活显示无可用服务器,《小森生活》无可用游戏服务器解决教程 无可用网关服务器怎么办...
  7. 随机漫步(random walk)
  8. 设置导航栏的相关属性
  9. 带着canvas去流浪系列之四 绘制散点图
  10. GIT常用快捷键配置
  11. php100视频教程75到100讲的解压密码
  12. 大数据分析平台架构(Big Data Analytics Platform)
  13. cad相对坐标快捷键_CAD常用的快捷键
  14. 阿里云mysql1227_Navicat连接阿里云Mysql遇到的的坑
  15. 电脑锁屏卡死以及任务栏卡死的解决办法
  16. 详解圆形头像Shader
  17. 三月的雨季给我带来了希望
  18. 微信开发者工具命令面版
  19. Arcgis使用教程(六)ARCGIS空间数据查询
  20. 相忘于江湖——记另一位朋友

热门文章

  1. 银行计算机记账比赛,在银行柜台业务技术比赛颁奖仪式上的讲话(一).doc
  2. android 减少布局层级,Android 布局优化
  3. 织梦地方php分类信息,织梦标签:infolink 分类信息地区与类型快捷链接
  4. matlab axb c,matlab调用C源代码(续)
  5. Django 笔记3 -- URL
  6. Java基础day2
  7. 【数据竞赛】可能是全网特征工程实操最通透的...
  8. 【数据竞赛】Kaggle竞赛如何保证线上线下一致性?
  9. 那些数学不好的程序员?最后都如何了(文末送书)
  10. 【效率】推荐几个不错的网站!