日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > windows >内容正文

windows

RuoYi后台系统权限管理解析

發布時間:2025/3/21 windows 89 豆豆
生活随笔 收集整理的這篇文章主要介紹了 RuoYi后台系统权限管理解析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、前言
最近在學習spring security,自己也了些小的demo。也看了幾個優秀的后臺管理的開源項目。今天聊一下若依系統的權限管理的詳細流程。

二、權限管理模型
若依使用的也是當前最流行的RBAC模型。如果不了解RBAC的小伙伴可以去網上查一下,其實很好理解。若依這里大致可以認為是實現了RBAC0。簡單來說,就是用戶不直接擁有權限,而是添加角色作為中轉,將權限賦予角色。然后再將角色賦予用戶。權限可以是菜單權限或者是按鈕權限等。

三、主要技術棧
1、后端
Springboot,SpringSecurity,JWT,Redis,Mybatis

2、前端
vue,vuex,router

四、數據庫設計
與權限相關的表主要有三張,sys_user,sys_role,sys_menu。次要的還有兩張關聯表sys_user_role,sys_role_menu。下面依次看一下主要的表。

1、User表

上面就是user表的基本結構,保存了一些用戶的基本資料,以及用戶狀態標志位。沒什么特別要說的地方。

2、Role表

上面是role表的基本結構,定義了角色的名稱以及角色的標識字符串。還包括了其他模塊的一些數據,比如數據權限的標識,這里不做討論。

3、Menu表

這張表要特別說一下。
首先是動態菜單的實現。表里包括了前端生成動態路由router的數據。
其次就是perms字段。這個字段將權限管理的粒度細化到了按鈕,也就是你可能可以進入某個頁面。但是無法使用這個頁面里的所有功能。菜單部分的權限是在渲染頁面時就確定了,如果你沒有某個菜單或目錄的所有權限,那你的頁面則不會出現這些目錄。

五、基本流程
我們觀察一下點擊登錄之后,前端一共發送了三個請求。

依次看一下這些請求都做了什么。

1、login


前后端分離的系統交互一般都是無狀態登錄,這里使用的是jwt實現。登錄后續的所有請求都會借助token進行權限驗證。

2、getInfo

登錄成功后,需要獲取一些公用狀態,比如用戶名稱,用戶頭像信息等。這些狀態都被保存在vuex中管理。其實這里不是很嚴謹,這里忽略了路由守衛的部分,但是感知不強,會在下面詳細說一下。

3、getRouter

到這里就進行到首頁渲染的最后一步,獲取路由信息。
其實圖中的過程不是很嚴謹,但是這樣稍微更好理解一些。
這一步的工作主要由前端來完成,登錄完成后,會跳轉至首頁。在首頁渲染之前,路由守衛會做一些操作,這一部分我在另一篇文章里有詳細描述。戳這里在這些操作里就包括上面的接口請求,以及這里的路由信息的請求。在獲取到路由信息后,將信息轉化為router對象,再動態掛載路由。然后在左邊欄的頁面部分,遍歷router對象生成邊欄。

六、具體實現(部分)
這里會貼一些我認為比較重要的代碼進行說明,更多具體的限于篇幅也不搞太多。

1、SpringSecurity
這里就直接看配置類了

@Override
? ? protected void configure(HttpSecurity httpSecurity) throws Exception
? ? {
? ? ? ? httpSecurity
? ? ? ? ? ? ? ? // CSRF禁用,因為不使用session
? ? ? ? ? ? ? ? .csrf().disable()
? ? ? ? ? ? ? ? // 認證失敗處理類
? ? ? ? ? ? ? ? .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
? ? ? ? ? ? ? ? // 基于token,所以不需要session
? ? ? ? ? ? ? ? .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
? ? ? ? ? ? ? ? // 過濾請求
? ? ? ? ? ? ? ? .authorizeRequests()
? ? ? ? ? ? ? ? // 對于登錄login 驗證碼captchaImage 允許匿名訪問
? ? ? ? ? ? ? ? .antMatchers("/login", "/captchaImage").anonymous()
? ? ? ? ? ? ? ? .antMatchers(
? ? ? ? ? ? ? ? ? ? ? ? HttpMethod.GET,
? ? ? ? ? ? ? ? ? ? ? ? "/*.html",
? ? ? ? ? ? ? ? ? ? ? ? "/**/*.html",
? ? ? ? ? ? ? ? ? ? ? ? "/**/*.css",
? ? ? ? ? ? ? ? ? ? ? ? "/**/*.js"
? ? ? ? ? ? ? ? ).permitAll()
? ? ? ? ? ? ? ? .antMatchers("/profile/**").anonymous()
? ? ? ? ? ? ? ? .antMatchers("/common/download**").anonymous()
? ? ? ? ? ? ? ? .antMatchers("/common/download/resource**").anonymous()
? ? ? ? ? ? ? ? .antMatchers("/swagger-ui.html").anonymous()
? ? ? ? ? ? ? ? .antMatchers("/swagger-resources/**").anonymous()
? ? ? ? ? ? ? ? .antMatchers("/webjars/**").anonymous()
? ? ? ? ? ? ? ? .antMatchers("/*/api-docs").anonymous()
? ? ? ? ? ? ? ? .antMatchers("/druid/**").anonymous()
? ? ? ? ? ? ? ? // 除上面外的所有請求全部需要鑒權認證
? ? ? ? ? ? ? ? .anyRequest().authenticated()
? ? ? ? ? ? ? ? .and()
? ? ? ? ? ? ? ? .headers().frameOptions().disable();
? ? ? ? httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
? ? ? ? // 添加JWT filter
? ? ? ? httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
? ? ? ? // 添加CORS filter
? ? ? ? httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
? ? ? ? httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
作者已經給配置類做了一些注解,這里比較重要的是添加了jwt過濾器,而且是所有的請求都會被這個過濾器攔截,包括"/login", "/captchaImage"。除此之外,配置類里并沒有聲明登錄接口。那么肯定在某個地方加入SpringSecurity的過濾鏈。
其實從Controller層順藤摸瓜,很快就能看到這個方法。這個方法驗證了用戶是否合法,并且將用戶信息保存進了Redis

public String login(String username, String password, String code, String uuid)
? ? {
? ? ? ? // 通過UUID,還原登錄前的秘鑰
? ? ? ? String verifyKey = Constants.CAPTCHA_CODE_KEY + uuid;
? ? ? ? // 通過秘鑰查詢Redis中存儲的驗證信息
? ? ? ? String captcha = redisCache.getCacheObject(verifyKey);
? ? ? ? // 刪除驗證信息
? ? ? ? redisCache.deleteObject(verifyKey);
? ? ? ? if (captcha == null)
? ? ? ? {
? ? ? ? ? ? AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
? ? ? ? ? ? throw new CaptchaExpireException();
? ? ? ? }
? ? ? ? if (!code.equalsIgnoreCase(captcha))
? ? ? ? {
? ? ? ? ? ? AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
? ? ? ? ? ? throw new CaptchaException();
? ? ? ? }
? ? ? ? // 用戶驗證
? ? ? ? Authentication authentication = null;
? ? ? ? try
? ? ? ? {
? ? ? ? ? ? // 該方法會去調用UserDetailsServiceImpl.loadUserByUsername
? ? ? ? ? ? authentication = authenticationManager
? ? ? ? ? ? ? ? ? ? .authenticate(new UsernamePasswordAuthenticationToken(username, password));
? ? ? ? }
? ? ? ? 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
? ? ? ? ? ? {
? ? ? ? ? ? ? ? AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
? ? ? ? ? ? ? ? throw new CustomException(e.getMessage());
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
? ? ? ? LoginUser loginUser = (LoginUser) authentication.getPrincipal();
? ? ? ? // 生成token
? ? ? ? return tokenService.createToken(loginUser);
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
就是在下面這個位置調用了authenticationManager,將用戶名密碼加入了整個驗證鏈。而且作者也在此做了注釋該方法會去調用UserDetailsServiceImpl.loadUserByUsername,也就是我們自定義的用戶驗證規則。

? ? ? ? // 用戶驗證
? ? ? ? Authentication authentication = null;
? ? ? ? try
? ? ? ? {
? ? ? ? ? ? // 該方法會去調用UserDetailsServiceImpl.loadUserByUsername
? ? ? ? ? ? authentication = authenticationManager
? ? ? ? ? ? ? ? ? ? .authenticate(new UsernamePasswordAuthenticationToken(username, password));
? ? ? ? }
1
2
3
4
5
6
7
8
在這里會從數據庫驗證用戶是否合法。到這基本上就算完成了完整的驗證流程。

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

? ? ? ? return createLoginUser(user);
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2、JWT
在配置類中只定義了一個jwt過濾器。將這個過濾器添加到了UsernamePasswordAuthenticationFilter過濾器之前。這部分我感覺沒有特別難理解的部分了,主要就是一些業務邏輯。

@Override
? ? protected 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);
? ? }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
七、總結
以上都是個人對項目的理解,如有錯誤還請指正。如果有其他問題,請在評論區提出討論。如果這篇文章幫到了你,請點個贊鼓勵一下我這個菜鳥。
?

總結

以上是生活随笔為你收集整理的RuoYi后台系统权限管理解析的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。