本教程是基于SpringMVC而創(chuàng)建的,不適用于WebFlux。(如果你不知道這兩者,可以忽略這句提示)
提出一個需求
所有的技術是為了解決實際問題而出現(xiàn)的,所以我們并不空談,也不去講那么多的概念。在這樣一個系統(tǒng)中,有三個接口,需要授權給三種權限的人使用,如下表:
接口地址 | 需要的權限描述 | 可訪問的權限組名稱 |
---|---|---|
visitor/main | 不需要權限,也不用登錄,誰都可以訪問 | ? |
admin/main | 必須登錄,只有管理員可以訪問 | ADMIN |
user/main | 必須登錄,管理員和用戶權限都能訪問 | USER和ADMIN |
解決方案:
-
在Controller中判斷用戶是否登錄和用戶的權限組判斷是否可以訪問
這是最不現(xiàn)實的解決方案,可是我剛進公司時的項目就是這樣設計的,當時我還覺得很高大尚呢。
-
使用Web應用的三大組件中和過濾器(Filter)進行判斷
這是正解,SpringSecurity也正是用的這個原理。如果你的項目足夠簡單,建議你直接使用這種方式就可以了,并不需要集成SpringSecurity。這部分的示例在代碼中有演示,自己下載代碼查看即可。
-
我們可以直接使用SpringSecurity框架來解決這個問題
使用SpringSecurity進行解決
? 網上的教程那么多,但是講的都不清不楚。所以,請仔細閱讀下段這些話,這要比后邊的代碼重要。
? SpringSecurity主要有兩部分內容:
- 認證 (你是誰,說白了就是一個用戶登錄的功能,幫我們驗證用戶名和密碼)
- 授權 (你能干什么,就是根據當前登錄用戶的權限,說明你能訪問哪些接口,哪些不能訪問。)
這里的登錄是對于瀏覽器訪問來說的,因為如果是前后端分離時,使用的是Token進行授權的,也可以理解為登錄用戶,這個后邊會講。這里只是為了知識的嚴謹性才提到了這點
SpringSecurity和SpringBoot結合
1. 首先在pom.xml中引入依賴:
<!-- 不用寫版本,繼承Springboot的版本-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
2. 配置用戶角色和接口的權限關系
是支持使用xml進行配置的,但是在SpringBoot中更建議使用Java注解配置
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 配置用戶權限組和接口路徑的關系
* 和一些其他配置
*
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() // 對請求進行驗證
.antMatchers("/visitor/**").permitAll()
.antMatchers("/admin/**").hasRole("ROLE_ADMIN") // 必須有ADMIN權限
.antMatchers("/user/**").hasAnyRole("ROLE_USER", "ROLE_ADMIN") //有任意一種權限
.anyRequest() //任意請求(這里主要指方法)
.authenticated() //// 需要身份認證
.and() //表示一個配置的結束
.formLogin().permitAll() //開啟SpringSecurity內置的表單登錄,會提供一個/login接口
.and()
.logout().permitAll() //開啟SpringSecurity內置的退出登錄,會為我們提供一個/logout接口
.and()
.csrf().disable(); //關閉csrf跨站偽造請求
}
}
上邊的配置主要內容有兩個:
- 配置訪問三個接口(實際上不僅僅是3個,/**是泛指)需要的權限;
- 配置了使用SpringSecurity的內置/login和/loginout接口(這個是完全可以自定義的)
- 權限被拒絕后的返回結果也可以自定義,它當權限被拒絕后,會拋出異常
說明:
- 上邊的配置中,其實就是調用http的這個對象的方法;
- 使用.and()只為了表示一上配置結束,并滿足鏈式調用的要求,不然之前的對象可能并不能進行鏈式調用
- 這個配置在SpringBoot應用啟動的時候就會調用,也就是會將這些配置加載進內存,當用戶調用對應的接口的時候,就會判斷它的角色是否可以調用這個接口,流程圖如下(我覺得圖要比文字更能說明過程):
3. 配置用戶名和密碼
? 配置了上邊的接口和用戶權限角色的關系后,就是要配置我們的用戶名和密碼了。如果沒有正確的用戶名和密碼,神仙也登錄不上去。
? 關于這個,網上的教程有各種各樣的配置,其實就一個接口,我們只需要實現(xiàn)這個接口中的方法就可以了。接口代碼如下:
package org.springframework.security.core.userdetails;
public interface UserDetailsService {
/**
* 在登錄的時候,就會調用這個方法,它的返回結果是一個UserDetails接口類
*/
UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}
? 來看一下這個接口,如果想擴展,可以自己寫一個實現(xiàn)類,也可以使用SpringSecurity提供的實現(xiàn)
public interface UserDetails extends Serializable {
// 用戶授權集合
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
? UserDetailsServicer接口的實現(xiàn)類
@Configuration
public class UserDetailsServiceImpl implements UserDetailsService {
/**
* 這個方法要返回一個UserDetails對象
* 其中包括用戶名,密碼,授權信息等
*
* @param username
* @return
* @throws UsernameNotFoundException
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
/**
* 將我們的登錄邏輯寫在這里
* 我是直接在這里寫的死代碼,其實應該從數據庫中根據用戶名去查
*/
if (username == null) {
//返回null時,后邊就會拋出異常,就會登錄失敗。但這個異常并不需要我們處理
return null;
}
if (username.equals("lyn4ever")) {
//這是構造用戶權限組的代碼
//但是這個權限上加了ROLE_前綴,而在之前的配置上卻沒有加。
//與其說這不好理解,倒不如說這是他設計上的一個小缺陷
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");
List<SimpleGrantedAuthority> list = new ArrayList<>();
list.add(authority);
//這個user是UserDetails的一個實現(xiàn)類
//用戶密碼實際是lyn4ever,前邊加{noop}是不讓SpringSecurity對密碼進行加密,使用明文和輸入的登錄密碼比較
//如果不寫{noop},它就會將表表單密碼進行加密,然后和這個對比
User user = new User("lyn4ever", "{noop}lyn4ever", list);
return user;
}
if (username.equals("admin")) {
SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");
SimpleGrantedAuthority authority1 = new SimpleGrantedAuthority("ROLE_ADMIN");
List<SimpleGrantedAuthority> list = new ArrayList<>();
list.add(authority);
list.add(authority1);
User user = new User("admiin", "{noop}admin", list);
return user;
}
//其他返回null
return null;
}
}
4.進行測試
? 分別訪問上邊三個接口,可以看到訪問結果和上邊的流程是一樣的。
總結:
- 仔細閱讀上邊的那個流程圖,是理解SpringSecurity最重要的內容,代碼啥的都很簡單;上邊也就兩個類,一個配置接口與角色的關系,一個實現(xiàn)了UserDetailsService類中的方法。
- 前邊說了,SpringSecurity主要就是兩個邏輯:
- 用戶登錄后,將用戶的角色信息保存在服務器(session中);
- 用戶訪問接口后,從session中取出用戶信息,然后和配置的角色和權限進行比對是否有這個權限訪問
- 上述方法中,我們只重寫了用戶登錄時的邏輯。而根據訪問接口來判斷當前用戶是否擁有這個接口的訪問權限部分,我們并沒有進行修改。所以這只適用于可以使用session的項目中。
- 對于前后端分離的項目,一般是利用JWT進行授權的,所以它的主要內容就在判斷token中的信息是否有訪問這個接口的權限,而并不在用戶登錄這一部分。
- 解決訪問的方案有很多種,選擇自己最適合自己的才是最好了。SpringSecurity只是提供了一系列的接口,他自己內部也有一些實現(xiàn),你也可以直接使用。
- 上邊配置和用戶登錄邏輯部分的內容是完全可以從數據庫中查詢出來進行配置的。
- 轉自:https://www.cnblogs.com/youngdeng/p/12869018.html
本文摘自 :https://www.cnblogs.com/