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>

猜你喜欢

转载自blog.csdn.net/wang_snake/article/details/81943572