SpringBoot集成Shiro權(quán)限管理

應(yīng)老大的要求,給項(xiàng)目集成shiro權(quán)限管理,之前沒(méi)有接觸過(guò),這幾天也是查了一些資料,初步實(shí)現(xiàn)了登錄驗(yàn)證和url的權(quán)限管理。

對(duì)Shiro的理解

shiro是Apache的開(kāi)源框架,是一種功能強(qiáng)大且易用的安全框架,主要有四種功能。

  • 認(rèn)證——用戶(hù)的身份識(shí)別,常用作用戶(hù)的登錄
  • 權(quán)限——訪問(wèn)的控制,對(duì)于每次訪問(wèn)都會(huì)檢查是否具有訪問(wèn)權(quán)限
  • 會(huì)話(huà)管理——會(huì)話(huà)管理,用戶(hù)session管理器,用戶(hù)相關(guān)的時(shí)間敏感的狀態(tài)
  • 密碼加密——把JDK中復(fù)雜的密碼加密方式進(jìn)行封裝,保護(hù)或隱藏?cái)?shù)據(jù)防止被偷窺

網(wǎng)上找到架構(gòu)圖如下:


shiro包含三個(gè)核心的組件:Subject,SecurityManager 和 Realms

  • Subject是與程序交互的對(duì)象,一般稱(chēng)之為用戶(hù),Subject 實(shí)例都必須綁定到一個(gè)SecurityManager上。一個(gè)applicationCode與Subject 的交互,在運(yùn)行時(shí)shiro會(huì)自動(dòng)將次subject轉(zhuǎn)化為與SecurityManager的特定subject的交互 。
  • SecurityManager是shiro的核心,協(xié)調(diào)各個(gè)模塊的運(yùn)行,無(wú)需我們對(duì)其操作,我們只要操作subject就好,其他都交給SecurityManager去管理。
  • Realms是應(yīng)用程序與持久化數(shù)據(jù)之間交互的橋梁,需要我們自己編程去實(shí)現(xiàn)相應(yīng)的功能。比如登錄認(rèn)證需要實(shí)現(xiàn)對(duì)doGetAuthenticationInfo方法的重寫(xiě),權(quán)限認(rèn)證需要實(shí)現(xiàn)對(duì)doGetAuthorizationInfo方法的重寫(xiě)

集成shiro

添加依賴(lài)

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.3.2</version>
</dependency>

權(quán)限與角色表結(jié)構(gòu)

總共分四張表,用戶(hù)角色表、角色表、角色權(quán)限表以及權(quán)限初始化表,用戶(hù)與角色掛鉤,角色與權(quán)限掛鉤,每個(gè)用戶(hù)可以擔(dān)任多個(gè)角色,每個(gè)角色可以擁有多個(gè)權(quán)限,權(quán)限初始化表主要是對(duì)Shiro 攔截的請(qǐng)求做聲明,提供訪問(wèn)頁(yè)面與權(quán)限的關(guān)系。
角色表

DROP TABLE IF EXISTS `role`;
CREATE TABLE `role` (
   `id` bigint(64) DEFAULT NULL,
   `name` varchar(32) DEFAULT NULL COMMENT '角色名稱(chēng)'
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;    

角色權(quán)限表

DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission` (
 `id` bigint(64) NOT NULL,
 `rid` bigint(64) DEFAULT NULL COMMENT '角色I(xiàn)D',
 `pname` varchar(255) DEFAULT NULL COMMENT '權(quán)限ID',
  PRIMARY KEY (`id`)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

用戶(hù)角色表

DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role` (
 `id` bigint(64) NOT NULL,
 `uid` bigint(64) DEFAULT NULL COMMENT '用戶(hù)ID',
 `rid` bigint(64) DEFAULT NULL COMMENT '角色I(xiàn)D',
  PRIMARY KEY (`id`)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

權(quán)限初始化表

DROP TABLE IF EXISTS `permission_init`;
CREATE TABLE `permission_init` (
 `id` bigint(11) NOT NULL,
 `url` varchar(255) DEFAULT NULL COMMENT '鏈接',
 `permission_init` varchar(255) DEFAULT NULL COMMENT '需要具備的權(quán)限',
 `sort` int(11) DEFAULT '0' COMMENT '排序',
  PRIMARY KEY (`id`)
  ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

權(quán)限初始化表用來(lái)存儲(chǔ)訪問(wèn)鏈接與其需要具備的權(quán)限,在shiro的配置類(lèi)中進(jìn)行動(dòng)態(tài)加載。

Shiro配置類(lèi)

/**
 * shiro配置
 * 
 * @title
 * @author liyan
 * @date 2018年5月23日 下午2:43:45
 */
 @Configuration
 public class ShiroConfig {
     @Autowired
     private PermissionService permissionService;
  @Bean
  public ShiroFilterFactoryBean shiroFilter(SecurityManager sec urityManager) {
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    // 設(shè)置 SecurityManager
    shiroFilterFactoryBean.setSecurityManager(securityManager);
    // 設(shè)置登錄頁(yè)
    shiroFilterFactoryBean.setLoginUrl("/login.html");
    // 登錄成功后要跳轉(zhuǎn)的鏈接
    shiroFilterFactoryBean.setSuccessUrl("/list.html");
    // 未授權(quán)界面;
    shiroFilterFactoryBean.setUnauthorizedUrl("/403.html");
    // 權(quán)限控制map.
    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
    // 從數(shù)據(jù)庫(kù)獲取訪問(wèn)鏈接的初始化權(quán)限,對(duì)shiro攔截的請(qǐng)求做聲明
    List<PermissionInit> permissionList = permissionService.getPermissionInit();
    for (PermissionInit permission : permissionList) {
        filterChainDefinitionMap.put(permission.getUrl(), permission.getPermissionInit());
    }
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    return shiroFilterFactoryBean;
}

 @Bean
 public SecurityManager securityManager() {
    DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
    // 設(shè)置realm.
    securityManager.setRealm(myShiroRealm());
    // 注入記住我管理器;
    securityManager.setRememberMeManager(rememberMeManager());
    return securityManager;
}

/**
 * 權(quán)限認(rèn)證realm
 * 
 * @return
 */
 @Bean
 public ShiroRealm shiroRealms() {
    ShiroRealm shiroRealm = new ShiroRealm();
    return shiroRealm;
}

/**
 * cookie管理對(duì)象;記住我功能
 * 
 * @return
 */
 public CookieRememberMeManager rememberMeManager() {
    CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
    cookieRememberMeManager.setCookie(rememberMeCookie());
    // rememberMe cookie加密的密鑰 建議每個(gè)項(xiàng)目都不一樣 默認(rèn)AES算法 密鑰長(zhǎng)度(128 256 512 位)
    cookieRememberMeManager.setCipherKey(Base64.decode("3AvVhmFLUs0KTA3Kprsdag=="));
    return cookieRememberMeManager;
}

/**
 * cookie對(duì)象;
 * 
 * @return
 */
 public SimpleCookie rememberMeCookie() {
    // 這個(gè)參數(shù)是cookie的名稱(chēng),對(duì)應(yīng)前端的checkbox的name = rememberMe
    SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
    // <!-- 記住我cookie生效時(shí)間30天 ,單位秒;-->
    simpleCookie.setMaxAge(2592000);
    return simpleCookie;
  }
}

在不添加cookie對(duì)象管理的時(shí)候,登錄接口里如果remeberMe為true,調(diào)用會(huì)報(bào)異常。需要注意的一點(diǎn)是,我在permission_init表中初始化的頁(yè)面鏈接沒(méi)有加頁(yè)面的后綴.html,比如字段url的值為/create,過(guò)濾器攔截一直失敗,只有改成/create.html才成功攔截進(jìn)入到realm中。ShiroRealm是需要我們自己繼承AuthorizingRealm類(lèi),并且重寫(xiě)doGetAuthenticationInfo和doGetAuthorizationInfo方法。

 UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);

實(shí)現(xiàn)ShiroRealm

登錄認(rèn)證

在Shiro中,最終的認(rèn)證都是通過(guò)realm來(lái)獲取應(yīng)用程序的用戶(hù)角色以及權(quán)限等信息,在對(duì)登錄認(rèn)證過(guò)程中,shiro會(huì)調(diào)用reaml的doGetAuthenticationInfo(AuthenticationToken authcToken)方法,驗(yàn)證通過(guò)后會(huì)返回一個(gè)封裝了用戶(hù)信息的SimpleAuthenticationInfo實(shí)例

/**
 * 登錄驗(yàn)證: Authentication 是用來(lái)驗(yàn)證用戶(hù)身份
 * 
 * @param token
 * @return
 * @throws AuthenticationException
 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
        throws AuthenticationException {
    UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
    String name = token.getUsername();
    String password = String.valueOf(token.getPassword());
    // 密碼進(jìn)行加密處理 明文為 password+name
    String paw = password + name;
    String pawDES = DesEncrypt.encryptBasedDes(paw);
    // 從數(shù)據(jù)庫(kù)獲取對(duì)應(yīng)用戶(hù)名密碼的用戶(hù)
    UserInfo userInfo = logionService.getUserInfo(name, pawDES);
    if (null == userInfo) {
        return null;
    } else {
        // 更新登錄時(shí)間 last login time
        userInfo.setLastLoginTime(new Date());
        logionService.updateLoginTime(userInfo);
    }
    System.out.println("身份認(rèn)證成功,登錄用戶(hù):" + name);
    return new SimpleAuthenticationInfo(String.valueOf(userInfo.getUserId()), password, getName());
}
權(quán)限認(rèn)證

訪問(wèn)的url只有在shiro的配置類(lèi)里面添加了權(quán)限的filterChainDefinitions配置后,才會(huì)被攔截并跳轉(zhuǎn)到重寫(xiě)的doGetAuthorizationInfo(PrincipalCollection principals)方法中,該方法獲取當(dāng)前用戶(hù)的角色和擁有的權(quán)限信息,并封裝到SimpleAuthorizationInfo的實(shí)例中,驗(yàn)證通過(guò)的允許訪問(wèn),未經(jīng)授權(quán)的會(huì)跳轉(zhuǎn)到shiro配置類(lèi)里面預(yù)先定義好的頁(yè)面中,參考上面描述。

/**
 * 權(quán)限認(rèn)證:Authorization用來(lái)進(jìn)行權(quán)限驗(yàn)證
 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    String userId = (String) SecurityUtils.getSubject().getPrincipal();
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    Long userid = StringUtil.parseLong(userId);
    // 查詢(xún)用戶(hù)所屬角色,放入到Authorization里。
    Set<String> roleSet = permissionService.getUserRole(userid);
    info.setRoles(roleSet);
    // 查詢(xún)用戶(hù)所擁有的權(quán)限(permission),放入到Authorization里。
    Set<String> permissionSet = permissionService.getUserPermission(userid);
    info.setStringPermissions(permissionSet);
    return info;
}

接口調(diào)用

在登錄接口中將登錄信息封裝在UsernamePasswordToken中,再調(diào)用subject的login方法執(zhí)行登錄,這時(shí)會(huì)進(jìn)入realm中進(jìn)行登錄信息的驗(yàn)證工作。

    UsernamePasswordToken token = new UsernamePasswordToken(username, password, rememberMe);
    Subject currentSubject=SecurityUtils.getSubject();
    currentSubject.login(token);
    currentSubject.getSession().setAttribute("name", username);
    serviceResult = new ServiceResult(HttpConstants.RESUTL_OK, "登錄成功");
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容