一只祠、Spring Security 概述
Spring Security是一個功能強大且高度可定制的身份驗證和訪問控制框架兜蠕。它實際上是保護基于spring的應(yīng)用程序的標準熊杨,也是一個專注于向Java應(yīng)用程序提供身份驗證和授權(quán)的框架晶府。
連接:Spring Security
博客地址:https://blog.ffspace.cn
項目代碼:https://github.com/Bootcap/spring-security-study-session
二钻趋、環(huán)境要求
- JDK ≥1.7
- Maven 3.0+
- IntelliJ IDEA/eclipse
【提示】: 本文以 IDEA 和 maven 以及Spring Boot 2.0 為例進行教程
三蛮位、使用Maven進行構(gòu)建
在pom.xml文件中引入相關(guān)的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
四购对、創(chuàng)建一個不受保護的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"連接點擊跳轉(zhuǎn)到hello.html頁面的簡單流程
五躲株、配置Spring MVC視圖控制器
由于Web應(yīng)用程序基于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頁面,說明已經(jīng)成功運行
6.3 點擊跳轉(zhuǎn)到hello頁面鏈接浑玛,無需任何認證即可進行跳轉(zhuǎn)
【小竅門】: 為什么要使用@ComponentScan注解:
- 如果你的其他包都在使用了@SpringBootApplication注解的main方法所在的包及其下級包顾彰,則SpringBoot會自動幫你把其他包都掃描胃碾。
- 如果你有一些bean所在的包仆百,不在@SpringBootApplication注解main方法的包及其下級包,那么你需要手動加上@ComponentScan注解并指定要尋找的bean所在的包路徑吁讨。(詳見上述代碼中package的不同之處)
七、引入并使用Spring Security
在上述的兩個視圖中排龄,我們希望在訪問"/hello"時需要登錄才能夠進入Hello頁面橄维。此時我們可以通過Spring Security來實現(xiàn)拴曲。(如果Spring Security在類路徑上,則Spring Boot會使用"Basic"認證自動保護所有HTTP請求兑障,也可以自定義設(shè)置)
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() // 在內(nèi)存中進行身份驗證
.withUser("user")
.password("password")
.roles("USER");
}
}
【說明】:
WebSecurityConfig類使用了@EnableWebSecurity注解,以啟用Spring Security的Web安全支持蕉汪。
configure(HttpSecurity)方法自定義有哪些url需要被認證流译,哪些不需要福澡。當用戶登錄后將會被重定向請求到需要身份認證的頁面(hello.html)驹马,否則在用戶未登錄的情況下將會跳轉(zhuǎn)到登錄頁面
configure(AuthenticationManagerBuilder)方法用于設(shè)置認證的條件保存于內(nèi)存中,用戶名為“user”算利,密碼為“123456”泳姐,角色為User。同時該方法也可以修改認證方式為jdbc進行認證
【注意】: 使用了Spring Boot 2.0以上的Security會存在There is no PasswordEncoder mapped for the id "null"異常缎患,解決方案見底部“常見問題”
7.3 創(chuàng)建登錄頁面(認證時需要用到)
路徑: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}">
你已經(jīng)退出登錄
</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",并且顯示相應(yīng)的錯誤信息骡楼。注銷成功后鸟整,Spring Security會將地址重定向到"/login?logout"朦蕴,我們即可在頁面中看到相應(yīng)的登出信息
7.4 修改hello.html
在認證成功后跳轉(zhuǎn)到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 啟動應(yīng)用程序
通過文章剛開始的 【配置Spring Boot啟動類】模塊啟動應(yīng)用程序移斩,這里不再過多介紹。
八肠套、測試
8.1 應(yīng)用啟動后, 在瀏覽器中訪問 http://localhost:8080. 就能訪問到Index頁面
8.2 點擊跳轉(zhuǎn)到hello.html頁面你稚,會發(fā)現(xiàn)沒有認證不允許進入入宦,程序自動跳轉(zhuǎn)到登錄頁
8.3 我們輸入錯誤的信息會提示用戶名或密碼不正確乾闰,同時地址欄的地址也變成了"/login?error"
8.4 下面我們輸入正確的賬號登錄(用戶名:user涯肩,密碼:123456)
我們會發(fā)現(xiàn)在控制臺出現(xiàn)了異常(解決方案:詳見底部【常見問題】)
8.5 異常解決后我們再進行測試已經(jīng)可以正常地跳轉(zhuǎn)到hello.html頁面,并顯示了登錄的用戶名
8.6 最后我們點擊退出登錄疗垛,即可完成注銷操作(此時地址欄"/login?logout"贷腕,并且跳回登錄頁面提示"你已經(jīng)退出登錄")
九泽裳、總結(jié)
恭喜破婆!你已經(jīng)開發(fā)了一個簡單的Spring Security程序。
常見問題
【問題一】:
在測試中瀑梗,我們正常登錄時候出現(xiàn)了異常:java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
【解答】:通過問題我們知道該異常來自認證信息WebSecurityConfig的方法configure(AuthenticationManagerBuilder)抛丽。所以我們查閱官方文檔嚎朽,發(fā)現(xiàn)在spring boot 2.0之上使用的是Spring Security 5.0版本,同時也指出使用密碼校驗時候需要進行密碼加密狡门。
因此其馏,改動如下:
方法1:在Application.java啟動類下加入該方法爆安,關(guān)閉密碼加密校驗.(不推薦) @Bean public static PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); } 方法2:在WebSecurityConfig.java類的configure(AuthenticationManagerBuilder)進行如下改動 @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // auth.inMemoryAuthentication() // 在內(nèi)存中進行身份驗證 // .withUser("user") // .password("password") // .roles("USER"); auth.inMemoryAuthentication() // 在內(nèi)存中進行身份驗證 .passwordEncoder(new BCryptPasswordEncoder()) .withUser("user") .password(new BCryptPasswordEncoder().encode("123456")) .roles("USER"); // auth.userDetailsService(userService).passwordEncoder(new BCryptPasswordEncoder()); }
小技巧
在我們開發(fā)的過程中扔仓,我們經(jīng)常會遇到修改一點點東西時候,都需要重啟操作來生效撬码。因此呜笑,spring boot為我們提供了一個devTool熱更新工具,無需重啟即可生效
pom.xml中引入相關(guān)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進行開發(fā),因此對eclipse的引入是否需要快捷鍵并不是很清楚凰慈,提供了相關(guān)鏈接
IDEA使用:
- 修改了java類的地方驼鹅,使用Ctrl+Shift+F9進行熱更新
- 靜態(tài)頁面/模板頁面,使用Ctrl+F9進行熱更新
- 快捷鍵使用后不生效堰酿?前往File-Settings-Compiler-Build Project automatically選項開始idea自動編譯
eclipse使用:
- 直接引入
- 不生效?Eclipse熱部署
附錄:HttpSecurity類的常用方法
方法 | 說明 |
---|---|
openidLogin() | 基于OpenId驗證 |
headers() | 將安全頭添加到響應(yīng) |
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() | 配置容器預(yù)認證,默認為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類位置添加過濾器 |