欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

springboot3整合SpringSecurity實(shí)現(xiàn)登錄校驗與權(quán)限認(rèn)證

 更新時間:2025年04月13日 11:05:14   作者:張喬24  
本文主要介紹了springboot3整合SpringSecurity實(shí)現(xiàn)登錄校驗與權(quán)限認(rèn)證,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

目前市面上常用的安全框架有:

Spring Security、Shiro,還有一個國人開發(fā)的框架目前也備受好評:SaToken

但是與spring boot項目融合度最高的還是Spring Security,所以目前我們講解一下基于spring boot項目來整合spring security來實(shí)現(xiàn)常用的登錄校驗與權(quán)限認(rèn)證;

Spring Security(安全框架)

1、介紹

Spring Security是一個能夠為基于Spring的企業(yè)應(yīng)用系統(tǒng)提供聲明式的安全訪問控制解決方案的安全框架。

如果項目中需要進(jìn)行權(quán)限管理,具有多個角色和多種權(quán)限,我們可以使用Spring Security。

采用的是責(zé)任鏈的設(shè)計模式,是一堆過濾器鏈的組合,它有一條很長的過濾器鏈。

2、功能

Authentication (認(rèn)證),就是用戶登錄
Authorization (授權(quán)),判斷用戶擁有什么權(quán)限,可以訪問什么資源
安全防護(hù),跨站腳本攻擊,session攻擊等
非常容易結(jié)合Springboot項目進(jìn)行使用,本次就著重與實(shí)現(xiàn)認(rèn)證和授權(quán)這兩個功能

版本spring boot3.1.16、spring security6.x

身份認(rèn)證:

1、創(chuàng)建一個spring boot項目,并導(dǎo)入一些初始依賴:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>2.0.21</version>
    </dependency>

    <dependency>
        <groupId>com.mysql</groupId>
        <artifactId>mysql-connector-j</artifactId>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <scope>test</scope>
    </dependency>
</dependencies>

2、由于我們加入了 spring-boot-starter-security 的依賴,所以security就會自動生效了。這時直接編寫一個controller控制器,并編寫一個接口進(jìn)行測試:

可以看到我們在訪問這個接口時出現(xiàn)了攔截,必須要我們進(jìn)行登錄之后才能訪問;

那么接下來我們就來實(shí)現(xiàn)第一個功能:用戶登錄認(rèn)證;

3、自定義用戶的登錄認(rèn)證:

Spring Security 6.x 的認(rèn)證實(shí)現(xiàn)流程如下:

用戶提交登錄請求

Spring Security 將請求交給 UsernamePasswordAuthenticationFilter 過濾器處理。

UsernamePasswordAuthenticationFilter 獲取請求中的用戶名和密碼,并生成一個 AuthenticationToken 對象,將其交給 AuthenticationManager 進(jìn)行認(rèn)證。

AuthenticationManager 通過 UserDetailsService 獲取用戶信息,然后使用 PasswordEncoder 對用戶密碼進(jìn)行校驗。

如果密碼正確,AuthenticationManager 會生成一個認(rèn)證通過的 Authentication 對象,并返回給 UsernamePasswordAuthenticationFilter 過濾器。如果密碼不正確,則 AuthenticationManager 拋出一個 AuthenticationException 異常。

UsernamePasswordAuthenticationFilter 將 Authentication 對象交給 SecurityContextHolder 進(jìn)行管理,并調(diào)用 AuthenticationSuccessHandler 處理認(rèn)證成功的情況。

如果認(rèn)證失敗,UsernamePasswordAuthenticationFilter 會調(diào)用 AuthenticationFailureHandler 處理認(rèn)證失敗的情況。

看起來有點(diǎn)復(fù)雜,其實(shí)寫起來很簡單的。spring security的底層就是一堆的過濾器來是實(shí)現(xiàn)的,而我們只需要編寫一些重要的過濾器即可,其他的就用spring security默認(rèn)的實(shí)現(xiàn),只要不影響我們正常的登錄功能即可。

(創(chuàng)建一個用戶表用來進(jìn)行登錄實(shí)現(xiàn),注意這個表中的用戶名不能重復(fù),我們將用戶名作為每一個用戶的唯一憑證,就如同人的手機(jī)號或者身份證號一樣)表的結(jié)構(gòu)非常簡單,一些配置我這里就不在描述了(實(shí)體類、mapper、service、controller等)

認(rèn)證的實(shí)現(xiàn)流程:

1、創(chuàng)建一個MyUserDetailsService類來實(shí)現(xiàn)SpringSecurity的UserDetailsService接口
(這里進(jìn)行用戶登錄和授權(quán)的邏輯處理)

UserDetailsService:此接口中定義了登錄服務(wù)方法,用來實(shí)現(xiàn)登錄邏輯。方法的返回值是UserDetails,也是spring security框架定義中的一個接口,用來存儲用戶信息,我們可以自定義一個類用來實(shí)現(xiàn)這個接口,將來返回的時候就返回我們自定義的用戶實(shí)體類。

實(shí)現(xiàn)UserDetailsService接口

@Component
public class MyUserDetailsService implements UserDetailsService {

    /*
    *  UserDetailsService:提供查詢用戶功能,如根據(jù)用戶名查詢用戶,并返回UserDetails
     *UserDetails,SpringSecurity定義的類, 記錄用戶信息,如用戶名、密碼、權(quán)限等
    * */
    @Autowired
    private SysUserMapper sysUserMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根據(jù)用戶名從數(shù)據(jù)庫中查詢用戶
        SysUser sysUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>()
                .eq(username != null, SysUser::getUsername, username));
if (sysUser==null){
    throw new UsernameNotFoundException("用戶不存在");
}
        MySysUserDetails mySysUserDetails=new MySysUserDetails(sysUser);
        return mySysUserDetails;
    }
}

(在原有數(shù)據(jù)庫表的基礎(chǔ)上)實(shí)現(xiàn)UserDetails接口:

@Data
@AllArgsConstructor
@NoArgsConstructor
public class MySysUserDetails implements UserDetails {


    private Integer id;

    private String username;

    private String password;

//    用戶擁有的權(quán)限集合,我這里先設(shè)置為null,將來會再更改的
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    public MySysUserDetails(SysUser sysUser) {
        this.id = sysUser.getId();
        this.username = sysUser.getUsername();
        this.password = sysUser.getPassword();
    }

    //    后面四個方法都是用戶是否可用、是否過期之類的。我都設(shè)置為true
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

2、通過配置類對AuthenticationManager與自定義的UserDetails和PasswordEncoder進(jìn)行關(guān)聯(lián)

Spring Security是通過AuthenticationManager實(shí)現(xiàn)的認(rèn)證,會借此來判斷用戶名和密碼的正確性

密碼解析器spring security框架定義的接口:PasswordEncoder

spring security框架強(qiáng)制要求,必須在spring容器中存在PasswordEncoder類型對象,且對象唯一

@Configuration
@EnableWebSecurity //開啟webSecurity服務(wù)
public class SecurityConfig {

    
    @Autowired
    private MyUserDetailsService myUserDetailsService;

@Bean
public AuthenticationManager authenticationManager(PasswordEncoder passwordEncoder){
    DaoAuthenticationProvider provider=new DaoAuthenticationProvider();
//將編寫的UserDetailsService注入進(jìn)來
    provider.setUserDetailsService(myUserDetailsService);
//將使用的密碼編譯器加入進(jìn)來
    provider.setPasswordEncoder(passwordEncoder);
//將provider放置到AuthenticationManager 中
    ProviderManager providerManager=new ProviderManager(provider);
    return providerManager;
}
    /*
     * 在security安全框架中,提供了若干密碼解析器實(shí)現(xiàn)類型。
     * 其中BCryptPasswordEncoder 叫強(qiáng)散列加密??梢员WC相同的明文,多次加密后,
     * 密碼有相同的散列數(shù)據(jù),而不是相同的結(jié)果。
     * 匹配時,是基于相同的散列數(shù)據(jù)做的匹配。
     * Spring Security 推薦使用 BCryptPasswordEncoder 作為密碼加密和解析器。
     * */
@Bean
    public PasswordEncoder passwordEncoder(){
    return new BCryptPasswordEncoder();
}

3、在登錄方法所在的類中注入AuthenticationManager,調(diào)用authenticate實(shí)現(xiàn)認(rèn)證邏輯,并且在認(rèn)證之后返回認(rèn)證過的用戶信息:

controller層:

//    用戶登錄
    @PostMapping("/login")
    public String login(@RequestBody LoginDto loginDto){

      String token=  sysUserService.login(loginDto);
        return token;
    }

對應(yīng)的service層的方法:

在這之前,介紹一個非常重要的類:UsernamePasswordAuthenticationToken

UsernamePasswordAuthenticationToken是Spring Security中用于表示基于用戶名和密碼的身份驗證令牌的類。它主要有以下兩個構(gòu)造方法:

UsernamePasswordAuthenticationToken(Object principal, Object credentials)

  • principal參數(shù)表示認(rèn)證主體,通常是用戶名或用戶對象。在身份驗證過程中,這通常是用來標(biāo)識用戶的信息,可以是用戶名、郵箱等。
  • credentials參數(shù)表示憑據(jù),通常是用戶的密碼或其他憑證信息。在身份驗證過程中,這用于驗證用戶的身份。

UsernamePasswordAuthenticationToken(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities)

  • 除了上述兩個參數(shù)外,這個構(gòu)造方法還接受一個授權(quán)權(quán)限集合(authorities參數(shù))。這個集合表示用戶所擁有的權(quán)限,通常是一個包含用戶權(quán)限信息的集合。
  • GrantedAuthority接口代表了用戶的權(quán)限信息,可以通過該接口的實(shí)現(xiàn)類來表示用戶具體的權(quán)限。

這兩個構(gòu)造方法的作用是創(chuàng)建一個包含用戶身份信息、憑據(jù)信息和權(quán)限信息的身份驗證令牌,以便在Spring Security中進(jìn)行身份驗證和授權(quán)操作。通過這些構(gòu)造方法,可以將用戶的相關(guān)信息封裝成一個完整的身份驗證對象,方便在安全框架中進(jìn)行處理和驗證。

總之,UsernamePasswordAuthenticationToken是在Spring Security中用于表示用戶名密碼身份驗證信息的重要類,通過不同的構(gòu)造方法可以滿足不同場景下的需求。

編寫具體的登錄方法,創(chuàng)建一個UsernamePasswordAuthenticationToken對象,并傳入相應(yīng)的用戶名和密碼;注入一個AuthenticationManager的bean,這個bean是spring security封裝的用來進(jìn)行認(rèn)證的類,調(diào)用這個類的authenticate方法并傳入UsernamePasswordAuthenticationToken對象;

@Autowired
    private AuthenticationManager authenticationManager;

//    登錄接口的具體實(shí)現(xiàn)
    @Override
    public String login(LoginDto loginDto) {
//        傳入用戶名和密碼
        UsernamePasswordAuthenticationToken usernamePassword =
                new UsernamePasswordAuthenticationToken(loginDto.getUsername(),loginDto.getPassword());
//是實(shí)現(xiàn)登錄邏輯,此時就回去調(diào)用LoadUserByUsername方法
        Authentication authenticate = authenticationManager.authenticate(usernamePassword);
//        獲取返回的用戶信息
        Object principal = authenticate.getPrincipal();
     //強(qiáng)轉(zhuǎn)為MySysUserDetails類型
MySysUserDetails mySysUserDetails = (MySysUserDetails) principal;
//        輸出用戶信息
        System.err.println(mySysUserDetails);
//返回token
        String token= UUID.randomUUID().toString();
        return token;
    }

  我在test類中設(shè)置一些用戶數(shù)據(jù),并進(jìn)行測試;

@Autowired private SysUserMapper sysUserMapper;
@Autowired private PasswordEncoder passwordEncoder;
@Test void contextLoads() { //導(dǎo)入了一個用戶
SysUser sysUser=new SysUser();
sysUser.setUsername("zhangsan"); sysUser.setPassword(passwordEncoder.encode("123456")); sysUserMapper.insert(sysUser);
}

這里我們已經(jīng)寫好了自定義的登錄流程,將項目運(yùn)行起來(我同時還寫了一個普通的test方法,類型是get,用來一起測試)

訪問http://localhost:8080/test

這是我們寫的一個普通的get方法,我們明明訪問的是http://localhost:8080/test這個路徑,但是卻自動跳轉(zhuǎn)到了Spring Security提供的默認(rèn)的登錄頁面;這是因為Spring Security默認(rèn)所有的請求都要先登錄才行,我們在這里登錄之后就可以繼續(xù)訪問test頁面了;

(由于我們已經(jīng)實(shí)現(xiàn)了UserDetailsService接口,并且在用戶表中導(dǎo)入了一條用戶數(shù)據(jù),那么,這里的用戶名和密碼就是我們在數(shù)據(jù)庫中存儲的用戶名和密碼)

登錄成功之后,我們就可以訪問到test的信息了:

既然這個test請求要先進(jìn)行攔截認(rèn)證才能訪問,那么,我們剛才編寫的登錄接口sys-user/login豈不是也要先進(jìn)行攔截認(rèn)證才能訪問,這就與我們編寫登錄接口的初衷違背了,我們這個接口就是用來登陸的,現(xiàn)在還要先登錄認(rèn)證,之后再訪問這個登錄接口。那么有沒有一種方法,不使用SpringSecurity默認(rèn)的登錄頁面呢,使我們編寫的登錄接口所有人都可以直接訪問呢?

4、使用(SecurityFilterChain)過濾器, 配置用戶登錄的接口可以暴露出來,被所有人都正常的訪問(還應(yīng)在暴露一個注冊接口,但我這里就先不寫了)

還是在第二步設(shè)置的SecurityConfig類中設(shè)置過濾器:

在spring security6.x版本之后,原先經(jīng)常用的and()方法被廢除了,現(xiàn)在spring官方推薦使用Lambda表達(dá)式的寫法。

(因為我們接下來要進(jìn)行測試,所以禁用CSRF保護(hù),CSRF(Cross-Site Request Forgery)是一種攻擊方式,攻擊者通過偽造用戶的請求來執(zhí)行惡意操作。)

/*
     * 配置權(quán)限相關(guān)的配置
     * 安全框架本質(zhì)上是一堆的過濾器,稱之為過濾器鏈,每一個過濾器鏈的功能都不同
     * 設(shè)置一些鏈接不要攔截
     * */
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
//       關(guān)閉csrf
        httpSecurity.csrf(it->it.disable());
httpSecurity.authorizeHttpRequests(it->
        it.requestMatchers("/sys-user/login").permitAll()  //設(shè)置登錄路徑所有人都可以訪問
                .anyRequest().authenticated()  //其他路徑都要進(jìn)行攔截
        );

        return httpSecurity.build();
    }

5、將項目運(yùn)行起來(我同時還寫了一個普通的test方法,類型是get,沒有放行,用于測試能不能攔截到):

訪問test請求:遇到攔截,說明我們的配置生效了

訪問登錄頁面:能正常訪問,且密碼正確,返回了一個我們自己生成的一個token。

6、自定義一個登錄頁面:

SpringSecurity雖然默認(rèn)有一個登錄頁面,但是我們一般情況下還是用我們自己寫的登錄頁面,這樣可操作性就大了很多;

引入thymeleaf依賴,我們直接在idea項目中建立一個登錄頁面;

編寫一個登錄頁面,主要是完成用戶的登錄,同時我們也不再需要頻繁的使用postman進(jìn)行測試了:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8" xmlns:th="http://www.thymeleaf.org">
    <title>自定義的登錄頁面</title>
</head>
<body>

<form action="/sys-user/login" method="post">
   用戶名: <input type="text" name="username" >
    <br>
    密碼: <input type="password" name="password">
    <br>
<input type="submit" value="登錄">
</form>

</body>
</html>

這是一個簡單的登錄頁面,就指定了用戶名和密碼。

并且指定from表單的提交路徑為我們自定義的登錄接口;將這個頁面放在resource/templates目錄下,方便我們將來的調(diào)用;

HTML中的form表單默認(rèn)情況下會將數(shù)據(jù)格式化為key-value形式,而不是JSON格式。

也就是說我們剛剛寫的自定義登錄接口時是用@RequestBody接受收json類型的數(shù)據(jù),這肯定是接受不到的,有兩種方法實(shí)現(xiàn):

1、直接用@RequestParam("username")  ,@RequestParam("password")接收這兩個參數(shù)

2、@ModelAttribute注解:@ModelAttribute("formData") User user   //在@ModelAttribute注解內(nèi)寫表單的id,還能使用對象進(jìn)行接收

我們也可以在前端將from表單的數(shù)據(jù)轉(zhuǎn)化為json之后,在進(jìn)行發(fā)送,但那樣需要寫js,我就直接在后端改一下了。

還是使用使用(SecurityFilterChain)過濾器,指定我們自定義的登錄表單路徑,(解釋一下fromLogin方法):

formLogin 方法是 Spring Security 中用于配置基于表單的登錄認(rèn)證的一種方式。它通常用于傳統(tǒng)的 Web 應(yīng)用程序,其中前端頁面由后端動態(tài)生成,并且用戶在頁面中輸入用戶名和密碼來進(jìn)行登錄。在這種情況下,Spring Security 負(fù)責(zé)處理登錄請求、驗證用戶身份、生成會話等操作。

/*
     * 配置權(quán)限相關(guān)的配置
     * 安全框架本質(zhì)上是一堆的過濾器,稱之為過濾器鏈,每一個過濾器鏈的功能都不同
     * 設(shè)置一些鏈接不要攔截
     * */

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
//       關(guān)閉csrf
        httpSecurity.csrf(it->it.disable());
//        配置路徑相關(guān)
httpSecurity.authorizeHttpRequests(it->
        it.requestMatchers("/login","sys-user/login").permitAll()  //設(shè)置登錄路徑所有人都可以訪問
                .anyRequest().authenticated()  //其他路徑都要進(jìn)行攔截
        );
//表單
httpSecurity.formLogin(from->
        from.loginPage("/login")   //跳轉(zhuǎn)到自定義的登錄頁面
.loginProcessingUrl("/sys-user/login")  //處理前端的請求,與from表單的action一致即可
     .defaultSuccessUrl("/index")  //默認(rèn)的請求成功之后的跳轉(zhuǎn)頁面,直接訪問登錄頁面
        );

        return httpSecurity.build();
    }

注意,這里還需要將/login這個接口進(jìn)行放行。

我們知道,不能直接訪問login.html這個自定義的登錄頁面,但是我們可以使用路徑映射。先寫一個login的get請求,并將這個請求映射到login.html頁面。

.defaultSuccessUrl("/index"):這個方法是我們默認(rèn)的登錄成功之后跳轉(zhuǎn)的請求地址。

如果你之前有請求的地址,但是這個地址沒有放行或者你沒有登錄,那么會自動跳轉(zhuǎn)到我們自定義的登錄頁面,完成登錄之后,會跳轉(zhuǎn)到你最先訪問的地址;如果你直接訪問的就是/login登錄地址,那么默認(rèn)的登錄成功之后跳轉(zhuǎn)到我們指定的地址:/index

@Controller
public class Login {


@GetMapping("/login")
public String login(){
    System.out.println("用戶進(jìn)入登錄頁面");
    return "login";   //沒使用json返回,直接映射到自定義登錄的頁面
}

@GetMapping("/index")
@ResponseBody
    public String index(){
    return "用戶登錄成功";
}
}

現(xiàn)在我們已經(jīng)自定義了一個登錄頁面,將項目啟動起來進(jìn)行測試:

我訪問/test地址,這個地址沒有放行,而且我們這是沒有登錄,那么會自動跳轉(zhuǎn)到我們自定義的登錄頁面:

我們進(jìn)行登錄之后,會跳轉(zhuǎn)到/test請求地址:

可以看到我們的結(jié)果與我們設(shè)想的一樣:

現(xiàn)在我們直接訪問/login登錄頁面:可以看到返回了/index頁面的內(nèi)容(這個是我們設(shè)置的默認(rèn)登錄成功之后返回的頁面)

7、退出接口

需要注意的是在Spring Security中,沒有專門用于處理退出失敗的接口。退出(注銷)操作通常是由瀏覽器發(fā)起的,Spring Security會攔截注銷請求并執(zhí)行相應(yīng)的注銷邏輯。

退出操作通常是通過調(diào)用SecurityContextLogoutHandler來完成的,它會清除用戶的安全上下文,包括認(rèn)證信息和會話信息。

在security框架中,默認(rèn)提供了退出登陸的功能。請求地址是  /lohout  此為默認(rèn)值,可以通過配置進(jìn)行修該。直接請求 /logout ,會實(shí)現(xiàn)自動退出登錄邏輯(默認(rèn)的/logout接收get、和post請求)

退出登陸時,會清楚內(nèi)存中的登錄用戶主體信息,銷毀會話對象等等。

自定義退出接口:

httpSecurity.logout(logout->{
   logout.logoutUrl("/user/login")   //自定義退出接口
           .logoutSuccessHandler(logoutSuccess);  //退出成功之后的邏輯

});

編寫退出成功之后的邏輯,我們可以在這里刪除掉redis中的數(shù)據(jù),清除登錄的上下文,設(shè)置返回的信息等等....(如果是前后端分離狀態(tài)下的spring security,這些工作都可以在自定義的退出接口中進(jìn)行實(shí)現(xiàn)。如果是前后端不分離的表單式登錄,還是使用傳統(tǒng)的Cookie和Session來進(jìn)行用戶信息的保存,我們自需要調(diào)用ogout.logoutUrl("/user/login")  方法來指定退出路徑即可,退出的邏輯不需要我們來實(shí)現(xiàn)。)

@Component
public class LogoutSuccess implements LogoutSuccessHandler {

    @Resource
    private RedisTemplate<String,String> redisTemplate;

/*
* 登錄成功之后的邏輯
* */
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        String token = request.getHeader("token");
        //    刪除redis中的數(shù)據(jù)
        redisTemplate.delete(token);
        Map<String,Object> map=new HashMap<>();
        map.put("msg","退出成功");
        map.put("code",200);

response.getWriter().write(JSON.toJSONString(map));
        response.setContentType("application/json;charset=utf-8");
    }

}

權(quán)限校驗:

我們費(fèi)了很多功夫完成了身份認(rèn)證,權(quán)限校驗相對來說是比較簡單的:

首先,我先解釋一下角色與權(quán)限在SpringSecurity中的作用:

  • 角色(Role):角色是一組權(quán)限的集合,通常代表著用戶的身份或職責(zé)。在Spring Security中,可以通過配置將角色分配給用戶或者用戶組,以此來控制用戶對系統(tǒng)資源的訪問。例如,管理員擁有添加、刪除和修改用戶的權(quán)限,而普通用戶只能查看自己的信息。

  • 權(quán)限(Permission):權(quán)限是指對某一特定資源的訪問控制,例如讀寫文件、訪問數(shù)據(jù)庫等。在Spring Security中,通常使用“資源-操作”命名方式來定義權(quán)限,例如“/admin/* - GET”表示允許訪問以/admin/開頭的所有URL的GET請求??梢詫?quán)限分配給角色,也可以將其分配給單獨(dú)的用戶。

角色與權(quán)限之間的關(guān)系是多對多的;

建立兩張簡單的表;一張用來存放角色、一張用來存放權(quán)限

角色表:

權(quán)限表:

這里建立的兩張表只是用來進(jìn)行測試,正常的數(shù)據(jù)不可能這么少的。建立相應(yīng)的實(shí)體類;

SpringSecurity要求將身份認(rèn)證信息存到GrantedAuthority對象列表中。代表了當(dāng)前用戶的權(quán)限。 GrantedAuthority對象由AuthenticationManager插入到Authentication對象中,然后在做出授權(quán)決策 時由AccessDecisionManager實(shí)例讀取。 GrantedAuthority 接口只有一個方法

AuthorizationManager實(shí)例通過該方法來獲得GrantedAuthority。通過字符串的形式表示, GrantedAuthority可以很容易地被大多數(shù)AuthorizationManager實(shí)現(xiàn)讀取。如果GrantedAuthority不 能精確地表示為String,則GrantedAuthorization被認(rèn)為是復(fù)雜的,getAuthority()必須返回null

告知權(quán)限的流程:

直接在登錄時查詢用戶的權(quán)限,并放在我們自定義的實(shí)現(xiàn)了UserDetail的接口類中,用來表示登錄用戶的全部信息;

在MySysUserDetails類中加入兩個屬性,記錄從數(shù)據(jù)庫中查處的角色和權(quán)限信息

我這里就簡單一點(diǎn),不在做多表關(guān)聯(lián)查詢了。直接把zhangsan用戶設(shè)置為超級管理員,擁有所有權(quán)限;lisi用戶設(shè)置為普通管理員,擁有基本權(quán)限。

在MyUserDetailsService中實(shí)現(xiàn)用戶權(quán)限的賦值:

@Component
public class MyUserDetailsService implements UserDetailsService {

    /*
    *  UserDetailsService:提供查詢用戶功能,如根據(jù)用戶名查詢用戶,并返回UserDetails
     *UserDetails,SpringSecurity定義的類, 記錄用戶信息,如用戶名、密碼、權(quán)限等
    * */
    @Autowired
    private SysUserMapper sysUserMapper;

@Autowired
private SysRoleMapper sysRoleMapper;
@Autowired
private SysPermissionsMapper sysPermissionsMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根據(jù)用戶名從數(shù)據(jù)庫中查詢用戶
        SysUser sysUser = sysUserMapper.selectOne(new LambdaQueryWrapper<SysUser>()
                .eq(username != null, SysUser::getUsername, username));
if (sysUser==null){
    throw new UsernameNotFoundException("用戶不存在");
}
        MySysUserDetails mySysUserDetails=new MySysUserDetails(sysUser);
if ("zhangsan".equals(username)){
//zhangsan用戶是超級管理員,擁有一切權(quán)限
    SysRole sysRole = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>().eq(SysRole::getRoleName, "超級管理員"));
    Set<SysRole> roles=new HashSet<>();
    roles.add(sysRole);
    mySysUserDetails.setRoles(roles);
    SysPermissions sysPermissions = sysPermissionsMapper.selectById(1);
    Set<String> permissions=new HashSet<>();
    permissions.add(sysPermissions.getPermissionsName());
    mySysUserDetails.setPermissions(permissions);
}

        if ("lisi".equals(username)){
//lisi用戶是普通管理員,擁有基本權(quán)限
            SysRole sysRole = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>().eq(SysRole::getRoleName, "普通管理員"));
            Set<SysRole> roles=new HashSet<>();
            roles.add(sysRole);
            mySysUserDetails.setRoles(roles);
            SysPermissions sysPermissions = sysPermissionsMapper.selectById(2);
            Set<String> permissions=new HashSet<>();
            permissions.add(sysPermissions.getPermissionsName());
            mySysUserDetails.setPermissions(permissions);
        }

        return mySysUserDetails;
    }
}

在實(shí)現(xiàn)了UserDetailes接口的用戶信息類MySysUserDetails中完成角色和權(quán)限的賦值:

//    角色信息
    private Set<SysRole> roles;
//    權(quán)限信息
    private Set<String> permissions;
    
//    用戶擁有的權(quán)限集合,我這里先設(shè)置為null,將來會再更改的
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        System.err.println("進(jìn)入權(quán)限的獲取方法");

        List<GrantedAuthority> authorities = new ArrayList<>(); // 授權(quán)信息列表
// 將角色名稱添加到授權(quán)信息列表中
        roles.forEach(role->
        authorities.add(new SimpleGrantedAuthority(role.getRoleName())));
        // 將權(quán)限名稱添加到授權(quán)信息列表中
        permissions.forEach(permission->
        authorities.add(new SimpleGrantedAuthority(permission))
                );
        return authorities; // 返回授權(quán)信息列表
    }

用戶認(rèn)證之后,會去存儲用戶對應(yīng)的權(quán)限,并且給資源設(shè)置對應(yīng)的權(quán)限,SpringSecurity支持兩種粒度 的權(quán)限:

1、基于請求的:在配置文件中配置路徑,可以使用**的通配符

2、基于方法的:在方法上使用注解實(shí)現(xiàn)

角色配置:在UserDetails接口中存在相關(guān)的權(quán)限和角色管理,只不過我們在實(shí)現(xiàn)這個接口的時候,將這些都設(shè)置為了null?,F(xiàn)在我們只需要將這些信息實(shí)現(xiàn)即可:

1、基于請求:

還是在SecurityFilter過濾器中實(shí)現(xiàn)請求地址的權(quán)限校驗

httpSecurity.authorizeHttpRequests(it->
//hello地址只有超級管理員角色才能訪問
it.requestMatchers("/hello").hasRole("超級管理員")
//hello2地址只有"擁有所有權(quán)限"的權(quán)限才能訪問
.requestMatchers("hello2").hasAuthority("擁有所有權(quán)限")
                .requestMatchers("/login","sys-user/login").permitAll()  //設(shè)置登錄路徑所有人都可以訪問
                .anyRequest().authenticated()  //其他路徑都要進(jìn)行攔截
        );

使用sili進(jìn)行登錄時,訪問hello2接口顯示權(quán)限不夠:

使用zhangsan進(jìn)行登錄時,訪問hello2接口可以訪問到:

2、基于方法:

基于方法的權(quán)限認(rèn)證要在SecurityConfig類上加上@EnableMethodSecurity注解,表示開啟了方法權(quán)限的使用;

常用的有四個注解:

@PreAuthorize

@PostAuthorize

@PreFilter

@PostFilter

/*測試@PreAuthorize注解
* 作用:使用在類或方法上,擁有指定的權(quán)限才能訪問(在方法運(yùn)行前進(jìn)行校驗)
* String類型的參數(shù):語法是Spring的EL表達(dá)式
* 有權(quán)限:test3權(quán)限
* hasRole:會去匹配authorities,但是會在hasRole的參數(shù)前加上一個ROLE_前綴,
* 所以在定義權(quán)限的時候需要加上ROLE_前綴
* role和authorities的關(guān)系是:role是一種復(fù)雜的寫法,有ROLE_前綴,authorities是role的簡化寫法
* 如果使用
* hasAnyRole:則匹配的權(quán)限是在authorities加上前綴ROLE_
* 推薦使用
* hasAnyAuthority:匹配authorities,但是不用在authorities的參數(shù)前加上ROLE_前綴
* */
@PreAuthorize("hasAnyAuthority('擁有所有權(quán)限')")
@ResponseBody
@GetMapping("/test3")
public String test3(){
    System.out.println("一個請求");

    return "一個test3請求";
}
/*
 @PostAuthorize:在方法返回時進(jìn)行校驗。
 可以還是校驗權(quán)限、或者校驗一些其他的東西(接下來我們校驗返回值的長度)
*返回結(jié)果的長度大于3、則認(rèn)為是合法的
returnObject:固定寫法,代指返回對象
* */
@ResponseBody
@PostAuthorize("returnObject.length()>4")
@GetMapping("/test4")
public String test4(){
    System.out.println("一個test4請求");

    return "小張自傲張最終";
}
/*
* @PreFilter:過濾符合條件的數(shù)據(jù)進(jìn)入到接口
* */
    @PostFilter("filterObject.length()>3")
    @ResponseBody
    @GetMapping("/test5")
    public String test5(){
        System.out.println("一個test4請求");
List<String> list = new ArrayList<>();
list.add("張三");
list.add("王麻子");
list.add("狗叫什么");

        return "一個test5請求";
    }
/*
* @PreFilter:過濾符合條件的數(shù)據(jù)返回,數(shù)據(jù)必須是Collection、map、Array【數(shù)組】
* */
@PreFilter("filterObject.length()>5")
@ResponseBody
@PostMapping("/test6")
public List<String> test6(@RequestBody List<String> list){
    return list;
}

這四個常用的權(quán)限校驗方法我都寫出來了,運(yùn)行結(jié)果我就不在一一截圖了。

需要注意的是這些方法不僅僅局限在權(quán)限的校驗,還能對返回的結(jié)果做一定的操作;

最需要注意的就是@PreFilter注解,它要求前端傳遞的參數(shù)一定是數(shù)組或集合;

還有在SpringSecurity框架中:

role和authorities的關(guān)系是:role是一種復(fù)雜的寫法,有ROLE_前綴,authorities是role的簡化寫法

基于方法鑒權(quán) 在SpringSecurity6版本中@EnableGlobalMethodSecurity被棄用,取而代之的是 @EnableMethodSecurity。默認(rèn)情況下,會激活pre-post注解,并在內(nèi)部使用 AuthorizationManager。

新老API區(qū)別 此@EnableMethodSecurity替代了@EnableGlobalMethodSecurity。提供了以下改進(jìn): 1. 使用簡化的AuthorizationManager。 2. 支持直接基于bean的配置,而不需要擴(kuò)展GlobalMethodSecurityConfiguration 3. 使用Spring AOP構(gòu)建,刪除抽象并允許您使用Spring AOP構(gòu)建塊進(jìn)行自定義 4. 檢查是否存在沖突的注釋,以確保明確的安全配置 5. 符合JSR-250 6. 默認(rèn)情況下啟用@PreAuthorize、@PostAuthorize、@PreFilter和@PostFilter

主要的權(quán)衡似乎是您希望您的授權(quán)規(guī)則位于何處。重要的是要記住,當(dāng)您使用基于注釋的方法安全性 時,未注釋的方法是不安全的。為了防止這種情況,請在HttpSecurity實(shí)例中聲明一個兜底授權(quán)規(guī)則。 如果方法上也定義了權(quán)限,則會覆蓋類上的權(quán)限

注意:使用注解的方式實(shí)現(xiàn),如果接口的權(quán)限發(fā)生變化,需要修改代碼了。

總結(jié):

  • 登錄校驗(Authentication):

    • 用戶提交用戶名和密碼進(jìn)行登錄。
    • Spring Security會攔截登錄請求,并將用戶名和密碼與存儲在系統(tǒng)中的憑據(jù)(如數(shù)據(jù)庫或LDAP)進(jìn)行比對。
    • 如果用戶名和密碼匹配,則認(rèn)為用戶通過了身份驗證,可以繼續(xù)訪問受限資源。
    • 認(rèn)證成功后,Spring Security會創(chuàng)建一個包含用戶信息和權(quán)限的安全上下文(Security Context)。
  • 權(quán)限認(rèn)證(Authorization):

    • 一旦用戶通過了身份驗證,Spring Security就會開始進(jìn)行權(quán)限認(rèn)證。
    • 針對每個受限資源或操作,可以配置相應(yīng)的權(quán)限要求,例如需要哪些角色或權(quán)限才能訪問。
    • Spring Security會根據(jù)配置的權(quán)限要求,檢查當(dāng)前用戶所擁有的角色和權(quán)限,判斷是否滿足訪問條件。
    • 如果用戶擁有足夠的角色或權(quán)限,就被允許訪問資源;否則將被拒絕訪問,并可能重定向到登錄頁面或返回相應(yīng)的錯誤信息。

Spring Security通過身份驗證(Authentication)來確認(rèn)用戶的身份,并通過授權(quán)(Authorization)來控制用戶對受保護(hù)資源的訪問。這種分離的設(shè)計使得安全配置更加靈活,并且可以輕松地對不同的用戶和角色進(jìn)行管理和控制。

這都是一些基礎(chǔ)的理論,只是一個小demo,帶你認(rèn)識一下spring security的工作原理,下面我將實(shí)戰(zhàn)中演示使用security的案例:

前后端分離,使用vue3整合SpringSecurity加JWT實(shí)現(xiàn)登錄認(rèn)證

到此這篇關(guān)于springboot3整合SpringSecurity實(shí)現(xiàn)登錄校驗與權(quán)限認(rèn)證的文章就介紹到這了,更多相關(guān)springboot 登錄校驗與權(quán)限認(rèn)證 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Jenkins的安裝配置詳解

    Jenkins的安裝配置詳解

    這篇文章主要介紹了Jenkins的安裝配置詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-06-06
  • 實(shí)現(xiàn)一個規(guī)則引擎的可視化具體方案

    實(shí)現(xiàn)一個規(guī)則引擎的可視化具體方案

    項目原因需要用到規(guī)則引擎,但是發(fā)現(xiàn)大部分不可以自由的進(jìn)行規(guī)則定義,通過不斷嘗試變換關(guān)鍵字在搜索引擎搜索,最終在stackoverflow找到了一個探討這個問題的帖子,特此將帖子中提到的方案分享一下,如果你跟我一樣在研究同樣的問題,也許對你有用
    2021-04-04
  • java正則替換括號中的逗號實(shí)現(xiàn)示例

    java正則替換括號中的逗號實(shí)現(xiàn)示例

    本文主要介紹了java正則替換括號中的逗號實(shí)現(xiàn)示例,主要介紹了兩種示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-01-01
  • 淺談Java垃圾回收機(jī)制

    淺談Java垃圾回收機(jī)制

    這篇文章主要介紹了淺談Java垃圾回收機(jī)制,文中有非常詳細(xì)的圖文示例及代碼示例,對正在學(xué)習(xí)java的小伙伴們有很好的幫助,需要的朋友可以參考下
    2021-05-05
  • Mybatis Generator逆向工程的使用詳細(xì)教程

    Mybatis Generator逆向工程的使用詳細(xì)教程

    這篇文章主要介紹了Mybatis Generator逆向工程的使用詳細(xì)教程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-06-06
  • dubbo將異常轉(zhuǎn)換成RuntimeException的原因分析?ExceptionFilter

    dubbo將異常轉(zhuǎn)換成RuntimeException的原因分析?ExceptionFilter

    這篇文章主要介紹了dubbo將異常轉(zhuǎn)換成RuntimeException的原因分析?ExceptionFilter問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • JavaCV獲取視頻文件時長的方法

    JavaCV獲取視頻文件時長的方法

    這篇文章主要為大家詳細(xì)介紹了JavaCV獲取視頻文件時長的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • 快速排序的原理及java代碼實(shí)現(xiàn)

    快速排序的原理及java代碼實(shí)現(xiàn)

    網(wǎng)上關(guān)于快速排序的算法原理和算法實(shí)現(xiàn)都比較多,不過java是實(shí)現(xiàn)并不多,而且部分實(shí)現(xiàn)很難理解,和思路有點(diǎn)不搭調(diào)。所以整理了這篇文章。如果有不妥之處還請建議。
    2016-02-02
  • mybatis-plus支持null字段全量更新的兩種方法

    mybatis-plus支持null字段全量更新的兩種方法

    本文主要介紹了mybatis-plus支持null字段全量更新的兩種方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-02-02
  • Java正則表達(dá)式學(xué)習(xí)之分組與替換

    Java正則表達(dá)式學(xué)習(xí)之分組與替換

    這篇文章主要給大家介紹了關(guān)于Java正則表達(dá)式學(xué)習(xí)之分組與替換的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09

最新評論