Spring Security - 方法级访问控制



Spring Security 提供请求级别和方法级别的访问控制。我们可以在任何配置中使用 @EnableMethodSecurity 注解启用方法级安全性,如下所示

@Controller 
@EnableMethodSecurity  // Enable method level security
public class AuthController { 
...
}

默认情况下,方法级安全性是关闭的。Spring Security 为方法级安全性提供以下选项。

  • @PreAuthorize − 如果提供的条件为假,则阻止方法调用。我们可以传递一个表达式来控制访问,如下所示

    // Accessible to user with Admin role
    @PreAuthorize("hasRole('ROLE_ADMIN')")  
    public String update() {    
       return "Details Updated.";    
    }  
    
  • @PostAuthorize − 如果提供的条件为真,则方法可以返回值。我们可以传递一个表达式来控制访问,如下所示

    class Account {
       string owner;
       ...
    }
    ...
    @PostAuthorize("returnObject.owner == authentication.name")
    public Account readAccount(Long id) {
        ...
        return account;	
    	
    }
    

    如果用户是账户所有者,则将返回账户详细信息,否则将抛出 AccessDeniedException 异常,并返回 403 状态码。

  • @PreFilter − 如果提供的条件为真,则方法会过滤任何值。我们可以传递一个表达式来过滤返回的值,如下所示

    class Account {
       string owner;
       ...
    }
    ...
    @PreFilter("filterObject.owner == authentication.name")
    public Colletion<Account> updateAccounts(Account... accounts) {
        ...
        return updatedAccounts;	
    	
    }
    

    以上方法将仅根据提供的条件过滤属于登录用户的那些账户,并仅更新它们。

  • @PostFilter − 如果提供的条件为真,则方法会过滤任何值。我们可以传递一个表达式来过滤返回的值,如下所示

    class Account {
       string owner;
       ...
    }
    ...
    @PostFilter("filterObject.owner == authentication.name")
    public Colletion<Account> readAccounts(Long... ids) {
        ...
        return accounts;	
    	
    }
    

    以上方法将仅根据提供的条件过滤属于登录用户的那些账户。

示例

让我们开始使用 Spring Security 进行实际编程。在开始使用 Spring 框架编写示例之前,您必须确保已正确设置 Spring 环境,如 Spring Security - 环境设置 章节中所述。我们还假设您对 Spring Tool Suite IDE 有基本的了解。

现在让我们继续编写一个基于 Spring MVC 的应用程序,该应用程序由 Maven 管理,它将要求用户登录、对用户进行身份验证,然后使用 Spring Security 表单登录功能提供注销选项。

使用 Spring Initializr 创建项目

Spring Initializr 是开始 Spring Boot 项目的好方法。它提供了一个易于使用的用户界面来创建项目、添加依赖项、选择 Java 运行时等。它生成一个骨架项目结构,下载后可以导入到 Spring Tool Suite 中,然后我们可以继续使用我们的现成项目结构。

我们选择一个 Maven 项目,将项目命名为 formlogin,Java 版本为 21。添加以下依赖项

  • Spring Web

  • Spring Security

  • Thymeleaf

  • Spring Boot DevTools

Spring Initializr

Thymeleaf 是一个用于 Java 的模板引擎。它允许我们快速开发静态或动态网页以在浏览器中呈现。它具有极强的可扩展性,允许我们详细定义和自定义模板的处理过程。此外,我们可以点击此 链接 了解更多关于 Thymeleaf 的信息。

让我们继续生成项目并下载它。然后,我们将其解压缩到我们选择的文件夹中,并使用任何 IDE 打开它。我将使用 Spring Tools Suite 4。它可以从 https://springframework.org.cn/tools 网站免费下载,并且针对 Spring 应用程序进行了优化。

包含所有相关依赖项的 pom.xml

让我们看看我们的 pom.xml 文件。它应该看起来类似于以下内容 -

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
   <modelVersion>4.0.0</modelVersion>
   <parent>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-parent</artifactId>
      <version>3.3.1</version>
      <relativePath/> <!-- lookup parent from repository -->
   </parent>
   <groupId>com.tutorialspoint.security</groupId>
   <artifactId>formlogin</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>formlogin</name>
   <description>Demo project for Spring Boot</description>
   <url/>
   <licenses>
      <license/>
   </licenses>
   <developers>
      <developer/>
   </developers>
   <scm>
      <connection/>
      <developerConnection/>
      <tag/>
      <url/>
   </scm>
   <properties>
      <java.version>21</java.version>
   </properties>
   <dependencies>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-security</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-thymeleaf</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
      </dependency>
      <dependency>
         <groupId>org.thymeleaf.extras</groupId>
         <artifactId>thymeleaf-extras-springsecurity6</artifactId>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-devtools</artifactId>
         <scope>runtime</scope>
         <optional>true</optional>
      </dependency>
      <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-test</artifactId>
         <scope>test</scope>
      </dependency>
      <dependency>
         <groupId>org.springframework.security</groupId>
         <artifactId>spring-security-test</artifactId>
         <scope>test</scope>
      </dependency>
   </dependencies>
   <build>
      <plugins>
         <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
         </plugin>
      </plugins>
   </build>
</project>

Spring Security 配置类

在我们的 config 包中,我们创建了 WebSecurityConfig 类。我们将使用此类进行我们的安全配置,因此让我们使用 @Configuration 注解和 @EnableWebSecurity 注解对其进行注释。因此,Spring Security 知道将此类视为配置类。如我们所见,Spring 使配置应用程序变得非常容易。

WebSecurityConfig

package com.tutorialspoint.security.formlogin.config; 

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.configurers.AbstractHttpConfigurer;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;  

@Configuration 
@EnableWebSecurity
public class WebSecurityConfig  { 

   @Bean 
   protected UserDetailsService userDetailsService() {
      UserDetails user = User.builder()
         .username("user")
         .password(passwordEncoder().encode("user123"))
         .roles("USER")
         .build();
      UserDetails admin = User.builder()
         .username("admin")
         .password(passwordEncoder().encode("admin123"))
         .roles("USER", "ADMIN")
         .build();
      return new InMemoryUserDetailsManager(user, admin);
   }

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

   @Bean
   protected SecurityFilterChain filterChain(HttpSecurity http) throws Exception { 
      return http
         .csrf(AbstractHttpConfigurer::disable)
         .authorizeHttpRequests(
            request -> request.requestMatchers("/login").permitAll()
            .requestMatchers("/**").authenticated()
         )
         .formLogin(form -> form.loginPage("/login")
            .defaultSuccessUrl("/")
            .failureUrl("/login?error=true")
            .permitAll())       
         .logout(config -> config  
            .logoutUrl("/logout") 
            .logoutSuccessUrl("/login")
         .build();
   }   
}

控制器类

在此类中,我们为 "/" 端点和 "/login" 端点创建了映射,分别用于此应用程序的首页和登录页面。我们使用了 @EnableMethodSecurity 注解来启用方法级安全性,因为它默认是关闭的。

AuthController.java

package com.tutorialspoint.security.formlogin.controllers; 

import org.springframework.stereotype.Controller; 
import org.springframework.web.bind.annotation.GetMapping; 

@Controller 
@EnableMethodSecurity
public class AuthController { 
   @GetMapping("/") 
   public String home() { 
      return "index"; 
   }
   @GetMapping("/login") 
   public String login() { 
      return "login"; 
   }
   
   // User with Admin role only can access this method
   @GetMapping("/update") 
   @ResponseBody  
   @PreAuthorize("hasRole('ROLE_ADMIN')")  
   public String update() {    
      return "Details Updated.";    
   }     
}

视图

让我们在 /src/main/resources/templates 文件夹中创建 index.html 文件,其内容如下,用作主页并显示登录用户的名称。

index.html

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml" 
   xmlns:th="https://www.thymeleaf.org" 
   xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> 
   <head> 
      <title>
         Hello World!
      </title> 
   </head>
   <body> 
      <h1 th:inline="text">Hello <span sec:authentication="name"></span>!</h1>
      <a href="/update" alt="update details">Update Details</a> 	  
      <form th:action="@{/logout}" method="post"> 
         <input type="submit" value="Sign Out"/> 
      </form>
   </body> 
<html> 

login.html

让我们在 /src/main/resources/templates 文件夹中创建 login.html 文件,其内容如下,用作登录页面。我们使用默认名称 usernamepasswordremember-me 作为文本字段。如果使用其他名称,则也需要在 Spring Security 配置类中设置相同的名称。

<!DOCTYPE html> 
<html xmlns="http://www.w3.org/1999/xhtml"      
   xmlns:th="https://www.thymeleaf.org" 
   xmlns:sec="https://www.thymeleaf.org/thymeleaf-extras-springsecurity3"> 
   <head> 
      <title>Spring Security Example</title> 
   </head> 
   <body> 
      <div th:if="${param.error}"> 
         <p>Bad Credentials</p>
      </div> 
      <div th:if="${param.logout}">You have been logged out.</div> 
      <form th:action="@{/login}" method="post">
         <h1>Please sign in</h1>
         <table>
            <tr>
               <td><label for="username"><b>Username</b></label></td>
               <td><input type="text" placeholder="Enter Username" name="username" id="username" required></td>
            </tr>
            <tr>
               <td><label for="password"><b>Password</b></label></td>
               <td><input type="password" placeholder="Enter Password" name="password" id="password" required></td>
            </tr>
            <tr>
               <td><label for="remember-me"><b>Remember Me</b></label> </td>
               <td><input type="checkbox" name="remember-me" /></td>
            </tr>
            <tr>
               <td> </td>
               <td><input type="submit"  value="Sign In" /></td>
            </tr>		
         </table>       
      </form> 
   </body>
</html>

在登录表单中,我们使用 POST 方法登录,同时使用名称和 ID 为 usernamepasswordremember-me 复选框的输入字段。如果选中复选框,则用户登录应用程序时将创建记住我的 Cookie。

运行应用程序

由于我们所有组件都已准备就绪,因此让我们运行应用程序。右键单击项目,选择 Run As,然后选择 Spring Boot App

它将启动应用程序,并且应用程序启动后,我们可以运行 localhost:8080 以检查更改。

输出

现在打开 localhost:8080,您可以看到我们的登录页面。使用 user 凭据登录

用户登录页面

Login Form for user

用户主页

使用用户的有效凭据,将显示一个用户页面,如下所示,其中包含一个“更新详细信息”链接

User Page with Update Link

单击“更新详细信息”链接,它将显示“无效凭据”,并且此方法对用户不可用。

Invalid Credential

使用管理员用户登录

现在使用浏览器后退按钮返回。单击注销按钮,这将再次加载登录页面。现在使用管理员凭据登录。

Login Form for user

管理员用户主页

使用管理员的有效凭据,将显示一个管理员页面,如下所示,其中包含一个“更新详细信息”链接

Admin Page with Update Link

单击“更新详细信息”链接,它将显示一条消息“详细信息已更新”,并且此方法对管理员可用。

Details Updated
广告

© . All rights reserved.