當(dāng)前位置:首頁 > IT技術(shù) > 編程語言 > 正文

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

本教程是基于SpringMVC而創(chuàng)建的,不適用于WebFlux。(如果你不知道這兩者,可以忽略這句提示)

提出一個(gè)需求

所有的技術(shù)是為了解決實(shí)際問題而出現(xiàn)的,所以我們并不空談,也不去講那么多的概念。在這樣一個(gè)系統(tǒng)中,有三個(gè)接口,需要授權(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)公司時(shí)的項(xiàng)目就是這樣設(shè)計(jì)的,當(dāng)時(shí)我還覺得很高大尚呢。

  • 使用Web應(yīng)用的三大組件中和過濾器(Filter)進(jìn)行判斷

    這是正解,SpringSecurity也正是用的這個(gè)原理。如果你的項(xiàng)目足夠簡單,建議你直接使用這種方式就可以了,并不需要集成SpringSecurity。這部分的示例在代碼中有演示,自己下載代碼查看即可。

  • 我們可以直接使用SpringSecurity框架來解決這個(gè)問題

使用SpringSecurity進(jìn)行解決

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

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

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

這里的登錄是對(duì)于瀏覽器訪問來說的,因?yàn)槿绻乔昂蠖朔蛛x時(shí),使用的是Token進(jìn)行授權(quán)的,也可以理解為登錄用戶,這個(gè)后邊會(huì)講。這里只是為了知識(shí)的嚴(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()     // 對(duì)請(qǐng)求進(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()     //任意請(qǐng)求(這里主要指方法)
                .authenticated()   //// 需要身份認(rèn)證
                .and()   //表示一個(gè)配置的結(jié)束
                .formLogin().permitAll()  //開啟SpringSecurity內(nèi)置的表單登錄,會(huì)提供一個(gè)/login接口
                .and()
                .logout().permitAll()  //開啟SpringSecurity內(nèi)置的退出登錄,會(huì)為我們提供一個(gè)/logout接口
                .and()
                .csrf().disable();    //關(guān)閉csrf跨站偽造請(qǐng)求
    }

}

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

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

說明:

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

3. 配置用戶名和密碼

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

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

package org.springframework.security.core.userdetails;

public interface UserDetailsService {
  	/**
  	 * 在登錄的時(shí)候,就會(huì)調(diào)用這個(gè)方法,它的返回結(jié)果是一個(gè)UserDetails接口類
  	 */
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

? 來看一下這個(gè)接口,如果想擴(kuò)展,可以自己寫一個(gè)實(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 {
    /**
     * 這個(gè)方法要返回一個(gè)UserDetails對(duì)象
     * 其中包括用戶名,密碼,授權(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時(shí),后邊就會(huì)拋出異常,就會(huì)登錄失敗。但這個(gè)異常并不需要我們處理
            return null;
        }
        if (username.equals("lyn4ever")) {
            //這是構(gòu)造用戶權(quán)限組的代碼
            //但是這個(gè)權(quán)限上加了ROLE_前綴,而在之前的配置上卻沒有加。
            //與其說這不好理解,倒不如說這是他設(shè)計(jì)上的一個(gè)小缺陷
            SimpleGrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");
            List<SimpleGrantedAuthority> list = new ArrayList<>();
            list.add(authority);
            //這個(gè)user是UserDetails的一個(gè)實(shí)現(xiàn)類
            //用戶密碼實(shí)際是lyn4ever,前邊加{noop}是不讓SpringSecurity對(duì)密碼進(jìn)行加密,使用明文和輸入的登錄密碼比較
            //如果不寫{noop},它就會(huì)將表表單密碼進(jìn)行加密,然后和這個(gè)對(duì)比
            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)行測(cè)試

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

總結(jié):

  • 仔細(xì)閱讀上邊的那個(gè)流程圖,是理解SpringSecurity最重要的內(nèi)容,代碼啥的都很簡單;上邊也就兩個(gè)類,一個(gè)配置接口與角色的關(guān)系,一個(gè)實(shí)現(xiàn)了UserDetailsService類中的方法。
  • 前邊說了,SpringSecurity主要就是兩個(gè)邏輯:
    • 用戶登錄后,將用戶的角色信息保存在服務(wù)器(session中);
    • 用戶訪問接口后,從session中取出用戶信息,然后和配置的角色和權(quán)限進(jìn)行比對(duì)是否有這個(gè)權(quán)限訪問
  • 上述方法中,我們只重寫了用戶登錄時(shí)的邏輯。而根據(jù)訪問接口來判斷當(dāng)前用戶是否擁有這個(gè)接口的訪問權(quán)限部分,我們并沒有進(jìn)行修改。所以這只適用于可以使用session的項(xiàng)目中。
  • 對(duì)于前后端分離的項(xiàng)目,一般是利用JWT進(jìn)行授權(quán)的,所以它的主要內(nèi)容就在判斷token中的信息是否有訪問這個(gè)接口的權(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/

開通會(huì)員,享受整站包年服務(wù)立即開通 >