基礎
核心
認證與授權
與Shiro聯絡
SpringSecurity 在 SpringBoot 出現前因為配置複雜使用較少,但是在SpringBoot 出現後搭配使用開發效率大大提高。是一款重量級框架。而 Shiro 是一款輕量級框架,配置簡單一些,所以如果不使用 SpringBoot,那麼一般搭配 Shiro,而使用SpringBoot 就搭配 SpringSecurity。
核心介面
UserDetailsService
定義了SpringSecurity 查詢使用者資訊的介面方法,在SpringSecurity 認證時,並不是直接通過使用者名稱密碼去資料庫比對,沒有對應就返回,而是先通過 username 去資料庫查到對應的使用者資訊,然後進行拼接成 SpringSecurity 內部維護的使用者物件,然後由內部方法進行密碼比對。而查詢資料庫返回使用者物件的介面方法就是由 UserDetailsService 介面定義的。
UserDetails
上面說到資料庫查詢使用者資訊會返回一個SpringSecurity 內部維護的使用者物件。這個使用者抽象類就是 UserDetails,其內部結構如下
public interface UserDetails extends Serializable { // ~ Methods // ======================================================================================================== /** * Returns the authorities granted to the user. Cannot return <code>null</code>. * 授權列表 * @return the authorities, sorted by natural key (never <code>null</code>) */ Collection<? extends GrantedAuthority> getAuthorities(); /** * Returns the password used to authenticate the user. * * @return the password */ String getPassword(); /** * Returns the username used to authenticate the user. Cannot return <code>null</code>. * * @return the username (never <code>null</code>) */ String getUsername(); /** * Indicates whether the user's account has expired. An expired account cannot be * authenticated. * 是否過期 * @return <code>true</code> if the user's account is valid (ie non-expired), * <code>false</code> if no longer valid (ie expired) */ boolean isAccountNonExpired(); /** * Indicates whether the user is locked or unlocked. A locked user cannot be * authenticated. * 是否鎖定,如果鎖定就無法驗證 * @return <code>true</code> if the user is not locked, <code>false</code> otherwise */ boolean isAccountNonLocked(); /** * Indicates whether the user's credentials (password) has expired. Expired * credentials prevent authentication. * 使用者憑證是否過期,過期的憑證會阻止身份驗證 * @return <code>true</code> if the user's credentials are valid (ie non-expired), * <code>false</code> if no longer valid (ie expired) */ boolean isCredentialsNonExpired(); /** * Indicates whether the user is enabled or disabled. A disabled user cannot be * authenticated. * 使用者是啟用還是禁用,無法對禁用的使用者進行身份驗證 * @return <code>true</code> if the user is enabled, <code>false</code> otherwise */ boolean isEnabled(); }
在使用時可以讓自定義使用者來實現這個介面。
PasswordEncoder
密碼介面,一般使用 BCryptPasswordEncoder 來作為預設的密碼轉換器。SpringSecurity 在加密時引入鹽,使得加密過程是不可逆的,而加密後的字串包含鹽資訊,在比較方法中會對加密後的密碼進行解析,解析出鹽值,然後對輸入密碼進行加密,比較輸入密碼加密後的結果是否與原密碼加密後的結果一致。使用 encode 方法進行加密, matches 方法進行密碼比較。如果一致返回 true。
常用配置
使用者名稱密碼配置
方式一、配置檔案
方式二、配置類
方式三、自定義配置
因為一般專案使用者名稱密碼都是存在資料庫的,所以這是最主流的。
1、配置UserDetails,返回使用者資訊
2、新增配置類,將userDetails註冊進 SpringSecurity
記住我
原理
在登陸後會向資料庫的 persistent_logins 表中插入一條記錄,表結構如下
series 是主鍵, 隨後將 series 和 token 進行演算法轉換成字串發給客戶端,後面客戶端會攜帶 Cookie ,當下次訪問時後端會解析 Cookie ,解析成 series 和 token ,然後去表中匹配,驗證token是否一致,以及 last_used + 存活時間是否到期,如果都滿足就再以 name 走 UserDetailsService 的方法,返回使用者資訊。
配置
建表語句:
DROP TABLE IF EXISTS `persistent_logins`; CREATE TABLE `persistent_logins` ( `username` varchar(64) NOT NULL, `series` varchar(64) NOT NULL, `token` varchar(64) NOT NULL, `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`series`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@Resource private DataSource datasource; /** * 注入記住我token表的資料來源 * @return */ @Bean public PersistentTokenRepository persistentTokenRepository(){ JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl(); jdbcTokenRepository.setDataSource(datasource); // jdbcTokenRepository.setCreateTableOnStartup(true); // 是否自動建立token資料表,如果是第一次可以勾選,後面表存在還開啟就會報錯 return jdbcTokenRepository; }
可以使用在配置方法中新增 ".rememberMeParameter("rem") " 配置記住我功能的name
注意:
1、這裡的過期時間是拒上次開啟瀏覽器登陸開始計算的,也就是每次開啟瀏覽器訪問一次上次登陸時間都會重新整理一次。而瀏覽器內部訪問並不會重新整理時間。
2、退出後(退出登陸狀態)會清除資料庫的token資料,再次訪問需要重新登入
登陸成功處理器
步驟一:增加元件
方式一、繼承實現類
方式二、實現底層介面
步驟二:將元件註冊進成功處理器配置中
登陸失敗處理器
步驟一:增加元件
方式一、實現介面
方式二、繼承實現類
步驟二:將元件註冊進失敗處理器配置中
許可權認證失敗處理器
1、元件
2、配置
使用者退出處理器
1、元件
2、配置
角色許可權
訪問一個需要許可權或角色的頁面需要先登陸,如果登陸後還是不能訪問就會返回500.
角色、許可權、使用者關係
許可權與角色是多對多,角色與使用者也是多對多。許可權指的是對某個表具體的增刪改查許可權,而角色是一系列許可權的集合。比如管理員角色擁有對所有表增刪改查的許可權,普通使用者角色只擁有對所有表查詢的許可權,而使用者 admin 擁有管理員角色,使用者 A 擁有普通使用者的角色。
定義許可權
在config裡配置路徑所需許可權,在UserDetailsService裡配置使用者所擁有的許可權。
1、hasAuthority 是與關係,如果在config裡配置了多個許可權,如”admin,manager”,那麼在UserDetailsService也必須對使用者配置兩個角色許可權才可以訪問
2、hasAnyAuthority 是或關係,如果在 config裡配置了多個許可權,如”admin,manager”,那麼在UserDetailsService只需要對使用者配置一個許可權就可以訪問
定義角色
角色在 UserDetailsService 實現類中配置需要加 "ROLE_" 字首
而hasRole 和hasAnyRole 對應許可權裡的hasAuthority 和hasAnyAuthority,是與和或的關係。
Access 來定義許可權、角色
上面的hasRole、hasAuthority 底層都是使用 access 來實現的,所以我們還可以通過底層的access 方法來主直接定義許可權、角色。
那麼 config 裡配置就是如下:
自定義 Access 校驗規則
1、元件
2、配置
基於 IP 來限制
這樣的話只能接收來自 127.0.0.1 的請求。
定義角色註解
@Secured單個””裡不支援使用,隔開,也就是不支援與關係。如果要配置多個或關係,可以使用{}, 在UserDetailsService裡只要配置一個就可以訪問。
並且只支援定義角色,不支援定義許可權,也就是Secured裡必須是ROLE_開頭
定義角色、許可權註解
可以定義角色、也可以定義許可權
如果使用者擁有的角色是abc,那麼在這裡可以配置hasRole(‘abc’),也可以配置hasRole(‘ROLE_abc’),而使用config配置類配置則不可以,會報錯。而大小寫則和配置類一樣會區分
先執行後校驗註解
可以用於記錄訪問日誌
對返回和傳入資料過濾註解
CSRF
CSRF 是為了防止使用者在開啟記住登陸後,其他非法使用者擷取到登陸使用者的 Cookie ,登陸其他使用者進行非法操作。
預設是開啟的,開啟後使用者登入時,系統發放一個CsrfToken值(key是 _csrf,value是token值),使用者攜帶該CsrfToken值與使用者名稱、密碼等引數完成登入。系統記錄該會話的 CsrfToken 值,之後在使用者的任何請求中,都必須帶上該CsrfToken值,並由系統進行校驗。
配置:
相關依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!--對Thymeleaf新增Spring Security標籤支援--> <dependency> <groupId>org.thymeleaf.extras</groupId> <artifactId>thymeleaf-extras-springsecurity5</artifactId> </dependency>
開啟 CSRF 時配置類不能配置 loginProcessingUrl 和 defaultSuccessUrl 。會影響登陸跳轉邏輯。
其他配置
1、如果配置了登陸的URL(也就是loginProcessingUrl),那麼自定義Controller裡處理的登陸請求就會用不到,走的是SpringSecurity內部的驗證方法。
2、anyRequest()必須配置在所有的antMatches後面,也就是籠統的許可權配置必須放在其他許可權的最後
3、and()是用於連線多個http配置。
4、在開發時需要新增@EnableWebSecurity註解,這個註解會自動配置安全認證策略和認證資訊。
整合OAuth2
關於 OAuth2 與 JWT 可以移步 淺談常見的認證機制 。
基礎依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
基礎配置
因為 OAuth2 涉及到資源伺服器和授權伺服器,所以除了配置 SpringSecurity ,還需要配置資源伺服器和授權伺服器。
1、SpringSecurity配置:
2、授權伺服器配置:定義 app_id、app_secret,以及重定向地址,授權範圍等
3、資源伺服器配置:定義資源伺服器資源許可權角色配置。
4、其他:userDetailsService 配置
自定義使用者實體類 user ,許可權屬性全部設為 true。
資源伺服器的資源Controller
授權碼模式
在上面的授權伺服器配置中,已將授權型別設為 授權碼模式,所以直接使用上面的配置。
驗證
1、獲取授權碼
訪問 http://localhost:8080/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all ,在登陸成功後(因為走的是上面 userDetailsService 的方法,所以使用者名稱任意,密碼123456就通過登陸),會重定向授權伺服器配置中配置好的 http://www.baidu.com 。並且攜帶授權伺服器返回的授權碼 code。
2、獲取授權令牌
接下來就可以再次訪問 localhost:8080/oauth/token 攜帶授權碼及其他資料來向授權伺服器獲取授權令牌。
3、通過令牌訪問資源伺服器的資源,訪問資源伺服器上資源的 URL,並攜帶授權令牌。
密碼模式
密碼模式因為是通過密碼直接獲取授權令牌,所以不需要先獲取授權碼,同時需要設定自定義的 userDetailsService 實現類,以及 authenticationManager 元件
1、ServurityConfig裡增加配置:
2、授權伺服器增加配置:
這樣配置是同時支援授權碼模式與密碼模式
驗證
通過密碼獲取授權令牌
訪問資源伺服器的資源則和授權碼模式驗證一樣。
整合 redis 將令牌存入 redis
1、引入依賴
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency>
2、註冊 redis 的 tokenStore 元件進容器
3、在授權伺服器裡註冊 tokenStore
4、在配置檔案裡配置 redis 地址密碼等。
使用 JWT 作為令牌
1、增加依賴
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
2、從容器中移除 redis 的 tokenStore 元件,同時想容器中加入 jwt 的 tokenStore 元件,並且配置 jwt 的轉換器
3、註冊進授權伺服器
JWT 增加額外資訊
1、增加 Jwt 附加資訊元件並註冊進容器
2、在授權伺服器裡配置 jwt 附加資訊元件
3、驗證,修改資源伺服器資源返回的資訊
設定過期時間和重新整理令牌
在授權伺服器裡增加配置:
在60s後token令牌(access_token)失效後,可以使用重新整理令牌重新獲取新的令牌,新的令牌過期時間也是60s。
因為密碼模式不支援重新整理令牌,所以通過授權碼模式使用重新整理令牌來獲取新的令牌
通過重新整理令牌獲取令牌
整合SSO(單點登陸)
1、引入依賴
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-oauth2</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency>
2、新建一個模組,配置 SSO 訪問授權伺服器的地址
3、增加 SSO 模組的資源
4、主程式開啟 OAuth2 自動配置
5、在授權伺服器的配置增加配置
隨後訪問客戶端資源 http://localhost:8081/user/getCurrentUser 就會先跳轉到 http://localhost:8080/login ,也就是授權伺服器進行授權驗證,通過後經重定向回到 http://localhost:8081/login ,也就是客戶端的登陸頁面,並且攜帶授權伺服器提供的jwt令牌,所以會自動解析通過驗證,最後再訪問客戶端的資源