本文通过逐步学习Spring Security,由浅入深,SpringBoot整合Spring Security 分别实现自定义的HTTP Basic认证 和 Form表单认证。
本文是学习笔记,网上的教程五花八门,由于时间久远,很难拿来就用。
在此特别感谢@IT老齐 老师,带我完整的用代码实现了一遍Spring Security的基本使用。
目录
主要内容:
- 用户信息管理
- 敏感信息加密解密
- 用户认证
- 权限控制
- 跨站点请求伪造保护
- 跨域支持
- 全局安全方法
- 单点登录
一、Spring Security 快速开始
$ tree -I test . ├── pom.xml └── src └── main ├── java │ └── com │ └── example │ └── demo │ ├── Application.java │ └── controller │ └── IndexController.java └── resources ├── application.yml ├── static └── templates
引入Spring Security依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
完整依赖 pom.xml
<?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.7.7</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <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> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
配置 application.yml
server: port: 8080
启动类 Application.java
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
控制器 IndexController.java
package com.example.demo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class IndexController { @GetMapping("/") public String index(){ return "Hello"; } }
直接访问应用会被重定向到登录页面
http://localhost:8080/ => 302 http://localhost:8080/login
现在使用默认的账号密码登录
- 默认的用户名:user
- 默认的密码:(控制台打印出的密码)
Using generated security password: cdd28beb-9a64-4130-be58-6bde1684476d
可以看到返回结果
二、认证与授权
- 认证 authentication 用户身份
- 授权 authorization 用户权限
单体应用
微服务架构
三、Spring Security基础认证与表单认证
认证方式 | 有无状态 | 简介 | 应用场景 |
基础认证 | 无状态 | 不使用cookie | 对外API |
表单认证 | 有状态 | 使用session会话 | 网站应用 |
- 用户对象 UserDetails
- 内存存储
- 数据库存储
- 认证对象 Authentication
- HTTP基础认证
- HTTP表单认证
1、HTTP基础认证
通过HTTP请求头携带用户名和密码进行登录认证
HTTP请求头格式
# 用户名和密码的Base64编码 Authonrization: Basic Base64-encoded(username:password)
Spring Boot2.4版本以前
package com.example.demo.config; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; @Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { // 所有请求都需要认证,认证方式:httpBasic http.authorizeHttpRequests((auth) -> { auth.anyRequest().authenticated(); }).httpBasic(Customizer.withDefaults()); } }
Spring Boot2.4版本之后
package com.example.demo.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration public class SecurityConfiguration { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // 所有请求都需要认证,认证方式:httpBasic http.authorizeHttpRequests((auth) -> { auth.anyRequest().authenticated(); }).httpBasic(Customizer.withDefaults()); return http.build(); } }
发送HTTP请求
GET http://localhost:8080/ Authorization: Basic dXNlcjo2ZjRhMGY5ZS1hY2ZkLTRmNTYtYjIzNy01MTZmYmZjMTk3NGM=
可以获得响应数据
Hello
base64解码之后可以得到用户名和密码
atob('dXNlcjo2ZjRhMGY5ZS1hY2ZkLTRmNTYtYjIzNy01MTZmYmZjMTk3NGM=') 'user:6f4a0f9e-acfd-4f56-b237-516fbfc1974c'
2、HTTP表单认证
Spring Security的默认认证方式
四、Spring Security 用户与认证对象
1、用户对象
接口名 | 说明 |
UserDetails | 用户对象 |
GrantedAuthority | 用户权限 |
UserDetailsService | 用户对象查询操作 |
UserDetailsManager | 创建用户、修改用户密码 |
UserDetails 用户对象接口
package org.springframework.security.core.userdetails; import java.io.Serializable; import java.util.Collection; import org.springframework.security.core.GrantedAuthority; public interface UserDetails extends Serializable { // 获取用户权限信息 Collection<? extends GrantedAuthority> getAuthorities(); // 获取密码 java.lang.String getPassword(); // 获取用户名 java.lang.String getUsername(); // 判断账户是否失效 boolean isAccountNonExpired(); // 判断账户是否锁定 boolean isAccountNonLocked(); // 判断账户凭证信息是否已失效 boolean isCredentialsNonExpired(); // 判断账户是否可用 boolean isEnabled(); }
GrantedAuthority 用户拥有权限接口
package org.springframework.security.core; import java.io.Serializable; public interface GrantedAuthority extends Serializable { // 获取权限信息 String getAuthority(); }
UserDetailsService 用户查询操作
package org.springframework.security.core.userdetails; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UsernameNotFoundException; public interface UserDetailsService { // 根据用户名获取用户信息 UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }
UserDetailsManager 用户CRUD操作
package org.springframework.security.provisioning; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; public interface UserDetailsManager extends UserDetailsService { // 创建用户 void createUser(UserDetails user); // 更新用户 void updateUser(UserDetails user); // 删除用户 void deleteUser(String username); // 修改密码 void changePassword(String oldPassword, String newPassword); // 判断用户是否存在 boolean userExists(String username); }
2、认证对象
接口名 | 说明 |
Authentication | 认证请求详细信息 |
AuthenticationProvider | 认证的业务执行者 |
Authentication 认证请求详细信息
package org.springframework.security.core; import java.io.Serializable; import java.security.Principal; import java.util.Collection; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.core.context.SecurityContextHolder; public interface Authentication extends Principal, Serializable { // 安全主体所具有的的权限 Collection<? extends GrantedAuthority> getAuthorities(); // 证明主体有效性的凭证 Object getCredentials(); // 认证请求的明细信息 Object getDetails(); // 主体的标识信息 Object getPrincipal(); // 是否认证通过 boolean isAuthenticated(); // 设置认证结果 void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException; }
AuthenticationProvider 认证的业务执行者
package org.springframework.security.authentication; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; public interface AuthenticationProvider { // 执行认证,返回认证结果 Authentication authenticate(Authentication authentication) throws AuthenticationException; // 判断是否支持当前的认证对象 boolean supports(Class<?> authentication); }
五、基于MySQL自定义认证过程
1、项目结构
$ tree -I target . ├── pom.xml └── src ├── main │ ├── java │ │ └── com │ │ └── example │ │ └── demo │ │ ├── Application.java │ │ ├── controller │ │ │ └── IndexController.java │ │ ├── entity │ │ │ └── User.java │ │ ├── mapper │ │ │ └── UserMapper.java │ │ ├── security │ │ │ ├── SecurityConfiguration.java │ │ │ └── UserAuthenticationProvider.java │ │ └── service │ │ ├── UserService.java │ │ └── impl │ │ └── UserServiceImpl.java │ └── resources │ ├── application.yml │ ├── sql │ │ └── schema.sql │ ├── static │ └── templates └── test ├── http │ └── IndexController.http └── java └── com └── example └── demo └── ApplicationTests.java
2、用户表
默认表结构的SQL路径
spring-security-core-5.7.6.jar!/org/springframework/security/core/userdetails/jdbc/users.ddl
create table users( username varchar_ignorecase(50) not null primary key, password varchar_ignorecase(500) not null, enabled boolean not null ); create table authorities ( username varchar_ignorecase(50) not null, authority varchar_ignorecase(50) not null, constraint fk_authorities_users foreign key(username) references users(username) ); create unique index ix_auth_username on authorities (username,authority);
一般情况下,我们使用自己创建的用户表
schema.sql
-- 创建用户表 CREATE TABLE `tb_user` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', `username` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名', `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码', `nickname` varchar(32) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '昵称', `enabled` tinyint NOT NULL DEFAULT '1' COMMENT '账号可用标识', PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表'; -- 添加初始数据 insert into `tb_user` values (1, "zhangsan", "zhangsan", "张三", 1); insert into `tb_user` values (2, "lisi", "lisi", "李四", 1); insert into `tb_user` values (3, "wangwu", "wangwu", "王五", 1);
3、依赖
- Spring Security
- MyBatis-Plus
- MySQL8 JDBC
- Lombok
完整依赖
pom.xml
<?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.7.7</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>demo</artifactId> <version>0.0.1-SNAPSHOT</version> <name>demo</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <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> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
4、数据库配置
application.yml
server: port: 8080 # DataSource Config spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/data?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: 123456 mybatis-plus: configuration: # 开启SQL语句打印 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: # 自增主键策略 id-type: AUTO
5、SpringBoot基本框架
启动类 Application.java
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); } }
实体类 User.java
package com.example.demo.entity; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Arrays; import java.util.Collection; @Data @TableName("tb_user") public class User implements UserDetails { /** * 主键id */ @TableId private Long id; /** * 用户名 */ private String username; /** * 密码 */ private String password; /** * 昵称 */ private String nickname; /** * 账号可用标识 */ private Integer enabled; /** * 获取用户权限信息 * * @return */ @Override public Collection<? extends GrantedAuthority> getAuthorities() { return Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")); } /** * 判断账户是否失效 * * @return */ @Override public boolean isAccountNonExpired() { return true; } /** * 判断账户是否锁定 * * @return */ @Override public boolean isAccountNonLocked() { return true; } /** * 判断账户凭证信息是否已失效 * * @return */ @Override public boolean isCredentialsNonExpired() { return true; } /** * 判断账户是否可用 * * @return */ @Override public boolean isEnabled() { return this.enabled == 1; } }
UserMapper.java
package com.example.demo.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.demo.entity.User; import org.apache.ibatis.annotations.Mapper; @Mapper public interface UserMapper extends BaseMapper<User> { }
UserService.java
package com.example.demo.service; import com.baomidou.mybatisplus.extension.service.IService; import com.example.demo.entity.User; public interface UserService extends IService<User> { }
UserServiceImpl.java
package com.example.demo.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.demo.entity.User; import com.example.demo.mapper.UserMapper; import com.example.demo.service.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; @Service @Slf4j public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService, UserDetailsService { /** * 根据用户名获取用户信息 * @param username * @return UserDetails * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>(); queryWrapper.eq(User::getUsername, username); User user = super.getOne(queryWrapper); if(user == null){ log.error("Access Denied, user not found:" + username); throw new UsernameNotFoundException("user not found:" + username); } return user; } }
IndexController.java
package com.example.demo.controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class IndexController { @GetMapping("/hello") public String hello(){ return "Hello"; } }
6、自动定义Spring Security
SecurityConfiguration.java
package com.example.demo.security; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration public class SecurityConfiguration { /** * 基于基础认证模式 * @param http * @return * @throws Exception */ @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // 所有请求都需要认证,认证方式:httpBasic http.authorizeHttpRequests((auth) -> { auth.anyRequest().authenticated(); }).httpBasic(Customizer.withDefaults()); return http.build(); } }
UserAuthenticationProvider.java
package com.example.demo.security; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Component; @Component @Slf4j public class UserAuthenticationProvider implements AuthenticationProvider { @Autowired private UserDetailsService userService; /** * 自己实现认证过程 * * @param authentication * @return * @throws AuthenticationException */ @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 从Authentication 对象中获取用户名和密码 String username = authentication.getName(); String password = authentication.getCredentials().toString(); UserDetails user = userService.loadUserByUsername(username); if (password.equals(user.getPassword())) { // 密码匹配成功 log.info("Access Success: " + user); return new UsernamePasswordAuthenticationToken(username, password, user.getAuthorities()); } else { // 密码匹配失败 log.error("Access Denied: The username or password is wrong!"); throw new BadCredentialsException("The username or password is wrong!"); } } @Override public boolean supports(Class<?> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } }
7、接口测试
IndexController.http
### # 不提供认证信息 GET http://localhost:8080/hello ### # 提供错误的认证信息 GET http://localhost:8080/hello Authorization: Basic dXNlcjo2YzVlMTUyOS1kMTc2LTRkYjItYmZlMy0zZTIzOTNlMjY2MTk= ### # 提供正确的认证信息 GET http://localhost:8080/hello Authorization: Basic emhhbmdzYW46emhhbmdzYW4= ###
六、使用PasswordEncoder加密密码
PasswordEncoder接口
package org.springframework.security.crypto.password; public interface PasswordEncoder { // 对原始密码编码 String encode(CharSequence rawPassword); // 密码比对 boolean matches(CharSequence rawPassword, String encodedPassword); // 判断加密密码是否需要再次加密 default boolean upgradeEncoding(String encodedPassword) { return false; } }
常见的实现类
实现类 | 说明 |
NoOpPasswordEncoder | 明文存储,仅用于测试 |
StandardPasswordEncoder | SHA-256算法(已过期) |
BCryptPasswordEncoder | bcrypt算法 |
Pbkdf2PasswordEncoder | Pbkdf2算法 |
Bcrypt算法简介
例如:
package com.example.demo; import org.junit.jupiter.api.Test; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; public class BCryptPasswordEncoderTest { @Test public void encode(){ BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(); String encode = bCryptPasswordEncoder.encode("123456"); System.out.println(encode); } }
输出
$2a$10$lKqmIKbEPNDx/RXssgN6POgb8YssAK7pVtMFDosmC8FxozUgQq58K
解释
$是分隔符 2a表示Bcrypt算法版本 10表示算法强度 中间22位表示盐值 中间面的位数表示加密后的文本 总长度60位
使用Bcrypt算法加密密码后的数据
-- 建表 CREATE TABLE `tb_user` ( `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id', `username` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名', `password` varchar(255) COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码', `nickname` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '昵称', `enabled` tinyint NOT NULL DEFAULT '1' COMMENT '账号可用标识', PRIMARY KEY (`id`), UNIQUE KEY `idx_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='用户表'; -- 数据 INSERT INTO `tb_user` VALUES (1, 'zhangsan', '$2a$10$/1XHgJYXtF4g/AiR41si8uvVC6Zc.Z9xVmXX4hO2z.b4.DX.H2j5W', '张三', 1); INSERT INTO `tb_user` VALUES (2, 'lisi', '$2a$10$PEcF03ina7x9mmt2VbB0ueVkLZWQo/yoKOfvfQpoL09/faBlNuuZ.', '李四', 1); INSERT INTO `tb_user` VALUES (3, 'wangwu', '$2a$10$PMumxkwwrELTbNDXCj0N4.jD/e/Hv.JiiZTFkdFqlDNLU2TahdYNq', '王五', 1);
UserAuthenticationProvider实现类替换如下
package com.example.demo.security; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @Component @Slf4j public class UserAuthenticationProvider implements AuthenticationProvider { @Autowired private UserDetailsService userService; // 密码加密 public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } /** * 自己实现认证过程 * * @param authentication * @return * @throws AuthenticationException */ @Override public Authentication authenticate(Authentication authentication) throws AuthenticationException { // 从Authentication 对象中获取用户名和密码 String username = authentication.getName(); String password = authentication.getCredentials().toString(); UserDetails user = userService.loadUserByUsername(username); // 替换密码比对方式 // if (password.equals(user.getPassword())) { if (this.passwordEncoder().matches(password, user.getPassword())) { // 密码匹配成功 log.info("Access Success: " + user); return new UsernamePasswordAuthenticationToken(username, password, user.getAuthorities()); } else { // 密码匹配失败 log.error("Access Denied: The username or password is wrong!"); throw new BadCredentialsException("The username or password is wrong!"); } } @Override public boolean supports(Class<?> authentication) { return authentication.equals(UsernamePasswordAuthenticationToken.class); } }
七、Session会话控制
修改配置类SecurityConfiguration
package com.example.demo.security; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; @Configuration public class SecurityConfiguration { /** * 基于基础认证模式 * @param http * @return * @throws Exception */ @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // 禁用session会话 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); // 所有请求都需要认证,认证方式:httpBasic http.authorizeHttpRequests((auth) -> { auth.anyRequest().authenticated(); }).httpBasic(Customizer.withDefaults()); return http.build(); } }
八、基于表单模式实现自定义认证
SecurityFormConfiguration 配置类
package com.example.demo.security; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; @Configuration public class SecurityFormConfiguration { /** * 基于表单认证模式 * @param http * @return * @throws Exception */ @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // 启用session会话 http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); // 认证方式:Form http.authorizeRequests() // 所有请求都需要认证 .anyRequest().authenticated() .and() // 启动表单认证模式 .formLogin() // 登录页面 .loginPage("/login.html") // 请求提交地址 .loginProcessingUrl("/login") // 放行上面的两个地址 .permitAll() // 设置提交的参数名 .usernameParameter("username") .passwordParameter("password") .and() // 开始设置注销功能 .logout() // 注销的url .logoutUrl("/logout") // session直接过期 .invalidateHttpSession(true) // 清除认证信息 .clearAuthentication(true) // 注销成功后跳转地址 .logoutSuccessUrl("/login.html") .and() // 禁用csrf安全防护 .csrf().disable(); return http.build(); } }
登录页面 static/login.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Login</title> </head> <body> <h2>Login</h2> <form action="/login" method="post"> <div><label>username:<input type="text" name="username"></label></div> <div><label>password:<input type="password" name="password"></label></div> <div><input type="submit"></div> </form> </body> </html>
显示效果
学习资料
- IT老齐的Spring Security实战课
- 完整代码: https://github.com/mouday/spring-boot-demo/tree/master/SpringBoot-Security-Basic-Form