Spring Security - 重定向



在 Web 应用程序中,我们经常需要根据用户配置文件跳转到不同的页面。例如,普通用户可能跳转到用户主页,而管理员可能跳转到管理员控制台。我们可以使用 Spring Security 很容易地实现此要求,它提供支持来处理登录成功,并根据用户角色决定向用户显示哪个页面,或者简单地将用户重定向到所需的页面。

为了实现重定向,我们需要在 Spring Security 配置中处理 formLogin 的 successHandler,如下所示

protected void configure(HttpSecurity http) throws Exception {
http 
   // ... 
   // key should be unique
   .formLogin(form -> form.loginPage("/login")
      .defaultSuccessUrl("/")
      .failureUrl("/login?error=true")
      .successHandler(authenticationSuccessHandler())
      .permitAll())   
   //
   .build();
}

这里 authenticationSuccessHandler() 方法是另一个处理登录成功并将用户重定向到所需页面的 bean。

@Bean
public AuthenticationSuccessHandler authenticationSuccessHandler() {
   return new AuthenticationHandler();
}

AuthenticationSuccessHandler

为了实现重定向,我们首先需要创建一个类来实现 AuthenticationSuccessHandler,如下所示。在这个类中,我们必须实现 onAuthenticationSuccess() 方法。onAuthenticationSuccess() 方法在用户成功登录后调用。现在使用 Authentication 对象,我们可以检查已登录用户的角色,然后确定重定向 URL。然后,使用 HttpServletResponse.sendRedirect() 方法,我们可以将用户重定向到所需的页面。

public class AuthenticationHandler implements AuthenticationSuccessHandler {

   @Override
   public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
      Authentication authentication) throws IOException, ServletException {
      String redirect = request.getContextPath();

      if (authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
         redirect = "/admin";
      } else if (authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_USER"))) {
         redirect = "/user";
      }

      response.sendRedirect(redirect);
   }
}

让我们开始使用 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;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

@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")
            .successHandler(authenticationSuccessHandler())
            .permitAll())       
         .rememberMe(config -> config.key("123456")
         .tokenValiditySeconds(3600))   
         .logout(config -> config  
            .logoutUrl("/logout") 
            .logoutSuccessUrl("/login")
            .invalidateHttpSession(true)
            .deleteCookies("JSESSIONID"))
         .build();
   }  
   
   @Bean
   public AuthenticationSuccessHandler authenticationSuccessHandler() {
      return new AuthenticationHandler();
   }
}

在这里,我们在 successHandler() 方法中使用了 authenticationSuccessHandler() 来进行所需的重定向。AuthenticationHandler 类应该在同一个包中。

AuthenticationHandler 类

下面的类实现了 AuthenticationSuccessHandler。在这个类中,我们实现了 onAuthenticationSuccess() 方法。onAuthenticationSuccess() 方法在用户成功登录后调用。

AuthenticationHandler

package com.tutorialspoint.security.formlogin.config;

import java.io.IOException;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

public class AuthenticationHandler implements AuthenticationSuccessHandler {

   @Override
   public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
      Authentication authentication) throws IOException, ServletException {
      String redirect = request.getContextPath();

      if (authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_ADMIN"))) {
         redirect = "/admin";
      } else if (authentication.getAuthorities().stream().anyMatch(a -> a.getAuthority().equals("ROLE_USER"))) {
         redirect = "/user";
      }

      response.sendRedirect(redirect);
   }
}

控制器类

在这个类中,我们为 "/" 端点和 "/login" 创建了映射,用于此应用程序的索引页面和登录页面。对于 user.html 和 admin.html,我们添加了两个方法 user() 和 admin()。

AuthController.java

package com.tutorialspoint.security.formlogin.controllers; 

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

@Controller 
public class AuthController { 
   @GetMapping("/") 
   public String home() { 
      return "index"; 
   }
   @GetMapping("/login") 
   public String login() { 
      return "login"; 
   }
   @GetMapping("/user")
   public String user() {
      return "user";
   }
   @GetMapping("/admin")
   public String admin() {
      return "admin";
   }
}

视图

让我们在/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> 
      <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复选框的输入字段。如果选中复选框,则用户登录应用程序时将创建一个 remember-me Cookie。

admin.html

让我们在/src/main/resources/templates文件夹中创建 admin.html,其内容如下,作为用户使用 ADMIN 角色凭据登录时的主页。

<!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 Admin!
      </title> 
   </head>
   <body> 
      <p>Admin Console</p>
      <h1 th:inline="text">Welcome <span sec:authentication="name"></span>!</h1> 
      <a href="/logout" alt="logout">Sign Out</a>
   </body> 
<html> 

user.html

让我们在/src/main/resources/templates文件夹中创建 user.html,其内容如下,作为用户使用 USER 角色凭据登录时的主页。

<!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 User!
      </title> 
   </head>
   <body> 
      <p>User Dashboard</p>
      <h1 th:inline="text">Hello <span sec:authentication="name"></span>!</h1> 
      <a href="/logout" alt="logout">Sign Out</a>
   </body> 
<html> 

运行应用程序

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

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

输出

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

输入用户详细信息的登录页面

Login Form with User credential

用户主页

当我们输入用户的有效凭据时,它将加载用户主页,即 user.html

User Home Page

现在单击“注销”链接注销,然后在登录页面上使用管理员详细信息。

Login Form with Admin credential

管理员主页

当我们输入管理员的有效凭据时,它将加载管理员主页,即 admin.html

Admin Home Page
广告
© . All rights reserved.