spring security+mybatis 和spring security + spring Data JPA 实现认证对比

以下是基于 Spring Security + MySQL + MyBatis 实现认证系统的完整步骤:

1. 项目初始化

pom.xml 中添加依赖:

<dependencies>
    
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-webartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-starter-securityartifactId>
    dependency>
    
    
    <dependency>
        <groupId>mysqlgroupId>
        <artifactId>mysql-connector-javaartifactId>
        <scope>runtimescope>
    dependency>
    <dependency>
        <groupId>org.mybatis.spring.bootgroupId>
        <artifactId>mybatis-spring-boot-starterartifactId>
        <version>2.2.0version>
    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;
    }

    @Bean
    public 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();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public 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;
    }

    @Override
    public 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_USER
        
        return ResponseEntity.ok("注册成功");
    }
}

8. 配置文件

# application.yml
spring:
  datasource:
    url: jdbc:mysql://localhost:3306/security_db?useSSL=false
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
    
  mybatis:
    mapper-locations: classpath:mapper/*.xml
    type-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─────────│                       │

关键点说明

  1. 密码加密:使用BCryptPasswordEncoder进行密码哈希处理
  2. 角色管理:通过关联表实现用户-角色多对多关系
  3. 认证流程
    • 用户提交登录请求
    • Security调用自定义UserDetailsService
    • 通过MyBatis查询数据库验证用户
  4. 扩展性
    • 可添加邮件验证功能
    • 可集成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=false
    username: root
    password: yourpassword
    driver-class-name: com.mysql.cj.jdbc.Driver
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    properties:
      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;
    }

    @Bean
    public 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();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public 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;
    }

    @Override
    public 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───────────────│                       │

十、关键特性说明

  1. 密码安全:使用 BCrypt 强哈希加密
  2. 角色管理:通过 JPA 实现多对多关系映射
  3. 自动建表:Hibernate 自动生成表结构(ddl-auto: update)
  4. RESTful 接口:提供注册/登录端点
  5. 扩展性:可轻松添加以下功能:
    • 邮箱验证
    • 手机号登录
    • JWT 令牌认证
    • OAuth2 第三方登录

十一、常见问题解决

问题现象 解决方案
角色未正确分配 检查角色初始化脚本或JPA自动生成逻辑
密码验证失败 确认密码加密方式是否一致
用户重复注册 检查existsByUsername查询逻辑
JPA关联关系失效 验证@ManyToMany注解配置

十二、扩展建议

  1. 添加验证码功能(集成Google Kaptcha)
  2. 实现密码重置功能
  3. 集成Spring Security OAuth2
  4. 添加审计字段(创建时间、修改时间)
  5. 实现权限的细粒度控制(@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: update
    show-sql: true
(2) MyBatis 配置(application.yml)
mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.entity

5. 性能优化

优化方向 Spring Data JPA MyBatis
SQL 调优 通过 Hibernate 的 fetch plan 优化 直接优化原生 SQL
批量操作 需配置 hibernate.jdbc.batch_size 通过 批量处理
懒加载 自动支持(@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 {
    @ManyToMany
    private 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_id
    LEFT JOIN roles r ON ur.role_id = r.id
    WHERE u.username = #{username}
</select>

总结

选择 JPA:当需要快速迭代、减少 SQL 编写、利用 ORM 特性时
选择 MyBatis:当需要完全控制 SQL、优化复杂查询、维护遗留系统时
Spring Security 集成:两者在认证授权流程上完全一致,区别仅在于用户数据访问层的实现方式

你可能感兴趣的:(spring,mybatis,java)