简介
为了督促自己的学习,新开一个坑,就是记录对Spring Security的探究与学习。使用Spring Security的目的是,之前实现后端时,用户验证与鉴权都是手动实现的而过于繁琐。后来了解到了Spring Security对Web的安全有很好的支持,因此打算系统地学习一下Spring Security,并将学习过程中的思考和见解一并分享出来,欢迎批评指出。
什么是Spring Security
先读官方文档对其的定义:
Spring Security是一个框架,提供了身份验证、授权和针对常见攻击的保护。
Spring Security是一个轻量级的安全框架,能为基于Spring的Java Web应用程序的安全提供保证。Spring Security能与Spring MVC很好地集成,因此如果项目是基于Spring搭建的,那么在原本项目的基础上加入Spring Security来进行安全校验是一个较好的选择。
快速开始
我们先来快速地搭建一个基础项目,实现Spring Security所提供的功能吧。首先,笔者所使用的的环境和IDE具体为:
- AdoptOpenJDK 11
- IntelliJ IDEA 2020.1.2 x64
推荐使用IDEA新建项目中选项中的Spring Initializr,使用该初始化器能够快速地搭建一个SpringBoot项目,并且可以自由地选择将要使用的组件,本文选择Spring Security组件。生成项目后,打开pol.xml文件看一下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
需要注意的是,本文所使用的的SpringBoot版本为2.4.5。
此时,直接运行该SpringBoot项目,打开浏览器访问localhost:8080/login
,可以看到如下画面:
这是Spring Security为我们默认生成的登录页面,并且用户名是user,密码是IDEA控制台打印出来的一串密码。到目前为止,Spring Security已经快速地集成到我们的项目中了,但这一切都是默认提供的,我们希望能够使用自定义的登录页面。下面介绍Spring Security的进一步使用。
往前一步
Spring Security提供了灵活的可定制化的配置,可以根据项目具体情况来进行不同的配置以实现不同的安全功能。Spring Security中进行配置的操作是在WebSecurityConfigurerAdapter
,我们只需要继承该类,并重写configure
方法即可。本文不会对各种类和接口进行繁琐的介绍,力求最快速地实现。因此下面我们介绍怎么样自定义登录页面。
Step.1 准备前端页面
我们先准备几个需要访问的页面,分别是登录页面、登录成功页面和登录失败页面。显然,登录成功和登录失败的页面是需要验证才能访问的,而登录页面则不需要任何的验证。下面的HTML页面均放置于resources/templates
文件夹下。
1. login.html
<!DOCTYPE html>
<html lang="cn">
<head>
<meta charset="UTF-8">
<title>简单登录页面</title>
</head>
<body>
<h3>简易表单登录</h3>
<form action="/doLogin" method="post">
<table>
<tr>
<td>用户名:</td>
<td><input type="text" name="myUsername"></td>
</tr>
<tr>
<td>密码:</td>
<td><input type="password" name="myPassword"></td>
</tr>
<tr>
<td colspan="10"><button type="submit" style="width: 100%; margin-top: 10px">登录</button></td>
</tr>
</table>
</form>
</body>
</html>
简单的登录页面,仅有一个表单、两个输入框和一个按钮。这里需要注意的是,表单的action属性设置为了doLogin
,代表了表单提交的目标URL地址。
2. success.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
<title>登录成功</title>
</head>
<body>
<h3>登录成功!</h3>
<div style="display: flex">
<p>用户名:</p><p th:text="${username}"></p>
</div>
</body>
</html>
这里笔者使用了thymeleaf,打印出username
。
3. failure.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" lang="en">
<head>
<meta charset="UTF-8">
<title>登录失败页面</title>
</head>
<body>
<h3>登录失败页面</h3>
<div style="display: flex">
<p>失败原因:</p><p th:text="${error}"></p>
</div>
</body>
</html>
这里笔者同样使用了thymeleaf,打印出error
。
Step.2 准备控制器
新建一个TestController,实现接口地址到View视图的映射。
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.WebAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
@Controller
public class TestController {
@RequestMapping(value = "/success")
public ModelAndView success(){
String username = null;
Object object = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); //取出身份信息
if (object instanceof UserDetails){
UserDetails userDetails = (UserDetails) object;
username = userDetails.getUsername();
}else {
Principal principal = (Principal) object;
username = principal.getName();
}
return new ModelAndView("/success", "username", username);
}
@RequestMapping(value = "/failure")
public ModelAndView failure(HttpServletRequest request){
AuthenticationException authenticationException = (AuthenticationException)
request.getSession().getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION); //取出错误信息
return new ModelAndView("/failure", "error", authenticationException.getLocalizedMessage());
}
@RequestMapping(value = "/login")
public String login(){
return "login";
}
}
由于笔者使用了thymeleaf
,这里声明的接口地址,如/success
等,Spring会到resources/templates
文件夹下寻找对应的HTML文件。
Step.3 配置Spring Security
新建一个SecurityConfig文件,如下所示:
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable(); //关闭CSRF验证
http.authorizeRequests().anyRequest().authenticated(); //对所有接口都进行拦截
http.formLogin()
.loginPage("/login") //自定义Login登录页面
.usernameParameter("myUsername") //自定义username的参数名称
.passwordParameter("myPassword") //自定义password的参数名称
.loginProcessingUrl("/doLogin") //定义的表单提交后的中转地址
.defaultSuccessUrl("/success") //验证成功后跳转的地址
.failureUrl("/failure") //验证失败跳转的地址
.permitAll(); //与表单登录相关的接口不拦截
}
}
配置很简单,这里实现了对登录页面的修改,并且实现了登录成功/登录失败后要跳转到哪个页面。
我们运行程序,在浏览器打开localhost:8080/login
,即可以看到如下图所示的页面。可以看出,Spring Security默认提供的登录页面已经被修改为我们自己实现的Login页面了。
我们先随便输入用户名和密码,此时会验证失败而跳转到失败的页面,如下图所示:
我们输入正确的用户名和密码,即user
和IDEA控制台给出的密码,会跳转到成功的页面,如下图所示:
到目前为止,已经实现了Spring Security的登录页面的简单定制。在上述流程中,我们使用的是默认的账号User以及生成的密码。除此之外,我们还能添加不同的账户,比如我们在系统中添加一个admin:admin
账号,需要重写SecurityConfig#configure(AuthenticationManagerBuilder)
方法:
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
.withUser("admin").password(new BCryptPasswordEncoder().encode("admin")).roles("admin");
}
}
上述代码的含义是,在内存中新增一个admin
账号,且密码是admin
,使用BCryptPasswordEncoder
对密码加密,同时该账号拥有的角色是admin
,表面了该账号拥有哪些权限。重新启动项目后,便能使用admin
账号来登录系统了。
小结
本文快速地介绍了怎么使用Spring Security,并以一个简单的例子来介绍了怎样更换Spring Security默认的登录页面。但到目前为止,我们使用的账号与密码是系统自带的,在实际中,账号数据应该从数据库中导入,而不是硬编码在程序中。在下一篇文章中,笔者将介绍在Spring Security中怎样使用数据库中的账户数据来实现身份和权限的校验。感谢你的阅读~