spring security 在controller層 方法級別使用注解 @PreAuthorize("hasRole('ROLE_xxx')")設置權限攔截 ,無權限則返回403


1.前言

以前學習的時候使用權限的攔截,一般都是對路徑進行攔截 ,要么用攔截器設置攔截信息,要么是在配置文件內設置攔截信息,

spring security 支持使用注解的形式 ,寫在方法和接口上攔截 ,

分別支持 三種  :

@PreAuthorize("hasRole('ROLE_xxx')" )   

@PostAuthorize("returnObject.type == authentication.name")  

@Secured({ "ROLE_DBA", "ROLE_ADMIN" })

 

常用的是 @PreAuthorize  和  @Secured  ,但是推薦使用  @PreAuthorize 因為支持Spring EL表達式 。

@PreAuthorize如果有多個權限設置,則使用如下:

  //必須有全部的權限才可以訪問
    @PreAuthorize("hasRole('ROLE_admin') and hasAnyRole('ROLE_user')")
    //至少有一個即可訪問
    @PreAuthorize("hasRole('ROLE_admin') or hasAnyRole('ROLE_user')")

 

 

重點 ,超級重點 !!!!坑了我好長時間:

使用 @PreAuthorize("hasRole('ROLE_admin'))

權限名字必須帶前綴 ROLE_ 后面的隨便寫 比如 ROLE_admin , ROLE_user , ROLE_kk22 , ROLE_love 否則無法正確識別權限
因為hasRole 就是定義角色的,轉成權限會自動添加前綴ROLE_ ,因此,不怎么使用。
如果使用
@PreAuthorize("hasAuthority('admin')") 則不需要帶前綴,是什么就是什么,不需要添加前綴,
因為 hasAuthority 就是定義權限的,寫入名字即可。
 

 

經過測試:
 
    路徑攔截權限的名稱必須與權限列表注冊的一樣,經過測試,使用hasRole的注解, 方法級別的注解權限需要
ROLE_前綴 ,因此,路徑攔截權限的名稱、注解權限名稱、數據庫存儲的權限名稱都要加
ROLE_前綴最好,避免出現錯誤, 如果數據庫的權限名稱不加ROLE_前綴,那么在注冊權
限列表的時候記得拼接ROLE_前綴。
如果不想麻煩,可以使用
hasAuthority ,不需要添加前綴,
不論是那種方法,注解的限制效果和在security配置文件設置的效果是一樣的。
 

 

2.操作

(1)准備一個建好的spring boot + spring security 工程 【具體操作這里不解釋 ,我的其他隨筆有具體記載】

目錄結構

 

 

 

在security 配置類 開啟 權限注解功能

@EnableGlobalMethodSecurity(prePostEnabled = true)

 

 

 

 注解 @EnableGlobalMethodSecurity 有3個參數,【默認是false  ,需要開啟時設為true】

prePostEnabled :確定 Spring Security 前置注釋 [@PreAuthorize,@PostAuthorize,..] 是否應該啟用;
 secureEnabled : 確定 Spring Security 安全注釋 [@Secured] 是否應該啟用;
 jsr250Enabled : 確定 JSR-250注釋 [@RolesAllowed..] 是否應該啟用;

 

完整的配置類

package com.example.security5500.securityConfig;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Component;

//這個加不加無所謂
@Configuration
//開啟security自定義配置
@EnableWebSecurity
//開啟 Controller層的訪問方法權限,與注解@PreAuthorize("hasRole('ROLE_admin')")配合,會攔截注解了@PreAuthrize注解的配置
// 想要@PreAuthorize正確執行 ,權限關鍵字必須帶前綴 ROLE_  ,后面的部分可以隨便寫!!!!靠,琢磨了4小時了 ,終於找到原因了
@EnableGlobalMethodSecurity(prePostEnabled = true)
//, securedEnabled = true
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    //實例自定義登錄校驗接口 【內部有 數據庫查詢】
    @Autowired
    private DbUserDetailsService dbUserDetailsService;

    //忽略攔截的靜態文件路徑
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers(
                        "/js/**",
                        "/css/**",
                        "/img/**",
                        "/webjars/**");
    }

    //攔截規則設置
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //允許基於使用HttpServletRequest限制訪問
                //即授權請求設置
                .authorizeRequests()
                //設置不攔截頁面,可直接通過,路徑訪問 "/", "/index",  則不攔截,
                .antMatchers("/", "/index", "/hhk/**")
                //是允許所有的意思
                .permitAll()
//                //訪問 /hai 需要admin權限 ,無權限則提示 403
//                .antMatchers("/hai").hasAuthority("admin")
//                //訪問 /kk 需要admin或user權限 ,無權限則提示 403
//                .antMatchers("/kk").hasAnyAuthority("admin", "user")
//                //路徑/admin/**所有的請求都需要admin權限 ,無權限則提示 403
//                .antMatchers("/admin/**").hasAuthority("admin")
                //其他頁面都要攔截,【需要在最后設置這個】
                .anyRequest().authenticated()
                .and()
                //設置自定義登錄頁面
                //即開啟登錄設置
                .formLogin()
                //指定自定義登錄頁面的訪問虛擬路徑
                .loginPage("/login")
                .permitAll()
                .and()
//        添加退出登錄支持。當使用WebSecurityConfigurerAdapter時,這將自動應用。默認情況是,訪問URL”/ logout”,使HTTP Session無效
//        來清除用戶,清除已配置的任何#rememberMe()身份驗證,清除SecurityContextHolder,然后重定向到”/login?success”
                //即開啟登出設置
                .logout()
//                //指定的登出操作的虛擬路徑,需要以post方式請求這個 http://localhost:5500/mylogout 才可以登出 ,也可以直接清除用戶認證信息達到登出目的
//                .logoutUrl("/mylogout")
                //使httpsession失效
                .invalidateHttpSession(true)
                //清除認證信息
                .clearAuthentication(true)
                //登出請求匹配器,新建一個螞蟻路徑請求匹配器 ,與 .logoutUrl("/mylogout")效果一樣
                .logoutRequestMatcher(new AntPathRequestMatcher("/mylogout"))
                //登出成功后訪問的地址
                .logoutSuccessUrl("/home")
                .permitAll()
                .and()
                //開啟記住我設置,用於自動登錄
                .rememberMe()
                //密鑰
                .key("unique-and-secret")
                //存在cookie的用戶名[用於cookie名]
                .rememberMeCookieName("remember-me-cookie-name")
                //生命周期,單位毫秒
                .tokenValiditySeconds(24 * 60 * 60);
        //登陸后"選擇記住我" ,會生成cookie  ,登出則會自動刪除該cookie  , 只要不登出且未超出生命周期 ,那么關閉瀏覽器后再次訪問將自動登錄
        //  [name]                          [value]                                                     [domain]   [path]      [expires/max-age]     [size]   [httponly]   [priority]
        //remember-me-cookie-name    eGk6MTU5MTIwODAzNDk5MTozZWUyN2FlMmEwMWQxNDczMDhhY2ZkYTAxZWQ5ZWQ5YQ    localhost    /       2020-06-03T18:13:54.992Z    89       ✓            Medium


    }


    /**
     * 添加 UserDetailsService, 實現自定義登錄校驗,數據庫查詢
     */
    @Override
    protected void configure(AuthenticationManagerBuilder builder) throws Exception {
        //注入用戶信息,每次登錄都會來這查詢一次信息,因此不建議每次都向mysql查詢,應該使用redis
        //密碼加密
        builder.userDetailsService(dbUserDetailsService);
//                .passwordEncoder(passwordEncoder());
    }

    /**
     * BCryptPasswordEncoder相關知識:
     * 用戶表的密碼通常使用MD5等不可逆算法加密后存儲,為防止彩虹表破解更會先使用一個特定的字符串(如域名)加密,然后再使用一個隨機的salt(鹽值)加密。
     * 特定字符串是程序代碼中固定的,salt是每個密碼單獨隨機,一般給用戶表加一個字段單獨存儲,比較麻煩。
     * BCrypt算法將salt隨機並混入最終加密后的密碼,驗證時也無需單獨提供之前的salt,從而無需單獨處理salt問題。
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


//    /**
//     * 選擇加密方式 ,密碼不加密的時候選擇 NoOpPasswordEncoder,不可缺少,否則報錯
//     * java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"
//     */
//    @Bean
//    public static PasswordEncoder passwordEncoder() {
//        return NoOpPasswordEncoder.getInstance();
//    }
    
}
View Code

 

 

(2)在需要權限攔截的方法上加入注解 @PreAuthorize("hasRole('ROLE_xxx')"  )   

有多個權限則使用

@PreAuthorize("hasRole('ROLE_xxx')  AND  hasRole('ROLE_xxx2')")   

[必須具備兩個權限才可以]

@PreAuthorize("hasRole('ROLE_xxx')  or  hasRole('ROLE_xxx2')")   

【只要有其中一個權限即可】

 

 

 

@PreAuthorize("hasRole('ROLE_love1')")

意思是需要權限 ROLE_love1才可以訪問這個方法 ,

 

 

 

(3)那么數據庫中的權限字符串該怎么寫?

 

 圖中是我測試的權限名稱, 以 符號 ,來分隔多個權限  ,經過測試 ,只有ROLE_前綴的權限才可以被注解識別 【原因不清楚,找不到相關資料】

因此,不論是注解還是數據庫存儲的值,都需要加 ROLE_前綴 ,

//

當然 ,數據庫中也可以不加,可以在注冊權限到內存的時候再加 ,

但是容易遺忘等原因出錯,因此不建議使用,如下圖寫法:

 

 

 

 

3.測試

(1)啟動工程 ,端口5500

訪問網址 http://localhost:5500/home

被攔截需要登錄

使用一個 無 ROLE_love1 權限 的賬戶登錄

username = xi

password = 11

 

 點擊登錄顯示403

 

控制台打印權限

 

 

 (2)

換一個有ROLE_love1 權限 的賬戶登錄

username = cen

password = 11

【因為我在配置類配置類登錄頁面路徑

 

 因此想要重新登錄,需要訪問 網址 http://localhost:5500/login  返回到登錄頁面  ,

注意 ,訪問登錄頁面不會自動登出,但是 進入登錄頁面后,提交以此表單登錄申請 后將自動登出 原本已經登錄的賬戶 ,即便登錄失敗 ,照樣會登出

 

 

  點擊登錄,成功進入頁面

 

 控制台打印權限

 

 

------------------------------

 

參考博文原址 : https://blog.csdn.net/HiBoyljw/article/details/84032839

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM