Spring Security 简单使用教程

2024-08-07 15:36:26 浏览数 (3)

Spring Security 是一个强大的、安全性框架,用于保护 Spring 应用程序。本文将详细介绍如何在一个 Spring Boot 项目中使用 Spring Security,从基础配置到自定义安全需求。

创建 Spring Boot 项目

首先,我们需要创建一个 Spring Boot 项目。可以通过 Spring Initializr 快速生成项目。

  1. 访问 Spring Initializr.
  2. 选择如下选项:
    • Project: Gradle Project 或 Maven Project
    • Language: Java
    • Spring Boot: 最新稳定版本
  3. 添加依赖:
    • Spring Web
    • Spring Security
    • Spring Data JPA(用于后续数据库操作)
    • H2 Database(用于演示)

生成并下载项目,解压后使用你喜欢的 IDE(如 IntelliJ IDEA 或 Eclipse)打开。

引入 Spring Security 依赖

如果你通过 Spring Initializr 创建了项目,Spring Security 依赖应该已经包括在内。如果没有,可以手动添加依赖:

Maven:

代码语言:javascript复制
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Gradle:

代码语言:javascript复制
implementation 'org.springframework.boot:spring-boot-starter-security'
默认配置和基本认证

Spring Security 默认提供了基本的 HTTP Basic 认证。启动项目后,访问任意端点,你会被要求输入用户名和密码。

默认情况下,Spring Boot 会生成一个随机密码并打印在控制台。默认用户名是 user

代码语言:javascript复制
Using generated security password: <generated-password>
自定义用户认证

让我们创建一个自定义的用户认证。首先,创建一个配置类来扩展 WebSecurityConfigurerAdapter

代码语言:javascript复制
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.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    protected UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withDefaultPasswordEncoder()
                .username("user")
                .password("password")
                .roles("USER")
                .build());
        manager.createUser(User.withDefaultPasswordEncoder()
                .username("admin")
                .password("admin")
                .roles("ADMIN")
                .build());
        return manager;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasRole("USER")
                .antMatchers("/", "/home").permitAll()
                .and()
                .formLogin();
    }
}
基于数据库的用户认证

接下来,配置基于数据库的用户认证。首先,创建用户实体类和数据库表。

User 实体类:

代码语言:javascript复制
import javax.persistence.*;

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String password;
    private String role;

    // getters and setters
}

UserRepository 接口:

代码语言:javascript复制
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
    User findByUsername(String username);
}

数据库初始化脚本:src/main/resources 目录下创建 data.sql 文件,初始化一些数据。

代码语言:javascript复制
INSERT INTO user (username, password, role) VALUES ('user', 'password', 'ROLE_USER');
INSERT INTO user (username, password, role) VALUES ('admin', 'admin', 'ROLE_ADMIN');

SecurityConfig 配置:

代码语言:javascript复制
import org.springframework.beans.factory.annotation.Autowired;
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserRepository userRepository;

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

    @Override
    protected UserDetailsService userDetailsService() {
        return username -> {
            User user = userRepository.findByUsername(username);
            if (user == null) {
                throw new UsernameNotFoundException("User not found");
            }
            return org.springframework.security.core.userdetails.User
                    .withUsername(user.getUsername())
                    .password(passwordEncoder().encode(user.getPassword()))
                    .roles(user.getRole())
                    .build();
        };
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/admin/**").hasRole("ADMIN")
                .antMatchers("/user/**").hasRole("USER")
                .antMatchers("/", "/home").permitAll()
                .and()
                .formLogin();
    }
}
自定义登录页面

创建一个自定义的登录页面。首先,在 src/main/resources/templates 目录下创建一个 login.html 文件。

代码语言:javascript复制
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login</title>
</head>
<body>
<h1>Login</h1>
<form th:action="@{/login}" method="post">
    <div>
        <label>Username:</label>
        <input type="text" name="username"/>
    </div>
    <div>
        <label>Password:</label>
        <input type="password" name="password"/>
    </div>
    <div>
        <button type="submit">Login</button>
    </div>
</form>
</body>
</html>

然后,在 SecurityConfig 中配置自定义登录页面。

代码语言:javascript复制
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .antMatchers("/user/**").hasRole("USER")
            .antMatchers("/", "/home").permitAll()
            .and()
            .formLogin()
            .loginPage("/login")
            .permitAll();
}
方法级别的安全性

Spring Security 还支持方法级别的安全性。我们可以在服务层使用注解来保护方法。

首先,启用全局方法安全性。在主应用程序类中添加 @EnableGlobalMethodSecurity(prePostEnabled = true) 注解。

代码语言:javascript复制
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;

@SpringBootApplication
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

然后,在服务层方法上使用 @PreAuthorize 注解。

代码语言:javascript复制
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    @PreAuthorize("hasRole('ROLE_ADMIN')")
    public void adminMethod() {
        // admin only logic
    }

    @PreAuthorize("hasRole('ROLE_USER')")
    public void userMethod() {
        // user only logic
    }
}
处理跨站请求伪造(CSRF)

Spring Security 默认启用了 CSRF 保护。在开发和测试过程中,我们可能需要禁用它。在 SecurityConfig 中进行配置。

代码语言:javascript复制
@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .antMatchers("/user/**").hasRole("USER")
            .antMatchers("/", "/home").permitAll()
            .and()
            .formLogin()
            .loginPage("/login")
            .permitAll();
}
日志记录和异常处理

我们可以通过自定义 AuthenticationEntryPointAccessDeniedHandler 来处理认证和授权异常。

代码语言:javascript复制
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;


import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
    }
}

@Component
public class CustomAccessDeniedHandler extends AccessDeniedHandlerImpl {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
        response.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden");
    }
}

SecurityConfig 中配置这些组件。

代码语言:javascript复制
@Autowired
private CustomAuthenticationEntryPoint customAuthenticationEntryPoint;

@Autowired
private CustomAccessDeniedHandler customAccessDeniedHandler;

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.csrf().disable()
            .authorizeRequests()
            .antMatchers("/admin/**").hasRole("ADMIN")
            .antMatchers("/user/**").hasRole("USER")
            .antMatchers("/", "/home").permitAll()
            .and()
            .formLogin()
            .loginPage("/login")
            .permitAll()
            .and()
            .exceptionHandling()
            .authenticationEntryPoint(customAuthenticationEntryPoint)
            .accessDeniedHandler(customAccessDeniedHandler);
}
总结

通过这篇详细的教程,我们已经学习了如何在 Spring Boot 项目中集成 Spring Security,包括基本配置、自定义用户认证、基于数据库的用户认证、自定义登录页面、方法级别的安全性、CSRF 保护以及日志记录和异常处理。Spring Security 提供了强大的功能和灵活的配置选项,使我们能够根据需求来保护应用程序的安全。

0 人点赞