MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java Spring Boot中的安全性配置

2022-05-317.4k 阅读

一、Spring Boot 安全框架概述

在当今的应用程序开发中,安全性是至关重要的一环。Spring Boot 提供了强大且灵活的安全框架,使得开发者能够轻松地为应用程序添加各种安全功能。Spring Security 是 Spring 生态系统中用于安全控制的核心框架,它基于 Servlet 过滤器机制,为 Web 应用提供全面的安全解决方案,涵盖身份验证(Authentication)、授权(Authorization)、加密(Encryption)等多个方面。

Spring Boot 与 Spring Security 的集成非常便捷,通过自动配置机制,只需少量的配置,即可为应用程序快速搭建起安全防线。这种集成方式不仅适用于传统的 Web 应用,也适用于诸如 RESTful API 等现代架构的应用。

二、基本身份验证配置

2.1 引入依赖

pom.xml 文件中添加 Spring Security 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

添加该依赖后,Spring Boot 会自动配置基本的安全功能,其中就包括基本身份验证(Basic Authentication)。

2.2 配置用户名和密码

Spring Boot 提供了两种方式来配置用户名和密码。一种是通过 application.properties 文件:

spring.security.user.name=admin
spring.security.user.password=password123

另一种方式是通过 Java 配置类。创建一个配置类,例如 SecurityConfig.java

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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
           .authorizeRequests()
               .antMatchers("/", "/home").permitAll()
               .anyRequest().authenticated()
               .and()
           .formLogin()
               .loginPage("/login")
               .permitAll()
               .and()
           .logout()
               .permitAll();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user =
            User.withDefaultPasswordEncoder()
               .username("user")
               .password("password")
               .roles("USER")
               .build();

        UserDetails admin =
            User.withDefaultPasswordEncoder()
               .username("admin")
               .password("adminpassword")
               .roles("ADMIN")
               .build();

        return new InMemoryUserDetailsManager(user, admin);
    }
}

在上述配置类中,通过 userDetailsService 方法定义了两个用户,一个是普通用户 user,另一个是管理员用户 admin

2.3 基本身份验证流程

当客户端发起请求时,Spring Security 过滤器链会拦截请求。如果请求的资源需要身份验证,且客户端尚未提供有效的凭据,服务器会返回一个 401 Unauthorized 响应,并在响应头中包含 WWW-Authenticate 字段,指示客户端使用基本身份验证方式。客户端收到该响应后,会弹出一个登录对话框,用户输入用户名和密码后,客户端将用户名和密码进行 Base64 编码,并将编码后的字符串放在请求头的 Authorization 字段中,格式为 Basic <Base64编码后的字符串>。服务器接收到请求后,会对 Authorization 头进行解码,获取用户名和密码,并与配置的用户信息进行比对,若匹配成功,则允许访问资源。

三、表单登录配置

3.1 配置表单登录

SecurityConfig.java 中,已经配置了表单登录的基本设置:

http
   .formLogin()
       .loginPage("/login")
       .permitAll()
       .and()
   .logout()
       .permitAll();

上述配置指定了登录页面为 /login,并且允许所有用户访问该页面。当用户提交登录表单时,Spring Security 会自动处理登录逻辑。默认情况下,表单的用户名和密码字段名分别为 usernamepassword

3.2 创建自定义登录页面

首先,在 src/main/resources/templates 目录下创建 login.html 文件:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login Page</title>
</head>
<body>
    <form th:action="@{/login}" method="post">
        <div>
            <label for="username">Username:</label>
            <input type="text" id="username" name="username" required>
        </div>
        <div>
            <label for="password">Password:</label>
            <input type="password" id="password" name="password" required>
        </div>
        <div>
            <button type="submit">Login</button>
        </div>
    </form>
</body>
</html>

SecurityConfig.java 中,确保配置了对静态资源和 Thymeleaf 模板的支持:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
           .authorizeRequests()
               .antMatchers("/", "/home", "/css/**", "/js/**").permitAll()
               .anyRequest().authenticated()
               .and()
           .formLogin()
               .loginPage("/login")
               .permitAll()
               .and()
           .logout()
               .permitAll();
    }

    //... userDetailsService 配置不变
}

这里通过 antMatchers("/css/**", "/js/**") 允许访问 CSS 和 JavaScript 等静态资源。

3.3 处理登录成功和失败

Spring Security 提供了方便的机制来处理登录成功和失败的情况。可以通过自定义 AuthenticationSuccessHandlerAuthenticationFailureHandler 来实现。

创建一个自定义的 LoginSuccessHandler

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

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

@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.sendRedirect("/home");
    }
}

SecurityConfig.java 中配置 LoginSuccessHandler

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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationSuccessHandler loginSuccessHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
           .authorizeRequests()
               .antMatchers("/", "/home", "/css/**", "/js/**").permitAll()
               .anyRequest().authenticated()
               .and()
           .formLogin()
               .loginPage("/login")
               .successHandler(loginSuccessHandler)
               .permitAll()
               .and()
           .logout()
               .permitAll();
    }

    //... userDetailsService 配置不变
}

类似地,可以创建一个 LoginFailureHandler 来处理登录失败的情况:

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

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

@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        response.sendRedirect("/login?error=true");
    }
}

SecurityConfig.java 中配置 LoginFailureHandler

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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private AuthenticationSuccessHandler loginSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler loginFailureHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
           .authorizeRequests()
               .antMatchers("/", "/home", "/css/**", "/js/**").permitAll()
               .anyRequest().authenticated()
               .and()
           .formLogin()
               .loginPage("/login")
               .successHandler(loginSuccessHandler)
               .failureHandler(loginFailureHandler)
               .permitAll()
               .and()
           .logout()
               .permitAll();
    }

    //... userDetailsService 配置不变
}

这样,当用户登录成功或失败时,会按照自定义的逻辑进行处理。

四、授权配置

4.1 基于角色的授权

SecurityConfig.java 中配置基于角色的授权:

http
   .authorizeRequests()
       .antMatchers("/admin/**").hasRole("ADMIN")
       .antMatchers("/user/**").hasAnyRole("USER", "ADMIN")
       .anyRequest().authenticated();

上述配置表示只有具有 ADMIN 角色的用户才能访问 /admin/** 路径下的资源,而具有 USERADMIN 角色的用户可以访问 /user/** 路径下的资源。其他任何请求都需要经过身份验证。

4.2 基于方法的授权

Spring Security 还支持基于方法的授权,通过在服务层方法上使用注解来进行授权控制。首先,在 pom.xml 中添加方法安全相关的依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter -security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security -config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security -aspects</artifactId>
</dependency>

在配置类中启用方法安全:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
           .authorizeRequests()
               .anyRequest().authenticated()
               .and()
           .formLogin()
               .permitAll()
               .and()
           .logout()
               .permitAll();
    }

    @Bean
    @Override
    public UserDetailsService userDetailsService() {
        UserDetails user =
            User.withDefaultPasswordEncoder()
               .username("user")
               .password("password")
               .roles("USER")
               .build();

        UserDetails admin =
            User.withDefaultPasswordEncoder()
               .username("admin")
               .password("adminpassword")
               .roles("ADMIN")
               .build();

        return new InMemoryUserDetailsManager(user, admin);
    }

    @Bean
    public MethodSecurityExpressionHandler createExpressionHandler() {
        return new DefaultMethodSecurityExpressionHandler();
    }
}

然后,在服务层方法上使用注解,例如:

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;

@Service
public class MyService {

    @PreAuthorize("hasRole('ADMIN')")
    public String adminOnlyMethod() {
        return "This is a method only accessible to admins.";
    }

    @PreAuthorize("hasAnyRole('USER', 'ADMIN')")
    public String userAndAdminMethod() {
        return "This method is accessible to both users and admins.";
    }
}

这样,通过方法上的注解,Spring Security 会在调用方法前进行授权检查,只有符合条件的用户才能调用相应的方法。

五、CSRF 防护

5.1 CSRF 原理

CSRF(Cross - Site Request Forgery,跨站请求伪造)是一种常见的网络攻击方式。攻击者通过在用户已登录的目标网站中嵌入恶意链接或脚本,当用户访问该恶意页面时,会自动向目标网站发送请求,而这些请求会携带用户的登录凭据(如会话 cookie),从而在用户不知情的情况下执行一些操作,如转账、修改密码等。

5.2 Spring Security 中的 CSRF 防护

Spring Security 默认启用了 CSRF 防护。当启用 Spring Security 后,每次表单提交时,Spring Security 会检查请求中是否包含 CSRF 令牌(CSRF Token)。如果请求中没有有效的 CSRF 令牌,或者令牌无效,Spring Security 会拒绝该请求,并返回 403 Forbidden 响应。

在 Thymeleaf 模板中,可以通过以下方式获取和使用 CSRF 令牌:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Login Page</title>
</head>
<body>
    <form th:action="@{/login}" method="post">
        <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/>
        <div>
            <label for="username">Username:</label>
            <input type="text" id="username" name="username" required>
        </div>
        <div>
            <label for="password">Password:</label>
            <input type="password" id="password" name="password" required>
        </div>
        <div>
            <button type="submit">Login</button>
        </div>
    </form>
</body>
</html>

这里通过 th:name="${_csrf.parameterName}"th:value="${_csrf.token}" 获取并添加了 CSRF 令牌到表单中。

5.3 禁用 CSRF 防护

在某些情况下,如开发 RESTful API 时,可能需要禁用 CSRF 防护。可以在 SecurityConfig.java 中进行如下配置:

http
   .csrf()
       .disable();

然而,禁用 CSRF 防护会带来一定的安全风险,因此在实际应用中,只有在充分评估风险并采取其他替代安全措施(如使用 OAuth 2.0 等)的情况下,才应禁用它。

六、HTTPS 配置

6.1 生成证书

在配置 HTTPS 之前,需要生成 SSL/TLS 证书。可以使用 Java 自带的 keytool 工具来生成自签名证书:

keytool -genkeypair -alias mykey -keyalg RSA -dname "CN=localhost, OU=Example, O=Example, L=Example, S=Example, C=US" -keystore keystore.jks -storepass password -validity 3650

上述命令生成了一个有效期为 10 年的自签名证书,别名为 mykey,存储在 keystore.jks 文件中,密码为 password

6.2 配置 Spring Boot 使用 HTTPS

application.properties 文件中配置证书路径和密码:

server.ssl.key-store=classpath:keystore.jks
server.ssl.key-store-password=password
server.ssl.key-alias=mykey
server.ssl.key-store-type=JKS

配置完成后,Spring Boot 应用将以 HTTPS 协议运行。此时,所有的通信都会被加密,有效防止数据在传输过程中被窃取或篡改。

七、OAuth 2.0 配置

7.1 OAuth 2.0 概述

OAuth 2.0 是一种授权框架,允许用户授权第三方应用访问其在另一个服务提供商上的资源,而无需将用户名和密码提供给第三方应用。它通过使用令牌(Token)来代表用户的授权,使得第三方应用能够在用户授权的范围内访问资源。

7.2 作为 OAuth 2.0 客户端配置

在 Spring Boot 中配置应用作为 OAuth 2.0 客户端,可以使用 spring - boot - starter - oauth2 - client 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter -oauth2 - client</artifactId>
</dependency>

application.properties 文件中配置客户端信息,例如:

spring.security.oauth2.client.registration.google.client-id=your - client - id
spring.security.oauth2.client.registration.google.client-secret=your - client - secret
spring.security.oauth2.client.registration.google.redirect-uri={baseUrl}/login/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.google.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.google.scope=openid,profile,email
spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/v2/auth
spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token
spring.security.oauth2.client.provider.google.user-info-uri=https://openidconnect.googleapis.com/v1/userinfo
spring.security.oauth2.client.provider.google.user-name-attribute=name

上述配置以 Google 作为授权服务器为例,配置了客户端 ID、客户端密钥、重定向 URI、授权类型、请求的权限范围以及授权服务器的相关 URI 等信息。

SecurityConfig.java 中配置 OAuth 2.0 客户端:

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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final ClientRegistrationRepository clientRegistrationRepository;

    public SecurityConfig(ClientRegistrationRepository clientRegistrationRepository) {
        this.clientRegistrationRepository = clientRegistrationRepository;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
           .authorizeRequests()
               .antMatchers("/", "/home").permitAll()
               .anyRequest().authenticated()
               .and()
           .oauth2Login()
               .loginPage("/login")
               .permitAll();

        http.addFilterAfter(new CustomOAuth2LoginSuccessHandler(clientRegistrationRepository), OAuth2LoginAuthenticationFilter.class);
    }
}

这里通过 oauth2Login() 配置了 OAuth 2.0 登录,并可以自定义登录成功后的处理逻辑,如通过 CustomOAuth2LoginSuccessHandler 类来处理。

7.3 作为 OAuth 2.0 资源服务器配置

如果应用需要作为 OAuth 2.0 资源服务器,接收并验证来自 OAuth 2.0 客户端的令牌,可以使用 spring - boot - starter - oauth2 - resource - server 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter -oauth2 - resource - server</artifactId>
</dependency>

SecurityConfig.java 中配置资源服务器:

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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
           .authorizeRequests()
               .antMatchers("/api/public").permitAll()
               .anyRequest().authenticated()
               .and()
           .oauth2ResourceServer()
               .jwt();
    }

    @Bean
    public JwtDecoder jwtDecoder() {
        return NimbusJwtDecoder.withJwkSetUri("https://your - authorization - server/.well - known/jwks.json").build();
    }
}

上述配置中,通过 oauth2ResourceServer().jwt() 启用了 JWT(JSON Web Token)验证,资源服务器会验证请求中携带的 JWT 令牌的有效性,只有有效令牌的请求才能访问受保护的资源。

通过以上详细的配置和代码示例,开发者可以全面地了解和掌握 Spring Boot 中的安全性配置,为应用程序构建坚实的安全防线,确保应用程序的稳定运行和用户数据的安全。无论是基本的身份验证、授权,还是更高级的 CSRF 防护、HTTPS 配置以及 OAuth 2.0 集成,Spring Boot 和 Spring Security 都提供了强大且灵活的解决方案。在实际开发中,应根据应用程序的具体需求和安全要求,合理选择和配置这些安全功能。