之前在 SSM 項目中使用過 shiro,發現 shiro 的權限管理做的真不錯,但是在 SSM 項目中的配置太繁雜了,于是這次在 SpringBoot 中使用了 shiro,下面一起看看吧
一、簡介
Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼和會話管理。使用Shiro的易于理解的API,您可以快速、輕松地獲得任何應用程序,從最小的移動應用程序到最大的網絡和企業應用程序。
三個核心組件:
1、Subject
即“當前操作用戶”。但是,在 Shiro 中,Subject 這一概念并不僅僅指人,也可以是第三方進程、后臺帳戶(Daemon Account)或其他類似事物。它僅僅意味著“當前跟軟件交互的東西”。Subject 代表了當前用戶的安全操作,SecurityManager 則管理所有用戶的安全操作。
2、SecurityManager
它是Shiro 框架的核心,典型的 Facade 模式,Shiro 通過 SecurityManager 來管理內部組件實例,并通過它來提供安全管理的各種服務。
3、Realm
Realm 充當了 Shiro 與應用安全數據間的“橋梁”或者“連接器”。也就是說,當對用戶執行認證(登錄)和授權(訪問控制)驗證時,Shiro 會從應用配置的 Realm 中查找用戶及其權限信息。從這個意義上講,Realm 實質上是一個安全相關的 DAO:它封裝了數據源的連接細節,并在需要時將相關數據提供給 Shiro。當配置 Shiro 時,你必須至少指定一個 Realm,用于認證和(或)授權。配置多個 Realm 是可以的,但是至少需要一個。Shiro 內置了可以連接大量安全數據源(又名目錄)的 Realm,如 LDAP、關系數據庫(JDBC)、類似 INI 的文本配置資源以及屬性文件等。如果缺省的 Realm 不能滿足需求,你還可以插入代表自定義數據源的自己的 Realm 實現。
二、整合 shiro
1、引入 maven 依賴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<!-- web支持 --> < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-web</ artifactId > </ dependency > <!-- thymeleaf 模板引擎 --> < dependency > < groupId >org.springframework.boot</ groupId > < artifactId >spring-boot-starter-thymeleaf</ artifactId > </ dependency > <!-- Shiro 權限管理 --> < dependency > < groupId >org.apache.shiro</ groupId > < artifactId >shiro-spring</ artifactId > < version >1.2.4</ version > </ dependency > <!-- 為了能夠在 html 中使用 shiro 的標簽引入 --> < dependency > < groupId >com.github.theborakompanioni</ groupId > < artifactId >thymeleaf-extras-shiro</ artifactId > < version >2.0.0</ version > </ dependency > |
我使用的 SpringBoot 版本是 2.3.1,其它依賴自己看著引入吧
2、創建 shiro 配置文件
關于 shiro 的配置信息,我們都放在 ShiroConfig.java 文件中
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
|
import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import java.util.LinkedHashMap; import java.util.Map; /** * shiro配置類 */ @Configuration public class ShiroConfig { /** * 注入這個是是為了在thymeleaf中使用shiro的自定義tag。 */ @Bean (name = "shiroDialect" ) public ShiroDialect shiroDialect() { return new ShiroDialect(); } /** * 地址過濾器 * @param securityManager * @return */ @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 設置securityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 設置登錄url shiroFilterFactoryBean.setLoginUrl( "/login" ); // 設置主頁url shiroFilterFactoryBean.setSuccessUrl( "/" ); // 設置未授權的url shiroFilterFactoryBean.setUnauthorizedUrl( "/unauthorized" ); Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); // 開放登錄接口 filterChainDefinitionMap.put( "/doLogin" , "anon" ); // 開放靜態資源文件 filterChainDefinitionMap.put( "/css/**" , "anon" ); filterChainDefinitionMap.put( "/img/**" , "anon" ); filterChainDefinitionMap.put( "/js/**" , "anon" ); filterChainDefinitionMap.put( "/layui/**" , "anon" ); // 其余url全部攔截,必須放在最后 filterChainDefinitionMap.put( "/**" , "authc" ); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 自定義安全管理策略 */ @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); /** 設置自定義的relam */ securityManager.setRealm(loginRelam()); return securityManager; } /** * 登錄驗證 */ @Bean public LoginRelam loginRelam() { return new LoginRelam(); } /** * 以下是為了能夠使用@RequiresPermission()等標簽 */ @Bean @DependsOn ({ "lifecycleBeanPostProcessor" }) public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); advisorAutoProxyCreator.setProxyTargetClass( true ); return advisorAutoProxyCreator; } @Bean public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager()); return authorizationAttributeSourceAdvisor; } } |
上面開放靜態資源文件,其它博客說的是 **filterChainDefinitionMap.put("/static/**", "anon");** ,但我發現,我們在 html 文件中引入靜態文件時,請求路徑根本沒有經過 static,thymeleaf 自動默認配置 **static/** 下面就是靜態資源文件,所以,我們開放靜態資源文件需要指定響應的目錄路徑
2、登錄驗證管理
關于登錄驗證的一些邏輯,以及賦權等操作,我們都放在 LoginRelam.java 文件中
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.zyxx.sbm.entity.UserInfo; import com.zyxx.sbm.service.RolePermissionService; import com.zyxx.sbm.service.UserInfoService; import com.zyxx.sbm.service.UserRoleService; import org.apache.shiro.authc.*; import org.apache.shiro.authc.credential.CredentialsMatcher; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; import java.util.Set; /** * 登錄授權 */ public class LoginRelam extends AuthorizingRealm { @Autowired private UserInfoService userInfoService; @Autowired private UserRoleService userRoleService; @Autowired private RolePermissionService rolePermissionService; /** * 身份認證 * * @param authenticationToken * @return * @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { // 獲取基于用戶名和密碼的令牌:實際上這個authcToken是從LoginController里面currentUser.login(token)傳過來的 UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; //根據用戶名查找到用戶信息 QueryWrapper<UserInfo> queryWrapper = new QueryWrapper<>(); queryWrapper.eq( "account" , token.getUsername()); UserInfo userInfo = userInfoService.getOne(queryWrapper); // 沒找到帳號 if ( null == userInfo) { throw new UnknownAccountException(); } // 校驗用戶狀態 if ( "1" .equals(userInfo.getStatus())) { throw new DisabledAccountException(); } // 認證緩存信息 return new SimpleAuthenticationInfo(userInfo, userInfo.getPassword(), ByteSource.Util.bytes(userInfo.getAccount()), getName()); } /** * 角色授權 * * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { UserInfo authorizingUser = (UserInfo) principalCollection.getPrimaryPrincipal(); if ( null != authorizingUser) { //權限信息對象info,用來存放查出的用戶的所有的角色(role)及權限(permission) SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); //獲得用戶角色列表 Set<String> roleSigns = userRoleService.listUserRoleByUserId(authorizingUser.getId()); simpleAuthorizationInfo.addRoles(roleSigns); //獲得權限列表 Set<String> permissionSigns = rolePermissionService.listRolePermissionByUserId(authorizingUser.getId()); simpleAuthorizationInfo.addStringPermissions(permissionSigns); return simpleAuthorizationInfo; } return null ; } /** * 自定義加密規則 * * @param credentialsMatcher */ @Override public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) { // 自定義認證加密方式 CustomCredentialsMatcher customCredentialsMatcher = new CustomCredentialsMatcher(); // 設置自定義認證加密方式 super .setCredentialsMatcher(customCredentialsMatcher); } } |
以上就是登錄時,需要指明 shiro 對用戶的一些驗證、授權等操作,還有自定義密碼驗證規則,在第3步會講到,獲取角色列表,權限列表,需要獲取到角色與權限的標識,每一個角色,每一個權限都有唯一的標識,裝入 Set 中
3、自定義密碼驗證規則
密碼的驗證規則,我們放在了 CustomCredentialsMatcher.java 文件中
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
|
import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.credential.SimpleCredentialsMatcher; import org.apache.shiro.crypto.hash.SimpleHash; /** * @ClassName CustomCredentialsMatcher * 自定義密碼加密規則 * @Author Lizhou * @Date 2020-07-10 16:24:24 **/ public class CustomCredentialsMatcher extends SimpleCredentialsMatcher { @Override public boolean doCredentialsMatch(AuthenticationToken authcToken, AuthenticationInfo info) { UsernamePasswordToken token = (UsernamePasswordToken) authcToken; //加密類型,密碼,鹽值,迭代次數 Object tokenCredentials = new SimpleHash( "md5" , token.getPassword(), token.getUsername(), 6 ).toHex(); // 數據庫存儲密碼 Object accountCredentials = getCredentials(info); // 將密碼加密與系統加密后的密碼校驗,內容一致就返回true,不一致就返回false return equals(tokenCredentials, accountCredentials); } } |
我們采用的密碼加密方式為 MD5 加密,加密 6 次,使用登錄賬戶作為加密密碼的鹽進行加密
4、密碼加密工具
上面我們自定義了密碼加密規則,我們創建一個密碼加密的工具類 PasswordUtils.java 文件
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
|
import org.apache.shiro.crypto.hash.Md5Hash; /** * 密碼加密的處理工具類 */ public class PasswordUtils { /** * 迭代次數 */ private static final int ITERATIONS = 6 ; private PasswordUtils() { throw new AssertionError(); } /** * 字符串加密函數MD5實現 * * @param password 密碼 * @param loginName 用戶名 * @return */ public static String getPassword(String password, String loginName) { return new Md5Hash(password, loginName, ITERATIONS).toString(); } } |
三、開始登錄
上面,我們已經配置了 shiro 的一系列操作,從登錄驗證、密碼驗證規則、用戶授權等等,下面我們就開始登錄,登錄的操作,放在了 LoginController.java 文件中
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
|
import com.zyxx.common.consts.SystemConst; import com.zyxx.common.enums.StatusEnums; import com.zyxx.common.kaptcha.KaptchaUtil; import com.zyxx.common.shiro.SingletonLoginUtils; import com.zyxx.common.utils.PasswordUtils; import com.zyxx.common.utils.ResponseResult; import com.zyxx.sbm.entity.UserInfo; import com.zyxx.sbm.service.PermissionInfoService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * @ClassName LoginController * @Description * @Author Lizhou * @Date 2020-07-02 10:54:54 **/ @Api (tags = "后臺管理端--登錄" ) @Controller public class LoginController { @Autowired private PermissionInfoService permissionInfoService; @ApiOperation (value = "請求登錄頁面" , notes = "請求登錄頁面" ) @GetMapping ( "login" ) public String init() { return "login" ; } @ApiOperation (value = "請求主頁面" , notes = "請求主頁面" ) @GetMapping ( "/" ) public String index() { return "index" ; } @ApiOperation (value = "登錄驗證" , notes = "登錄驗證" ) @ApiImplicitParams ({ @ApiImplicitParam (name = "account" , value = "賬號" , required = true ), @ApiImplicitParam (name = "password" , value = "密碼" , required = true ), @ApiImplicitParam (name = "resCode" , value = "驗證碼" , required = true ), @ApiImplicitParam (name = "rememberMe" , value = "記住登錄" , required = true ) }) @PostMapping ( "doLogin" ) @ResponseBody public ResponseResult doLogin(String account, String password, String resCode, Boolean rememberMe, HttpServletRequest request, HttpServletResponse response) throws Exception { // 驗證碼 if (!KaptchaUtil.validate(resCode, request)) { return ResponseResult.getInstance().error(StatusEnums.KAPTCH_ERROR); } // 驗證帳號和密碼 Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(account, password); // 記住登錄狀態 token.setRememberMe(rememberMe); try { // 執行登錄 subject.login(token); // 將用戶保存到session中 UserInfo userInfo = (UserInfo) subject.getPrincipal(); request.getSession().setAttribute(SystemConst.SYSTEM_USER_SESSION, userInfo); return ResponseResult.getInstance().success(); } catch (UnknownAccountException e) { return ResponseResult.getInstance().error( "賬戶不存在" ); } catch (DisabledAccountException e) { return ResponseResult.getInstance().error( "賬戶已被凍結" ); } catch (IncorrectCredentialsException e) { return ResponseResult.getInstance().error( "密碼不正確" ); } catch (ExcessiveAttemptsException e) { return ResponseResult.getInstance().error( "密碼連續輸入錯誤超過5次,鎖定半小時" ); } catch (RuntimeException e) { return ResponseResult.getInstance().error( "未知錯誤" ); } } @ApiOperation (value = "登錄成功,跳轉主頁面" , notes = "登錄成功,跳轉主頁面" ) @PostMapping ( "success" ) public String success() { return "redirect:/" ; } @ApiOperation (value = "初始化菜單數據" , notes = "初始化菜單數據" ) @GetMapping ( "initMenu" ) @ResponseBody public String initMenu() { return permissionInfoService.initMenu(); } @ApiOperation (value = "退出登錄" , notes = "退出登錄" ) @GetMapping (value = "loginOut" ) public String logout() { Subject subject = SecurityUtils.getSubject(); subject.logout(); return "login" ; } } |
當執行 subject.login(token); 時,就會進入我們在 第二步中第二條登錄驗證中,對用戶密碼、狀態進行檢查,對用戶授權等操作,登錄的密碼,一定是通過密碼加密工具得到的,不然驗證不通過
四、頁面權限控制
我們本次使用的是 thymeleaf 模板引擎,我們需要在 html 文件中加入以下內容
1
2
|
<!DOCTYPE html> < html xmlns = "http://www.w3.org/1999/xhtml" xmlns:th = "http://www.thymeleaf.org" xmlns:shiro = "http://www.pollix.at/thymeleaf/shiro" > |
引入了 thymeleaf 的依賴,以及 shiro 的依賴,這樣我們就能在 html 文件中使用 thymeleaf、shiro 的標簽了
例如:
1、判斷當前用戶有無此權限,通過權限標識
1
|
< button class = "layui-btn" shiro:hasPermission = "user_info_add" >< i class = "layui-icon" ></ i > 新增 </ button > |
2、與上面相反,判斷當前用戶無此權限,通過權限標識,沒有時驗證通過
1
|
< button class = "layui-btn" shiro:lacksPermission = "user_info_add" >< i class = "layui-icon" ></ i > 新增 </ button > |
3、判斷當前用戶有無以下全部權限,通過權限標識
1
|
< button class = "layui-btn" shiro:hasAllPermissions = "user_info_add" >< i class = "layui-icon" ></ i > 新增 </ button > |
4、判斷當前用戶有無以下任一權限,通過權限標識
1
|
< button class = "layui-btn" shiro:hasAnyPermissions = "user_info_add" >< i class = "layui-icon" ></ i > 新增 </ button > |
5、判斷當前用戶有無此角色,通過角色標識
1
|
< a shiro:hasRole = "admin" href = "admin.html" rel = "external nofollow" rel = "external nofollow" rel = "external nofollow" rel = "external nofollow" >Administer the system</ a > |
6、與上面相反,判斷當前用戶無此角色,通過角色標識,沒有時驗證通過
1
|
< a shiro:lacksRole = "admin" href = "admin.html" rel = "external nofollow" rel = "external nofollow" rel = "external nofollow" rel = "external nofollow" >Administer the system</ a > |
7、判斷當前用戶有無以下全部角色,通過角色標識
1
|
< a shiro:hasAllRoles = "admin,role1,role2" href = "admin.html" rel = "external nofollow" rel = "external nofollow" rel = "external nofollow" rel = "external nofollow" >Administer the system</ a > |
8、判斷當前用戶有無以下任一角色,通過角色標識
1
|
< a shiro:hasAnyRoles = "admin,role1,role2" href = "admin.html" rel = "external nofollow" rel = "external nofollow" rel = "external nofollow" rel = "external nofollow" >Administer the system</ a > |
到此這篇關于SpringBoot中整合Shiro實現權限管理的示例代碼的文章就介紹到這了,更多相關SpringBoot整合Shiro權限內容請搜索服務器之家以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持服務器之家!
原文鏈接:https://blog.csdn.net/qq_40065776/article/details/107300987