一、Spring Security 概述
Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。它实际上是保护基于spring的应用程序的标准,也是一个专注于向Java应用程序提供身份验证和授权的框架。
连接:Spring Security
博客地址:https://blog.ffspace.cn
项目代码:https://github.com/Bootcap/spring-security-study-session
二、环境要求
- JDK ≥1.7
- Maven 3.0+
- IntelliJ IDEA/eclipse
【提示】: 本文以 IDEA 和 maven 以及Spring Boot 2.0 为例进行教程
三、使用Maven进行构建
在pom.xml文件中引入相关的jar包(不含security)
<?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 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<name>spring-security-study-session</name>
<groupId>com.bootcap.session.security</groupId>
<artifactId>spring-security-study-session</artifactId>
<version>1.0.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
<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>
<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>
在项目的src/main/resources下进行配置
# 端口号
server:
port: 8080
spring:
# thymeleaf配置
thymeleaf:
enabled: true
encoding: UTF-8
mode: HTML
servlet:
content-type: text/html
prefix: classpath:/templates/
suffix: .html
四、创建一个不受保护的web页面
Web页面包含两个简单的视图:index主页和“hello”页面,都定义在Thymeleaf模板中。
4.1 Index页面
路径:src/main/resources/templates/index.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Spring Security Index</title>
</head>
<body>
<h1>Index Page</h1>
<a th:href="@{/hello}">点击前往hello页面</a>
</body>
</html>
【注意】: 使用thymeleaf时需要在<html>标签后面加入xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" 属性,才能被编译器解析为thymeleaf模板
4.2 Hello页面
路径:src/main/resources/templates/hello.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<h1>Hello Spring Security</h1>
</body>
</html>
从上面index.html可以看到包含了一个"/hello"连接点击跳转到hello.html页面的简单流程
五、配置Spring MVC视图控制器
由于Web应用程序基于Spring MVC。 因此,需要配置视图控制器来暴露这些模板。
路径:src/main/java/com/bootcap/session/security/configuration/TemplateConfig.java
package com.bootcap.session.security.configuration;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class TemplateConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("index");
registry.addViewController("/index").setViewName("index");
registry.addViewController("/hello").setViewName("hello");
registry.addViewController("/login").setViewName("login");
}
}
六、配置Spring Boot启动类
6.1 到了这一步,我们需要将项目运行查看是否无异常,才进行下一步操作,因此需要配置spring boot启动类
路径:src/main/java/com/bootcap/session/security/app/Application.java
package com.bootcap.session.security.app;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@SpringBootApplication
@ComponentScan(basePackages = {"com.bootcap.session.security"})
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}
6.2 运行main方法,并在浏览器地址栏输入:http://localhost:8080/ 如果看到index.html页面,说明已经成功运行
6.3 点击跳转到hello页面链接,无需任何认证即可进行跳转
【小窍门】: 为什么要使用@ComponentScan注解:
- 如果你的其他包都在使用了@SpringBootApplication注解的main方法所在的包及其下级包,则SpringBoot会自动帮你把其他包都扫描。
- 如果你有一些bean所在的包,不在@SpringBootApplication注解main方法的包及其下级包,那么你需要手动加上@ComponentScan注解并指定要寻找的bean所在的包路径。(详见上述代码中package的不同之处)
七、引入并使用Spring Security
在上述的两个视图中,我们希望在访问"/hello"时需要登录才能够进入Hello页面。此时我们可以通过Spring Security来实现。(如果Spring Security在类路径上,则Spring Boot会使用"Basic"认证自动保护所有HTTP请求,也可以自定义设置)
7.1 pom.xml加入Spring Security
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
...
</dependencies>
7.2 配置只有认证过的用户才能访问hello.html
路径:src/main/java/com/bootcap/session/security/configuration/WebSecurityConfig.java
package com.bootcap.session.security.configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/","/index").permitAll() // permitAll被允许访问
.anyRequest().authenticated() // 其余的请求需要认证后才可允许访问
.and()
.formLogin()
.loginPage("/login")
.permitAll()
.and()
.logout()
.permitAll();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication() // 在内存中进行身份验证
.withUser("user")
.password("password")
.roles("USER");
}
}
【说明】:
WebSecurityConfig类使用了@EnableWebSecurity注解,以启用Spring Security的Web安全支持。
configure(HttpSecurity)方法自定义有哪些url需要被认证,哪些不需要。当用户登录后将会被重定向请求到需要身份认证的页面(hello.html),否则在用户未登录的情况下将会跳转到登录页面
configure(AuthenticationManagerBuilder)方法用于设置认证的条件保存于内存中,用户名为“user”,密码为“123456”,角色为User。同时该方法也可以修改认证方式为jdbc进行认证
【注意】: 使用了Spring Boot 2.0以上的Security会存在There is no PasswordEncoder mapped for the id "null"异常,解决方案见底部“常见问题”
7.3 创建登录页面(认证时需要用到)
路径:src/main/resources/templates/login.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<div th:if="${param.error}">
用户名或密码不正确
</div>
<div th:if="${param.logout}">
你已经退出登录
</div>
<form th:action="@{/login}" method="post">
<div><label> 用户名: <input type="text" name="username"/> </label></div>
<div><label> 密 码: <input type="password" name="password"/> </label></div>
<div><input type="submit" value="登录"/></div>
</form>
</body>
</html>
【说明】:
该登录页面会将用户名和密码以表单形式提交到"/login"。
在Spring Security提供了一个拦截请求并验证的过滤器,在用户未通过认证的情况下会重定向到"/login?error",并且显示相应的错误信息。注销成功后,Spring Security会将地址重定向到"/login?logout",我们即可在页面中看到相应的登出信息
7.4 修改hello.html
在认证成功后跳转到hello.html页面,我们希望能够看到登录的用户名,同时允许用户退出登录,因此我们需要修改hello.html页面
路径:src/main/resources/templates/hello.html
<!DOCTYPE html>
<html lang="en" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Hello</title>
</head>
<body>
<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]</h1>
<form th:action="@{/logout}" method="post">
<input type="submit" value="退出登录" />
</form>
</body>
</html>
【说明】:
我们在hello.html页面中使用了HttpServletRequest#getRemoteUser()的thymeleaf集成来显示用户名。
页面中退出登录表单会将请求提交到"/logout",成功注销后程序会重定向到"/login?logout"。
7.5 启动应用程序
通过文章刚开始的 【配置Spring Boot启动类】模块启动应用程序,这里不再过多介绍。
八、测试
8.1 应用启动后, 在浏览器中访问 http://localhost:8080. 就能访问到Index页面
8.2 点击跳转到hello.html页面,会发现没有认证不允许进入,程序自动跳转到登录页
8.3 我们输入错误的信息会提示用户名或密码不正确,同时地址栏的地址也变成了"/login?error"
8.4 下面我们输入正确的账号登录(用户名:user,密码:123456)
我们会发现在控制台出现了异常(解决方案:详见底部【常见问题】)
8.5 异常解决后我们再进行测试已经可以正常地跳转到hello.html页面,并显示了登录的用户名
8.6 最后我们点击退出登录,即可完成注销操作(此时地址栏"/login?logout",并且跳回登录页面提示"你已经退出登录")
九、总结
恭喜!你已经开发了一个简单的Spring Security程序。
常见问题
【问题一】:
在测试中,我们正常登录时候出现了异常:java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
【解答】:通过问题我们知道该异常来自认证信息WebSecurityConfig的方法configure(AuthenticationManagerBuilder)。所以我们查阅官方文档,发现在spring boot 2.0之上使用的是Spring Security 5.0版本,同时也指出使用密码校验时候需要进行密码加密。
因此,改动如下:
方法1:在Application.java启动类下加入该方法,关闭密码加密校验.(不推荐) @Bean public static PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } 方法2:在WebSecurityConfig.java类的configure(AuthenticationManagerBuilder)进行如下改动 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // auth.inMemoryAuthentication() // 在内存中进行身份验证 // .withUser("user") // .password("password") // .roles("USER"); auth.inMemoryAuthentication() // 在内存中进行身份验证 .passwordEncoder(new BCryptPasswordEncoder()) .withUser("user") .password(new BCryptPasswordEncoder().encode("123456")) .roles("USER"); // auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder()); }
小技巧
在我们开发的过程中,我们经常会遇到修改一点点东西时候,都需要重启操作来生效。因此,spring boot为我们提供了一个devTool热更新工具,无需重启即可生效
pom.xml中引入相关jar
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
...
<dependencies>
如何使用
【提示】: 由于我自己使用的是idea进行开发,因此对eclipse的引入是否需要快捷键并不是很清楚,提供了相关链接
IDEA使用:
- 修改了java类的地方,使用Ctrl+Shift+F9进行热更新
- 静态页面/模板页面,使用Ctrl+F9进行热更新
- 快捷键使用后不生效?前往File-Settings-Compiler-Build Project automatically选项开始idea自动编译
eclipse使用:
- 直接引入
- 不生效?Eclipse热部署
附录:HttpSecurity类的常用方法
方法 | 说明 |
---|---|
openidLogin() | 基于OpenId验证 |
headers() | 将安全头添加到响应 |
cors() | 跨域配置 |
sessionManagement() | session会话管理 |
portMapper() | 配置一个PortMapper(HttpSecurity#(getSharedObject(class))),供SecurityConfigurer对象使用 PortMapper 从 HTTP 重定向到 HTTPS 或者从 HTTPS 重定向到 HTTP。默认情况下,Spring Security使用一个PortMapperImpl映射 HTTP 端口8080到 HTTPS 端口8443,HTTP 端口80到 HTTPS 端口443 |
jee() | 配置容器预认证,默认为Servlet容器进行管理 |
x509() | 配置x509认证 |
rememberMe() | 配置“记住我”的验证 |
authorizeRequests() | 允许HttpServletRequest限制访问 |
requestCache() | 允许配置请求缓存 |
exceptionHandling() | 允许配置错误处理 |
logout() | 退出登录。访问URL”/ logout”,使HTTP Session无效来清除用户,清除已配置的任何#rememberMe()身份验证,清除SecurityContextHolder,然后重定向到”/login?logout” |
anonymous() | 允许配置匿名用户访问。默认情况下,匿名用户将使用org.springframework.security.authentication.AnonymousAuthenticationToken表示,并包含角色 “ROLE_ANONYMOUS” |
formLogin() | 指定用于表单身份验证。 |
oauth2Login() | 用于OAuth 2.0 或OpenID的身份验证 |
requiresChannel() | 配置通道安全。 |
httpBasic() | 配置Http Basic验证 |
addFilterAt() | 在指定的Filter类位置添加过滤器 |