Spring Security 简明教程
Spring Security - Taglibs
在 JSP 中使用 Spring MVC 应用程序时,我们可以使用 Spring Security 标签来应用安全约束以及访问安全信息。Spring Security 标签库为这样的操作提供了基本支持。使用这样的标签,我们可以根据用户的角色或权限控制向用户显示的信息。此外,我们可以在我们的表单中包含 CSRF 保护功能。
In Spring MVC applications using JSP, we can use the Spring Security tags for applying security constraints as well as for accessing security information. Spring Security Tag library provides basic support for such operations. Using such tags, we can control the information displayed to the user based on his roles or permissions. Also, we can include CSRF protection features in our forms.
为了使用 Spring 安全标签,我们必须在 JSP 文件中声明安全标记库。
To use Spring security tags, we must have the security taglib declared in our JSP file.
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %>
现在,我们可以使用 sec 前缀的 Spring Security 标记。现在让我们了解一下标记的用法。
Now, we can use Spring Security tags with the sec prefix. Let’s now see the usage of the tags.
The authorize Tag
The authorize Tag
我们将讨论的第一个标签是 authorize 标签。让我们看一下一些使用示例。
The first tag we will be discussing is the authorize tag. Let’s check out some usage examples.
<sec:authorize access="!isAuthenticated()"> Login </sec:authorize>
<sec:authorize access="isAuthenticated()"> Logout </sec:authorize>
<sec:authorize access="hasRole('ADMIN')"> Hello Admin. </sec:authorize>
正如我们看到的那样,我们可以使用此标记根据访问权限或角色隐藏或显示信息部分。
As we can see, we can use this tag to hide or show sections of information based on access or roles.
-
hasRole(*ADMIN)* − evaluates to true if the current user has the admin role.
-
hasAnyRole(‘ADMIN’,’USER’) − evaluates to true if the current user has any of the listed roles
-
isAnonymous() − evaluates to true if the current user is an anonymous user
-
isRememberMe() − evaluates to true if the current user is a remember-me user
-
isFullyAuthenticated() − evaluates to true if the user is authenticated and is neither anonymous nor a remember-me user
正如我们看到的那样,access 属性是指定 Web 安全表达式的属性。然后,Spring Security 将评估该表达式。求值通常委托给 SecurityExpressionHandler<FilterInvocation>,它在应用程序上下文中定义。如果它返回 true,那么用户可以访问该部分中给出的信息。
As we can see, the access attribute is where the web-security expression is specified. Then, Spring Security evaluates the expression. The evaluation is generally delegated to SecurityExpressionHandler<FilterInvocation>, which is defined in the application context. If it returns true, then the user can get access to the information given in that section.
如果我们使用 Spring Security 的 Permission Evaluator 将 authorize 标签与 Spring Security 权限评估器一起使用,我们还可以检查用户权限,如下所示 −
If we use the authorize tag with Spring Security ‘s Permission Evaluator, we can also check user permissions as given below −
<p sec:authorize="hasPermission(#domain,'read') or hasPermission(#domain,'write')">
This content is visible to users who have read or write permission.
</p>
我们还可以允许或限制用户点击内容中的特定链接。
We can also allow or restrict the user from clicking on certain links within our content.
<a sec:authorize href="/admin">
This content will only be visible to users who are authorized to send requests to the "/admin" URL.
</agt;
The authentication tag
The authentication tag
当我们需要访问存储在 Spring Security 上下文中的当前身份验证对象时,我们可以使用 authentication 标记。然后,我们可以使用它来直接在我们的 JSP 页面中呈现对象的属性。例如,如果我们想要在页面中呈现 Authentication 对象的 principal 属性,我们可以按照如下所示进行操作 −
When we want access to the current Authentication object stored in the Spring Security Context, we can use the authentication tag. Then we can use it to render properties of the object directly in our JSP page. For example, if we want to render the principal property of the Authentication object in our page, we can do it as follows −
<p sec:authentication="name" />
The csrfInput Tag
The csrfInput Tag
当启用 CSRF 保护时,我们可以使用 csrfInput 标记插入带 CSRF 保护令牌正确值的隐藏表单字段。如果未启用 CSRF 保护,此标记不输出任何内容。
We can use the csrfInput tag to insert a hidden form field with the correct values for the CSRF protection token when CSRF protection is enabled. If CSRF protection is not enabled, this tag outputs nothing.
我们可以将此标记放置在 HTML <form></form> 块中,以及其他输入字段。但是,我们不能将此标记放置在 <form:form></form:form> 块中,因为 Spring Security 会自动在这些标记中插入 CSRF 表单字段,还会自动处理 Spring 表单。
We can place the tag within the HTML <form></form> block along with other input fields. However, we must not place the tag within the <form:form></form:form> block as Spring Security automatically inserts a CSRF form field within those tags and also takes care of Spring forms automatically.
<form method="post" action="/do/something">
<sec:csrfInput />
Username:<br />
<input type="text" username="username" />
...
</form>
The csrfMetaTags Tag
The csrfMetaTags Tag
我们可以使用此标记插入包含 CSRF 保护标记表单字段和标题名称及 CSRF 保护标记值的元标记。这些元标记可用于在应用程序的 Javascript 中使用 CSRF 保护。但是,此标记仅在我们已在应用程序中启用了 CSRF 保护时才起作用,否则此标记不输出任何内容。
We can use this tag to insert meta tags which contain the CSRF protection token form field and header names and CSRF protection token value. These meta tags can be useful for employing CSRF protection within Javascript in our application. However, this tag only works when we have enabled CSRF protection in our application, otherwise, this tag outputs nothing.
<html>
<head>
<title>CSRF Protection in Javascript</title>
<sec:csrfMetaTags />
<script type="text/javascript" language="javascript">
var csrfParam = $("meta[name='_csrf_param']").attr("content");
var csrfToken = $("meta[name='_csrf']").attr("content");
</script>
</head>
<body>
...
</body>
</html>
让我们使用 Spring Security 开始实际编程。在开始使用 Spring 框架编写示例之前,您必须确保已正确设置 Spring 环境,如 Spring Security - Environment Setup 章节所述。我们还假设您具备 Spring Tool Suite IDE 方面的部分工作知识。
Let us start actual programming with Spring Security. Before you start writing your example using Spring framework, you have to make sure that you have set up your Spring environment properly as explained in Spring Security - Environment Setup Chapter. We also assume that you have a bit of working knowledge on Spring Tool Suite IDE.
现在,让我们开始编写一个由 Maven 管理的基于 Spring MVC 的应用程序,该应用程序将要求用户登录、认证用户,然后提供使用 Spring Security 表单登录功能注销的选项。
Now let us proceed to write a Spring MVC based Application managed by Maven, which will ask user to login, authenticate user and then provide option to logout using Spring Security Form Login Feature.
Create Project using Spring Initializr
Spring Initializr 是开始 Spring Boot 项目的一个好方法。它提供了一个易于使用的用户界面来创建项目、添加依赖项、选择 Java 运行时等。它会生成一个框架项目结构,下载后可以在 Spring Tool Suite 中导入,然后我们可以使用现成的项目结构继续进行。
Spring Initializr is great way to start with Spring Boot project. It provides a easy to use User Interface to create a project, add dependencies, select java runtime etc. It generates a skeleton project structure which once downloaded can be imported in spring tool suite and we can proceed with our readymade project structure.
我们选择了一个 maven 项目,将项目命名为 formlogin,并将 java 版本指定为 21。添加了以下依赖项:
We’re choosing a maven project, naming the project as formlogin, with java version as 21. Following dependencies are added:
-
Spring Web
-
Spring Security
-
Spring Boot DevTools
Thymeleaf 是 Java 的模板引擎。它使我们能够快速开发用于在浏览器中渲染的静态或动态网页。它已得到极大的扩展,它允许我们定义和定制精细处理的模板。除此之外,我们可以通过点击 link 来了解更多有关 Thymeleaf 的信息。
Thymeleaf is a templating engine for Java. It allows us to quickly develop static or dynamic web pages for rendering in the browser. It is extremely extensible and allows us to define and customize the processing of our templates in fine detail. In addition to this, we can learn more about Thymeleaf by clicking this link.
让我们开始生成并下载项目。然后我们将它解压到我们选择的文件夹中并使用任何 IDE 来打开它。我将使用 Spring Tools Suite 4 。它可以从 https://spring.io/tools 网站免费下载,且已针对 spring 应用进行优化。
Let’s move on to generate our project and download it. We then extract it to a folder of our choice and use any IDE to open it. I shall be using Spring Tools Suite 4. It is available for free downloading from the https://spring.io/tools website and is optimized for spring applications.
pom.xml with all relevant dependencies
让我们看一看我们的 pom.xml 文件。它应该与以下内容类似 −
Let’s take a look at our pom.xml file. It should look something similar to this −
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 Configuration Class
在我们的 config 包中,我们创建了 WebSecurityConfig 类。我们将使用此类来进行我们的安全配置,因此让我们用 @Configuration 注解和 @EnableWebSecurity 为它添加注释。因此,Spring Security 知道将此类视为配置类。正如我们所看到的,Spring 让我们能很轻松地配置应用。
Inside of our config package, we have created the WebSecurityConfig class. We shall be using this class for our security configurations, so let’s annotate it with an @Configuration annotation and @EnableWebSecurity. As a result, Spring Security knows to treat this class a configuration class. As we can see, configuring applications have been made very easy by 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();
}
}
Controller Class
在此类中,我们分别为这个应用程序的索引页、登录页和管理员页创建了 "/" 端点、"/login" 和 "/admin" 的映射。
In this class, we’ve created a mapping for "/" endpoint, "/login" and "/admin" for the index page, login page and admin page of this application respectively.
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("/admin")
public String admin() {
return "admin";
}
}
Views
使用以下内容在 /src/main/resources/templates 文件夹中创建 index.html 以作为主页。
Create index.html in /src/main/resources/templates folder with following content to act as a home page.
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 World!</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="Sign Out"/>
</form>
</body>
<html>
让我们在 /src/main/resources/templates 文件夹中创建 login.html,并使其内容如下,以用作登录页面。我们对文本字段使用默认名称 username 、 password 和 remember-me 。对于其他名称,我们需要在 Spring 安全性配置类中设置相同的名称。
Let’s create the login.html in /src/main/resources/templates folder with following content to act as a login page. We’re using default name username, password and remember-me for text fields. In case of other name, we need to set the same in spring security config class as well.
login.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>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>
我们创建 admin.html 来使用授权标记。在这里,我们添加了 <%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec"%>。正如之前讨论的那样,这将让我们了解 Spring 安全标记库。
Let’s create admin.html to use authorize tag. Here, we have added <%@ taglib uri="http://www.springframework.org/security/tags" prefix="sec"%>. This is going to let us the Spring security tag libs as discussed before.
admin.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-springsecurity6">
<head>
<title>
Hello Admin!
</title>
</head>
<body>
<p>Admin Console</p>
<h1 th:inline="text">Welcome <span sec:authentication="name"></span>!</h1>
<p sec:authorize="hasRole('ADMIN')"> Control Panel </p>
<a href="/logout" alt="logout">Sign Out</a>
</body>
<html>
正如我们看到的那样,我们在内容周围添加了 authorize 标记。该内容仅供管理员访问。访问此页面的任何其他用户将无法查看此内容。另一个标记 authentication 用于获取委托,以便检索已登录用户的用户名。
As we can see, we have the added the authorize tag around the content. This content is will be only accessible by our admin. Any other user accessing this page will not be able to view this content. Another tag authentication is used to get the principal in order to retrieve the username of the user logged in.
Running the Application
一旦我们准备好了所有组件,接下来运行应用程序。右击项目,选择 Run As 然后选择 Spring Boot App 。
Once we’ve all component ready let’s run the Application. Right Click on the project, select Run As and then Spring Boot App.
它将启动该应用,并且一旦应用启动,我们就可以运行 localhost:8080 来查看更改。
It will boot up the application and once application is started, we can run localhost:8080 to check the changes.
Output
现在打开 localhost:8080,您将看到我们的登录页面。
Now open localhost:8080, you can see our login page.
Admin Page
当我们输入有效的管理员证书后,它将加载主页。现在打开 localhost:8080/admin。您可以看到管理员用户的 Control Panel 文本 visible 。
When we enter valid credential for a Admin and it will load home page. Now open localhost:8080/admin. You can see the Control Panel text visible for admin user.
Login Page with User Details entered
现在单击退出链接从应用程序注销,并使用用户证书登录。
Now logout from the application by clicking the signout link and login using user credential.
Admin Page for User
当我们输入有效的用户证书后,它将加载主页。现在加载 localhost:8080/admin 以打开管理员页面。您可以看到 Control Panel 文本对普通用户来说是 not visible 。
When we enter valid credential for a User and it will load home page. Now load localhost:8080/admin to open admin page. You can see the Control Panel text is not visible for normal user.