SpringBoot3安全管理

来源:https://www.cnblogs.com/cicada-smile/archive/2023/08/14/17627749.html
-Advertisement-
Play Games

SpringSecurity組件可以為服務提供安全管理的能力,比如身份驗證、授權和針對常見攻擊的保護,是保護基於spring應用程式的事實上的標準; ...


目錄

標簽:Security.登錄.許可權;

一、簡介

SpringSecurity組件可以為服務提供安全管理的能力,比如身份驗證、授權和針對常見攻擊的保護,是保護基於spring應用程式的事實上的標準;

在實際開發中,最常用的是登錄驗證和許可權體系兩大功能,在登錄時完成身份的驗證,載入相關信息和角色許可權,在訪問其他系統資源時,進行許可權的驗證,保護系統的安全;

二、工程搭建

1、工程結構

2、依賴管理

starter-security依賴中,實際上是依賴spring-security組件的6.1.1版本,對於該框架的使用,主要是通過自定義配置類進行控制;

<!-- 安全組件 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>${spring-boot.version}</version>
</dependency>

三、配置管理

1、核心配置類

在該類中涉及到的配置非常多,主要是服務的攔截控制,身份認證的處理流程以及過濾器等,很多自定義的處理類通過該配置進行載入;

@EnableWebSecurity
@EnableMethodSecurity
@Configuration
public class SecurityConfig {

    /**
     * 基礎配置
     */
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        // 配置攔截規則
        httpSecurity.authorizeHttpRequests(authorizeHttpRequests->{
            authorizeHttpRequests
                    .requestMatchers(WhiteConfig.whiteList()).permitAll()
                    .anyRequest().authenticated();
        });
        // 禁用預設的登錄和退出
        httpSecurity.formLogin(AbstractHttpConfigurer::disable);
        httpSecurity.logout(AbstractHttpConfigurer::disable);
        httpSecurity.csrf(AbstractHttpConfigurer::disable);

        // 異常時認證處理流程
        httpSecurity.exceptionHandling(exeConfig -> {
            exeConfig.authenticationEntryPoint(authenticationEntryPoint());
        });

        // 添加過濾器
        httpSecurity.addFilterAt(authTokenFilter(),CsrfFilter.class);
        return httpSecurity.build() ;
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationEntryPoint authenticationEntryPoint() {
        return new AuthExeHandler();
    }

    @Bean
    public OncePerRequestFilter authTokenFilter () {
        return new AuthTokenFilter();
    }

    /**
     * 認證管理
     */
    @Bean
    public AuthenticationManager authenticationManager() {
        return new ProviderManager(authenticationProvider()) ;
    }

    /**
     * 自定義用戶認證流
     */
    @Bean
    public AbstractUserDetailsAuthenticationProvider authenticationProvider() {
        return new AuthProvider() ;
    }
}

2、認證數據源

UserDetailsService是載入用戶特定數據的核心介面,編寫用戶服務類並實現該介面,提供用戶信息和許可權體系的數據查詢和載入,作為用戶身份識別的關鍵憑據;

@Service
public class UserService implements UserDetailsService {

    @Resource
    private UserBaseMapper userBaseMapper;
    @Resource
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        UserBase queryUser = geyByUserName(userName);
        if (Objects.isNull(queryUser)){
            throw new AuthException("該用戶不存在");
        }
        List<GrantedAuthority> grantedAuthorityList = new ArrayList<>() ;
        grantedAuthorityList.add(new SimpleGrantedAuthority(queryUser.getUserRole())) ;
        return new User(queryUser.getUserName(),queryUser.getPassWord(),grantedAuthorityList);
    }

    public int register (UserBase userBase){
        if (!Objects.isNull(userBase)){
            userBase.setPassWord(passwordEncoder.encode(userBase.getPassWord()));
            userBase.setCreateTime(new Date()) ;
            return userBaseMapper.insert(userBase) ;
        }
        return 0 ;
    }

    public UserBase getById (Integer id){
        return userBaseMapper.selectById(id) ;
    }

    public UserBase geyByUserName (String userName){
        List<UserBase> userBaseList = new LambdaQueryChainWrapper<>(userBaseMapper)
                .eq(UserBase::getUserName,userName).last("limit 1").list();
        if (userBaseList.size() > 0){
            return userBaseList.get(0) ;
        }
        return null ;
    }
}

3、認證流程

自定義用戶名和密碼的身份令牌認證邏輯,基於用戶名Username從上面的用戶服務類中載入數據並校驗,在驗證成功後將用戶的身份令牌返回給調用者;

@Component
public class AuthProvider extends AbstractUserDetailsAuthenticationProvider {
    private static final Logger log = LoggerFactory.getLogger(AuthProvider.class);
    
    @Resource
    private UserService userService;
    @Resource
    private BCryptPasswordEncoder passwordEncoder;

    @Override
    protected void additionalAuthenticationChecks(
            UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        User user = (User) userDetails;
        String loginPassword = authentication.getCredentials().toString();
        log.info("user:{},loginPassword:{}",user.getPassword(),loginPassword);
        if (!passwordEncoder.matches(loginPassword, user.getPassword())) {
            throw new AuthException("賬號或密碼錯誤");
        }
        authentication.setDetails(user);
    }
    @Override
    protected UserDetails retrieveUser(
            String username, UsernamePasswordAuthenticationToken authentication)
            throws AuthenticationException {
        log.info("username:{}",username);
        return userService.loadUserByUsername(username);
    }
}

4、身份過濾器

通過繼承OncePerRequestFilter抽象類,實現用戶身份的過濾器,如果不是白名單請求,需要驗證令牌是否正確有效,SecurityContextHolder預設狀態下使用ThreadLocal存儲信息;

@Component
public class AuthTokenFilter extends OncePerRequestFilter {
    @Resource
    private AuthTokenService authTokenService ;
    @Resource
    private AuthExeHandler authExeHandler ;

    @Override
    protected void doFilterInternal(@Nonnull HttpServletRequest request,
                                    @Nonnull HttpServletResponse response,
                                    @Nonnull FilterChain filterChain) throws ServletException, IOException {
        String uri = request.getRequestURI();
        if (Arrays.asList(WhiteConfig.whiteList()).contains(uri)){
            // 如果是白名單直接放行
            filterChain.doFilter(request,response);
        } else {
            String token = request.getHeader("Auth-Token");
            if (Objects.isNull(token) || token.isEmpty()){
                // Token不存在,攔截返回
                authExeHandler.commence(request,response,null);
            } else {
                Object object = authTokenService.getToken(token);
                if (!Objects.isNull(object) && object instanceof User user){
                    UsernamePasswordAuthenticationToken authentication =
                            new UsernamePasswordAuthenticationToken(user, null,user.getAuthorities());
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                    filterChain.doFilter(request,response);
                } else {
                    // Token驗證失敗,攔截返回
                    authExeHandler.commence(request,response,null);
                }
            }
        }
    }
}

四、核心功能

1、登錄退出

自定義登錄退出兩個介面,基於用戶名和密碼執行上述的身份認證流程,如果認證成功則返回用戶的身份令牌,在請求「非」白名單介面時需要在請求頭中Auth-Token:token攜帶該令牌,在退出時會清除身份信息;

@Service
public class LoginService {

    private static final Logger log = LoggerFactory.getLogger(LoginService.class);

    @Resource
    private AuthTokenService authTokenService ;
    @Resource
    private AuthenticationManager authenticationManager;

    public String doLogin (UserBase userBase){
        AbstractAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(
                userBase.getUserName().trim(), userBase.getPassWord().trim());
        Authentication authentication = authenticationManager.authenticate(authToken) ;
        User user = (User) authentication.getDetails();
        return authTokenService.createToken(user) ;
    }

    public Boolean doLogout (String authToken){
        SecurityContextHolder.clearContext();
        return authTokenService.deleteToken(authToken) ;
    }
}

@Service
public class AuthTokenService {

    private static final Logger log = LoggerFactory.getLogger(AuthTokenService.class);
    @Resource
    private RedisTemplate<String,Object> redisTemplate ;

    public String createToken (User user){
        String userName = user.getUsername();
        String token = DigestUtils.md5DigestAsHex(userName.getBytes());
        log.info("user-name:{},create-token:{}",userName,token);
        redisTemplate.opsForValue().set(token,user,10, TimeUnit.MINUTES);
        return token ;
    }

    public Object getToken (String token){
        return redisTemplate.opsForValue().get(token);
    }

    public Boolean deleteToken (String token){
        return redisTemplate.delete(token);
    }
}

2、許可權校驗

UserWeb類中提供用戶的註冊介面,在用戶表中創建兩個測試用戶:admin對應ROLE_Admin角色,user對應ROLE_User角色,驗證如下幾個介面的許可權控制;

select介面不需要鑒權,攔截器放行即可訪問;getUser介面校驗ROLE_User角色;getAdmin介面校驗ROLE_Admin角色;query介面校驗兩個角色中的任意一個即可;

兩個不同用戶登錄獲取到各自的身份令牌,使用不同的令牌請求介面,在PreAuthorize驗證通過後才可以正常訪問;

@RestController
public class UserWeb {

    @Resource
    private UserService userService ;

    @PostMapping("/register")
    public String register (@RequestBody UserBase userBase){
        return "register-"+userService.register(userBase) ;
    }

    @GetMapping("/select/{id}")
    public UserBase select (@PathVariable Integer id){
        return userService.getById(id) ;
    }

    @PreAuthorize("hasRole('User')")
    @GetMapping("/user/{id}")
    public UserBase getUser (@PathVariable Integer id){
        return userService.getById(id) ;
    }

    @PreAuthorize("hasRole('Admin')")
    @GetMapping("/admin/{id}")
    public UserBase getAdmin (@PathVariable Integer id){
        return userService.getById(id) ;
    }

    @PreAuthorize("hasAnyRole('User','Admin')")
    @GetMapping("/query/{id}")
    public UserBase query (@PathVariable Integer id){
        return userService.getById(id) ;
    }
}

五、參考源碼

文檔倉庫:
https://gitee.com/cicadasmile/butte-java-note

源碼倉庫:
https://gitee.com/cicadasmile/butte-spring-parent
Gitee主頁: https://gitee.com/cicadasmile/butte-java-note
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • python 進程與線程是併發編程的兩種常見方式。進程是操作系統中的一個基本概念,表示程式在操作系統中的一次執行過程,擁有獨立的地址空間、資源、優先順序等屬性。線程是進程中的一條執行路徑,可以看做是輕量級的進程,與同一個進程中的其他線程共用相同的地址空間和資源。 ...
  • # Linux 之 shell 編程學習筆記(並不完全正確,有誤請指正) ## 概念性知識點 ### 腳本概念 >**腳本(Script),是使用一種特定的描述性語言,依據一定的格式編寫的 可執行文件** ### 運行腳本要求 >**腳本須有 ==可執行== 許可權,即 ==x== 許可權** > >* ...
  • emm,又又遇到問題啦,現有業務系統應用上線存在視窗期,不能滿足正常任務迭代上線。在非視窗期上線容易導致資料庫、mq、jsf等線程中斷,進而導致需要手動修單問題。故而通過添加優雅停機功能進行優化,令其在上線前選擇優雅停機後,會優先斷掉新流量的涌入,並預留一定時間處理現存連接,最後完全下線,可有效擴大... ...
  • Lua程式設計第四版第一部分語言基礎自做練習題答案,帶:star:為重點。 ## 1.1 > 運行階乘的示例並觀察,如果輸入負數,程式會出現什麼問題?試著修改代碼來解決問題 輸入負數,程式會死迴圈,修改如下 ```lua -- 定義一個計算階乘的函數 function fact(n) if n 分別 ...
  • 日期處理相關內容之前`pandas基礎`系列中有一篇專門介紹過,本篇補充兩個常用的技巧。 # 1. 多列合併為日期 當收集來的數據中,年月日等信息分散在多個列時,往往需要先合併成日期類型,然後才能做分析處理。合併多列轉換為日期類型,可以直接用 `to_datetime`函數來處理: ```pytho ...
  • 自 2014 年發佈以來, JDK 8 一直都是相當熱門的 JDK 版本。其原因就是對底層數據結構、JVM 性能以及開發體驗做了重大升級,得到了開發人員的認可。但距離 JDK 8 發佈已經過去了 9 年,那麼這 9 年的時間,JDK 做了哪些升級?是否有新的重大特性值得我們嘗試?能否解決一些我們現在... ...
  • 隨著需求不斷迭代,業務系統的業務代碼突飛猛進,在你自豪於自己的代碼量產出很高時,有沒有回頭看看線上真正的客戶使用量又有多少呢? ...
  • ![](https://img2023.cnblogs.com/other/1218593/202308/1218593-20230814093834285-1226325272.png) Chat2DB 是一款有開源免費的多資料庫客戶端工具,支持windows、mac本地安裝,也支持伺服器端部署, ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...