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