在所有的開發的系統中,都必須做認證(authentication)和授權(authorization),以保證系統的安全性。
一、基礎使用
1.依賴
<dependencies><!-- 實現對 Spring MVC 的自動化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 實現對 Spring Security 的自動化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency></dependencies>
2.application.yml
spring:# Spring Security 配置項,對應 SecurityProperties 配置類security:# 配置默認的 InMemoryUserDetailsManager 的用戶賬號與密碼。user:name: user # 賬號password: user # 密碼roles: ADMIN # 擁有角色
3.Controller
@RestController
@RequestMapping("/admin")
public class AdminController {@GetMapping("/demo")public String demo() {return "success!";}}
項目啟動成功后,瀏覽器訪問?http://127.0.0.1:8080/admin/demo?接口。因為未登錄,所以被 Spring Security 攔截到登錄界面。
因為我們沒有自定義登錄界面,所以默認會使用??DefaultLoginPageGeneratingFilter??類,生成上述界面。
登錄完成后,因為 Spring Security 會記錄被攔截的訪問地址,所以瀏覽器自動動跳轉返回界面;
二、自定義規則(自定義 Spring Security 的配置,實現權限控制。)
1.SecurityConfig
創建 SecurityConfig 配置類,繼承 WebSecurityConfigurerAdapter 抽象類,實現 Spring Security 在 Web 場景下的自定義配置。代碼如下:
// SecurityConfig.java@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {// ...}
重寫 #configure(AuthenticationManagerBuilder auth) 方法,實現 AuthenticationManager 認證管理器。
// SecurityConfig.java@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.//使用內存中的 InMemoryUserDetailsManagerinMemoryAuthentication()//不使用 PasswordEncoder 密碼編碼器.passwordEncoder(NoOpPasswordEncoder.getInstance())// 配置 admin 用戶.withUser("admin").password("admin").roles("ADMIN")// 配置 normal 用戶.and().withUser("normal").password("normal").roles("NORMAL");
}
重寫?#configure(HttpSecurity http)
?方法,主要配置 URL 的權限控制。代碼如下:
// SecurityConfig.java@Override
protected void configure(HttpSecurity http) throws Exception {http// <X> 配置請求地址的權限.authorizeRequests().antMatchers("/test/echo").permitAll() // 所有用戶可訪問.antMatchers("/test/admin").hasRole("ADMIN") // 需要 ADMIN 角色.antMatchers("/test/normal").access("hasRole('ROLE_NORMAL')") // 需要 NORMAL 角色。// 任何請求,訪問的用戶都需要經過認證.anyRequest().authenticated().and()// <Y> 設置 Form 表單登錄.formLogin()
// .loginPage("/login") // 登錄 URL 地址.permitAll() // 所有用戶可訪問.and()// 配置退出相關.logout()
// .logoutUrl("/logout") // 退出 URL 地址.permitAll(); // 所有用戶可訪問
}
調用?HttpSecurity#authorizeRequests()
?方法,開始配置 URL 的權限控制。注意看艿艿配置的四個權限控制的配置。下面,是配置權限控制會使用到的方法:
#(String... antPatterns)
?方法,配置匹配的 URL 地址,可傳入多個。- 【常用】
#permitAll()
?方法,所有用戶可訪問。 - 【常用】
#denyAll()
?方法,所有用戶不可訪問。 - 【常用】
#authenticated()
?方法,登錄用戶可訪問。 #anonymous()
?方法,無需登錄,即匿名用戶可訪問。#rememberMe()
?方法,通過remember me??登錄的用戶可訪問。#fullyAuthenticated()
?方法,非remember me?登錄的用戶可訪問。#hasIpAddress(String ipaddressExpression)
?方法,來自指定 IP 表達式的用戶可訪問。- 【常用】
#hasRole(String role)
?方法, 擁有指定角色的用戶可訪問。 - 【常用】
#hasAnyRole(String... roles)
?方法,擁有指定任一角色的用戶可訪問。 - 【常用】
#hasAuthority(String authority)
?方法,擁有指定權限(authority
)的用戶可訪問。 - 【常用】
#hasAuthority(String... authorities)
?方法,擁有指定任一權限(authority
)的用戶可訪問。 - 【最牛】
#access(String attribute)
?方法,執行結果為?true
?時,可以訪問。
2.Controller
@RestController
@RequestMapping("/test")
public class TestController {@GetMapping("/echo")public String demo() {return "示例返回";}@GetMapping("/home")public String home() {return "我是首頁";}@GetMapping("/admin")public String admin() {return "我是管理員";}@GetMapping("/normal")public String normal() {return "我是普通用戶";}}
- 對于?
/test/echo
?接口,直接訪問,無需登錄。 - 對于?
/test/home
?接口,無法直接訪問,需要進行登錄。 - 對于?
/test/admin
?接口,需要登錄「admin/admin」用戶,因為需要 ADMIN 角色。 - 對于?
/test/normal
?接口,需要登錄「normal/normal」用戶,因為需要 NORMAL 角色。
3.Spring Security 的注解,實現權限控制
1.修改權限配置類?
修該 security config?配置類,增加?@EnableGlobalMethodSecurity?注解,開啟對 Spring Security 注解的方法,進行權限驗證。代碼如下:
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
然后即可添加方法級別的權限驗證
@PreAuthorize("hasRole('ROLE_NORMAL')")@GetMapping("/normal")public String normal() {return "我是普通用戶";}
三、Spring Session
Session 的一致性,簡單來理解,就是相同 sessionid 在多個 Web 容器下,Session 的數據要一致。
我們先以用戶使用瀏覽器,Web 服務器為單臺?TomcatA 舉例子。
- 瀏覽器在第一次訪問 Web 服務器 TomcatA 時,TomcatA 會發現請求的 Cookie 中不存在 sessionid ,所以創建一個 sessionid 為 X 的 Session ,同時將該 sessionid 寫回給瀏覽器的 Cookie 中。
- 瀏覽器在下一次訪問 Web 服務器 TomcatA 時,TomcatA 會發現請求的 Cookie 中已存在 sessionid 為 X ,則直接獲得 X 對應的 Session 。
但是如果情況如下:
- 瀏覽器訪問 TomcatA ,獲得 sessionid 為 X 。同時,在多臺 Tomcat 的情況下,我們需要采用 Nginx 做負載均衡。
- 瀏覽器又發起一次請求訪問 Web 服務器,Nginx 負載均衡轉發請求到 TomcatB 上。TomcatB 會發現請求的 Cookie 中已存在 sessionid 為 X ,則直接獲得 X 對應的 Session 。結果呢,找不到 X 對應的 Session ,只好創建一個 sessionid 為 X 的 Session 。
- 此時,雖然說瀏覽器的 sessionid 是 X ,但是對應到兩個 Tomcat 中兩個 Session 。那么,如果在 TomcatA 上做的 Session 修改,TomcatB 的 Session 還是原樣,這樣就會出現?Session 不一致的問題。
三種解決方案:
第一種,Session 黏連。
使用 Nginx 實現會話黏連,將相同 sessionid 的瀏覽器所發起的請求,轉發到同一臺服務器。這樣,就不會存在多個 Web 服務器創建多個 Session 的情況,也就不會發生 Session 不一致的問題。
不過,這種方式目前基本不被采用。因為,如果一臺服務器重啟,那么會導致轉發到這個服務器上的 Session 全部丟失。
第二種,Session 復制。
Web 服務器之間,進行 Session 復制同步。僅僅適用于實現 Session 復制的 Web 容器,例如說 Tomcat 、Weblogic 等等。
不過,這種方式目前基本也不被采用。試想一下,如果我們有 5 臺 Web 服務器,所有的 Session 都要同步到每一個節點上,一個是效率低,一個是浪費內存。
第三種,Session 外部化存儲。
不同于上述的兩種方案,Session 外部化存儲,考慮不再采用 Web 容器的內存中存儲 Session ,而是將 Session 存儲外部化,持久化到 MySQL、Redis、MongoDB 等等數據庫中。這樣,Tomcat 就可以無狀態化,專注提供 Web 服務或者 API 接口,未來拓展擴容也變得更加容易。
而實現 Session 外部化存儲也有兩種方式:
① 基于 Tomcat、Jetty 等 Web 容器自帶的拓展,使用讀取外部存儲器的 Session 管理器。例如說:tomcat 使用 Redis 存儲 Session 、實現 Jetty 使用 MySQL、MongoDB 存儲 Session 。
② 基于應用層封裝 HttpServletRequest?請求對象,包裝成自己的 RequestWrapper 對象,從而讓實現調用 HttpServletRequest#getSession() 方法時,獲得讀寫外部存儲器的 SessionWrapper 對象。
依賴:
<dependencies><!-- 實現對 Spring MVC 的自動化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 實現對 Spring Session 使用 Redis 作為數據源的自動化配置 --><dependency><groupId>org.springframework.session</groupId><artifactId>spring-session-data-redis</artifactId></dependency><!-- 實現對 Spring Data Redis 的自動化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><!-- 去掉對 Lettuce 的依賴,因為 Spring Boot 優先使用 Lettuce 作為 Redis 客戶端 --><exclusion><groupId>io.lettuce</groupId><artifactId>lettuce-core</artifactId></exclusion></exclusions></dependency><!-- 引入 Jedis 的依賴,這樣 Spring Boot 實現對 Jedis 的自動化配置 --><dependency><groupId>redis.clients</groupId><artifactId>jedis</artifactId></dependency><!-- 實現對 Spring Security 的自動化配置 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency></dependencies>
配置文件:
spring:# 對應 RedisProperties 類redis:host: 127.0.0.1port: 6379password: # Redis 服務器密碼,默認為空。生產中,一定要設置 Redis 密碼!database: 0 # Redis 數據庫號,默認為 0 。timeout: 0 # Redis 連接超時時間,單位:毫秒。# 對應 RedisProperties.Jedis 內部類jedis:pool:max-active: 8 # 連接池最大連接數,默認為 8 。使用負數表示沒有限制。max-idle: 8 # 默認連接數最大空閑的連接數,默認為 8 。使用負數表示沒有限制。min-idle: 0 # 默認連接池最小空閑的連接數,默認為 0 。允許設置 0 和 正數。max-wait: -1 # 連接池最大阻塞等待時間,單位:毫秒。默認為 -1 ,表示不限制。# 對應 SecurityProperties 類security:user: # 配置內存中,可登錄的用戶名和密碼name: nihaopassword: nihao
SessionConfiguration:
@Configuration
@EnableRedisHttpSession // 自動化配置 Spring Session 使用 Redis 作為數據源
public class SessionConfiguration {}