Spring Security入门学习(一)Helloworld项目

Spring security详细上手教学(一)hello world项目

1. 构建hello world程序

引入依赖,笔者这里使用Spring boot3.2.x,Spring security6.2.x版本

java 复制代码
        <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>

创建一个Rest endpoint

java 复制代码
@RestController
public class HelooController {

    @GetMapping("/hello")
    public String hello() {
        return "hello!";
    }
}

运行主程序,发现打印日志中,有自动生成一串UUID格式的security密码

复制代码
2025-04-24T13:21:10.114+08:00  WARN 59924 --- [           main] .s.s.UserDetailsServiceAutoConfiguration : 

Using generated security password: c3681247-1323-4cc3-a55f-c730046900d8

This generated password is for development use only. Your security configuration must be updated before running your application in production.

如果直接访问localhost:8080/hello

会报401状态码

复制代码
{
"status":401,
"error":"Unauthorized",
"message":"Unauthorized",
"path":"/hello"
}

此时此接口就必须使用鉴权才可以访问了,在认证(Autherization)中填写默认的用户名user,密码是刚才日志中生成的一串UUID ,类型选择Basic auth。可以发现添加鉴权之后能够正确返回hello!

实际上在发送http请求的时候,会将 user:c3681247-1323-4cc3-a55f-c730046900d8 用base64编码之后,加载http请求头的Authorization参数上,进行传输。所以在命令行,也可以使用

复制代码
curl -H "Authorizaiton: Basic dXNlcjpjMzY4MTI0Ny0xMzIzLTRjYzMtYTU1Zi1jNzMwMDQ2OTAwZDg=" localhost:8080/hello

2. 了解机制

  1. 首先客户端请求被Authentication filter拦截
  2. Authentication filter将身份验证委托给管理器
  3. Authentication管理器调用Authentication provider,Authentication provider中提供了鉴权的逻辑
  4. UserDetailService实现用户管理,PasswordEncoder实现密码管理(加密和匹配)
  5. Security上下文环境中包含鉴权的数据,返回给filter

这里UserDetailService和PasswordEncoder被自动配置并初始化bean,Spring Boot默认配置一个user用户,并随机生成一个UUID密码

3. 重载配置

3.1 用户密码管理的重载

这一小节,关于如何使用InMemoryUserDetailManager 重载Spring boot默认的UserDetailService bean,需要注意的是InMemoryUserDetailManager 不用作生产环境,只是一个样例,用来学习理解如何覆盖重载默认的UserDetailService

java 复制代码
public class ProjectConfig {
    
    @Bean
    UserDetailsService userDetailsService() {
        return new InMemoryUserDetailsManager();
    }
}

创建InMemoryUserDetailsManager bean之后,发现不再有默认的user用户名和生成的UUID密码了。此时我们没有默认的PasswordEncoder了

我们需要做:

  1. 创建用户/密码
  2. 添加用户到userDetailsService管理
  3. 定义一个PasswordEncoder bean

这里使用了var去隐藏类型,后续再讨论

复制代码
@Configuration
public class ProjectConfig {

    @Bean
    UserDetailsService userDetailsService() {
        var user = User.withUsername("john")
                .password("{noop}12345")
                .authorities("read")
                .build();
        return new InMemoryUserDetailsManager(user);
    }
}

随后我们还得添加一个PasswordEncoder bean

复制代码
@Bean
public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}

NoOpPasswordEncoder不会对密码做任何加密或者哈希。匹配的时候也只是对字符串进行equals,生产环境我们不能使用这个,这里只是用来理解这个章节的内容。

现在我们可以用john用户访问了

3.2 灵活管理接口

有些情况下,我们不是所有的接口都需要鉴权,而且有些时候可能需要除了http basic之外的其他鉴权类型。这个时候需要定义SecurityFilterChain bean

java 复制代码
    @Bean
    SecurityFilterChain configure(HttpSecurity http) throws Exception {
        http.httpBasic(Customizer.withDefaults());
        http.authorizeHttpRequests(auth -> {
            auth.anyRequest().authenticated();
        });
        return http.build();
    }

以上代码和Springboot的默认情况是一致的。

复制代码
@Bean
SecurityFilterChain configure(HttpSecurity http) throws Exception {
    http.httpBasic(Customizer.withDefaults());
    http.authorizeHttpRequests(auth -> {
        auth.anyRequest().permitAll();
    });
    return http.build();
}

如果我们改成permitAll,那么再次访问hello接口,将不再需要用户名密码。

httpBasic方法,用来设置鉴权方式,这里设置的是HTTP Basic

authorizeHttpRequests方法,用来设置不同访问接口的鉴权规则,这个方法规定了对于不同的接口请求,我们的程序如何处理

  • 这些方法都需要传入Customizer对象作为参数,这里还可以设置 CSRF\CORS等等机制。
java 复制代码
@FunctionalInterface
public interface Customizer<T> {
    void customize(T t);

    static <T> Customizer<T> withDefaults() {
        return (t) -> {
        };
    }
}

过去版本的Spring Security,采用如下的方式进行配置,这种方式不如使用Customizer+lambda表达式的方式更加灵活。

复制代码
http.authorizeHttpRequests().anyRequest().authenticated()

3.3 在SecurityFilterChain中配置UserDetailsService

复制代码
@Bean
SecurityFilterChain configure(HttpSecurity http) throws Exception {
    http.httpBasic(Customizer.withDefaults());
    http.authorizeHttpRequests(auth -> {
        auth.anyRequest().authenticated();
    });
    var user = User.withUsername("john")
            .password("12345")
            .authorities("read")
            .build();
    http.userDetailsService(new InMemoryUserDetailsManager(user));
    return http.build();
}

这样配置也是可以的

3.4 定义鉴权逻辑

这小节,主要是为了理解Authentication Provider是如何制定鉴权逻辑的。

java 复制代码
@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    /**
     * @param authentication
     * @return Authentication
     * @throws AuthenticationException
     */
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String userName = authentication.getName();
        String password = String.valueOf(authentication.getCredentials());

        if ("john".equals(userName) && "123".equals(password)) {
            return new UsernamePasswordAuthenticationToken(userName, password, List.of());
        } else {
            throw new AuthenticationCredentialsNotFoundException("Error");
        }
    }

    /**
     * @param authenticationType
     * @return
     */
    @Override
    public boolean supports(Class<?> authenticationType) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authenticationType);
    }
}
java 复制代码
package com.zhao.springSecurityDemo.config;

import org.springframework.beans.factory.annotation.Autowired;
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.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

import java.security.Security;

/**
 * @author zhaozhe
 * @description
 * @since 2025/4/24 14:13
 */
@Configuration
public class ProjectConfig {

    private final CustomAuthenticationProvider customAuthenticationProvider;

    @Autowired
    public ProjectConfig(CustomAuthenticationProvider customAuthenticationProvider) {
        this.customAuthenticationProvider = customAuthenticationProvider;
    }

    @Bean
    SecurityFilterChain configure(HttpSecurity http) throws Exception {
        http.httpBasic(Customizer.withDefaults());
        http.authenticationProvider(customAuthenticationProvider);
        http.authorizeHttpRequests(auth -> {
            auth.anyRequest().authenticated();
        });
        return http.build();
    }
}

3.5 在多个类中配置

我们可以在不同的配置类中分别配置UserDetailsService、PasswordEncoder和SecurityFilterChain。推荐这样做的原因是,可以增加我们代码的可读性。

java 复制代码
@Configuration
public class UserManagementConfig {

    @Bean
    public UserDetailsService userDetailsService() {
        var userDetailsService = new InMemoryUserDetailsManager();
        var user = User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("read")
                .build();
        userDetailsService.createUser(user);
        return userDetailsService;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
}
java 复制代码
@Configuration
public class WebAuthorizationConfig {
    
    @Bean
    public SecurityFilterChain configure(HttpSecurity http) throws Exception {
        http.httpBasic(Customizer.withDefaults());
        http.authorizeHttpRequests(
          auth -> {auth.anyRequest().authenticated();}      
        );
        return http.build();
    }
}
相关推荐
极客智谷2 分钟前
深入理解Java线程池:从原理到实战的完整指南
java·后端
虾球xz4 分钟前
游戏引擎学习第247天:简化DEBUG_VALUE
c++·学习·游戏引擎
程序员小陈在成都9 分钟前
Spring Ioc源码引入:什么是IoC,IoC解决了什么问题
spring
代码不行的搬运工10 分钟前
HTML快速入门-4:HTML <meta> 标签属性详解
java·前端·html
bug菌1 小时前
面十年开发候选人被反问:当类被标注为@Service后,会有什么好处?我...🫨
spring boot·后端·spring
mask哥1 小时前
详解最新链路追踪skywalking框架介绍、架构、环境本地部署&配置、整合微服务springcloudalibaba 、日志收集、自定义链路追踪、告警等
java·spring cloud·架构·gateway·springboot·skywalking·链路追踪
XU磊2601 小时前
javaWeb开发---前后端开发全景图解(基础梳理 + 技术体系)
java·idea
学也不会1 小时前
雪花算法
java·数据库·oracle
晓华-warm1 小时前
国产免费工作流引擎star 5.9k,Warm-Flow版本升级1.7.0(新增大量好用功能)
java·中间件·流程图·开源软件·flowable·工作流·activities
superior tigre1 小时前
C++学习:六个月从基础到就业——模板编程:模板特化
开发语言·c++·学习