一、SpringSecurity介紹
1、介紹
Spring Security是一個功能強大且高度可定制的身份驗證和訪問控制框架。它是用於保護基於Spring的應用程序的事實上的標准。
Spring Security是一個框架,致力於為Java應用程序提供身份驗證和授權。像所有Spring項目一樣,Spring Security的真正強大之處在於它可以輕松擴展以滿足定制需求的能力。
主要功能:
1、認證 (你是誰)
2、授權 (你能干什么)
3、攻擊防護 (防止偽造身份)
2、特征
對身份驗證和授權的全面且可擴展的支持
保護免受會話固定,點擊劫持,跨站點請求偽造等攻擊
Servlet API集成
與Spring Web MVC的可選集成
多得多…
3、使用條件
j8以上
二、SpringSecurity使用
1、導包
maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
gradle
dependencies {
compile "org.springframework.boot:spring-boot-starter-security"
}
2、繼承WebSecurityConfigurerAdapter開始操作
(1)、重寫configure方法
簡單整理下為什么開啟跨域
如果沒有同源策略,不同源的數據和資源(如HTTP頭、Cookie、DOM、localStorage等)就能相互隨意訪問,根本沒有隱私和安全可言。為了安全起見和資源的有效管理,瀏覽器當然要采用這種策略。
@Component
@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
//開啟跨域
http.formLogin().and().cors().and().
//后面都是授權配置
authorizeRequests()
//匹配 許可證 (該請求放行)
.antMatchers("/demoController").permitAll()
//或者除了放行的url攔截所有請求
.anyRequest().authenticated();
}
}
如果說沒有請求放行則會返回如下

(2)、內置控制訪問方法
瞅一瞅源碼可得知
static final String permitAll = "permitAll";...............................................permitAll是允許所有
private static final String denyAll = "denyAll";...........................................denyAll是拒絕所有
private static final String anonymous = "anonymous";.......................................anonymous是允許匿名的
private static final String authenticated = "authenticated";...............................authenticated是需要認證
private static final String fullyAuthenticated = "fullyAuthenticated";.....................fullyAuthenticated是需要完整的認證
private static final String rememberMe = "rememberMe";.....................................rememberMe是記住,比如7天免登錄這種
(3)、登錄頁
SpringBoot自帶登錄頁開啟 formLogin 后地址加端口即可訪問,賬號隨意密碼在項目啟動時會在控制台打印
//使用Springboot自帶的登錄頁
http.formLogin().and()
//后面都是授權配置
authorizeRequests()
//匹配 證明 (攔截全部請求)
.antMatchers("/**").authenticated();


(4)、使用自定義登錄頁
http.formLogin()
//當發現login時認為是登錄需要執行我們自定義的登錄邏輯 >里面的url是登錄頁面表單的提交地址
.loginProcessingUrl("/login")
//登錄成功后請求地址 請求方法必須是post的
.successForwardUrl("/toMain")
//設置登錄頁面
.loginPage("/login.html")
//放行登錄頁面
.and
.authorizeRequests()
.antMatchers("/login").permitAll()
//關閉csrf防護 >只有關閉了,才能接受來自表單的請求
http.csrf().disable();
扯一嘴csrf攻擊就是利用瀏覽器返回的Cookies和session
(5)、登出頁
看源碼注釋會發現登出可以做很多操作,例如刪除cookie

//登出后跳轉頁面
http.logout().logoutSuccessUrl("/");
附上api
| 表達式 | 說明 |
|---|---|
| * hasRole([role]) | 當前賬戶有指定角色時返回true, 默認情況下,角色都是以ROLE_開頭,當然也可以在修改DefaultWebSecurityExpressionHandler中修改defaultRolePrefix自定義角色前綴 |
| * hasAnyRole([role1,role2]) | 當前賬戶有指定角色中的任意一個時返回true, 默認情況下,角色都是以ROLE_開頭,當然也可以在修改DefaultWebSecurityExpressionHandler中修改defaultRolePrefix自定義角色前綴 |
| hasAuthority([authority]) | 當前賬戶有指定權限時返回true |
| hasAnyAuthority([authority1,authority2]) | 當前賬戶有指定權限中任何一個時返回true |
| principal | 允許當前用戶直接訪問的對象主體 |
| authentication | 允許直接訪問從SecurityContext獲得的當前身份驗證對象 |
| permitAll | 允許所有 |
| denyAll | 拒絕所有 |
| isAnonymous() | 是否匿名用戶 |
| isRememberMe() | 當前是否被記住 |
| * isAuthenticated() | 是否已經登錄 |
| isFullyAuthenticated() | 是否已經登錄 或 被記住 |
| * hasPermission(Object target, Object permission) | Returns true if the user has access to the provided target for the given permission. For example, hasPermission(domainObject, ‘read’) |
| * hasPermission(Object targetId, String targetType, Object permission) | Returns true if the user has access to the provided target for the given permission. For example, hasPermission(1, ‘com.example.domain.Message’, ‘read’) |
| hasIpAddress([ip address]) | IP地址是否是??? |
三、角色權限判斷
利用@PreAuthorize注解自定義權限校驗
開啟@EnableGlobalMethodSecurity(prePostEnabled = true)注解, 在繼承 WebSecurityConfigurerAdapter 這個類的類上面貼上這個注解.並且prePostEnabled設置為true,@PreAuthorize這個注解才能生效,SpringSecurity默認是關閉注解功能的.
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
(1)、UserDetailsService(基於數據庫自定義UserDetailsService實現認證)
UserDetailsService接口用於返回用戶相關數據。它有loadUserByUsername()方法,根據username查詢用戶實體,可以實現該接口覆蓋該方法,實現自定義獲取用戶過程。該接口實現類被DaoAuthenticationProvider 類使用,用於認證過程中載入用戶信息。
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
private final String user = "admin";
private final String pas = "$2a$10$kkPpRCSQJii1BdV77TqAbuWtBFAvtUGqFor.AbyGxGT.avFH/buU2";
@Override
//通過username去數據庫查詢賬號密碼
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
List<GrantedAuthority> authorityList = new ArrayList<>();
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + "admin");
authorityList.add(authority);
SimpleGrantedAuthority authority1 = new SimpleGrantedAuthority("user:add");
authorityList.add(authority1);
return new User(user,pas,authorityList);
}
}
//當然實際場景中賬號密碼和權限列表是通過數據庫的查詢獲得的,最終返回一個User對象即可
//Security執行的權限檢查不管你角色還是權限,他只比較字符
GrantedAuthority 有三個實現類,通常選用核心包中的SimpleGrantedAuthority(單純形權限)

(2)、配置Security
需要注意的是 WebSecurityConfigurerAdapter中有重載了三個configure
configure(AuthenticationManagerBuilder)認證管理器配置
用於通過允許輕松添加AuthenticationProviders來建立身份驗證機制
configure(HttpSecurity)安全過濾器鏈配置
允許基於選擇匹配在資源級別配置基於Web的安全性
configure(WebSecurity)核心過濾器配置
用於影響全局安全性的配置設置(忽略資源,設置調試模式,通過實現自定義防火牆定義拒絕請求),可設置UserDetails 和加密方式
通常使用過濾器鏈 configure(HttpSecurity)用來配置 HttpSecurity 。 HttpSecurity 用於構建一個安全過濾器鏈 SecurityFilterChain 。SecurityFilterChain 最終被注入核心過濾器 。
@Component
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
UserDetailsService userDetailsService;
/**
* 配置密碼解析
* @return
*/
@Bean
protected PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.formLogin().successForwardUrl("/index")
.and()
.authorizeRequests()
.anyRequest()//所有請求
.authenticated();//都需要身份認證
}
}
(3)、請求鑒權(@PreAuthorize)
@PreAuthorize("hasAuthority('user:add')")
public String create(){
return "添加功能:create";
}
需要給每個請求使用 @PreAuthorize("hasAuthority('user:add')") 標識當然也可以用 @PreAuthorize("hasAnyRole('ROLE_admin')")
注意:一定要以大寫ROLE_開頭后面隨意(指定角色可以訪問)
四、SpringSecurity提供支持表達式的注解
(1)、@PreAuthorize
可以用來控制一個方法是否能夠被調用。也就是說先判斷后執行
(2)、@PostAuthorize
先調用該接口,獲得結果后在去判斷該用戶是否有權限(有意思的是如果接口有返回值用戶沒有權限最終不去返回結果罷了,但是操作還是做了)
(3)、@PreFilter
對集合類型的參數或返回值進行過濾,Spring Security將移除使對應表達式的結果為false的元素。
(4)、@PostFilter
注意@PostFilter注解只有在控制器方法的return返回值是一個集合的時候才可以使用;@PostFilter注解的作用:如果控制器方法的return返回值是一個集合,此注解可以對return的這個集合進行過濾輸出;
(5)、@Secured注解
@Secured注解的作用:在用戶向瀏覽器發送一個請求時會去訪問控制器中的方法,然后在訪問此控制器中的方法之前會先去UserDetailsService用戶細節實現類的實現方法中return的User對象查看是否具有@Secured注解中指定的角色,如果有指定的角色,那么系統允許用戶訪問此控制器方法,否則,系統不允許訪問此控制器方法;注意在使用@Secured設置角色名字的時候,角色名的前面一定要加上ROLE_前綴;
五、OAuth2
1、簡介
很多網站、APP 弱化甚至沒有搭建自己的賬號體系,而是直接使用社會化登錄的方式,這樣不僅免去了用戶注冊賬號的麻煩、還可以獲取用戶的好友關系來增強自身的社交功能。
比如我們可以使用微博登錄簡書,簡書會自動將你的微博頭像設置為你的簡書頭像,將你的微博昵稱設置為你的簡書昵稱,甚至還可以獲取你微博中的好友列表,提示你哪些朋友已經在使用簡書,這是如何做到的呢?
最傳統的辦法是讓用戶直接在簡書的登錄頁面輸微博的賬號和密碼,簡書通過用戶的賬號和密碼去微博那里獲取用戶數據,但這樣做有很多嚴重的缺點:
簡書需要明文保存用戶的微博賬號和密碼,這樣很不安全。
簡書擁有了獲取用戶在微博所有的權限,包括刪除好友、給好友發私信、更改密碼、注銷賬號等危險操作。
用戶只有修改密碼,才能收回賦予簡書的權限。但是這樣做會使得其他所有獲得用戶授權的第三方應用程序全部失效。
只要有一個第三方應用程序被破解,就會導致用戶密碼泄漏,以及所有使用微博登錄的網站的數據泄漏。
為了解決以上的問題,OAuth 協議應運而生。
2、原理概要
新浪微博作為服務提供商,擁有用戶的頭像、昵稱、郵箱、好友以及所有的微博內容,簡書希望獲取用戶存儲在微博的頭像和昵稱,假設它們是三個人:
1、簡書問新浪微博:我想要獲取用戶 A 的頭像和昵稱,請你提供
2、微博說:我需要經過用戶A 本人的許可,然后去問用戶 A 是否要授權簡書訪問自己的頭像和昵稱
3、用戶 A 對微博說:我給簡書一個臨時的鑰匙,如果他給你出示了這把鑰匙,你就把我的資料給他
4、簡書使用戶給它的鑰匙獲取用戶頭像和昵稱信息。
也就是說這玩意就是搞第三方授權的咳咳。。。

3、流程圖


4、授權模式
(1)、授權碼模式
授權碼模式是四種模式中最繁瑣也是最安全的一種模式。
client向資源服務器請求資源,被重定向到授權服務器(AuthorizationServer)
瀏覽器向資源擁有者索要授權,之后將用戶授權發送給授權服務器
授權服務器將授權碼(AuthorizationCode)轉經瀏覽器發送給client
client拿着授權碼向授權服務器索要訪問令牌
授權服務器返回Access Token和Refresh Token給cilent
這種模式是四種模式中最安全的一種模式。一般用於client是Web服務器端應用或第三方的原生App調用資源服務的時候。因為在這種模式中AccessToken不會經過瀏覽器或移動端的App,而是直接從服務端去交換,這樣就最大限度的減小了AccessToken泄漏的風險。

(2)、簡化模式/隱式授權模式
簡化模式相對於授權碼模式省略了,提供授權碼,然后通過服務端發送授權碼換取AccessToken的過程。
client請求資源被瀏覽器轉發至授權服務器
瀏覽器向資源擁有者索要授權,之后將用戶授權發送給授權服務器
授權服務器將AccessToken以Hash的形式存放在重定向uri的fargment中發送給瀏覽器
瀏覽器訪問重定向URI
資源服務器返回一個腳本,用以解析Hash中的AccessToken
瀏覽器將Access Token解析出來
將解析出的Access Token發送給client
一般簡化模式用於沒有服務器端的第三方單頁面應用,因為沒有服務器端就無法使用授權碼模式。

(3)、密碼模式
用戶將認證密碼發送給client
client拿着用戶的密碼向授權服務器請求Access Token
授權服務器將Access Token和Refresh Token發送給client
這種模式十分簡單,但是卻意味着直接將用戶敏感信息泄漏給了client,因此這就說明這種模式只能用於client是我們自己開發的情況下。因此密碼模式一般用於我們自己開發的,第一方原生App或第一方單頁面應用。

(4)、客戶端模式
這是一種最簡單的模式,只要client請求,我們就將AccessToken發送給它。
client向授權服務器發送自己的身份信息,並請求AccessToken
確認client信息無誤后,將AccessToken發送給client
這種模式是最方便但最不安全的模式。因此這就要求我們對client完全的信任,而client本身也是安全的。因此這種模式一般用來提供給我們完全信任的服務器端服務。在這個過程中不需要用戶的參與。

(5)、四種模式的應用場景
1、授權碼模式:第三方Web服務器端應用與第三方原生App
2、簡化模式:第三方單頁面應用
3、密碼模式:第一方單頁應用與第一方原生App
4、客戶端模式:沒有用戶參與的,完全信任的服務器端服務
