springboot listener、filter登录实战
转载自: www.javaman.cn
博客系统访问: http://175.24.198.63:9090/front/index

登录功能
1、前端页面
采用的是layui-admin框架,文中的验证码内容,请参考作者之前的验证码功能
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<title>ds博客</title>
<div th:replace="common/link::header"></div>
<link rel="stylesheet" th:href="@{/static/layuiadmin/style/login.css}" media="all">
</head>
<body>
<div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login" style="display: none;">
<div class="layadmin-user-login-main">
<div class="layadmin-user-login-box layadmin-user-login-header">
<h2>ds博客</h2>
<p>后台登录</p>
</div>
<div class="layadmin-user-login-box layadmin-user-login-body layui-form">
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-username" for="LAY-user-login-username"></label>
<input type="text" name="userName" value="test" id="LAY-user-login-username" lay-verify="required" placeholder="用户名" class="layui-input">
</div>
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-password"></label>
<input type="password" name="passWord" value="test" id="LAY-user-login-password" lay-verify="required" placeholder="密码" class="layui-input">
</div>
<div class="layui-form-item">
<div class="layui-row">
<div class="layui-col-xs7">
<label class="layadmin-user-login-icon layui-icon layui-icon-vercode"></label>
<input type="text" name="code" lay-verify="required" placeholder="图形验证码" class="layui-input">
</div>
<div class="layui-col-xs5">
<div style="margin-left: 10px;">
<img id="codeImg" class="layadmin-user-login-codeimg">
</div>
</div>
</div>
</div>
<div class="layui-form-item" style="margin-bottom: 20px;">
<input type="checkbox" name="remember-me" lay-skin="primary" title="记住密码">
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid layui-bg-blue" lay-submit lay-filter="login">登 录</button>
</div>
</div>
</div>
<!-- <div class="layui-trans layadmin-user-login-footer">-->
<!-- <p>版权所有 2022 <a href="#" target="_blank">济南高新开发区微本地软件开发工作室</a> 鲁ICP备20002306号-1</p>-->
<!-- </div>-->
</div>
<div th:replace="common/script::footer"></div>
<script th:inline="javascript">
layui.config({
base: '/static/layuiadmin/' //静态资源所在路径
}).extend({
index: 'lib/index' //主入口模块
}).use(['index', 'user'], function(){
let $ = layui.$,
form = layui.form;
// 初始化
getImgCode();
form.render();
//提交
form.on('submit(login)', function(obj) {
// 打开loading
let loading = layer.load(0, {
shade: false,
time: 2 * 1000
});
// 禁止重复点击按钮
$('.layui-btn').attr("disabled",true);
//请求登入接口
$.ajax({
type: 'POST',
url: ctx + '/login',
data: obj.field,
dataType: 'json',
success: function(result) {
if (result.code === 200) {
layer.msg('登入成功', {
icon: 1
,time: 1000
}, function(){
window.location.href = '/';
});
} else {
layer.msg(result.message);
// 刷新验证码
getImgCode();
// 关闭loading
layer.close(loading);
// 开启点击事件
$('.layui-btn').attr("disabled", false);
}
}
});
});
$("#codeImg").on('click', function() {
// 添加验证码
getImgCode();
});
$(document).keydown(function (e) {
if (e.keyCode === 13) {
$('.layui-btn').click();
}
});
// 解决session过期跳转到登录页并跳出iframe框架
$(document).ready(function () {
if (window != top) {
top.location.href = location.href;
}
});
});
/**
* 获取验证码
*/
function getImgCode() {
let url = ctx + '/getImgCode';
let xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = "blob";
xhr.onload = function() {
if (this.status === 200) {
let blob = this.response;
document.getElementById("codeImg").src = window.URL.createObjectURL(blob);
}
}
xhr.send();
}
</script>
</body>
</html>
2、后端处理/login请求

通过springsecurity的.loginProcessingUrl("/login")处理,处理逻辑如下:
.loginProcessingUrl("/login") 用于指定处理登录操作的URL地址,而具体的验证逻辑是由 Spring Security 提供的认证过滤器链负责的。在Spring Security中,主要的认证过程是由UsernamePasswordAuthenticationFilter来完成的。
当用户提交登录表单,请求到达.loginProcessingUrl("/login")配置的URL时,UsernamePasswordAuthenticationFilter会拦截这个请求,然后进行以下主要步骤:
获取用户名和密码:从请求中获取用户输入的用户名和密码。
创建认证令牌:使用获取到的用户名和密码创建一个认证令牌(
UsernamePasswordAuthenticationToken)。将认证令牌传递给认证管理器:将认证令牌传递给配置的认证管理器(
AuthenticationManager)进行认证。执行认证逻辑:认证管理器会调用已配置的身份验证提供者(
AuthenticationProvider)来执行实际的身份验证逻辑。通常,会使用用户提供的用户名和密码与系统中存储的用户信息进行比对。处理认证结果:认证提供者返回认证结果,如果认证成功,则将认证令牌标记为已认证,并设置用户权限等信息。如果认证失败,会抛出相应的异常。
处理认证成功或失败:根据认证的结果,
UsernamePasswordAuthenticationFilter将请求重定向到成功或失败的处理器,执行相应的操作,比如跳转页面或返回错误信息。
这个整个过程是由 Spring Security 提供的默认配置完成的,通常情况下,开发者只需要配置好认证管理器、用户信息服务(UserDetailsService),以及成功和失败的处理器,Spring Security 就会负责处理登录验证的整个流程。
package com.ds.core.config;
import com.ds.blog.system.service.SysUserService;
import com.ds.core.security.CustomAccessDeniedHandler;
import com.ds.core.security.DefaultAuthenticationFailureHandler;
import com.ds.core.security.DefaultAuthenticationSuccessHandler;
import com.ds.core.security.filter.ValidateCodeFilter;
import net.bytebuddy.asm.Advice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 放过
.antMatchers("/loginPage", "/getImgCode").permitAll()
.antMatchers("/**/*.jpg", "/**/*.png", "/**/*.gif", "/**/*.jpeg").permitAll()
// 剩下的所有的地址都是需要在认证状态下才可以访问
.anyRequest().authenticated()
.and()
// 过滤登录验证码
.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
// 配置登录功能
.formLogin()
.usernameParameter("userName")
.passwordParameter("passWord")
// 指定指定要的登录页面
.loginPage("/loginPage")
// 处理认证路径的请求
.loginProcessingUrl("/login")
.successHandler(defaultAuthenticationSuccessHandler)
.failureHandler(defaultAuthenticationFailureHandler)
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.and()
// 登出
.logout()
.invalidateHttpSession(true)
.deleteCookies("remember-me")
.logoutUrl("/logout")
.logoutSuccessUrl("/loginPage")
.and()
.rememberMe()
// 有效期7天
.tokenValiditySeconds(3600 * 24 * 7)
// 开启记住我功能
.rememberMeParameter("remember-me")
.and()
//禁用csrf
.csrf().disable()
// header response的X-Frame-Options属性设置为SAMEORIGIN
.headers().frameOptions().sameOrigin()
.and()
// 配置session管理
.sessionManagement()
//session失效默认的跳转地址
.invalidSessionUrl("/loginPage");
}
}
3、登录成功监听器(记录登录日志)
创建监听器,在登录成功的时候记录登录日志。
@Slf4j:
@Slf4j是 Lombok 提供的注解,用于自动生成日志对象,这里是为了方便使用日志。
@Component:
@Component注解将类标识为一个 Spring 组件,使得 Spring 能够自动扫描并将其纳入容器管理。
AuthenticationSuccessListener 实现 ApplicationListener 接口:
AuthenticationSuccessListener类实现了ApplicationListener<AuthenticationSuccessEvent>接口,表明它是一个事件监听器,监听的是用户认证成功的事件。
SysLoginLogService 注入:
SysLoginLogService是一个服务类,通过@Autowired注解注入到当前类中。该服务类用于对登录日志的持久化操作。
onApplicationEvent 方法:
onApplicationEvent方法是实现ApplicationListener接口的回调方法,在用户认证成功的时候会被触发。- 通过
authenticationSuccessEvent.getAuthentication().getPrincipal()获取登录的用户信息,这里假设用户信息是User类型。 - 通过
ServletUtil.getClientIP获取客户端的IP地址,这里使用了ServletUtil工具类,可以通过请求上下文获取IP地址。 - 创建一个
SysLoginLog对象,将登录成功的相关信息设置进去,包括账号、登录IP、备注等。 - 调用
sysLoginLogService.save(sysLoginLog)将登录日志持久化存储。
总的来说,这段代码的作用是在用户成功登录后,通过监听 Spring Security 的认证成功事件,记录用户的登录日志信息,包括登录账号、登录IP和登录成功的备注。这样可以实现登录成功后的自定义操作,例如记录登录日志等。
@Slf4j
@Component
public class AuthenticationSuccessListener implements ApplicationListener<AuthenticationSuccessEvent> {
@Autowired
private SysLoginLogService sysLoginLogService;
@Override
public void onApplicationEvent(AuthenticationSuccessEvent authenticationSuccessEvent) {
// 登录账号
User user = (User) authenticationSuccessEvent.getAuthentication().getPrincipal();
// 请求IP
String ip = ServletUtil.getClientIP(((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(), "");
SysLoginLog sysLoginLog = new SysLoginLog();
sysLoginLog.setAccount(user.getUsername());
sysLoginLog.setLoginIp(ip);
sysLoginLog.setRemark("登录成功");
sysLoginLogService.save(sysLoginLog);
}
}
4、登录失败监听器(记录登录日志)
创建监听器,在登录失败的时候记录异常登录日志。
@Slf4j
@Component
public class AuthenticationFailureListener implements ApplicationListener<AbstractAuthenticationFailureEvent> {
@Autowired
private SysLoginLogService sysLoginLogService;
@Override
public void onApplicationEvent(AbstractAuthenticationFailureEvent abstractAuthenticationFailureEvent) {
// 登录账号
String username = abstractAuthenticationFailureEvent.getAuthentication().getPrincipal().toString();
// 登录失败原因
String message ;
if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureBadCredentialsEvent) {
//提供的凭据是错误的,用户名或者密码错误
message = "提供的凭据是错误的,用户名或者密码错误";
} else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureCredentialsExpiredEvent) {
//验证通过,但是密码过期
message = "验证通过,但是密码过期";
} else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureDisabledEvent) {
//验证过了但是账户被禁用
message = "验证过了但是账户被禁用";
} else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureExpiredEvent) {
//验证通过了,但是账号已经过期
message = "验证通过了,但是账号已经过期";
} else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureLockedEvent) {
//账户被锁定
message = "账户被锁定";
} else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureProviderNotFoundEvent) {
//配置错误,没有合适的AuthenticationProvider来处理登录验证
message = "配置错误";
} else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureProxyUntrustedEvent) {
// 代理不受信任,用于Oauth、CAS这类三方验证的情形,多属于配置错误
message = "代理不受信任";
} else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureServiceExceptionEvent) {
// 其他任何在AuthenticationManager中内部发生的异常都会被封装成此类
message = "内部发生的异常";
} else {
message = "其他未知错误";
}
// 请求IP
String ip = ServletUtil.getClientIP(((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(), "");
SysLoginLog sysLoginLog = new SysLoginLog();
sysLoginLog.setAccount(username);
sysLoginLog.setLoginIp(ip);
sysLoginLog.setRemark(message);
sysLoginLogService.save(sysLoginLog);
}
}
5、认证成功处理器
下面是一个认证成功处理器,登录成功后,会返回响应的信息给前端
@Component
@Slf4j
public class DefaultAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
log.info("-----login in success----");
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(JSON.toJSONString(Result.success()));
writer.flush();
}
}
.successHandler(defaultAuthenticationSuccessHandler)
.failureHandler(defaultAuthenticationFailureHandler)
6、认证失败处理器
下面是一个认证成功处理器,登录成功后,会返回响应的信息给前端
@Component
@Slf4j
public class DefaultAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.info("login in failure : " + exception.getMessage());
response.setContentType("application/json;charset=utf-8");
response.setCharacterEncoding("utf-8");
PrintWriter writer = response.getWriter();
String message;
if (exception instanceof BadCredentialsException) {
message = "用户名或密码错误,请重试。";
writer.write(JSON.toJSONString(Result.failure(message)));
}else{
writer.write(JSON.toJSONString(Result.failure(exception.getMessage())));
}
writer.flush();
}
.successHandler(defaultAuthenticationSuccessHandler)
.failureHandler(defaultAuthenticationFailureHandler)
7、前端页面
返回200,就代表成功,跳转到/请求,进去index或者main页面
if (result.code === 200) {
layer.msg('登入成功', {
icon: 1
,time: 1000
}, function(){
window.location.href = '/';
});
} else {

总结
AuthenticationSuccessEvent 是 Spring Security 中用于表示用户认证成功的事件。判断登录成功的主要依据是在认证过程中,用户提供的凭据(通常是用户名和密码)与系统中存储的凭据匹配。以下是判断登录成功的基本流程:
- 用户提交登录表单:
- 用户在浏览器中输入用户名和密码,然后点击登录按钮,提交登录表单。
- Spring Security 拦截登录请求:
- 配置的
.loginProcessingUrl("/login")指定了登录请求的URL,Spring Security会拦截这个URL的请求。
- 配置的
- UsernamePasswordAuthenticationFilter处理登录请求:
UsernamePasswordAuthenticationFilter是 Spring Security 内置的过滤器之一,用于处理用户名密码登录认证。- 当用户提交登录表单时,
UsernamePasswordAuthenticationFilter会拦截该请求,尝试进行身份验证。
- AuthenticationManager执行身份验证:
UsernamePasswordAuthenticationFilter将用户名密码等信息封装成一个UsernamePasswordAuthenticationToken。- 通过
AuthenticationManager进行身份验证,AuthenticationManager是一个接口,实际的实现为ProviderManager。 ProviderManager通过配置的AuthenticationProvider来执行实际的身份验证逻辑。
- AuthenticationProvider处理身份验证:
DaoAuthenticationProvider是AuthenticationProvider的默认实现之一,用于处理基于数据库的身份验证。DaoAuthenticationProvider会从配置的UserDetailsService中获取用户信息,然后与用户提交的信息进行比对。
- 认证成功:
- 如果认证成功,
AuthenticationProvider会返回一个已认证的Authentication对象。 - 这个已认证的
Authentication对象包含了用户的信息,通常是UserDetails的实现。
- 如果认证成功,
- AuthenticationSuccessHandler处理认证成功:
- 在配置中,通过
.successHandler()方法指定了处理认证成功的AuthenticationSuccessHandler。 - 在这个处理器中,可以执行一些额外的逻辑,例如记录登录日志等。
- 在配置中,通过
- AuthenticationSuccessEvent被发布:
- 在处理成功的阶段,Spring Security 发布了
AuthenticationSuccessEvent事件,表示认证成功。
- 在处理成功的阶段,Spring Security 发布了
在上述流程中,认证成功的判断主要是在 AuthenticationProvider 中完成的。DaoAuthenticationProvider 会检查用户提供的密码与数据库中存储的密码是否匹配。如果匹配,就认为认证成功。当认证成功后,后续的处理流程包括 AuthenticationSuccessHandler 的执行和 AuthenticationSuccessEvent 的发布。你可以通过监听 AuthenticationSuccessEvent 事件来执行一些额外的自定义逻辑,例如记录登录日志。

springboot listener、filter登录实战的更多相关文章
- springboot之filter/listener/servlet
简介 SpringBoot可以简化开发流程,但是在其中如何使用传统的J2EE servlet/listener/filter呢 @Bean配置 在Configuration类中加入filter和ser ...
- springBoot 整合 mybatis 项目实战
二.springBoot 整合 mybatis 项目实战 前言 上一篇文章开始了我们的springboot序篇,我们配置了mysql数据库,但是我们sql语句直接写在controller中并且使用 ...
- 清晰梳理最全日志框架关系与日志配置-SpringBoot 2.7.2 实战基础
优雅哥 SpringBoot 2.7.2 实战基础 - 07 - 日志配置 Java 中日志相关的 jar 包非常多,log4j.log4j2.commons-logging.logback.slf4 ...
- web.xml中的主要元素说明(listener, filter, servlet)
web.xml中加载的顺序为:context-param ---> listener ---> filter ---> servlet. listener:主要针对的是对象的操作,如 ...
- Springboot添加filter方法
在springboot添加filter有两种方式: (1).通过创建FilterRegistrationBean的方式(建议使用此种方式,统一管理,且通过注解的方式若不是本地调试,如果在filter中 ...
- Springboot使用Filter以及踩过的坑
Springboot使用Filter以及踩过的坑 在Springboot中使用Filter有两种方式,注解方式,注册bean方式 一.注解@WebFilter 1.实现Filter接口(javax.s ...
- 【SpringBoot】单元测试进阶实战、自定义异常处理、t部署war项目到tomcat9和启动原理讲解
========================4.Springboot2.0单元测试进阶实战和自定义异常处理 ============================== 1.@SpringBoot ...
- 02-02:springboot 整合filter
1.通过注解扫描完成Filter组件的注册 1.1编写filter (添加拦截的servlet) //@WebFilter(filterName = "FirstFilter",u ...
- web.xml之context-param,listener,filter,servlet加载顺序及其周边
先以加载spring为例子看看加载顺序的作用: Spring加载可以利用ServletContextListener 实现,也可以采用load-on-startup Servlet 实现,但比如fil ...
- servlet,listener,filter,interceptor的关系
1.servlet:servlet是一种运行服务器端的java应用程序,具有独立于平台和协议的特性,并且可以动态的生成web页面,它工作在客户端请求与服务器响应的中间层.最早支持 Servlet 技术 ...
随机推荐
- ChatGPT - 圈里的百科
ChatGPT(全名:Chat Generative Pre-trained Transformer),美国OpenAI [1] 研发的聊天机器人程序 [12] ,于2022年11月30日发布 [ ...
- 开发必备,开源 or 免费的 AI 编程助手
AI 大模型的火热,让开发圈近来如虎添翼,各种各样基于 AI 技术的开发者工具和新范式不断涌现,尤其是 Github 和 OpenAI 共同推出的 Copilot X ,更是一骑绝尘.本文推荐一些开源 ...
- PHP截取文章内容
<?php /** * 实现中文字串截取无乱码的方法. */ function getSubstr($string, $start, $length) { if (mb_strlen($stri ...
- 2022年“腾讯杯”大学生程序设计竞赛 死去的 Elo 突然开始攻击我 题解
题目链接:死去的 Elo 突然开始攻击我 容易知道,如果暴力对某个区间而言进行查询,我们可以考虑使用并查集,开一个桶,每次添加一个数 \(val\),那么如果已经存在了 \(val-1\) 或者 \( ...
- NC213912 芭芭拉冲鸭~(续)
题目链接 题目 题目描述 芭芭拉这次来到了一棵字母树,这同样是一棵无根树,每个节点上面有一个小写字母. 芭芭拉想知道,自己从x冲刺到y,从x走到y收集所有字母,选择其中一部分字母组成一个回文串,这个回 ...
- NC208250 牛牛的最美味和最不美味的零食
题目链接 题目 题目描述 牛牛为了减(吃)肥(好),希望对他的零食序列有更深刻的了解,所以他把他的零食排成一列,然后对每一个零食的美味程度都打了分,现在他有可能执行两种操作: eat k:吃掉当前的第 ...
- NC20811 蓝魔法师
题目链接 题目 题目描述 "你,你认错人了.我真的,真的不是食人魔."--蓝魔法师 给出一棵树,求有多少种删边方案,使得删后的图每个连通块大小小于等于k,两种方案不同当且仅当存在一 ...
- Linux 中Yum命令使用方法
Linux系统下常用yum安装命令详解 yum常用安装命令 使用yum安装和卸载软件,有个前提是yum安装的软件包都是rpm格式的. 1.安装killall命令yum install -y psm ...
- Rancher 2.x 安装
Rancher 是一个容器管理平台.Rancher 简化了使用 Kubernetes 的流程. 下面记录一下手动安装Rancher的步骤 1. 部署 Rancher Server 执行以下命令即可( ...
- thymeleaf利用fragment解决html页面间获取context-path问题
问题说明 我使用spring boot+thymeleaf做了个项目,那前台页面都是html,里面有各种api调用和路径跳转. 大家都知道这些路径不能写死,为保证任何情况下路径的正确性,一般都是这种格 ...