基于JWT+SpringSecurity整合一個單點認證授權機制

基于 JWT + Spring Security 的授權認證機制,在整體架構設計上體現了高度的安全性與靈活性。其在整合框架中的應用,充分展示了模塊化、可擴展性和高效鑒權的設計理念,為開發者提供了一種值得借鑒的安全架構模式。

1.SpringSecurity概念理解

1.1 一般的Web應用需要進行認證和授權。

認證:驗證當前訪問系統的是不是本系統的用戶,并且要確認具體是哪個用戶。

授權:經過認證后判斷當前用戶是否有權限進行某些操作

1.2 RBAC權限模型的理解

可以理解為用戶對應角色,角色又對應權限,在不考慮加入部門等參數的干預下,整個RBAC模型可以簡化為5張表來描述:

1.3 SpringSecurity機制:

整個SpringSecurity機制可以理解為是一個過濾器鏈機制,前端發請求給后端,請求會經過整個過濾器鏈的認證和授權邏輯才能執行后續正常的系統接口邏輯。下方三個為核心過濾器

第一個負責處理在登錄頁面填寫用戶名和密碼后登錄請求

第二個負責處理過濾器鏈中拋出的授權和認證異常

第三個是負責權限校驗的過濾器

認證點認證流程:

2.認證架構設計

2.1 登錄的整體流程設計:

從login()邏輯開始執行

2.1 SpringSecurity的配置類

登錄接口實現:

 /*** 登錄方法* @param loginBody 登錄信息* @return 結果*/
@Operation(summary = "登錄方法")
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginBody loginBody) {AjaxResult ajax = AjaxResult.success();// 生成令牌String token = loginService.login(loginBody.getUsername(),loginBody.getPassword(),loginBody.getCode(),loginBody.getUuid());ajax.put(Constants.TOKEN, token);return ajax;
}

/*** 登錄驗證* @param username 用戶名* @param password 密碼* @param code 驗證碼* @param uuid 唯一標識* @return 結果*/
public String login(String username, String password, String code, String uuid)
{// 驗證碼校驗validateCaptcha(username, code, uuid);// 登錄前置校驗loginPreCheck(username, password);String ip = IpUtils.getIpAddr();// 驗證 IP 是否被封鎖passwordService.validateIp(ip);// 用戶驗證Authentication authentication = null;try{UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);AuthenticationContextHolder.setContext(authenticationToken);// 該方法會去調用UserDetailsServiceImpl.loadUserByUsernameauthentication = authenticationManager.authenticate(authenticationToken);}catch (Exception e){if (e instanceof BadCredentialsException){AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));throw new UserPasswordNotMatchException();}else{passwordService.incrementIpFailCount(ip);AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));throw new ServiceException(e.getMessage());}}finally{AuthenticationContextHolder.clearContext();}AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));LoginUser loginUser = (LoginUser) authentication.getPrincipal();recordLoginInfo(loginUser.getUserId());// 生成tokenreturn tokenService.createToken(loginUser);
}

2.2 SpringSecurity配置類

/*** spring security配置** @author Dftre*/
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Configuration
public class SecurityConfig {/*** 自定義用戶認證邏輯*/@Autowiredprivate UserDetailsService userDetailsService;/*** 認證失敗處理類*/@Autowiredprivate AuthenticationEntryPointImpl unauthorizedHandler;/*** 退出處理類*/@Autowiredprivate LogoutSuccessHandlerImpl logoutSuccessHandler;/*** token認證過濾器*/@Autowiredprivate JwtAuthenticationTokenFilter authenticationTokenFilter;/*** 跨域過濾器*/@Autowiredprivate CorsFilter corsFilter;/*** 允許匿名訪問的地址*/@Autowiredprivate PermitAllUrlProperties permitAllUrl;/*** @return* @throws Exception*/@BeanAuthenticationManager authenticationManager() {DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider(userDetailsService);daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());return new ProviderManager(daoAuthenticationProvider);}/*** anyRequest | 匹配所有請求路徑* access | SpringEl表達式結果為true時可以訪問* anonymous | 匿名可以訪問* denyAll | 用戶不能訪問* fullyAuthenticated | 用戶完全認證可以訪問(非remember-me下自動登錄)* hasAnyAuthority | 如果有參數,參數表示權限,則其中任何一個權限可以訪問* hasAnyRole | 如果有參數,參數表示角色,則其中任何一個角色可以訪問* hasAuthority | 如果有參數,參數表示權限,則其權限可以訪問* hasIpAddress | 如果有參數,參數表示IP地址,如果用戶IP和參數匹配,則可以訪問* hasRole | 如果有參數,參數表示角色,則其角色可以訪問* permitAll | 用戶可以任意訪問* rememberMe | 允許通過remember-me登錄的用戶訪問* authenticated | 用戶登錄后可訪問*/@BeanSecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {return httpSecurity// CSRF禁用,因為不使用session.csrf(csrf -> csrf.disable())// 禁用HTTP響應標頭.headers((headersCustomizer) -> {headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());})// 認證失敗處理類.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))// 基于token,所以不需要session.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))// 注解標記允許匿名訪問的url.authorizeHttpRequests((requests) -> {permitAllUrl.getUrls().forEach(url -> requests.requestMatchers(url).permitAll());// 對于登錄login 注冊register 驗證碼captchaImage 允許匿名訪問requests.requestMatchers("/login", "/register", "/captchaImage").permitAll()// 靜態資源,可匿名訪問.requestMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js","/profile/**").permitAll().requestMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs","/druid/**", "/*/api-docs/**").permitAll().requestMatchers("/websocket/**").permitAll()// 除上面外的所有請求全部需要鑒權認證.anyRequest().authenticated();})// 添加Logout filter.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))// 添加JWT filter.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)// 添加CORS filter.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class).addFilterBefore(corsFilter, LogoutFilter.class).build();}/*** 強散列哈希加密實現*/@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder() {return new BCryptPasswordEncoder();}
}

2.3?UserDetailsServiceImpl 重寫 UserDetailsService接口的loadUserbyUsername方法

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {SysUser user = userService.selectUserByUserName(username);if (StringUtils.isNull(user)) {log.info("登錄用戶:{} 不存在.", username);throw new ServiceException("登錄用戶:" + username + " 不存在");} else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {log.info("登錄用戶:{} 已被刪除.", username);throw new ServiceException("對不起,您的賬號:" + username + " 已被刪除");} else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {log.info("登錄用戶:{} 已被停用.", username);throw new ServiceException("對不起,您的賬號:" + username + " 已停用");}passwordService.validate(user);return createLoginUser(user);
}public UserDetails createLoginUser(SysUser user) {return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
}

2.4 保存LoginUser到Redis的邏輯在創建token的方法邏輯中實現

2.5?攔截器配置

/*** token過濾器 驗證token有效性* * @author ruoyi*/
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
{@Autowiredprivate TokenService tokenService;@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)throws ServletException, IOException{LoginUser loginUser = tokenService.getLoginUser(request);if (StringUtils.isNotNull(loginUser) && StringUtils.isNull(SecurityUtils.getAuthentication())){tokenService.verifyToken(loginUser);UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authenticationToken);}chain.doFilter(request, response);}
}

3.授權架構設計

權限的驗證最核心的是使用的Spring Security的提供的權限注解`@PreAuthorize `

當 @PreAuthorize 注解被應用于某個方法時,Spring Security 在該方法執行前會先對當前認證的用戶進行權限檢查。如果檢查通過,方法調用得以繼續;否則,框架會拋出相應的權限異常(如 AccessDeniedException),阻止方法執行。

  • @ss?引用了名為 "ss" 的 Spring Bean,即我們的?PermissionService
  • hasPermi('manage:order:list')?調用了?PermissionService?的?hasPermi?方法,檢查用戶是否擁有 "manage:order:list" 權限

授權相關校驗操作實現:

@Service("ss")
public class PermissionService implements IPermissionService {/** 所有權限標識 */private static final String ALL_PERMISSION = "*:*:*";/** 管理員角色權限標識 */private static final String SUPER_ADMIN = "admin";private static final String ROLE_DELIMETER = ",";private static final String PERMISSION_DELIMETER = ",";/*** 驗證用戶是否具備某權限* * @param permission 權限字符串* @return 用戶是否具備某權限*/public boolean hasPermi(String permission) {if (StringUtils.isEmpty(permission)) {return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {return false;}PermissionContextHolder.setContext(permission);return hasPermissions(loginUser.getPermissions(), permission);}/*** 驗證用戶是否具有以下任意一個權限** @param permissions 以 PERMISSION_NAMES_DELIMETER 為分隔符的權限列表* @return 用戶是否具有以下任意一個權限*/public boolean hasAnyPermi(String permissions) {if (StringUtils.isEmpty(permissions)) {return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getPermissions())) {return false;}PermissionContextHolder.setContext(permissions);Set<String> authorities = loginUser.getPermissions();for (String permission : permissions.split(PERMISSION_DELIMETER)) {if (permission != null && hasPermissions(authorities, permission)) {return true;}}return false;}/*** 判斷用戶是否擁有某個角色* * @param role 角色字符串* @return 用戶是否具備某角色*/public boolean hasRole(String role) {if (StringUtils.isEmpty(role)) {return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {return false;}for (SysRole sysRole : loginUser.getUser().getRoles()) {String roleKey = sysRole.getRoleKey();if (SUPER_ADMIN.equals(roleKey) || roleKey.equals(StringUtils.trim(role))) {return true;}}return false;}/*** 驗證用戶是否具有以下任意一個角色** @param roles 以 ROLE_NAMES_DELIMETER 為分隔符的角色列表* @return 用戶是否具有以下任意一個角色*/public boolean hasAnyRoles(String roles) {if (StringUtils.isEmpty(roles)) {return false;}LoginUser loginUser = SecurityUtils.getLoginUser();if (StringUtils.isNull(loginUser) || CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {return false;}for (String role : roles.split(ROLE_DELIMETER)) {if (hasRole(role)) {return true;}}return false;}/*** 判斷是否包含權限* * @param permissions 權限列表* @param permission  權限字符串* @return 用戶是否具備某權限*/private boolean hasPermissions(Set<String> permissions, String permission) {return permissions.contains(ALL_PERMISSION) || permissions.contains(StringUtils.trim(permission));}
}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/86032.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/86032.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/86032.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

HarmonyOS運動開發:如何用mpchart繪制運動配速圖表

##鴻蒙核心技術##運動開發##Sensor Service Kit&#xff08;傳感器服務&#xff09;# 前言 在運動類應用中&#xff0c;運動數據的可視化是提升用戶體驗的重要環節。通過直觀的圖表展示運動過程中的關鍵數據&#xff0c;如配速、距離、卡路里消耗等&#xff0c;用戶可以更清晰…

Git 切換到舊提交,同時保證當前修改不丟失

在 Git 中&#xff0c;可以通過以下幾種方式切換到之前的提交&#xff0c;同時保留當前的修改 1. 使用 git checkout 創建臨時分離頭指針&#xff08;推薦用于查看代碼&#xff09; git checkout <commit-hash>這會讓你進入"分離頭指針"狀態&#xff0c;你可…

東芝Toshiba DP-4528AG打印機信息

東芝 Toshiba DP 4528AG 是一款黑白激光數碼復合機&#xff1a; 類型&#xff1a;激光數碼復合機&#xff0c;涵蓋復印、打印、掃描、傳真功能&#xff0c;能滿足辦公室多樣化的文檔處理需求。速度類型&#xff1a;中速&#xff0c;黑白復印和打印速度可達 45 頁 / 分鐘&#…

Qt生成日志與以及捕獲崩潰文件(mingw64位,winDbg)————附帶詳細解說

文章目錄 Qt生成日志與以及報錯文件(mingw64位&#xff0c;winDbg)0 背景與結果0.1 背景0.2 結果1 WinDbg1.1 安裝1.2 使用 2 編寫代碼2.1 ccrashstack類2.2 編寫輸出捕獲異常的dmp文件2.2 編寫輸出日志文件2.3 調用生成日志和dmp文件 參考 Qt生成日志與以及報錯文件(mingw64位…

Nginx + Tomcat負載均衡群集

目錄 一、案例環境 二、部署 Tomcat&#xff08;102/103&#xff09; 1、準備環境 &#xff08;1&#xff09;關閉firewalld 防火墻 &#xff08;2&#xff09;安裝JDK 2、安裝配置 Tomcat &#xff08;1&#xff09;Tomcat 的安裝和配置 &#xff08;2&#xff09;移動…

三、元器件的選型

前言&#xff1a;我們確立了題目的功能后&#xff0c;就可以開始元器件的選型&#xff0c;元器件的選型關乎到我們后面代碼編寫的一個難易。 一、主控的選擇 主控的選擇很大程度上決定我們后續使用的代碼編譯器&#xff0c;比如ESP32使用的是VScode&#xff0c;或者Arduino&a…

API是什么意思?如何實現開放API?

目錄 一、API 是什么 &#xff08;一&#xff09;API 的定義 &#xff08;二&#xff09;API 的作用 二、API 的類型 &#xff08;一&#xff09;Web API 1. RESTful API 2. SOAP API &#xff08;二&#xff09;操作系統 API &#xff08;三&#xff09;數據庫 API …

AI生成的基于html+marked.js實現的Markdown轉html工具,離線使用,可實時預覽 [

有一個markdown格式的文檔&#xff0c;手頭只有notepad的MarkdownPanel插件可以預覽&#xff0c;但是只能預覽&#xff0c;不能直接轉換為html文件下載&#xff0c;直接復制預覽的內效果又不太好&#xff0c;度娘也能找到很多工具&#xff0c;但是都需要在線使用。所以考慮用AI…

Java-前置基礎

前言 基礎基礎 package org.example;public class Main {int a 10;String s1 "你好";public static void main(String[] args) {System.out.println(a);System.out.println(s1);} } 發現報錯位置 public class Main {static int a 10;static String s1 "你好…

python字符串方法

1. capitalize&#xff1a; 是第一個字符大寫&#xff0c;其余小寫 2. encode&#xff1a; 將字符串轉換為字節串&#xff08;bytes&#xff09;&#xff0c;默認使用 UTF-8 編碼。 3. format&#xff1a; format是 Python 中字符串對象的內置方法&#xff0c;語法為S.form…

Java詳解LeetCode 熱題 100(24):LeetCode 234. 回文鏈表(Palindrome Linked List)詳解

文章目錄 1. 題目描述1.1 鏈表節點定義 2. 理解題目2.1 回文鏈表的特征2.2 核心難點 3. 解法一&#xff1a;轉換為數組法3.1 算法思路3.2 詳細圖解3.3 Java代碼實現3.4 詳細執行過程演示3.5 執行結果示例3.6 使用數組而非ArrayList的優化版本3.7 復雜度分析3.8 優缺點分析 4. 解…

平板電腦如何通過EN 18031認證

平板電腦若需通過 EN 18031 認證&#xff08;歐盟無線電設備網絡安全標準&#xff0c;屬于 CE RED 指令的一部分&#xff09;&#xff0c;需滿足其針對互聯網連接設備和數據處理設備的安全要求。以下是詳細的認證流程、技術要求和操作指南&#xff1a; 一、認證背景與法規基礎…

KaiwuDB在邊緣計算領域的應用與優勢

KaiwuDB 在邊緣計算場景中主要應用于 工業物聯網&#xff08;IIoT&#xff09;、智能電網、車聯網 等領域&#xff0c;通過其分布式多模架構和輕量化設計&#xff0c;在邊緣側承擔 數據實時處理、本地存儲與協同分析 的核心作用。以下是具體案例和功能解析&#xff1a; 1. 典型…

MP4文件聲音與視頻分離

最近學習PR剪輯 要添加視頻文件和音頻文件 但是直接給MP4文件 得到的是一個整體 不管怎么切分 都是無法得到單獨的整體 這就需要將視頻文件和音頻文件分離 我推薦使用ffmpeg工具進行分離 夸克鏈接&#xff1a;https://pan.quark.cn/s/8dbc3bfbc5d4 百度鏈接: https://pan.ba…

山洪徑流過程及洪水淹沒數值模擬

氣候變化背景下&#xff0c;極端天氣導致的洪水事件將更加頻發。快速城市化對流域下墊面的改變&#xff0c;及人類活動向洪泛區的擴張。二者共同使得全世界多數人類活動高度聚集區的洪水風險增加。洪水淹沒危險性&#xff08;各種年遇型洪水淹沒&#xff09;是洪水損失評估、風…

Rust 通用代碼生成器:蓮花,紅蓮嘗鮮版三十六,圖片初始化功能介紹

Rust 通用代碼生成器&#xff1a;蓮花&#xff0c;紅蓮嘗鮮版三十六&#xff0c;圖片初始化功能介紹 Rust 通用代碼生成器蓮花&#xff0c;紅蓮嘗鮮版三十六。支持全線支持圖片預覽&#xff0c;可以直接輸出帶圖片的啞數據模式快速原型。啞數據模式和枚舉支持圖片。啞數據和枚…

Mysql中select查詢語句的執行過程

目錄 1、介紹 1.1、組件介紹 1.2、Sql執行順序 2、執行流程 2.1. 連接與認證 2.2. 查詢緩存 2.3. 語法解析&#xff08;Parser&#xff09; 2.4、執行sql 1. 預處理&#xff08;Preprocessor&#xff09; 2. 查詢優化器&#xff08;Optimizer&#xff09; 3. 執行器…

Acrobat DC v25.001 最新專業版已破,像word一樣編輯PDF!

在數字化時代&#xff0c;PDF文件以其穩定性和通用性成為了文檔交流和存儲的熱門選擇。無論是閱讀、編輯、轉換還是轉曲&#xff0c;大家對PDF文件的操作需求日益增加。因此&#xff0c;一款出色的PDF處理軟件不僅要滿足多樣化的需求&#xff0c;還要通過簡潔的界面和強大的功能…

CSS中justify-content: space-between首尾貼邊中間等距(兩端元素緊貼左右邊緣,中間元素等距均勻分布)

justify-content: space-between; 是 CSS Flexbox 布局中的一個屬性值&#xff0c;主要作用是在彈性容器的主軸方向上均勻分布子元素&#xff0c;具有以下核心特性&#xff1a; 作用效果&#xff1a; 首尾貼邊 第一個子元素緊貼容器起始端 最后一個子元素緊貼容器結束端 中…

Web 架構之 CDN 加速原理與落地實踐

文章目錄 一、思維導圖二、正文內容&#xff08;一&#xff09;CDN 基礎概念1. 定義2. 組成部分 &#xff08;二&#xff09;CDN 加速原理1. 請求路由2. 內容緩存3. 內容更新 &#xff08;三&#xff09;CDN 落地實踐1. 選擇 CDN 服務商2. 配置 CDN3. 集成到 Web 架構 &#xf…