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

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

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

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

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

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

Spring Security(安全框架)

1、介紹

Spring Security是一個(gè)能夠?yàn)榛赟pring的企業(yè)應(yīng)用系統(tǒng)提供聲明式的安全訪問控制解決方案的安全框架。

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

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

2、功能

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

版本spring boot3.1.16、spring security6.x

身份認(rèn)證:

1、創(chuàng)建一個(gè)spring boot項(xiàng)目,并導(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就會(huì)自動(dòng)生效了。這時(shí)直接編寫一個(gè)controller控制器,并編寫一個(gè)接口進(jìn)行測(cè)試:

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

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

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

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

用戶提交登錄請(qǐng)求

Spring Security 將請(qǐng)求交給 UsernamePasswordAuthenticationFilter 過濾器處理。

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

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

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

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

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

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

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

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

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

UserDetailsService:此接口中定義了登錄服務(wù)方法,用來實(shí)現(xiàn)登錄邏輯。方法的返回值是UserDetails,也是spring security框架定義中的一個(gè)接口,用來存儲(chǔ)用戶信息,我們可以自定義一個(gè)類用來實(shí)現(xiàn)這個(gè)接口,將來返回的時(shí)候就返回我們自定義的用戶實(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,將來會(huì)再更改的
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

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

    //    后面四個(gè)方法都是用戶是否可用、是否過期之類的。我都設(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、通過配置類對(duì)AuthenticationManager與自定義的UserDetails和PasswordEncoder進(jìn)行關(guān)聯(lián)

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

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

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

@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í),是基于相同的散列數(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;
    }

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

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

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

UsernamePasswordAuthenticationToken(Object principal, Object credentials)

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

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

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

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

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

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

@Autowired
    private AuthenticationManager authenticationManager;

//    登錄接口的具體實(shí)現(xiàn)
    @Override
    public String login(LoginDto loginDto) {
//        傳入用戶名和密碼
        UsernamePasswordAuthenticationToken usernamePassword =
                new UsernamePasswordAuthenticationToken(loginDto.getUsername(),loginDto.getPassword());
//是實(shí)現(xiàn)登錄邏輯,此時(shí)就回去調(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)行測(cè)試;

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

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

訪問http://localhost:8080/test

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

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

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

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

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

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

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

(因?yàn)槲覀兘酉聛硪M(jìn)行測(cè)試,所以禁用CSRF保護(hù),CSRF(Cross-Site Request Forgery)是一種攻擊方式,攻擊者通過偽造用戶的請(qǐng)求來執(zhí)行惡意操作。)

/*
     * 配置權(quán)限相關(guān)的配置
     * 安全框架本質(zhì)上是一堆的過濾器,稱之為過濾器鏈,每一個(gè)過濾器鏈的功能都不同
     * 設(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、將項(xiàng)目運(yùn)行起來(我同時(shí)還寫了一個(gè)普通的test方法,類型是get,沒有放行,用于測(cè)試能不能攔截到):

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

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

6、自定義一個(gè)登錄頁面:

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

引入thymeleaf依賴,我們直接在idea項(xiàng)目中建立一個(gè)登錄頁面;

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

<!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>

這是一個(gè)簡(jiǎn)單的登錄頁面,就指定了用戶名和密碼。

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

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

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

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

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

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

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

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

/*
     * 配置權(quán)限相關(guān)的配置
     * 安全框架本質(zhì)上是一堆的過濾器,稱之為過濾器鏈,每一個(gè)過濾器鏈的功能都不同
     * 設(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")  //處理前端的請(qǐng)求,與from表單的action一致即可
     .defaultSuccessUrl("/index")  //默認(rèn)的請(qǐng)求成功之后的跳轉(zhuǎn)頁面,直接訪問登錄頁面
        );

        return httpSecurity.build();
    }

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

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

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

如果你之前有請(qǐng)求的地址,但是這個(gè)地址沒有放行或者你沒有登錄,那么會(huì)自動(dòng)跳轉(zhuǎn)到我們自定義的登錄頁面,完成登錄之后,會(huì)跳轉(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)自定義了一個(gè)登錄頁面,將項(xiàng)目啟動(dòng)起來進(jìn)行測(cè)試:

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

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

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

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

7、退出接口

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

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

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

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

自定義退出接口:

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)限校驗(yàn):

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

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

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

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

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

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

角色表:

權(quán)限表:

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

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

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

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

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

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

我這里就簡(jiǎn)單一點(diǎn),不在做多表關(guān)聯(lián)查詢了。直接把zhangsan用戶設(shè)置為超級(jí)管理員,擁有所有權(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用戶是超級(jí)管理員,擁有一切權(quán)限
    SysRole sysRole = sysRoleMapper.selectOne(new LambdaQueryWrapper<SysRole>().eq(SysRole::getRoleName, "超級(jí)管理員"));
    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,將來會(huì)再更改的
    @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)證之后,會(huì)去存儲(chǔ)用戶對(duì)應(yīng)的權(quán)限,并且給資源設(shè)置對(duì)應(yīng)的權(quán)限,SpringSecurity支持兩種粒度 的權(quán)限:

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

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

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

1、基于請(qǐng)求:

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

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

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

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

2、基于方法:

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

常用的有四個(gè)注解:

@PreAuthorize

@PostAuthorize

@PreFilter

@PostFilter

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

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

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

        return "一個(gè)test5請(qǐng)求";
    }
/*
* @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;
}

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

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

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

還有在SpringSecurity框架中:

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

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

新老API區(qū)別 此@EnableMethodSecurity替代了@EnableGlobalMethodSecurity。提供了以下改進(jìn): 1. 使用簡(jiǎn)化的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)您使用基于注釋的方法安全性 時(shí),未注釋的方法是不安全的。為了防止這種情況,請(qǐng)?jiān)贖ttpSecurity實(shí)例中聲明一個(gè)兜底授權(quán)規(guī)則。 如果方法上也定義了權(quán)限,則會(huì)覆蓋類上的權(quán)限

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

總結(jié):

  • 登錄校驗(yàn)(Authentication):

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

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

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

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

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

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

相關(guān)文章

  • Jenkins的安裝配置詳解

    Jenkins的安裝配置詳解

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

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

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

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

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

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

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

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

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

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

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

    JavaCV獲取視頻文件時(shí)長(zhǎng)的方法

    這篇文章主要為大家詳細(xì)介紹了JavaCV獲取視頻文件時(shí)長(zhǎng)的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    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)。所以整理了這篇文章。如果有不妥之處還請(qǐng)建議。
    2016-02-02
  • mybatis-plus支持null字段全量更新的兩種方法

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

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

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

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

最新評(píng)論