本文通过逐步学习Spring Security,由浅入深,SpringBoot整合Spring Security 分别实现自定义的HTTP Basic认证和Form表单认证。 本文是学习笔记,网上的教程五花八门,由于时间久远,很难拿来就用。
主要内容:
用户信息管理 敏感信息加密解密 用户认证 权限控制 跨站点请求伪造保护 跨域支持 全局安全方法 单点登录 一、Spring Security 快速开始一个例子创建SpringBoot项目
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ tree -I test . ├── pom.xml └── src └── main ├── java │ └── com │ └── example │ └── demo │ ├── Application.java │ └── controller │ └── IndexController.java └── resources ├── application.yml ├── static └── templates
引入Spring Security依赖
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency >
完整依赖 pom.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 <?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 /> </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
启动类 Application.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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
1 2 3 4 5 6 7 8 9 10 11 12 13 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" ; } }
直接访问应用会被重定向到登录页面
1 2 3 http://localhost:8080/ => 302 http://localhost:8080/login
现在使用默认的账号密码登录
默认的用户名:user 默认的密码:(控制台打印出的密码) 1 Using generated security password: cdd28beb-9a64-4130-be58-6bde1684476d
再次访问 http://localhost:8080/
可以看到返回结果
看到上图说明成功集成Spring Security。
二、认证与授权说明认证authentication用户身份 授权authorization用户权限 单体应用
微服务架构
三、Spring Security基础认证与表单认证
用户对象 UserDetails 认证对象 Authentication 1、HTTP基础认证通过HTTP请求头携带用户名和密码进行登录认证
HTTP请求头格式
1 2 # 用户名和密码的Base64编码 Authonrization: Basic Base64-encoded(username:password)
Spring Boot2.4版本以前
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 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 { http.authorizeHttpRequests((auth) -> { auth.anyRequest().authenticated(); }).httpBasic(Customizer.withDefaults()); } }
Spring Boot2.4版本之后
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 { http.authorizeHttpRequests((auth) -> { auth.anyRequest().authenticated(); }).httpBasic(Customizer.withDefaults()); return http.build(); } }
发送HTTP请求
1 2 GET http://localhost:8080/ Authorization: Basic dXNlcjo2ZjRhMGY5ZS1hY2ZkLTRmNTYtYjIzNy01MTZmYmZjMTk3NGM=
可以获得响应数据
base64解码之后可以得到用户名和密码
1 2 3 atob('dXNlcjo2ZjRhMGY5ZS1hY2ZkLTRmNTYtYjIzNy01MTZmYmZjMTk3NGM=') 'user:6f4a0f9e-acfd-4f56-b237-516fbfc1974c'
2、HTTP表单认证Spring Security的默认认证方式
四、Spring Security 用户与认证对象说明 1、用户对象
UserDetails 用户对象接口说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 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 用户拥有权限接口说明
1 2 3 4 5 6 7 8 package org.springframework.security.core;import java.io.Serializable;public interface GrantedAuthority extends Serializable { String getAuthority () ; }
UserDetailsService 用户查询操作说明
1 2 3 4 5 6 7 8 9 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操作说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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 认证请求详细信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 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 认证的业务执行者
1 2 3 4 5 6 7 8 9 10 11 12 13 14 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、项目结构1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 $ tree -I target . ├── pom.xml └── src ├── main │ ├── java │ │ └── com.example.springboot │ │ ├── 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 │ │ └── login.html │ └── templates └── test └── java └── com.example.springboot └── ApplicationTests.java
2、用户表默认表结构的SQL路径
spring-security-core-5.7.6.jar!/org/springframework/security/core/userdetails/jdbc/users.ddl
1 2 3 4 5 6 7 8 9 10 11 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 <?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 > 3.5.4</version > <relativePath /> </parent > <groupId > com.example</groupId > <artifactId > springboot</artifactId > <version > 0.0.1-SNAPSHOT</version > <name > springboot</name > <description > springboot学习</description > <url /> <licenses > <license /> </licenses > <developers > <developer /> </developers > <scm > <connection /> <developerConnection /> <tag /> <url /> </scm > <properties > <java.version > 17</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-starter-thymeleaf</artifactId > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-security</artifactId > </dependency > <dependency > <groupId > org.springframework.security</groupId > <artifactId > spring-security-test</artifactId > <scope > test</scope > </dependency > <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <optional > true</optional > </dependency > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-jdbc</artifactId > </dependency > <dependency > <groupId > com.mysql</groupId > <artifactId > mysql-connector-j</artifactId > <version > 8.3.0</version > </dependency > <dependency > <groupId > com.baomidou</groupId > <artifactId > mybatis-plus-spring-boot3-starter</artifactId > <version > 3.5.6</version > </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-devtools</artifactId > <optional > true</optional > </dependency > </dependencies > <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build > </project >
4、数据库配置application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: zaq1xsw2 hikari: maximum-pool-size: 10 minimum-idle: 5 idle-timeout: 300000 connection-timeout: 20000 mybatis: mapper-locations: - classpath:mapper/*.xml mybatis-plus: mapper-locations: classpath:mapper/*.xml global-config: db-config: id-type: auto field-strategy: NOT_EMPTY db-type: MYSQL configuration: map-underscore-to-camel-case: true call-setters-on-nulls: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
5、SpringBoot基本框架启动类 Application.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package com.example.springboot;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication public class SpringbootApplication { public static void main (String[] args) { SpringApplication.run(SpringbootApplication.class, args); } }
实体类 User.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 package com.example.springboot.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 { @TableId private Long id; private String username; private String password; private String nickname; private Integer enabled; @Override public Collection<? extends GrantedAuthority > getAuthorities() { return Arrays.asList(new SimpleGrantedAuthority ("ROLE_USER" )); } @Override public boolean isAccountNonExpired () { return true ; } @Override public boolean isAccountNonLocked () { return true ; } @Override public boolean isCredentialsNonExpired () { return true ; } @Override public boolean isEnabled () { return this .enabled == 1 ; } }
UserMapper.java
1 2 3 4 5 6 7 8 9 10 package com.example.springboot.mapper;import com.baomidou.mybatisplus.core.mapper.BaseMapper;import com.example.springboot.entity.User;import org.apache.ibatis.annotations.Mapper;@Mapper public interface UserMapper extends BaseMapper <User> {}
UserService.java
1 2 3 4 5 6 7 8 package com.example.springboot.service;import com.baomidou.mybatisplus.extension.service.IService;import com.example.springboot.entity.User;public interface UserService extends IService <User> {}
UserServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 package com.example.springboot.service.impl;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.example.springboot.entity.User;import com.example.springboot.mapper.UserMapper;import com.example.springboot.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 { @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
1 2 3 4 5 6 7 8 9 10 11 12 13 package com.example.springboot.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 SecuritySecurityConfiguration.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package com.example.springboot.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 { @Bean public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception { http.authorizeHttpRequests((auth) -> { auth.anyRequest().authenticated(); }).httpBasic(Customizer.withDefaults()); return http.build(); } }
UserAuthenticationProvider.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 package com.example.springboot.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; @Override public Authentication authenticate (Authentication authentication) throws AuthenticationException { 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ### # 不提供认证信息 GET http://localhost:8080/hello ### # 提供错误的认证信息 GET http://localhost:8080/hello Authorization: Basic dXNlcjo2YzVlMTUyOS1kMTc2LTRkYjItYmZlMy0zZTIzOTNlMjY2MTk= ### # 提供正确的认证信息 GET http://localhost:8080/hello Authorization: Basic emhhbmdzYW46emhhbmdzYW4= ###
六、使用PasswordEncoder加密密码PasswordEncoder接口说明
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 ; } }
常见的实现类
Bcrypt算法简介
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.example.sprintboot; 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); } }
输出
1 $2a$10$lKqmIKbEPNDx/RXssgN6POgb8YssAK7pVtMFDosmC8FxozUgQq58K
解释
1 2 3 4 5 6 $是分隔符 2a表示Bcrypt算法版本 10表示算法强度 中间22位表示盐值 中间面的位数表示加密后的文本 总长度60位
使用Bcrypt算法加密密码后的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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实现类替换如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 package com.example.springboot.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 (); } @Override public Authentication authenticate (Authentication authentication) throws AuthenticationException { String username = authentication.getName(); String password = authentication.getCredentials().toString(); UserDetails user = userService.loadUserByUsername(username); 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.example.springboot.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 { @Bean public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception { http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.authorizeHttpRequests((auth) -> { auth.anyRequest().authenticated(); }).httpBasic(Customizer.withDefaults()); return http.build(); } }
八、基于表单模式实现自定义认证SecurityFormConfiguration 配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 package com.example.springboot.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 { @Bean public SecurityFilterChain securityFilterChain (HttpSecurity http) throws Exception { http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); http.authorizeRequests() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login.html" ) .loginProcessingUrl("/login" ) .defaultSuccessUrl("/hello" , true ) .permitAll() .usernameParameter("username" ) .passwordParameter("password" ) .and() .logout() .logoutUrl("/logout" ) .invalidateHttpSession(true ) .clearAuthentication(true ) .logoutSuccessUrl("/login.html" ) .and() .csrf().disable(); return http.build(); } }
登录页面 static/login.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <!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 >
显示效果