以下是基于 Spring Security + MySQL + MyBatis 实现认证系统的完整步骤:
1. 项目初始化
在 pom.xml
中添加依赖:
<dependencies><!-- Spring Boot Starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- 数据库相关 --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.0</version></dependency>
</dependencies>
2. 数据库设计
创建用户表:
CREATE TABLE `sys_user` (`id` INT NOT NULL AUTO_INCREMENT,`username` VARCHAR(50) UNIQUE NOT NULL,`password` VARCHAR(100) NOT NULL,`enabled` TINYINT(1) NOT NULL DEFAULT 1,`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;CREATE TABLE `sys_role` (`id` INT NOT NULL AUTO_INCREMENT,`name` VARCHAR(50) NOT NULL,PRIMARY KEY (`id`)
);CREATE TABLE `sys_user_role` (`user_id` INT NOT NULL,`role_id` INT NOT NULL,PRIMARY KEY (`user_id`,`role_id`)
);-- 初始化角色数据
INSERT INTO sys_role(name) VALUES('ROLE_USER'), ('ROLE_ADMIN');
3. 实体类实现
// User.java
@Data
public class User {private Integer id;private String username;private String password;private Boolean enabled;private Date createTime;private List<Role> roles;
}// Role.java
@Data
public class Role {private Integer id;private String name;
}
4. MyBatis Mapper 接口
@Mapper
public interface UserMapper {@Insert("INSERT INTO sys_user(username, password) VALUES(#{username}, #{password})")@Options(useGeneratedKeys = true, keyProperty = "id")int insert(User user);@Select("SELECT * FROM sys_user WHERE username = #{username}")@Results({@Result(property = "roles", column = "id", many = @Many(select = "com.example.mapper.RoleMapper.findByUserId"))})User findByUsername(String username);
}@Mapper
public interface RoleMapper {@Select("SELECT r.* FROM sys_role r " +"JOIN sys_user_role ur ON r.id = ur.role_id " +"WHERE ur.user_id = #{userId}")List<Role> findByUserId(Integer userId);
}
5. 安全配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig {private final UserDetailsServiceImpl userDetailsService;public SecurityConfig(UserDetailsServiceImpl userDetailsService) {this.userDetailsService = userDetailsService;}@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.authorizeRequests(auth -> auth.antMatchers("/register").permitAll().anyRequest().authenticated()).formLogin(form -> form.loginProcessingUrl("/login").successHandler((req, res, auth) -> {res.getWriter().write("Login success!");})).csrf().disable();return http.build();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic AuthenticationManager authManager(HttpSecurity http) throws Exception {return http.getSharedObject(AuthenticationManagerBuilder.class).userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()).and().build();}
}
6. 自定义UserDetailsService
@Service
public class UserDetailsServiceImpl implements UserDetailsService {private final UserMapper userMapper;public UserDetailsServiceImpl(UserMapper userMapper) {this.userMapper = userMapper;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userMapper.findByUsername(username);if (user == null) {throw new UsernameNotFoundException("用户不存在");}return org.springframework.security.core.userdetails.User.withUsername(username).password(user.getPassword()).authorities(getAuthorities(user.getRoles())).build();}private String[] getAuthorities(List<Role> roles) {return roles.stream().map(Role::getName).toArray(String[]::new);}
}
7. 注册接口实现
@RestController
public class AuthController {private final UserMapper userMapper;private final PasswordEncoder passwordEncoder;public AuthController(UserMapper userMapper, PasswordEncoder passwordEncoder) {this.userMapper = userMapper;this.passwordEncoder = passwordEncoder;}@PostMapping("/register")public ResponseEntity<?> register(@RequestBody User user) {// 检查用户名是否已存在if (userMapper.findByUsername(user.getUsername()) != null) {return ResponseEntity.badRequest().body("用户名已存在");}// 加密密码user.setPassword(passwordEncoder.encode(user.getPassword()));userMapper.insert(user);// 分配默认角色userMapper.insertUserRole(user.getId(), 1); // ROLE_USERreturn ResponseEntity.ok("注册成功");}
}
8. 配置文件
# application.yml
spring:datasource:url: jdbc:mysql://localhost:3306/security_db?useSSL=falseusername: rootpassword: 123456driver-class-name: com.mysql.cj.jdbc.Drivermybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.example.entity
9. 测试验证
注册测试(使用curl):
curl -X POST http://localhost:8080/register \
-H "Content-Type: application/json" \
-d '{"username":"test","password":"123456"}'
登录测试:
curl -X POST http://localhost:8080/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=test&password=123456"
系统架构图
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Browser │ │ Spring Security Filter Chain │
└──────┬───────┘ └───────┬───────┘ └───────┬───────┘│ /register (POST) │ ││──────────────────────>│ ││ │─User Insert──────────>│ MySQL│ │<─Success Response─────││<──────────────────────│ ││ │ ││ /login (POST) │ ││──────────────────────>│ ││ │─Query User───────────>││ │<─User Details─────────││ │─BCrypt Verify────────>││<─Auth Success─────────│ │
关键点说明
- 密码加密:使用BCryptPasswordEncoder进行密码哈希处理
- 角色管理:通过关联表实现用户-角色多对多关系
- 认证流程:
• 用户提交登录请求
• Security调用自定义UserDetailsService
• 通过MyBatis查询数据库验证用户 - 扩展性:
• 可添加邮件验证功能
• 可集成JWT实现无状态认证
• 可添加验证码等二次验证机制
建议使用Postman进行接口测试,并使用MySQL Workbench验证数据存储的正确性。
spring data JDA
以下是基于 Spring Security + MySQL + Spring Data JPA 的完整认证系统实现方案:
一、项目结构
src/main/java
├── com/example
│ ├── config # 安全配置
│ ├── controller # 控制器
│ ├── entity # JPA实体
│ ├── repository # JPA仓库
│ └── security # 安全相关类
二、数据库配置(application.yml)
spring:datasource:url: jdbc:mysql://localhost:3306/auth_db?useSSL=falseusername: rootpassword: yourpassworddriver-class-name: com.mysql.cj.jdbc.Driverjpa:hibernate:ddl-auto: updateshow-sql: trueproperties:hibernate:format_sql: true
三、实体类定义
1. 用户实体
@Entity
@Table(name = "users")
public class User {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(unique = true, nullable = false)private String username;@Column(nullable = false)private String password;@ManyToMany(fetch = FetchType.EAGER)@JoinTable(name = "user_roles",joinColumns = @JoinColumn(name = "user_id"),inverseJoinColumns = @JoinColumn(name = "role_id"))private Set<Role> roles = new HashSet<>();// Getters and Setters
}
2. 角色实体
@Entity
@Table(name = "roles")
public class Role {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;@Column(unique = true, nullable = false)private String name;// Getters and Setters
}
四、Repository 接口
1. 用户仓库
public interface UserRepository extends JpaRepository<User, Long> {Optional<User> findByUsername(String username);boolean existsByUsername(String username);
}
2. 角色仓库
public interface RoleRepository extends JpaRepository<Role, Long> {Optional<Role> findByName(String name);
}
五、安全配置
1. 安全配置类
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {private final UserDetailsServiceImpl userDetailsService;public SecurityConfig(UserDetailsServiceImpl userDetailsService) {this.userDetailsService = userDetailsService;}@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.csrf().disable().authorizeRequests(auth -> auth.antMatchers("/register", "/login").permitAll().anyRequest().authenticated()).formLogin(form -> form.loginProcessingUrl("/login").successHandler((request, response, authentication) -> {response.getWriter().println("Login success!");})).logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler((request, response, authentication) -> {response.getWriter().println("Logout success!");}));return http.build();}@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}@Beanpublic AuthenticationManager authManager(HttpSecurity http) throws Exception {return http.getSharedObject(AuthenticationManagerBuilder.class).userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()).and().build();}
}
2. 自定义 UserDetailsService
@Service
public class UserDetailsServiceImpl implements UserDetailsService {private final UserRepository userRepository;public UserDetailsServiceImpl(UserRepository userRepository) {this.userRepository = userRepository;}@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = userRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("User not found"));return org.springframework.security.core.userdetails.User.withUsername(username).password(user.getPassword()).authorities(getAuthorities(user.getRoles())).build();}private String[] getAuthorities(Set<Role> roles) {return roles.stream().map(Role::getName).toArray(String[]::new);}
}
六、控制器实现
1. 注册控制器
@RestController
public class AuthController {private final UserRepository userRepository;private final RoleRepository roleRepository;private final PasswordEncoder passwordEncoder;public AuthController(UserRepository userRepository, RoleRepository roleRepository,PasswordEncoder passwordEncoder) {this.userRepository = userRepository;this.roleRepository = roleRepository;this.passwordEncoder = passwordEncoder;}@PostMapping("/register")public ResponseEntity<?> register(@RequestBody UserRegistrationDto dto) {if (userRepository.existsByUsername(dto.getUsername())) {return ResponseEntity.badRequest().body("Username already exists");}User user = new User();user.setUsername(dto.getUsername());user.setPassword(passwordEncoder.encode(dto.getPassword()));// 分配默认角色Role userRole = roleRepository.findByName("ROLE_USER").orElseGet(() -> roleRepository.save(new Role("ROLE_USER")));user.getRoles().add(userRole);userRepository.save(user);return ResponseEntity.ok("Registration success");}
}// DTO类
public class UserRegistrationDto {private String username;private String password;// Getters and Setters
}
七、数据库初始化(可选)
-- 手动初始化角色表(或通过JPA自动生成)
INSERT INTO roles(name) VALUES('ROLE_USER'), ('ROLE_ADMIN');
八、系统测试
1. 注册用户
curl -X POST http://localhost:8080/register \
-H "Content-Type: application/json" \
-d '{"username":"test", "password":"123456"}'
2. 用户登录
curl -X POST http://localhost:8080/login \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "username=test&password=123456"
九、系统架构图
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ Client │ │ Spring Security Filter Chain │ │ MySQL
└──────┬───────┘ └───────┬───────┘ └───────┬───────┘│ /register (POST) │ ││──────────────────────>│ ││ │─Save User─────────────>││ │<─JPA Transaction──────││<─201 Created──────────│ ││ │ ││ /login (POST) │ ││──────────────────────>│ ││ │─JPA Query─────────────>││ │<─User Entity───────────││ │─BCrypt Verify─────────>││<─200 OK───────────────│ │
十、关键特性说明
- 密码安全:使用 BCrypt 强哈希加密
- 角色管理:通过 JPA 实现多对多关系映射
- 自动建表:Hibernate 自动生成表结构(ddl-auto: update)
- RESTful 接口:提供注册/登录端点
- 扩展性:可轻松添加以下功能:
• 邮箱验证
• 手机号登录
• JWT 令牌认证
• OAuth2 第三方登录
十一、常见问题解决
问题现象 | 解决方案 |
---|---|
角色未正确分配 | 检查角色初始化脚本或JPA自动生成逻辑 |
密码验证失败 | 确认密码加密方式是否一致 |
用户重复注册 | 检查existsByUsername 查询逻辑 |
JPA关联关系失效 | 验证@ManyToMany 注解配置 |
十二、扩展建议
- 添加验证码功能(集成Google Kaptcha)
- 实现密码重置功能
- 集成Spring Security OAuth2
- 添加审计字段(创建时间、修改时间)
- 实现权限的细粒度控制(@PreAuthorize)
对比
以下是 Spring Security + MySQL + Spring Data JPA 与 Spring Security + MySQL + MyBatis 的核心区别对比:
1. 核心架构差异
对比维度 | Spring Data JPA | MyBatis |
---|---|---|
持久层框架 | 基于 JPA 规范(如 Hibernate 实现)的 ORM 框架 | SQL 映射框架(非 ORM) |
SQL 控制 | 自动生成 SQL(可自定义 JPQL/HQL) | 完全手动编写 SQL |
对象关系映射 | 自动映射实体与表结构(@Entity 注解) | 手动配置映射(XML 或注解) |
缓存机制 | 一级/二级缓存自动管理 | 需手动配置缓存(如集成 Redis) |
2. 与 Spring Security 的集成差异
(1) 用户查询实现
// Spring Data JPA 方式(自动方法名推导)
public interface UserRepository extends JpaRepository<User, Long> {Optional<User> findByUsername(String username);
}// MyBatis 方式(需手动写 SQL)
@Mapper
public interface UserMapper {@Select("SELECT * FROM users WHERE username = #{username}")User findByUsername(String username);
}
(2) UserDetailsService 实现
// JPA 方式(直接注入 Repository)
@Service
public class JpaUserDetailsService implements UserDetailsService {private final UserRepository userRepo;public UserDetails loadUserByUsername(String username) {return userRepo.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("用户不存在"));}
}// MyBatis 方式(注入 Mapper)
@Service
public class MyBatisUserDetailsService implements UserDetailsService {private final UserMapper userMapper;public UserDetails loadUserByUsername(String username) {User user = userMapper.findByUsername(username);if (user == null) throw new UsernameNotFoundException("用户不存在");return user;}
}
3. 开发效率对比
场景 | Spring Data JPA | MyBatis |
---|---|---|
简单 CRUD | 快速(方法名自动推导) | 需手写 SQL 和映射配置 |
复杂联表查询 | 需自定义 JPQL/HQL 或 Native Query | 直接编写优化后的 SQL(更灵活) |
动态条件查询 | 使用 Specification 或 QueryDSL | 通过 XML 动态拼接 SQL |
数据库移植性 | 高(Hibernate 自动适配方言) | 低(SQL 需适配不同数据库) |
4. 配置差异
(1) JPA 配置(application.yml)
spring:jpa:hibernate:ddl-auto: updateshow-sql: true
(2) MyBatis 配置(application.yml)
mybatis:mapper-locations: classpath:mapper/*.xmltype-aliases-package: com.example.entity
5. 性能优化
优化方向 | Spring Data JPA | MyBatis |
---|---|---|
SQL 调优 | 通过 Hibernate 的 fetch plan 优化 | 直接优化原生 SQL |
批量操作 | 需配置 hibernate.jdbc.batch_size | 通过 <foreach> 批量处理 |
懒加载 | 自动支持(@ManyToOne(fetch=FetchType.LAZY)) | 需手动处理关联查询 |
6. 适用场景建议
技术组合 | 推荐场景 |
---|---|
Spring Security + JPA | 快速开发、需求变化频繁、团队熟悉 ORM 规范、需要数据库移植性 |
Spring Security + MyBatis | 复杂 SQL 优化需求、遗留系统维护、需要精细控制 SQL、团队熟悉传统 SQL 开发模式 |
7. 具体场景示例
(1) 分页查询用户列表
// JPA 方式(自动分页)
Page<User> findAll(Pageable pageable);// MyBatis 方式(手动编写分页 SQL)
@Select("SELECT * FROM users LIMIT #{offset}, #{pageSize}")
List<User> findUsers(@Param("offset") int offset, @Param("pageSize") int pageSize);
(2) 关联角色查询
// JPA 方式(自动关联查询)
@Entity
public class User {@ManyToManyprivate Set<Role> roles;
}// MyBatis 方式(手动联表查询)
<select id="findUserWithRoles" resultMap="userRoleMap">SELECT u.*, r.id as role_id, r.name FROM users u LEFT JOIN user_roles ur ON u.id = ur.user_idLEFT JOIN roles r ON ur.role_id = r.idWHERE u.username = #{username}
</select>
总结
• 选择 JPA:当需要快速迭代、减少 SQL 编写、利用 ORM 特性时
• 选择 MyBatis:当需要完全控制 SQL、优化复杂查询、维护遗留系统时
• Spring Security 集成:两者在认证授权流程上完全一致,区别仅在于用户数据访问层的实现方式