1.1 什么是 Spring Security

Spring Security 是一个基于 Spring 框架的安全框架,它为 Java 应用程序提供了全面的安全解决方案,专注于认证(Authentication)和授权(Authorization)两大核心安全领域。作为 Spring 生态系统的重要组成部分,它与 Spring 框架无缝集成,能够轻松应用于各种 Spring 应用,如 Spring Boot、Spring MVC 等。

其设计理念是通过一系列拦截器和过滤器,在应用程序的各个层级(如 Web 请求、方法调用等)实施安全控制,帮助开发者快速构建安全可靠的应用,而无需从零开始实现复杂的安全逻辑。

1.2 核心功能

  1. 认证(Authentication)
  • 验证用户身份的合法性,即确认 “你是谁”。
  • 支持多种认证方式,包括用户名 / 密码认证、OAuth2.0、记住我功能、单点登录(SSO)等。
  • 提供灵活的用户信息来源配置,可从内存、数据库、自定义服务等获取用户数据。
  1. 授权(Authorization)
  • 确定已认证用户是否有权限执行特定操作,即判断 “你能做什么”。
  • 支持基于角色的访问控制(RBAC)、基于权限的访问控制,以及细粒度的方法级、URL 级权限控制。
  • 可通过配置文件、注解(如 @PreAuthorize)、编程式方式定义权限规则。
  1. 其他安全特性
  • 防护常见攻击:内置对跨站请求伪造(CSRF)、跨站脚本攻击(XSS)、会话固定攻击、点击劫持等常见 Web 攻击的防护机制。
  • 会话管理:提供会话创建、失效、并发控制等功能,可配置会话超时时间、会话 ID 生成策略等。
  • 密码加密:集成多种密码加密算法(如 BCrypt、PBKDF2 等),避免明文存储密码,增强数据安全性。
  • 安全事件监听:允许通过事件机制捕获认证成功 / 失败、授权拒绝等安全事件,便于日志记录和后续处理。

1.3 工作原理

Spring Security 的核心是过滤器链(Filter Chain),它通过一系列过滤器对请求进行拦截和处理,实现安全控制。

  1. 当一个请求进入应用时,会依次经过 Spring Security 过滤器链中的各个过滤器。
  2. 过滤器链中的关键过滤器(如 UsernamePasswordAuthenticationFilter)会处理认证相关的逻辑,例如验证用户名和密码。
  3. 认证成功后,会创建一个包含用户信息的 Authentication 对象,并将其存储在 SecurityContextHolder 中,以便后续的授权操作使用。
  4. 在请求处理过程中,授权过滤器(如 FilterSecurityInterceptor)会根据配置的权限规则,检查当前用户是否有权限访问请求的资源。如果没有权限,则会拒绝请求并返回相应的错误信息。
  5. 请求处理完成后,SecurityContextPersistenceFilter 会清理 SecurityContextHolder 中的信息,确保线程安全。

1.4 主要组件

  1. SecurityContextHolder:用于存储当前线程的安全上下文信息,其中包含了当前认证用户的 Authentication 对象。
  2. Authentication:表示用户的认证信息,包含用户名、密码、权限列表等内容。
  3. UserDetails:封装用户详细信息的接口,通常包含用户名、密码、是否启用等信息,开发者可实现该接口自定义用户信息。
  4. UserDetailsService:用于加载用户信息的接口,通过用户名获取 UserDetails 对象,是连接用户数据来源和认证流程的关键组件。
  5. AuthenticationManager:认证管理器,负责协调认证过程,它会委托给多个 AuthenticationProvider 进行认证尝试,直到其中一个成功或全部失败。
  6. AuthenticationProvider:具体执行认证逻辑的组件,不同的认证方式对应不同的 AuthenticationProvider(如 DaoAuthenticationProvider 用于用户名 / 密码认证)。
  7. AccessDecisionManager:访问决策管理器,在授权过程中根据用户的权限和请求的资源,决定是否允许访问。

1.5 应用场景

  • Web 应用安全:保护 Web 应用的 URL 资源,限制未认证用户访问,控制不同角色用户的操作权限。
  • REST API 安全:为 RESTful API 提供认证和授权支持,常用 OAuth2.0 等方式实现第三方应用的安全访问。
  • 企业级应用:在复杂的企业环境中,与 LDAP、Active Directory 等集成,实现统一的身份认证和权限管理。
  • 单点登录(SSO):通过与 Spring Security SAML、OAuth2.0 等集成,实现多个应用之间的单点登录,提升用户体验和系统安全性。

二、Spring Security基础

2.1 基本使用

创建springboot项目添加对应的springsecurity依赖:

添加热部署、Lombok、Spring Web, springsecurity依赖

编写controller并测试

package com.sy.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @GetMapping("/hello")
    public String hello(){
        return "Hello Security";
    }
}

启动项目并访问

此时我们发现不能正常访问 http://localhost:8080/hello 请求了,为什么访问不了呢?

因为springsecurity框架帮我们做了一个验证,如果不是登录的请求,全部需要先登录才能继续访问,那么我们怎么知道用户名和密码呢?

不要着急,我来告诉你,来看一看我们启动项目的时候的过程,如图:

在该图上我们发现有一个密码,但是我们依然不知道用户名是啥,我就不在去找源码了,直接告诉大家,用户名默认user

2.2 内存配置

但是这样也不是一个办法,用户名固定,密码随机,是不是意味着我们每次登录用户名相同,而密码都不一样呢,别急,我来告诉大家

我们还可以在springboot项目中的这个文件中这么配置

这个时候你还有一个疑问,那么下一次其它的请求或者相同的请求还需要登录吗?

答案是否定的,只需要登录一次,就可以继续发送其他请求了。

但是这样也不行呀!这样子的话这个项目所有人的用户名和密码都成了你在配置文件中的值。也是一个固定值,所以应该怎么解决呢?

三、Spring Security登录认证

3.1 基于数据库查询登录

最终我们使用的还是需要将登录的用户名和密码存储在数据库中,然后去做登录认证处理。

但是我们应该怎么做呢?

添加mybatis和驱动,lombok相关依赖

<properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <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>

    <!-- Spring JDBC 支持 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-jdbc</artifactId>
    </dependency>

    <!-- MyBatis Spring Boot Starter -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>3.0.3</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.30</version>
    </dependency>
    <!-- MySQL 驱动 -->
    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
</dependencies>

配置文件

spring:
  datasource:
    # 默认的数据库类型
    type: com.zaxxer.hikari.HikariDataSource
    username: root
    password: root
    url: jdbc:mysql://localhost:3306/sy_db
    driver-class-name: com.mysql.cj.jdbc.Driver

mybatis:
  mapper-locations: classpath:mapper/*.xml #映射文件的位置
  #mybatis的配置
  configuration:
    map-underscore-to-camel-case: true #驼峰命名转换

数据表

-- 创建数据库
CREATE DATABASE IF NOT EXISTS sy_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 使用数据库
USE sy_db;

-- 创建用户表
CREATE TABLE IF NOT EXISTS `user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username` varchar(50) NOT NULL COMMENT '用户名',
  `password` varchar(100) NOT NULL COMMENT '密码',
  `real_name` varchar(50) DEFAULT NULL COMMENT '真实姓名',
  `phone` varchar(20) DEFAULT NULL COMMENT '手机号',
  `status` tinyint(4) DEFAULT 1 COMMENT '状态:0-禁用,1-正常',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_username` (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 插入
INSERT INTO `user` (`username`, `password`, `real_name`, `phone`, `status`, `create_time`) VALUES
('user01', 'e10adc3949ba59abbe56e057f20f883e', '张三', '13800138001', 1, '2024-01-05 09:30:00'),
('user14', 'e10adc3949ba59abbe56e057f20f883e', '刘十五', '13800138014', 0, '2024-02-05 15:40:00');

接下来我们该生成pojo,mapper,service,controller等,

今天我们学习一个新的东西叫mybatis的逆向工程(之前代码不熟练的还是手动写代码),

快速帮我们生成pojo,mapper,*.xml

逆向工程

IDEA下载 Free MyBatis Tool 插件

具体配置:

结果如下:

自定义service

但是我们接下来该干什么呢?

controller我们发现需要自己写,关于登录的业务我们能使用Spring Security的吗?

明眼人就知道不能,但是我们应该怎么让框架走我们自己写的service呢?不要着急,马上就来了。

先去定义一个UserService接口和实现类

然后

让我们的 UserService 接口继承一个UserDetailsService,

让框架走我们自己定义的实现,不要走默认框架的就可以了,同时返回值中有获取用户名和密码的相关方法,这样我们就可以获取到页面上我们输入的账号和密码了,我们就可以通过这个账号和密码去数据库中查询了。这样问题就完美的解决了。

那么接下来我们就实现我们自己的 UserServiceImpl 就行了。

到底是不是这样呢,好,我们试一试,先去写个Controller来调用service中的这个方法试试,然后打个断点。

package com.sy.controller;

import com.sy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserController {

    @Autowired
    private UserService userService;

    /**
     * 用户登录功能
     * @param username
     * @param password
     * @return
     */
    @GetMapping("/login")
    public String login(String username, String password){
        UserDetails userDetails = userService.loadUserByUsername(username);
        System.out.println(userDetails);
        return "we are comming login";
    }
}

大家 在 UserServiceImpl 实现类中打断点看效果,我就不带着大家去看了

我们发现逆向工程中没有生成根据用户名查询用户的相关方法,因此需要我们自己定义去。

去 UserMapper 中定义个根据用户名查询用户的抽象方法,并编写相关的映射文件内容。

编写完成之后我们来看一下业务层

UserServiceImpl实现类的代码:

package com.sy.service.impl;

import com.sy.mapper.UserMapper;
import com.sy.pojo.User;
import com.sy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
 * @author Mr·Zhai
 * @version 1.0
 * @description: TODO
 * @date 2026/1/18 16:58
 */
@Service("userService")
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 根据用户名查询用户信息
     * @param username
     * @return
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 根据用户名查询用户信息   此方法需要自己自定义
        User user = userMapper.findUserByUsername(username);
        // 判断用户是否为null 如果为null 抛异常  说明不存在
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        //构建UserDetails对象,怎么构建呢?看源码
        return null;
    }
}

UserDetail 是一个接口,实现类如下图:

推荐使用User,特别注意这里的User不是我们的User(所以最好我们在创建实体类的时候不要叫User,不然不好区分) 进去查看:

package com.sy.service.impl;

import com.sy.mapper.UserMapper;
import com.sy.pojo.User;
import com.sy.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

/**
 * @author Mr·Zhai
 * @version 1.0
 * @description: TODO
 * @date 2026/1/18 16:58
 */
@Service("userService")
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    /**
     * 根据用户名查询用户信息
     * @param username
     * @return
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 根据用户名查询用户信息   此方法需要自己自定义
        User user = userMapper.findUserByUsername(username);
        // 判断用户是否为null 如果为null 抛异常  说明不存在
        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        //构建UserDetails对象,怎么构建呢?看源码
        UserDetails userDetails = org.springframework.security.core.userdetails.User.builder() //User前边一定要加包名,不然和我们自己的User就区分不清楚了
                .username(username) //设置用户名
                .password(user.getPassword()) //设置密码
                .authorities(AuthorityUtils.NO_AUTHORITIES)//设置权限,但是我们还没有讲到,这里权限设置为空先
                .build();
        return userDetails; // 返回UserDetails交给security做密码的认证处理
    }
}

先运行项目查看效果:

我们发现报错了...怎么办,怎么办,啥意思啊,啥意思。

这个其实是没有配置 Spring Security 的加密器造成的,可以解决。

加密器配置

该加密器的配置是Spring Security的内部配置的,那么我们怎么加载呢,很简单,其实我们之前就说过,通过配置类然后利用@Bean注解就可以实现了。

添加一个config包SecrityConfig类来配置

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

配置完之后就不报错了

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐