當前位置:首頁 > IT技術 > 編程語言 > 正文

在SpringBoot中使用SpringSecurity
2022-02-14 10:42:03

本教程是基于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進行解決

? 網(wǎng)上的教程那么多,但是講的都不清不楚。所以,請仔細閱讀下段這些話,這要比后邊的代碼重要。

? SpringSecurity主要有兩部分內(nèi)容:

  • 認證 (你是誰,說白了就是一個用戶登錄的功能,幫我們驗證用戶名和密碼)
  • 授權 (你能干什么,就是根據(jù)當前登錄用戶的權限,說明你能訪問哪些接口,哪些不能訪問。)

這里的登錄是對于瀏覽器訪問來說的,因為如果是前后端分離時,使用的是Token進行授權的,也可以理解為登錄用戶,這個后邊會講。這里只是為了知識的嚴謹性才提到了這點

SpringSecurity和SpringBoot結(jié)合

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()   //表示一個配置的結(jié)束
                .formLogin().permitAll()  //開啟SpringSecurity內(nèi)置的表單登錄,會提供一個/login接口
                .and()
                .logout().permitAll()  //開啟SpringSecurity內(nèi)置的退出登錄,會為我們提供一個/logout接口
                .and()
                .csrf().disable();    //關閉csrf跨站偽造請求
    }

}

上邊的配置主要內(nèi)容有兩個:

  1. 配置訪問三個接口(實際上不僅僅是3個,/**是泛指)需要的權限;
  2. 配置了使用SpringSecurity的內(nèi)置/login和/loginout接口(這個是完全可以自定義的)
  3. 權限被拒絕后的返回結(jié)果也可以自定義,它當權限被拒絕后,會拋出異常

說明:

  1. 上邊的配置中,其實就是調(diào)用http的這個對象的方法;
  2. 使用.and()只為了表示一上配置結(jié)束,并滿足鏈式調(diào)用的要求,不然之前的對象可能并不能進行鏈式調(diào)用
  3. 這個配置在SpringBoot應用啟動的時候就會調(diào)用,也就是會將這些配置加載進內(nèi)存,當用戶調(diào)用對應的接口的時候,就會判斷它的角色是否可以調(diào)用這個接口,流程圖如下(我覺得圖要比文字更能說明過程):

3. 配置用戶名和密碼

? 配置了上邊的接口和用戶權限角色的關系后,就是要配置我們的用戶名和密碼了。如果沒有正確的用戶名和密碼,神仙也登錄不上去。

? 關于這個,網(wǎng)上的教程有各種各樣的配置,其實就一個接口,我們只需要實現(xiàn)這個接口中的方法就可以了。接口代碼如下:

package org.springframework.security.core.userdetails;

public interface UserDetailsService {
  	/**
  	 * 在登錄的時候,就會調(diào)用這個方法,它的返回結(jié)果是一個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 {
        /**
         * 將我們的登錄邏輯寫在這里
         * 我是直接在這里寫的死代碼,其實應該從數(shù)據(jù)庫中根據(jù)用戶名去查
         */
        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.進行測試

? 分別訪問上邊三個接口,可以看到訪問結(jié)果和上邊的流程是一樣的。

總結(jié):

  • 仔細閱讀上邊的那個流程圖,是理解SpringSecurity最重要的內(nèi)容,代碼啥的都很簡單;上邊也就兩個類,一個配置接口與角色的關系,一個實現(xiàn)了UserDetailsService類中的方法。
  • 前邊說了,SpringSecurity主要就是兩個邏輯:
    • 用戶登錄后,將用戶的角色信息保存在服務器(session中);
    • 用戶訪問接口后,從session中取出用戶信息,然后和配置的角色和權限進行比對是否有這個權限訪問
  • 上述方法中,我們只重寫了用戶登錄時的邏輯。而根據(jù)訪問接口來判斷當前用戶是否擁有這個接口的訪問權限部分,我們并沒有進行修改。所以這只適用于可以使用session的項目中。
  • 對于前后端分離的項目,一般是利用JWT進行授權的,所以它的主要內(nèi)容就在判斷token中的信息是否有訪問這個接口的權限,而并不在用戶登錄這一部分。
  • 解決訪問的方案有很多種,選擇自己最適合自己的才是最好了。SpringSecurity只是提供了一系列的接口,他自己內(nèi)部也有一些實現(xiàn),你也可以直接使用。
  • 上邊配置和用戶登錄邏輯部分的內(nèi)容是完全可以從數(shù)據(jù)庫中查詢出來進行配置的。
  • 轉(zhuǎn)自:https://www.cnblogs.com/youngdeng/p/12869018.html

本文摘自 :https://www.cnblogs.com/

開通會員,享受整站包年服務立即開通 >