---
title: SpringSecurity笔记
index: true
icon: devicon:spring
category:
  - 笔记
  - 记录
  - 学习
  - Java
  - Spring
headerDepth: 4
dir:
  order: 3
---


## SpringSecurity笔记

### 密码转换器（Password Encoder）

Spring Security 提供了多种密码转换器（Password Encoder），这些转换器用于对用户密码进行加密和验证。常见的密码转换器包括：

1. **BCryptPasswordEncoder**：

- 使用 **BCrypt** 算法对密码进行加密。
- 是最常用的密码加密方案，具有强大的加密性，并且支持自动加盐（salt），防止暴力破解攻击。
- 示例：

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

1. **NoOpPasswordEncoder**：

- 不对密码进行加密，直接返回明文密码。
- 主要用于开发和测试环境，**不推荐在生产环境中使用**。
- 示例：

```java
@Bean
public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();
}
```

1. **Pbkdf2PasswordEncoder**：

- 使用 **PBKDF2** 算法进行密码加密。
- 提供较强的安全性，并且支持对密码进行哈希。
- 示例：

```java
@Bean
public PasswordEncoder passwordEncoder() {
    return new Pbkdf2PasswordEncoder();
}
```

1. **Argon2PasswordEncoder**：

- 使用 **Argon2** 算法对密码进行加密。
- Argon2 是目前被认为最强的密码哈希算法，支持内存密集型计算，从而防止硬件加速破解。
- 示例：

```java
@Bean
public PasswordEncoder passwordEncoder() {
    return new Argon2PasswordEncoder();
}
```

1. **SCryptPasswordEncoder**：

- 使用 **SCrypt** 算法进行密码加密。
- SCrypt 是另一种内存密集型的密码加密算法，与 Argon2 类似，旨在防止硬件加速破解。
- 示例：

```java
@Bean
public PasswordEncoder passwordEncoder() {
    return new SCryptPasswordEncoder();
}
```

1. **MessageDigestPasswordEncoder** (已废弃)：

- 基于 **MessageDigest** 算法进行加密（如 SHA-1、SHA-256 等）。
- 由于缺乏盐和密钥加密机制，已被其他更强的加密方式所替代。

选择密码转换器的建议：

- 在现代应用中，推荐使用 **BCryptPasswordEncoder** 或 **Argon2PasswordEncoder**，这两种算法提供了强大的加密性。
- **Pbkdf2PasswordEncoder** 和 **SCryptPasswordEncoder** 也可以作为备选方案，尤其是当你希望加密算法能够承受更多资源密集型攻击时。
- **NoOpPasswordEncoder** 仅限于开发和测试环境。

### 访问主页

需要使用`http://localhost:8080/index`来访问主页，可以在配置中配置，访问根路径直接跳转

```java
@RequestMapping
@Controller
public class HomeController {

    @Operation(summary = "主页内容")
    @GetMapping("index")
    public String index() {
        return "index";
    }

    @Operation(summary = "订单内容")
    @GetMapping("order")
    public String order() {
        return "order";
    }

    @Operation(summary = "login")
    @GetMapping("login")
    public String login() {
        return "login";
    }
}
```

在配置中

```java
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/")
                // .setViewName("forward:/index")  //两种方式写法
                .setViewName("index");
        registry.addViewController("/login");
    }
}
```

### 自定义登录

```java
package cn.bunny.springdemo.configuration;

import cn.bunny.springdemo.dao.entity.AdminUser;
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.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.crypto.scrypt.SCryptPasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;

import java.util.ArrayList;

@Configuration
public class SecurityConfiguration {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new SCryptPasswordEncoder(4, 8, 1, 8, 32);
    }

    /**
     * 使用内存方式
     *
     * @param encoder 密码加密器
     * @return 基于内存的用户
     */
    @Bean
    public UserDetailsService userDetailsService(PasswordEncoder encoder) {
        ArrayList<UserDetails> userDetails = new ArrayList<>();
        userDetails.add(new AdminUser("admin", encoder.encode("admin"), true));
        userDetails.add(new AdminUser("bunny", encoder.encode("password"), true));

        return new InMemoryUserDetailsManager(userDetails);
    }

    // /**
    //  * 使用数据库方式
    //  *
    //  * @param userService 获取用户数据（如数据库）
    //  * @return 基于数据库的用户
    //  */
    // @Bean
    // public UserDetailsService userDetailsService(UserService userService) {
    //     return username -> {
    //         AdminUser adminUser = userService.getOne(Wrappers.<AdminUser>lambdaQuery().eq(AdminUser::getUsername, userService));
    //         if (adminUser != null) {
    //             return adminUser;
    //         }
    //         throw new UsernameNotFoundException("未找到 AdminUser");
    //     };
    // }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity security) throws Exception {
        return security
                .authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
                        authorizationManagerRequestMatcherRegistry
                                .requestMatchers("/order").hasRole("USER")// 请求需要含有USER角色
                                .requestMatchers("/", "/index", "/login", "/images/**").permitAll()
                )
                /* 自定义登录页面 */
                // .formLogin(AbstractAuthenticationFilterConfigurer::permitAll)// 默认的登录页会自动启用，无需额外配置
                .formLogin(formLoginConfigurer -> formLoginConfigurer
                                .loginPage("/login")
                                // .loginProcessingUrl("/authenticate")
                                .usernameParameter("username")// 自定义用户名名称
                                .usernameParameter("password")// 自定义密码名称
                                .defaultSuccessUrl("/index")// 登录成功后默认跳转页面
                        // .defaultSuccessUrl("/index", true)// 登录成功后默认跳转页面，如果用户之前访问页面也需要强制跳转到 /index 可以传递第二个参数
                )
                .build();
    }
}
```

### CSRF 伪造

通常在自定义登录页面中加入

```html
<label>
    <input name="_csrf" placeholder="_csrf" th:value="${_csrf.token}" type="hidden"/>
</label>
```

如果需要禁用

```java
.csrf(AbstractHttpConfigurer::disable)// 前后端分离可以禁用
```

### 开发授权服务器

```xml
<!--资源服务器-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource server</artifactId>
</dependency>
<!-- 客户应用 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<!-- 授权服务器 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-authorization-server</artifactId>
</dependency>
```
