news 2026/4/18 23:40:06

SpringSecurity认证原理与实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringSecurity认证原理与实战

项目前期准备

首先我们需要初始化我们的项目。

添加maven依赖

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.4.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.guslegend</groupId> <artifactId>security-management</artifactId> <version>0.0.1-SNAPSHOT</version> <name>security-management</name> <description>security-management</description> <properties> <java.version>11</java.version> </properties> <dependencies> <!--添加thymeleaf依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--添加web依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--添加热部署依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!--添加lombok 依赖 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--添加mp 依赖 --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.2</version> </dependency> <!--添加mysql 依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.21</version> </dependency> <!--添加redis 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>

添加数据库表结构

/* Navicat Premium Data Transfer Source Server : localhost Source Server Type : MySQL Source Server Version : 50540 Source Host : localhost:3306 Source Schema : security_management Target Server Type : MySQL Target Server Version : 50540 File Encoding : 65001 Date: 31/10/2020 14:35:33 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for persistent_logins -- ---------------------------- DROP TABLE IF EXISTS `persistent_logins`; CREATE TABLE `persistent_logins` ( `username` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, `series` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, `token` varchar(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`series`) USING BTREE ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact; -- ---------------------------- -- Records of persistent_logins -- ---------------------------- INSERT INTO `persistent_logins` VALUES ('admin', 'zaPEuwaT/bdMeJNmVtgY8g==', 'ZkVyYVK/o1RJMt8nOO2k0A==', '2020-10-31 10:39:10'); -- ---------------------------- -- Table structure for t_permission -- ---------------------------- DROP TABLE IF EXISTS `t_permission`; CREATE TABLE `t_permission` ( `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号', `permission_name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限名称', `permission_tag` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限标签', `permission_url` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限地址', PRIMARY KEY (`ID`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 9 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; -- ---------------------------- -- Records of t_permission -- ---------------------------- INSERT INTO `t_permission` VALUES (1, '查询所有用户', 'user:findAll', '/user/findAll'); INSERT INTO `t_permission` VALUES (2, '用户添加或修改', 'user:saveOrUpdate', '/user/saveOrUpadate'); INSERT INTO `t_permission` VALUES (3, '用户删除', 'user:delete', '/delete/{id}'); INSERT INTO `t_permission` VALUES (4, '根据ID查询用户', 'user:getById', '/user/{id}'); INSERT INTO `t_permission` VALUES (5, '查询所有商品', 'product:findAll', '/product/findAll'); INSERT INTO `t_permission` VALUES (6, '商品添加或修改', 'product:saveOrUpdate', '/product/saveOrUpadate'); INSERT INTO `t_permission` VALUES (7, '商品删除', 'product:delete', '/product//delete/{id}'); INSERT INTO `t_permission` VALUES (8, '商品是否显示', 'product:show', '/product/show/{id}/{isShow}'); -- ---------------------------- -- Table structure for t_product -- ---------------------------- DROP TABLE IF EXISTS `t_product`; CREATE TABLE `t_product` ( `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id', `name` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT '商品名称', `price` decimal(10, 2) NULL DEFAULT NULL COMMENT '商品价格', `stock` int(11) NULL DEFAULT NULL COMMENT '库存', `is_show` tinyint(4) NULL DEFAULT NULL COMMENT '是否展示', `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact; -- ---------------------------- -- Records of t_product -- ---------------------------- INSERT INTO `t_product` VALUES (1, '华为mate30', 4500.00, 1001, 0, '2020-10-24 13:53:25'); INSERT INTO `t_product` VALUES (2, '红米10', 3500.00, 100, 1, '2020-10-24 13:53:52'); INSERT INTO `t_product` VALUES (3, '苹果12', 6000.00, 100, 1, '2020-10-24 13:54:24'); -- ---------------------------- -- Table structure for t_role -- ---------------------------- DROP TABLE IF EXISTS `t_role`; CREATE TABLE `t_role` ( `ID` int(11) NOT NULL AUTO_INCREMENT COMMENT '编号', `ROLE_NAME` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色名称', `ROLE_DESC` varchar(60) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色描述', PRIMARY KEY (`ID`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; -- ---------------------------- -- Records of t_role -- ---------------------------- INSERT INTO `t_role` VALUES (1, 'ADMIN', '超级管理员'); INSERT INTO `t_role` VALUES (2, 'USER', '用户管理'); INSERT INTO `t_role` VALUES (3, 'PRODUCT', '商品管理员'); INSERT INTO `t_role` VALUES (4, 'PRODUCT_INPUT', '商品录入员'); INSERT INTO `t_role` VALUES (5, 'PRODUCT_SHOW', '商品审核员'); -- ---------------------------- -- Table structure for t_role_permission -- ---------------------------- DROP TABLE IF EXISTS `t_role_permission`; CREATE TABLE `t_role_permission` ( `RID` int(11) NOT NULL COMMENT '角色编号', `PID` int(11) NOT NULL COMMENT '权限编号', PRIMARY KEY (`RID`, `PID`) USING BTREE, INDEX `FK_Reference_12`(`PID`) USING BTREE, CONSTRAINT `FK_Reference_11` FOREIGN KEY (`RID`) REFERENCES `t_role` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT `FK_Reference_12` FOREIGN KEY (`PID`) REFERENCES `t_permission` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; -- ---------------------------- -- Records of t_role_permission -- ---------------------------- INSERT INTO `t_role_permission` VALUES (1, 1); INSERT INTO `t_role_permission` VALUES (2, 1); INSERT INTO `t_role_permission` VALUES (1, 2); INSERT INTO `t_role_permission` VALUES (2, 2); INSERT INTO `t_role_permission` VALUES (1, 3); INSERT INTO `t_role_permission` VALUES (2, 3); INSERT INTO `t_role_permission` VALUES (1, 4); INSERT INTO `t_role_permission` VALUES (2, 4); INSERT INTO `t_role_permission` VALUES (1, 5); INSERT INTO `t_role_permission` VALUES (3, 5); INSERT INTO `t_role_permission` VALUES (4, 5); INSERT INTO `t_role_permission` VALUES (5, 5); INSERT INTO `t_role_permission` VALUES (1, 6); INSERT INTO `t_role_permission` VALUES (3, 6); INSERT INTO `t_role_permission` VALUES (4, 6); INSERT INTO `t_role_permission` VALUES (1, 7); INSERT INTO `t_role_permission` VALUES (3, 7); INSERT INTO `t_role_permission` VALUES (4, 7); INSERT INTO `t_role_permission` VALUES (1, 8); INSERT INTO `t_role_permission` VALUES (3, 8); INSERT INTO `t_role_permission` VALUES (5, 8); -- ---------------------------- -- Table structure for t_user -- ---------------------------- DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL, `password` varchar(100) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL, `status` int(1) NULL DEFAULT NULL COMMENT '用户状态1-启用 0-关闭', PRIMARY KEY (`id`) USING BTREE ) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Compact; -- ---------------------------- -- Records of t_user -- ---------------------------- INSERT INTO `t_user` VALUES (1, 'admin', '$2a$10$m8WqgTzr0TO.XG.aR91.jegJJmDnGSvWs69aMWPR.WNvCzemHpLum', 1); INSERT INTO `t_user` VALUES (2, 'zhaoyang', '$2a$10$m8WqgTzr0TO.XG.aR91.jegJJmDnGSvWs69aMWPR.WNvCzemHpLum', 1); INSERT INTO `t_user` VALUES (3, 'user1', '$2a$10$m8WqgTzr0TO.XG.aR91.jegJJmDnGSvWs69aMWPR.WNvCzemHpLum', 1); INSERT INTO `t_user` VALUES (4, 'user2', '$2a$10$m8WqgTzr0TO.XG.aR91.jegJJmDnGSvWs69aMWPR.WNvCzemHpLum', 1); INSERT INTO `t_user` VALUES (5, 'user3', '$2a$10$Wk1jWJPoMQ5s7UIp0S/tu.WTcUZUspUUQH6K3BQpa8uHXWRUQc3/a', 1); -- ---------------------------- -- Table structure for t_user_role -- ---------------------------- DROP TABLE IF EXISTS `t_user_role`; CREATE TABLE `t_user_role` ( `UID` int(11) NOT NULL COMMENT '用户编号', `RID` int(11) NOT NULL COMMENT '角色编号', PRIMARY KEY (`UID`, `RID`) USING BTREE, INDEX `FK_Reference_10`(`RID`) USING BTREE, CONSTRAINT `FK_Reference_10` FOREIGN KEY (`RID`) REFERENCES `t_role` (`ID`) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT `FK_Reference_9` FOREIGN KEY (`UID`) REFERENCES `t_user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT ) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; -- ---------------------------- -- Records of t_user_role -- ---------------------------- INSERT INTO `t_user_role` VALUES (1, 1); INSERT INTO `t_user_role` VALUES (2, 2); INSERT INTO `t_user_role` VALUES (3, 4); INSERT INTO `t_user_role` VALUES (4, 5); SET FOREIGN_KEY_CHECKS = 1;

配置配置文件

server: port: 8080 spring: # Thymeleaf 模板配置 thymeleaf: prefix: classpath:/templates/ suffix: .html mode: LEGACYHTML5 servlet: content-type: text/html encoding: utf-8 cache: false # 静态资源配置 resources: chain: strategy: content: enabled: true paths: /** # 数据库配置 datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://127.0.0.1:3307/security_management username: root password: 123456 # Redis 配置(默认注释状态) redis: database: 7 host: 127.0.0.1 port: 6379 password: 123456

访问网站:http://127.0.0.1:8080/

访问网站:http://127.0.0.1:8080/toLoginPage

SpringSecurity认证基本原理与认证2中方式

过滤链介绍

首先我们需要添加SpringSecurity的maven依赖

<!--添加Spring Security 依赖 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
  • 在使用SpringSecurity框架时,该框架会默认自动地替我们将系统中的资源进行保护;
  • 每次访问资源的时候都必须经过一层身份校验;
  • 如果通过了则重定向到我们输入的url中,否则访问会被拒绝;
  • SpringSecurity是通过一系列过滤器相互配合完成的,也被称为过滤器链。


Spring Security 默认过滤器链(15 个)

  1. WebAsyncManagerIntegrationFilter从请求中获取WebAsyncManager,并从中获取 / 注册安全上下文的可调用处理拦截器。
  2. SecurityContextPersistenceFilter通过SecurityContextRepository在 session 中保存 / 更新SecurityContext(存储当前用户认证、权限信息),并传递给后续过滤器。
  3. HeaderWriterFilter向请求 Header 中添加指定信息,可通过security:headers配置控制。
  4. CsrfFilter验证所有 POST 请求是否包含系统生成的 CSRF Token,无则拒绝,防止跨域请求伪造攻击。
  5. LogoutFilter匹配/logout请求,实现用户退出并清除认证信息。
  6. UsernamePasswordAuthenticationFilter处理表单认证,默认匹配/login且仅支持 POST 请求。
  7. DefaultLoginPageGeneratingFilter未配置自定义认证页面时,生成默认登录页。
  8. DefaultLogoutPageGeneratingFilter生成默认的退出登录页面。
  9. BasicAuthenticationFilter自动解析 HTTP 请求头中以Basic开头的Authentication信息。
  10. RequestCacheAwareFilter通过HttpSessionRequestCache缓存HttpServletRequest
  11. SecurityContextHolderAwareRequestFilter包装ServletRequest,使其具备更丰富的 API。
  12. AnonymousAuthenticationFilterSecurityContextHolder中无认证信息,则创建匿名用户存入,兼容未登录访问。
  13. SessionManagementFilter通过securityContextRepository限制同一用户的并发会话数量。
  14. ExceptionTranslationFilter位于过滤器链后方,转换链路中出现的异常。
  15. FilterSecurityInterceptor获取资源访问的授权信息,根据SecurityContextHolder中的用户信息判断是否有权限。

认证方式

  1. HttpBasic认证

登录模式是用户名密码使用Base64模式进行加密;

Base64的加密算法是可逆的,破解不难;

  1. formLogin认证

只是进行了通过携带Http的Header进行简单的登录验证。

表单认证

自定义表单登录页面

首先我们需要添加配置类SecurityConfiguration

@Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { super.configure(auth); } @Override protected void configure(HttpSecurity http) throws Exception { http.httpBasic()// 开启httpBasic认证 .and().authorizeRequests().anyRequest().authenticated();// 任何请求,登录后可以访问 } @Override public void configure(WebSecurity web) throws Exception { super.configure(web); } }

首先我们需要解决重定向问题

这个出现的原因是登录页login.html后配置的是所以请求都登录认证,陷入了死循环,所以login.html不放。

@Override protected void configure(HttpSecurity http) throws Exception { // http.httpBasic()// 开启httpBasic认证 // .and().authorizeRequests().anyRequest().authenticated();// 任何请求,登录后可以访问 http.formLogin().loginPage("/login.html")// 登录页面 .and().authorizeRequests() .antMatchers("/login.html").permitAll()// 登录页面可以匿名访问 .anyRequest().authenticated(); }

接下来我们需要解决访问页面404的问题,因为我们的项目使用了thymeleaf,那么所以的静态页面要放在resource/template下面,所以改为

@Override protected void configure(HttpSecurity http) throws Exception { // http.httpBasic()// 开启httpBasic认证 // .and().authorizeRequests().anyRequest().authenticated();// 任何请求,登录后可以访问 http.formLogin().loginPage("/toLoginPage")// 登录页面 .and().authorizeRequests() .antMatchers("/toLoginPage").permitAll()// 登录页面可以匿名访问 .anyRequest().authenticated(); }

我们发现我们的css样式没有显示成功,接下来要放行css样式

@Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/css/**", "/js/**", "/fonts/**", "/img/**","/favicon.ico"); }

SpringSecurity中,安全构建器HttpSecurity和WebSecurity的区别为:

  1. webSecurity不仅控制httpSecurity定义某些请求的安全控制,也通过其他方式定义其他某些请求可以忽略安全控制;
  2. httpSecurity仅用于定义需要安全控制的请求(当然httpSecurity也可以指定某些请求不需要安全控制);
  3. 可以认为httpSecurity是WebSecurity的一部分。

表单登录

接下来,我们来分析表达登录功能怎么实现,首先我们需要到UsernamePasswordAuthenticationFilter这个过滤器里面

通过观察我们可以发现,表单中的input的name值是username和password,并且表单提交的路径为/login,表单提交的方式为method为post请求。

@Override protected void configure(HttpSecurity http) throws Exception { // http.httpBasic()// 开启httpBasic认证 // .and().authorizeRequests().anyRequest().authenticated();// 任何请求,登录后可以访问 http.formLogin().loginPage("/toLoginPage")// 登录页面 .loginProcessingUrl("/login")// 登录处理接口url .usernameParameter("username").passwordParameter("password")//修改登录参数 .successForwardUrl("/")// 登录成功后跳转的页面 .and().authorizeRequests() .antMatchers("/toLoginPage").permitAll()// 登录页面可以匿名访问 .anyRequest().authenticated(); http.csrf().disable();// 关闭csrf }

这个时候又出现了新的问题

发现行内框架iframe这里出现问题了. Spring Security下,X-Frame-Options默认为DENY,非Spring Security环境下,X-Frame-Options的默认大多也是DENY,这种情况下,浏览器拒绝当前页面加载任何 Frame页面,设置含义如下:

  • DENY:浏览器拒绝当前页面加载任何Frame页面 此选择是默认的.
  • SAMEORIGIN:frame页面的地址只能为同源域名下的页面

允许加载iframe

@Override protected void configure(HttpSecurity http) throws Exception { // http.httpBasic()// 开启httpBasic认证 // .and().authorizeRequests().anyRequest().authenticated();// 任何请求,登录后可以访问 http.formLogin().loginPage("/toLoginPage")// 登录页面 .loginProcessingUrl("/login")// 登录处理接口url .usernameParameter("username").passwordParameter("password")//修改登录参数 .successForwardUrl("/")// 登录成功后跳转的页面 .and().authorizeRequests() .antMatchers("/toLoginPage").permitAll()// 登录页面可以匿名访问 .anyRequest().authenticated(); http.csrf().disable();// 关闭csrf http.headers().frameOptions().sameOrigin();// 允许同源iframe访问 }

基于数据库实现认证功能

  • 之前我们登录时候使用的用户名和密码是源于框架自己本身自动生成的;
  • 那么我们如何实现基于数据库中的用户名和密码功能呢?
  • 要实现这个功能需要security的一个UserDetailsService接口,要重写这个接口里面的loadUserByUsername即可。

首先我们需要定义一个方法去实现UserDetailsService接口,重新loadUserByUsername方法

@Service public class MyUserDetailsServiceImpl implements MyUserDetailsService { @Autowired private UserService userService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userService.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("用户不存在"); } Collection<? extends GrantedAuthority> authorities = new ArrayList<>(); UserDetails userDetails = new org.springframework.security.core.userdetails.User( user.getUsername(), "{noop}" + user.getPassword(), // 测试用明文密码 authorities ); return userDetails; } }

然后他springSecurity去指定自定义用户认证

@Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(myUserDetailsService); }

密码加密认证

  • 前面我们使用数据库登录用户的过程使用的是密码为明文,通过前面加入{noop}前缀,接下来我们需要进行密文加密。
  • Spring Security中PasswordEncoder就是我们对密码进行编码的工具接口,该接口只有两个功能:匹配验证,密码编码。

通过观看源码我们可以知道,我们使用noop表示不加密使用明文密码,现在我们只需要将noop改为bcrypt即可。

@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userService.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("用户不存在"); } Collection<? extends GrantedAuthority> authorities = new ArrayList<>(); UserDetails userDetails = new org.springframework.security.core.userdetails.User( user.getUsername(), "{bcrypt}" + user.getPassword(), // 测试用明文密码 authorities ); return userDetails; }

记得要更改数据库里面的密码

public class passwordTest { public static void main(String[] args) { BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); System.out.println(bCryptPasswordEncoder.encode("123456")); } }

获取当前登录用户

在传统web 项目中,我们将登录成功的用户放到session中,在需要的时候可以从session中获取当前用户,那么我们在spring Security中如何获取呢?

  • SecurityContextHolder:保留系统当前的安全上下文SecurityContext,其中就包含了当前系统使用的用户信息;
  • SecurityContext:安全上下文,获取当前经过身份验证的主体或身份验证请求令牌。
@RequestMapping("/loginUser") @ResponseBody public UserDetails getCurrentUser() { UserDetails userDetails = (UserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); return userDetails; }

remember me 记住我

大多数网站中,都会实现Remember me这个功能,方便用户在下一次登录时直接登录,避免再次输入用户名和密码。

前端网站:

后端开启remember-me功能

@Override protected void configure(HttpSecurity http) throws Exception { // http.httpBasic()// 开启httpBasic认证 // .and().authorizeRequests().anyRequest().authenticated();// 任何请求,登录后可以访问 http.formLogin().loginPage("/toLoginPage")// 登录页面 .loginProcessingUrl("/login")// 登录处理接口url .usernameParameter("username").passwordParameter("password")//修改登录参数 .successForwardUrl("/")// 登录成功后跳转的页面 .and().rememberMe()// 开启记住我功能 .tokenValiditySeconds(60 * 60 * 24 * 7).rememberMeParameter("remember-me") .and().authorizeRequests() .antMatchers("/toLoginPage").permitAll()// 登录页面可以匿名访问 .anyRequest().authenticated(); http.csrf().disable();// 关闭csrf http.headers().frameOptions().sameOrigin();// 允许同源iframe访问 }

持久化token方法

@Override protected void configure(HttpSecurity http) throws Exception { // http.httpBasic()// 开启httpBasic认证 // .and().authorizeRequests().anyRequest().authenticated();// 任何请求,登录后可以访问 http.formLogin().loginPage("/toLoginPage")// 登录页面 .loginProcessingUrl("/login")// 登录处理接口url .usernameParameter("username").passwordParameter("password")//修改登录参数 .successForwardUrl("/")// 登录成功后跳转的页面 .and().rememberMe()// 开启记住我功能 .tokenValiditySeconds(60 * 60 * 24 * 7).rememberMeParameter("remember-me") .tokenRepository(getPersistentTokenRepository())// 设置记住我功能使用的tokenRepository .and().authorizeRequests() .antMatchers("/toLoginPage").permitAll()// 登录页面可以匿名访问 .anyRequest().authenticated(); http.csrf().disable();// 关闭csrf http.headers().frameOptions().sameOrigin();// 允许同源iframe访问 } @Autowired private DataSource dataSource ; private PersistentTokenRepository getPersistentTokenRepository() { JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(dataSource);// 设置数据源 jdbcTokenRepository.setCreateTableOnStartup(true); return jdbcTokenRepository; }

我们查看数据库发现其自动生成了一个表

退出登录

前端代码:

后端代码:

public class MyAuthenticationServiceImpl implements MyAuthenticationService, AuthenticationSuccessHandler, AuthenticationFailureHandler, LogoutSuccessHandler { private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException { AuthenticationSuccessHandler.super.onAuthenticationSuccess(request, response, chain, authentication); } @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { redirectStrategy.sendRedirect(httpServletRequest, httpServletResponse, "/toLoginPage"); } @Override public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { System.out.println("退出成功后续处理..."); } }
@Override protected void configure(HttpSecurity http) throws Exception { // http.httpBasic()// 开启httpBasic认证 // .and().authorizeRequests().anyRequest().authenticated();// 任何请求,登录后可以访问 http.formLogin().loginPage("/toLoginPage")// 登录页面 .loginProcessingUrl("/login")// 登录处理接口url .usernameParameter("username").passwordParameter("password")//修改登录参数 .successForwardUrl("/")// 登录成功后跳转的页面 .and().rememberMe()// 开启记住我功能 .tokenValiditySeconds(60 * 60 * 24 * 7).rememberMeParameter("remember-me") .tokenRepository(getPersistentTokenRepository())// 设置记住我功能使用的tokenRepository .and().logout().logoutUrl("/logout")// 设置退出url .logoutSuccessHandler(myAuthenticationService)// 设置退出成功处理器 .and().authorizeRequests() .antMatchers("/toLoginPage").permitAll()// 登录页面可以匿名访问 .anyRequest().authenticated(); http.csrf().disable();// 关闭csrf http.headers().frameOptions().sameOrigin();// 允许同源iframe访问 }

图片验证码验证

Spring Security生成图片验证码主要是分为三步:

  • 根据随机数生成图片验证码;
  • 将验证码图片显示到登录页面;
  • 认证流程中加入验证码校验。
@Component public class ValidateCodeFilter { @Autowired private MyAuthenticationService myAuthenticationService; @Autowired private StringRedisTemplate stringRedisTemplate; protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException { //判断是否是登录请求,只有登录请求才去校验验证码 if (httpServletRequest.getRequestURI().equals("/login") && httpServletRequest.getMethod().equalsIgnoreCase("post")) { try { validate(httpServletRequest); } catch (ValidateCodeException e) { return; } } //如果不是登录请求,直接调用后面的过滤器 filterChain.doFilter(httpServletRequest, httpServletResponse); } private void validate(HttpServletRequest request) throws ServletRequestBindingException, ValidateCodeException { //获取ip String remoteAddr = request.getRemoteAddr(); //拼接redis的key String redisKey = ValidateCodeController.REDIS_KEY_IMAGE_CODE + "-" + remoteAddr; //从redis获取imageCode String redisImageCode = stringRedisTemplate.boundValueOps(redisKey).get(); String imageCode = request.getParameter("imageCode"); if (StringUtils.isEmpty(imageCode)) { throw new ValidateCodeException("验证码的值不能为空!"); } if (redisImageCode == null) { throw new ValidateCodeException("验证码已过期!"); } if (!redisImageCode.equals(imageCode)) { throw new ValidateCodeException("验证码不正确!"); } // 从redis中删除imageCode stringRedisTemplate.delete(redisKey); } }

Session管理

Session库可以做一些简单的配置可以实现会话过期,单点登录等

会话超时

  1. 首先要配置session超时时间,默认时间为30分钟
server: servlet: session: timeout: 60
  1. 自定义设置session超时后跳转的地址
http.sessionManagement().invalidSessionUrl("/toLoginPage");// session失效后跳转的页面

并发控制

并发控制即同一个账号同时在线个数,同一个账号在线个数如果为1表示,该账号在同一时间内只能有一个有效登录;

  1. 修改超时时间
server: servlet: session: timeout: 60
  1. 设置最大会话数量
http.sessionManagement()// session管理 .invalidSessionUrl("/toLoginPage")// session失效后跳转的页面 .maximumSessions(1)// 同一个用户最多只能登录一个session .expiredUrl("/toLoginPage");// session失效后跳转的页面

集群session

在实际应用场景中,一个服务至少有两台服务器在使用,我们怎么让这两个session做共享?

  1. 首先我们需要引入redis的session共享依赖
<!-- 基于redis实现session共享 --> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>
  1. 然后设置session存储类型
spring: session: store-type: redis

CSRF防护机制

你可以这么理解 CSRF 攻击:攻击者盗用了你的身份,以你的名义发送恶意请求。CSRF 能够做的事情包括:以你名义发邮件、发消息、盗取你的账号,甚至于购买商品、虚拟货币转账…… 造成的问题包括:个人隐私泄露以及财产安全。

CSRF 这种攻击方式在 2000 年就被国外的安全人员提出,但在国内,直到 06 年才开始被关注,08 年,国内外的多个大型社区和交互网站分别爆出 CSRF 漏洞,如:Metafilter(一个大型的 BLOG 网站)、YouTube 和百度 HI…… 而现在,互联网上的许多站点仍对此毫无防备,以至于安全业界称 CSRF 为 “沉睡的巨人”。

csrf攻击的三步骤:

  1. 登录授信网站A,本地生成cookie;
  2. 在不登出A情况,访问危险网站B;
  3. 触发网站B中的一些元素。
//开启csrf防护, 可以设置哪些不需要防护 http.csrf().ignoringAntMatchers("/user/save");

跨域

声明跨域配置源

@Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration corsConfiguration = new CorsConfiguration(); // 设置允许跨域的站点 // corsConfiguration.addAllowedOrigin("*"); // 设置允许跨域的http方法 corsConfiguration.addAllowedMethod("*"); // 设置允许跨域的请求头 corsConfiguration.addAllowedHeader("*"); // 允许带cookie corsConfiguration.setAllowCredentials(true); // 对所有的url生效 UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfiguration); return source; }

开启跨域支持

http.cors().configurationSource(corsConfigurationSource());
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 9:13:10

第1节:项目性能优化(上)

本章学习目标&#xff1a; 了解应用性能问题分析方法论&#xff1b;掌握压力测试基础概念&#xff1b;掌握压力测试&#xff1a;线程组配置&#xff0c;结果分析&#xff0c;插件使用&#xff1b;理解性能关键的指标&#xff1b; 性能问题分析方法论 首先我们需要知道性能优化…

作者头像 李华
网站建设 2026/4/17 23:22:06

学习日记day51

Day51_1216专注时间&#xff1a;2H59min每日任务&#xff1a;2h复习数据库&#xff08;完成情况及时长&#xff1a;&#xff09;&#xff1b;1h二刷2道力扣hot100(如果是hard&#xff0c;只做一道就好&#xff0c;完成情况及时长&#xff1a;今天都在做算法题&#xff0c;也懈怠…

作者头像 李华
网站建设 2026/4/18 15:48:32

FlutterOpenHarmony商城App订单列表组件开发

前言 订单列表是商城应用中用户查看和管理订单的核心页面&#xff0c;用户可以在这里查看所有订单的状态、进行订单操作如取消、确认收货、申请退款等。一个设计良好的订单列表组件需要清晰地展示订单信息&#xff0c;并提供便捷的操作入口。本文将详细介绍如何在Flutter和Open…

作者头像 李华
网站建设 2026/4/17 18:48:01

了解陇南支腿凿岩机出厂行情查询报价享折扣

在矿山、隧道及大型基建工程中&#xff0c;凿岩设备的选型常因需求错配与参数混乱而陷入低效甚至停工风险。面对风动凿岩机、手持式气动凿岩机、气腿式凿岩机等众多品类&#xff0c;用户往往难以精准匹配作业场景与设备性能——尤其在陇南这类地形复杂、岩石硬度多变的区域&…

作者头像 李华
网站建设 2026/4/18 6:11:16

金仓新势力:不止兼容,三重革新引领数据库未来

兼容 是对企业历史投资的尊重 是确保业务平稳过渡的基石 然而 这仅仅是故事的起点 在数字化转型的深水区&#xff0c;企业对数据库的需求早已超越“语法兼容”的基础诉求。无论是核心业务系统的稳定运行&#xff0c;还是敏感数据的安全防护&#xff0c;亦或是复杂场景下的性能优…

作者头像 李华
网站建设 2026/4/17 23:15:50

AI agent 最新 进展

AI Agent 最新进展&#xff08;2025 年 12 月&#xff09; 一、巨头竞相发布新一代 Agent 1. 谷歌&#xff1a;Gemini Deep Research Agent&#xff08;12 月 11 日&#xff09; 性能突破&#xff1a;在 "人类最后的考试"(HLE) 测试中达46.4%&#xff0c;超越 GPT-5 …

作者头像 李华