SpringBoot框架之Shiro權限

Apache Shiro是一個功能強大、靈活的,開源的輕量級安全框架。它可以乾淨利落地處理身份驗證、授權、企業會話管理和加密,與spring security 相比較,簡單易用,靈活性高,springboot本身是提供了對security的支持,畢竟是自家的東西。springboot暫時沒有集成shiro,需要自己配置。

一.Shiro能做什麼呢?

(1)驗證用戶身份

(2)用戶訪問權限控制,比如:1、判斷用戶是否分配了一定的安全角色。2、判斷用戶是否被授予完成某個操作的權限

(3)在非 web 或 EJB 容器的環境下可以任意使用Session API

(4)可以響應認證、訪問控制,或者 Session 生命週期中發生的事件

(5)可將一個或以上用戶安全數據源數據組合成一個複合的用戶 “view”(視圖)

(6)支持單點登錄(SSO)功能

(7)支持提供“Remember Me”服務,獲取用戶關聯信息而無需登錄

等等——都集成到一個有凝聚力的易於使用的API。

二.Apache Shiro Features 特性

Apache Shiro是一個全面的、蘊含豐富功能的安全框架。下圖爲描述Shiro功能的框架圖:

Authentication(認證), Authorization(授權), Session Management(會話管理), Cryptography(加密)被 Shiro 框架的開發團隊稱之爲應用安全的四大基石。那麼就讓我們來看看它們吧:

(1)Authentication(認證):用戶身份識別,通常被稱爲用戶“登錄”

(2)Authorization(授權):訪問控制。比如某個用戶是否具有某個操作的使用權限。

(3)Session Management(會話管理):特定於用戶的會話管理,甚至在非web 或 EJB 應用程序。

(4)Cryptography(加密):在對數據源使用加密算法加密的同時,保證易於使用。

三具體實現

管理都是層級相互依賴的,權限賦予給角色,而把角色又賦予用戶,這樣的權限設計很清楚,管理起來很方便。

1、pom.xml添加依賴

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

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.2.2</version>
</dependency>

2、編寫Shiro配置類

Apache Shiro 核心通過 Filter 來實現,就好像SpringMvc 通過DispachServlet 來主控制一樣。 既然是使用 Filter 一般也就能猜到,是通過URL規則來進行過濾和權限校驗,所以我們需要定義一系列關於URL的規則和訪問權限。

​package com.java.core.config;

import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;

@Configuration
public class ShiroConfig {
	@Bean
    public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必須設置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // setLoginUrl 如果不設置值,默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 或 "/login" 映射
        shiroFilterFactoryBean.setLoginUrl("/");
        // 設置無權限時跳轉的 url;
        shiroFilterFactoryBean.setUnauthorizedUrl("/login.html");

        // 設置攔截器
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        filterChainDefinitionMap.put("/left", "anon");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/fonts/**", "anon");
        filterChainDefinitionMap.put("/img/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        //開放登陸接口
        filterChainDefinitionMap.put("/login", "anon");
        // TODO
        filterChainDefinitionMap.put("/test/test1", "anon");
        //其餘接口一律攔截
        //主要這行代碼必須放在所有權限設置的最後,不然會導致所有 url 都被攔截
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        System.out.println("Shiro攔截器工廠類注入成功");
        return shiroFilterFactoryBean;
    }

    /**
     * 注入 securityManager
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 設置realm.
        securityManager.setRealm(customRealm());
        //注入緩存管理器;
        securityManager.setCacheManager(ehCacheManager());
        return securityManager;
    }

    /**
     * 自定義身份認證 realm;
     * <p>
     * 必須寫這個類,並加上 @Bean 註解,目的是注入 CustomRealm,
     * 否則會影響 CustomRealm類 中其他類的依賴注入
     */
    @Bean
    public CustomRealm customRealm() {
        return new CustomRealm();
    }
    
    /**
     * shiro緩存管理器;
     * 需要注入對應的其它的實體類中:
     * 1、安全管理器:securityManager
     * 可見securityManager是整個shiro的核心;
     * @return
     */
    @Bean
    public EhCacheManager ehCacheManager(){
       EhCacheManager cacheManager = new EhCacheManager();
       cacheManager.setCacheManagerConfigFile("classpath:ehcache.xml");
       return cacheManager;
    }
    
    /**
     * thymeleaf和shiro
     */
    @Bean
    public ShiroDialect shiroDialect() {
        return new ShiroDialect();
    }
}

3、編寫自定義Realm驗證類,user表、user_role表和role_permissions表

package com.java.core.config;

import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import tk.mybatis.mapper.entity.Example;
import tk.mybatis.mapper.entity.Example.Criteria;

import com.java.system.dao.SystemUserMapper;
import com.java.system.model.SystemUser;
public class CustomRealm extends AuthorizingRealm {
	
	@Autowired
	private SystemUserMapper systemUserMapper;

    /**
     * 獲取身份驗證信息
     * Shiro中,最終是通過 Realm 來獲取應用程序中的用戶、角色及權限信息的。
     *
     * @param authenticationToken 用戶身份信息 token
     * @return 返回封裝了用戶信息的 AuthenticationInfo 實例
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        // 身份認證
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        Example example = new Example(SystemUser.class);
        Criteria criteria = example.createCriteria();
        // 登錄名
        criteria.andEqualTo("loginName", token.getUsername());

        List<SystemUser> users = systemUserMapper.selectByExample(example);
        String password = "";
        if (users != null && users.size() > 0) {
        	password = users.get(0).getPassword();
        }

        if (users == null || users.size() == 0) {
            throw new AccountException("用戶名不正確");
        }
        if (!password.equals(new String((char[]) token.getCredentials()))) {
            throw new AccountException("密碼不正確");
        }
        if (users.get(0).getStatus() == null || users.get(0).getStatus() != 0) {
        	throw new AccountException("用戶已被禁用");
        }
        Set<String> permissions = systemUserMapper.queryPermissions(token.getUsername());
        if (permissions == null || permissions.size() == 0) {
        	throw new AccountException("該用戶無任何權限");
        }
        
        // 把用戶信息放到session中
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        request.getSession().setAttribute("user", users.get(0));
        
        // 在登陸的時候清楚一次緩存信息,這樣每次登陸重新獲取授權信息
        PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
    	super.clearCache(principals);
    	// 若存在,將此用戶存放到登錄認證info中,無需自己做密碼對比,Shiro會爲我們進行密碼對比校驗 
        return new SimpleAuthenticationInfo(token.getPrincipal(), password, getName());
    }

    /**
     * 獲取授權信息
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    	
    	// 權限認證
        String userName = (String) SecurityUtils.getSubject().getPrincipal();
        Set<String> permissions = systemUserMapper.queryPermissions(userName);
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setStringPermissions(permissions);
        return info;
    }

}

5、ehcache.xml的配置文件

<?xml version="1.0" encoding="UTF-8"?> 
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
  updateCheck="false"> 
  <diskStore path="java.io.tmpdir/Tmp_EhCache" />  
  <!-- 
    name:緩存名稱。 
    maxElementsInMemory:緩存最大數目 
    maxElementsOnDisk:硬盤最大緩存個數。 
    eternal:對象是否永久有效,一但設置了,timeout將不起作用。 
    overflowToDisk:是否保存到磁盤,當系統當機時 
    timeToIdleSeconds:設置對象在失效前的允許閒置時間(單位:秒)。僅當eternal=false對象不是永久有效時使用,可選屬性,默認值是0,也就是可閒置時間無窮大。 
    timeToLiveSeconds:設置對象在失效前允許存活時間(單位:秒)。最大時間介於創建時間和失效時間之間。僅當eternal=false對象不是永久有效時使用,默認是0.,也就是對象存活時間無窮大。 
    diskPersistent:是否緩存虛擬機重啓期數據 Whether the disk store persists between restarts of the Virtual Machine. The default value is false. 
    diskSpoolBufferSizeMB:這個參數設置DiskStore(磁盤緩存)的緩存區大小。默認是30MB。每個Cache都應該有自己的一個緩衝區。 
    diskExpiryThreadIntervalSeconds:磁盤失效線程運行時間間隔,默認是120秒。 
    memoryStoreEvictionPolicy:當達到maxElementsInMemory限制時,Ehcache將會根據指定的策略去清理內存。默認策略是LRU(最近最少使用)。你可以設置爲FIFO(先進先出)或是LFU(較少使用)。 
    clearOnFlush:內存數量最大時是否清除。 
     memoryStoreEvictionPolicy: 
      Ehcache的三種清空策略; 
      FIFO,first in first out,這個是大家最熟的,先進先出。 
      LFU, Less Frequently Used,就是上面例子中使用的策略,直白一點就是講一直以來最少被使用的。如上面所講,緩存的元素有一個hit屬性,hit值最小的將會被清出緩存。 
      LRU,Least Recently Used,最近最少使用的,緩存的元素有一個時間戳,當緩存容量滿了,而又需要騰出地方來緩存新的元素的時候,那麼現有緩存元素中時間戳離當前時間最遠的元素將被清出緩存。 
  -->
  
  <defaultCache eternal="false" maxElementsInMemory="1000"
    overflowToDisk="false" diskPersistent="false" timeToIdleSeconds="0"
    timeToLiveSeconds="600" memoryStoreEvictionPolicy="LRU" /> 
  <!-- 登錄記錄緩存鎖定10分鐘 -->
  <cache name="passwordRetryCache"
      maxEntriesLocalHeap="2000"
      eternal="false"
      timeToIdleSeconds="3600"
      timeToLiveSeconds="0"
      overflowToDisk="false"
      statistics="true"> 
  </cache> 
</ehcache> 

6、以登錄身份驗證爲例,LoginController.java編寫

/**
     * 登陸
     *
     * @param username 用戶名
     * @param password 密碼
     */
    @RequestMapping(value = "/login", method = RequestMethod.POST)
    @ResponseBody
    public Object login(HttpServletRequest request, String loginName, String password) {
        // 從SecurityUtils裏邊創建一個 subject
        Subject subject = SecurityUtils.getSubject();
        // 在認證提交前準備 token(令牌)
        String username = loginName;
        UsernamePasswordToken token = new UsernamePasswordToken(username, DigestUtils.md5Hex(password));
        // 執行認證登陸
        subject.login(token);
        
        Map<String,Object> map = new HashMap<String,Object>();
    	map.put("status",200);
    	map.put("message","成功登陸");
    	
    	return map;
    }

7、前端頁面添加菜單權限管理,name -- 權限名

​<shiro:hasAnyPermissions name="expend,recommend,recipe,moonKill,cookbook,carousel"> 
	    <ul class="borderBlue">
	        <p class="yyyy blue" ><span class="span1"></span>種花管理<span class="jiantou span2"></span></p>
	        <p class="slideP">
	            <span id="expend" class="slideInside" shiro:hasAnyPermissions="article,recommend" th:onclick="'javascript:go(\''+@{/test}+'\')'">種菜管理</span>
	            <span id="cookbook" class="slideInside" shiro:hasAnyPermissions="recipe,moonKill,cookbook" th:onclick="'javascript:go(\''+@{/test}+'\')'">買種管理</span>
	            <span id="carousel" class="slideInside" shiro:hasAnyPermissions="carousel" onclick="go('slideImage.html')">營銷管理</span>
	        </p>
	    </ul>
    </shiro:hasPermission>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章