SpringSecurity5.7+最新案例 -- 用户名密码+验证码+记住我······

news/2025/1/14 22:11:57/

简介

根据最近一段时间的设计以及摸索,对SpringSecurity进行总结,目前security采用的是5.7+版本,和以前的版本最大的差别就是,以前创建SecurityConfig需要继承WebSecurityConfigurerAdapter,而到了5.7以后,并不推荐这种做法,查了网上一些教程,其实并不好,绝大多数用的都是老版本,所以出此文案。

一些原理什么的,就不过多说明了,一般搜索资料的,其实根本不想你说什么原理 T·T。

写法区别

最大的区别就是不需要继承WebSecurityConfigurerAdapter(官方也开始弃用此方法),所有配置不需要用and()方法链接,采用lambda处理,个人觉得lambda写法更加的美观,可阅读性更高

这是以前的写法
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeHttpRequests().mvcMatchers("/login.html").permitAll().mvcMatchers("/index").permitAll().anyRequest().authenticated().and().formLogin().loginPage("/login.html").loginProcessingUrl("/doLogin").usernameParameter("uname").passwordParameter("passwd").successForwardUrl("/index") 		 //forward 跳转           注意:不会跳转到之前请求路径//.defaultSuccessUrl("/index")   //redirect 重定向    注意:如果之前请求路径,会有优先跳转之前请求路径.failureUrl("/login.html").and().csrf().disable();//关闭 CSRF}
}
这是现在的写法
@Configuration
public class WebSecurityConfigurer{@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {return http.authorizeHttpRequests(auth ->auth.mvcMatchers(loadExcludePath()).permitAll().anyRequest().authenticated()).cors(conf ->conf.configurationSource(corsConfigurationSource())).rememberMe(conf -> {conf.useSecureCookie(true).rememberMeServices(rememberMeServices());}).formLogin(conf ->conf.loginPage(loginPage).defaultSuccessUrl(defaultSuccessUrl, true).failureUrl(loginPage)).logout(conf ->conf.invalidateHttpSession(true).clearAuthentication(true).logoutSuccessUrl(logoutSuccessUrl)).csrf(AbstractHttpConfigurer::disable).build();}
}

案例包含

自定义认证数据源、密码加密、remember-me、session会话管理、csrf漏洞保护、跨域处理、异常处理 等核心模块,授权以后再说

目录结构

image-20230803194417226

核心代码

主配置SecurityConfig
@Configuration
public class SecurityConfig<S extends Session> {// 基于数据库验证,自定义实现UserDetailService@ResourceMyUserDetailsService myUserDetailsService;// 注入自定义认证失败处理器@BeanMyAuthFailureHandler myAuthFailureHandler() {return new MyAuthFailureHandler();}// 注入数据源@ResourceDataSource dataSource;// 注入自定义认证成功处理器@BeanMyAuthSuccessHandler myAuthSuccessHandler() {return new MyAuthSuccessHandler();}// 注入自定义注销登录处理器@BeanMyLogoutSuccessHandler myLogoutSuccessHandler() {return new MyLogoutSuccessHandler();}// 注入自定义未认证访问处理器@BeanMyAuthEntryPointHandler myAuthEntryPointHandler() {return new MyAuthEntryPointHandler();}// 注入自定义session会话管理处理器@BeanMySessionExpiredHandler mySessionExpiredHandler() {return new MySessionExpiredHandler();}// 注入自定义未授权访问处理器@BeanMyAccessDeniedHandler myAccessDeniedHandler() {return new MyAccessDeniedHandler();}// 注入redis-session管理@Resourceprivate FindByIndexNameSessionRepository<S> sessionRepository;// 注入redis-session管理@Beanpublic SpringSessionBackedSessionRegistry<S> sessionRegistry() {return new SpringSessionBackedSessionRegistry<>(sessionRepository);}// 登录urlprivate final String loginUrl = "/login";/*** 配置放行请求*/private String[] loadExcludePath() {return new String[]{"/pm", loginUrl, "/error"};}/*** 配置密码加密规则*/@Beanpublic BCryptPasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}/*** 跨域配置*/@BeanCorsConfigurationSource corsConfigurationSource() {CorsConfiguration corsConfiguration = new CorsConfiguration();corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));corsConfiguration.setAllowedMethods(Collections.singletonList("*"));corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));corsConfiguration.setMaxAge(3600L);UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();source.registerCorsConfiguration("/**", corsConfiguration);return source;}/*** 记住我 令牌 持久化存储*/@Beanpublic PersistentTokenRepository persistentTokenRepository() {// 这个sql可手动执行到数据库中,当setCreateTableOnStartup 为 false的时候String initSql = "create table persistent_logins (username varchar(64) not null, series varchar(64) primary key,token varchar(64) not null, last_used timestamp not null)";JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();//只需要没有表时设置为 true,也就是说,第一次启动的时候设置为true,后续都要设置为falsejdbcTokenRepository.setCreateTableOnStartup(false);jdbcTokenRepository.setDataSource(dataSource);return jdbcTokenRepository;}/*** 记住我 service 注入*/@Beanpublic RememberMeServices rememberMeServices() {return new MyRememberMeServices(UUID.randomUUID().toString(), myUserDetailsService, persistentTokenRepository());}/*** 构建认证管理器*/@BeanAuthenticationManager authenticationManager(HttpSecurity httpSecurity) throws Exception {// 开启自定义userDetail,开启密码加密return httpSecurity.getSharedObject(AuthenticationManagerBuilder.class).userDetailsService(myUserDetailsService).passwordEncoder(passwordEncoder()).and().build();}/*** 自定义认证过滤器*/@Beanpublic MyAuthenticationFilter myAuthenticationFilter(HttpSecurity httpSecurity) throws Exception {MyAuthenticationFilter myAuthenticationFilter = new MyAuthenticationFilter();// 设置认证管理器myAuthenticationFilter.setAuthenticationManager(authenticationManager(httpSecurity));// 设置登录成功后返回myAuthenticationFilter.setAuthenticationSuccessHandler(myAuthSuccessHandler());// 设置登录失败后返回myAuthenticationFilter.setAuthenticationFailureHandler(myAuthFailureHandler());// 设置记住我功能  认证登录的时候,往数据库写值使用
//        myAuthenticationFilter.setRememberMeServices(rememberMeServices());return myAuthenticationFilter;}/*** 安全认证过滤器链*/@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {return http.authorizeHttpRequests(auth ->// 配置需要放行的请求auth.mvcMatchers(loadExcludePath()).permitAll()// 除了以上放行请求,其它都需要进行认证.anyRequest().authenticated())// 跨域处理.cors(conf ->// 配置跨域conf.configurationSource(corsConfigurationSource()))// csrf 关闭.csrf(AbstractHttpConfigurer::disable)// csrf 开启请求,并且将login请求放行, 登录成功后,在cookies中会有一个XCSRF-TOKEN的值(value)// 后续的所有接口,在 header中加入 X-XSRF-TOKEN:value即可
//                .csrf(conf -> conf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).ignoringAntMatchers(loginUrl))// 开启请求登录.formLogin(// 这里是自定义json body请求登录// 将默认登录页面关闭,只能采用发请求的方式登录AbstractHttpConfigurer::disable// 这里适用于form表单登录
//                        conf ->
//                                conf.successHandler(myAuthSuccessHandler())
//                                        .failureHandler(myAuthFailureHandler()))// 开启记住我功能  --  自动登录的时候使用
//                .rememberMe(conf ->
//                        conf
//                                .useSecureCookie(true)
//                                .rememberMeServices(rememberMeServices())
//                                .tokenRepository(persistentTokenRepository())
//                )// 请求 未认证,未授权 时提示.exceptionHandling(conf ->// 未认证conf.authenticationEntryPoint(myAuthEntryPointHandler())// 未授权.accessDeniedHandler(myAccessDeniedHandler()))// 注销登录返回提示.logout(conf ->conf.logoutSuccessHandler(myLogoutSuccessHandler()).invalidateHttpSession(true).clearAuthentication(true))// session 会话管理.sessionManagement(conf ->// 同一个用户 只允许 创建 多少个 会话conf.maximumSessions(2)// 同一个用户登录之后,禁止再次登录.maxSessionsPreventsLogin(true)// 会话过期处理.expiredSessionStrategy(mySessionExpiredHandler())// 会话信息注册,交由redis管理// 需要引入 org.springframework.boot:spring-boot-starter-data-redis// 和 org.springframework.session:spring-session-data-redis
//                                .sessionRegistry(sessionRegistry()))// 自定义过滤器替换 默认的 UsernamePasswordAuthenticationFilter// 如果用form表单登录,这里的过滤器就需要注释掉.addFilterAt(myAuthenticationFilter(http), UsernamePasswordAuthenticationFilter.class)// 构建 HttpSecurity.build();}}
MyAuthenticationFilter
public class MyAuthenticationFilter extends UsernamePasswordAuthenticationFilter {@Overridepublic Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {if (!request.getMethod().equals("POST")) {throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());}Map<String, String> loginInfo;try {loginInfo = new ObjectMapper().readValue(request.getInputStream(), Map.class);} catch (IOException e) {throw new RuntimeException(e);}String username = loginInfo.get(getUsernameParameter());// 用来接收用户名String password = loginInfo.get(getPasswordParameter());// 用来接收密码String code = loginInfo.get("code");// 用来接收验证码// 获取记住我 值String rememberValue = loginInfo.get(AbstractRememberMeServices.DEFAULT_PARAMETER);if (!ObjectUtils.isEmpty(rememberValue)) {request.setAttribute(AbstractRememberMeServices.DEFAULT_PARAMETER, rememberValue);}// 咱们 假装 对验证码进行校验if (StringUtils.isEmpty(code))throw new BadCredentialsException("验证码不能为空 !");if (!"123".equalsIgnoreCase(code))throw new BadCredentialsException("验证码错误 !");UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);setDetails(request, authRequest);return this.getAuthenticationManager().authenticate(authRequest);}
}
MyRememberMeServices
/*** 自定义记住我 services 实现类*/
public class MyRememberMeServices extends PersistentTokenBasedRememberMeServices {public MyRememberMeServices(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) {super(key, userDetailsService, tokenRepository);}/*** 自定义前后端分离获取 remember-me 方式*/@Overrideprotected boolean rememberMeRequested(HttpServletRequest request, String parameter) {Object paramValue = request.getAttribute(parameter);if (paramValue != null) {String paramValue2 = paramValue.toString();return paramValue2.equalsIgnoreCase("true") || paramValue2.equalsIgnoreCase("on")|| paramValue2.equalsIgnoreCase("yes") || paramValue2.equals("1");}return false;}}
MyUserDetails
public class MyUserDetails implements UserDetails {private final String uname;private final String passwd;public MyUserDetails(String uname, String passwd) {this.uname = uname;this.passwd = passwd;}// 这里是设置权限的,需要使用的话,你自定义吧// 也就是说,在MyUserDetailsService中从数据库里获取,然后调用set方法即可@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return AuthorityUtils.createAuthorityList();}@Overridepublic String getPassword() {return passwd;}@Overridepublic String getUsername() {return uname;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}
MyUserDetailsService
@Service
public class MyUserDetailsService implements UserDetailsService {// 这里是获取数据库里的数据,你自定义把@ResourceUserRepository userRepository;@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 1. find userList<UserEntity> users = userRepository.findByName(username);if (CollectionUtils.isEmpty(users)) throw new UsernameNotFoundException("用户不存在");UserEntity user = users.get(0);return new MyUserDetails(user.getName(), user.getPasswd());}
}
handler
MyAccessDeniedHandler
public class MyAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding(StandardCharsets.UTF_8.name());response.setStatus(HttpStatus.FORBIDDEN.value());response.getWriter().write("无权访问!");}
}
MyAuthEntryPointHandler
public class MyAuthEntryPointHandler implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding(StandardCharsets.UTF_8.name());response.setStatus(HttpStatus.BAD_REQUEST.value());response.getWriter().println("必须认证之后才能访问!");}
}
MyAuthFailureHandler
public class MyAuthFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {Map<String, Object> result = new HashMap<>();result.put("msg", "登录失败: " + exception.getMessage()); // 用户名或密码错误response.setStatus(HttpStatus.UNAUTHORIZED.value());response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding(StandardCharsets.UTF_8.name());String s = new ObjectMapper().writeValueAsString(result);response.getWriter().println(s);}
}
MyAuthSuccessHandler
public class MyAuthSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {Map<String, Object> result = new HashMap<>();result.put("msg", "登录成功");response.setStatus(HttpStatus.OK.value());response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding(StandardCharsets.UTF_8.name());String s = new ObjectMapper().writeValueAsString(result);response.getWriter().println(s);}
}
MyLogoutSuccessHandler
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {Map<String, Object> result = new HashMap<>();result.put("msg", "注销成功");response.setStatus(HttpStatus.OK.value());response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding(StandardCharsets.UTF_8.name());String s = new ObjectMapper().writeValueAsString(result);response.getWriter().println(s);}
}
MySessionExpiredHandler
public class MySessionExpiredHandler implements SessionInformationExpiredStrategy {@Overridepublic void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException, ServletException {HttpServletResponse response = event.getResponse();Map<String, Object> result = new HashMap<>();result.put("msg", "当前会话已经失效,请重新登录!");response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.setCharacterEncoding(StandardCharsets.UTF_8.name());response.setStatus(HttpStatus.BAD_REQUEST.value());String s = new ObjectMapper().writeValueAsString(result);response.getWriter().println(s);response.flushBuffer();}
}

http://www.ppmy.cn/news/1001826.html

相关文章

学习系统编程No.35【基于信号量的CP问题】

引言&#xff1a; 北京时间&#xff1a;2023/8/2/12:52&#xff0c;时间飞逝&#xff0c;恍惚间已经来到了八月&#xff0c;给我的第一感觉就是快开学了&#xff0c;别的感觉其实没有&#xff0c;哈哈&#xff01;看着身边的好友网络相关知识都要全部学完了&#xff0c;就好像…

JS中常见的模块管理规范梳理

一、CommonJS规范 CommonJS规范是一种用于JavaScript模块化开发的规范&#xff0c;它定义了模块的导入、导出方式和加载机制&#xff0c;主要用在Node开发中。 1. 使用场景 服务器端开发&#xff1a;Node.js是使用CommonJS规范的&#xff0c;因此在服务器端开发中&#xff0…

初识集合和背后的数据结构

目录 集合 Java集合框架 数据结构 算法 集合 集合&#xff0c;是用来存放数据的容器。其主要表现为将多个元素置于一个单元中&#xff0c;用于对这些元素进行增删查改。例如&#xff0c;一副扑克牌(一组牌的集合)、一个邮箱(一组邮件的集合&#xff09;。 Java中有很多种集…

Netty 入门指南

文章目录 前言Netty介绍Netty发展历程Netty核心组件实现HTTP服务器总结 前言 上文《BIO、NIO、IO多路复用模型详细介绍&Java NIO 网络编程》介绍了几种IO模型以及Java NIO&#xff0c;了解了在网络编程时使用哪种模型可以提高系统性能及效率。即使Java NIO可以帮助开发人员…

考研408 | 【计算机网络】概述

计算机网络体系结构 计算机网络概述&#xff1a;1.概念&#xff0c;组成&#xff0c;功能&#xff0c;分类2.标准化工作及相关组织3.性能指标体系结构&参考模型&#xff1a;1.分层结构2.协议&#xff0c;接口&#xff0c;服务3.ISO/OSI模型4.TCP/IP模型 目录 计算机网络体…

高质量代码究竟依赖设计还是重构而来?

点击链接了解详情 导读 一个有所追求的程序员一定都希望自己能够写出高质量的代码&#xff0c;但高质量代码从何而来呢&#xff1f;有人认为是设计出来的&#xff0c;就像一栋稳固的大厦&#xff0c;如果没有前期优秀的设计那么肯定难逃豆腐渣工程的命运&#xff1b;也有人认为…

策略模式:优雅地实现可扩展的设计

策略模式&#xff1a;优雅地实现可扩展的设计 摘要&#xff1a; 策略模式是一种常用的设计模式&#xff0c;它可以帮助我们实现可扩展的、灵活的代码结构。本文将通过一个计算器案例来介绍策略模式的概念、使用场景以及如何在实际项目中应用策略模式来提高代码的可维护性和可扩…

【学习】Java全栈相关学习教程

包含: Java 基础, Java 部分源码, JVM, Spring, Spring Boot, Spring Cloud, 数据库原理, MySQL, ElasticSearch, MongoDB, Docker, k8s, CI&CD, Linux, DevOps, 分布式, 中间件, 开发工具, Git, IDE, 源码阅读&#xff0c;读书笔记, 开源项目... 著作权归pdai所有 原文链接…