1. 程式人生 > >springboot+shiro+mybatis實現角色許可權控制

springboot+shiro+mybatis實現角色許可權控制

背景

spring+spirngmvc+shiro的整合已經有很多了,之前的專案中也用過,但是最近想在springboot中使用shiro這樣,其他專案需要的時候只需要把它依賴進來就可以直接使用,至於shiro的原理其他的blog都有很多介紹。這裡只講幾個重點在專案中注意的地方。
shiro官網
http://shiro.apache.org/

shiro配置中重要的幾個檔案

這裡寫圖片描述

其實最重要的就是shiro配置檔案
ShiroConfiguration.java:shiro啟動時候的初始化工作,比如哪些是需要認證,哪些不需要認證;快取配置設定;shiro許可權資料在頁面展示時整合需要的模板套件配置,等等。

ShiroRealm.java:shiro許可權認證的具體實現程式碼,因為shiro本身只提供攔截路由,而具體如何資料來源則由使用者自己提供,不同的專案不同的要求,要不要加快取登陸驗證次數,要不要密碼加密設定其他具體方式,這些都由使用者自己決定,而shiro只提供給使用者許可權驗證的格式介面,通過使用者提供的資料來源shrio判斷要不要給具體使用者授權請求路徑的判斷。
ShiroRealm 涉及到以下點:
principal:主體,就是登陸的當前使用者型別的資料實體
credentials:憑證,使用者的密碼,具體加密方式使用者自己實現,什麼都不做就是原文

  • Roles:使用者擁有的角色標識(角色名稱,admin,account,customer_service),字串格式列表:使用者擁有多個角色的可能
  • Permissions:使用者擁有的許可權標識(每個許可權唯一標識,比如主鍵或者許可權唯一標識編碼),字串格式列表:使用者擁有多個許可權的可能

pom

在springboot+mybatis基礎上加入shiro依賴

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
>
<modelVersion>4.0.0</modelVersion> <groupId>com.xm.shiro</groupId> <artifactId>springboot-shiro</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>jar</packaging> <name>springboot-shiro</name> <url>http://maven.apache.org</url> <repositories><!--ali 程式碼庫 --> <repository> <id>maven-ali</id> <url>http://maven.aliyun.com/nexus/content/groups/public/</url> <releases> <enabled>true</enabled> </releases> <snapshots> <enabled>true</enabled> <updatePolicy>always</updatePolicy> <checksumPolicy>fail</checksumPolicy> </snapshots> </repository> </repositories> <!-- Spring Boot 啟動父依賴 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.1.RELEASE</version> </parent> <properties> <mybatis-spring-boot>1.2.0</mybatis-spring-boot> <mysql-connector>5.1.39</mysql-connector> </properties> <dependencies> <!-- Spring Boot Web 依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Spring Boot Test 依賴 --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!-- Spring Boot Mybatis 依賴 --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis-spring-boot}</version> </dependency> <!-- MySQL 連線驅動依賴 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-connector}</version> </dependency> <!-- shiro相關 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.5</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-ehcache</artifactId> <version>1.2.5</version> </dependency> </dependencies> </project>

表結構

/*
Navicat MySQL Data Transfer

Source Server         : local
Source Server Version : 50553
Source Host           : localhost:3306
Source Database       : springboot_shiro

Target Server Type    : MYSQL
Target Server Version : 50553
File Encoding         : 65001

Date: 2017-05-10 20:40:01
*/

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `u_permission`
-- ----------------------------
DROP TABLE IF EXISTS `u_permission`;
CREATE TABLE `u_permission` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `url` varchar(256) DEFAULT NULL COMMENT 'url地址',
  `name` varchar(64) DEFAULT NULL COMMENT 'url描述',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of u_permission
-- ----------------------------
INSERT INTO `u_permission` VALUES ('1', '/user', 'usermanager');

-- ----------------------------
-- Table structure for `u_role`
-- ----------------------------
DROP TABLE IF EXISTS `u_role`;
CREATE TABLE `u_role` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) DEFAULT NULL COMMENT '角色名稱',
  `type` varchar(10) DEFAULT NULL COMMENT '角色型別',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of u_role
-- ----------------------------
INSERT INTO `u_role` VALUES ('1', 'admin', '1');

-- ----------------------------
-- Table structure for `u_role_permission`
-- ----------------------------
DROP TABLE IF EXISTS `u_role_permission`;
CREATE TABLE `u_role_permission` (
  `rid` bigint(20) DEFAULT NULL COMMENT '角色ID',
  `pid` bigint(20) DEFAULT NULL COMMENT '許可權ID'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of u_role_permission
-- ----------------------------
INSERT INTO `u_role_permission` VALUES ('1', '1');

-- ----------------------------
-- Table structure for `u_user`
-- ----------------------------
DROP TABLE IF EXISTS `u_user`;
CREATE TABLE `u_user` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `nickname` varchar(20) DEFAULT NULL COMMENT '使用者暱稱',
  `email` varchar(128) DEFAULT NULL COMMENT '郵箱|登入帳號',
  `pswd` varchar(32) DEFAULT NULL COMMENT '密碼',
  `create_time` datetime DEFAULT NULL COMMENT '建立時間',
  `last_login_time` datetime DEFAULT NULL COMMENT '最後登入時間',
  `status` bigint(1) DEFAULT '1' COMMENT '1:有效,0:禁止登入',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=15 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of u_user
-- ----------------------------
INSERT INTO `u_user` VALUES ('1', 'admin', null, '123456', '2017-05-10 20:22:59', null, '1');

-- ----------------------------
-- Table structure for `u_user_role`
-- ----------------------------
DROP TABLE IF EXISTS `u_user_role`;
CREATE TABLE `u_user_role` (
  `uid` bigint(20) DEFAULT NULL COMMENT '使用者ID',
  `rid` bigint(20) DEFAULT NULL COMMENT '角色ID'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of u_user_role
-- ----------------------------
INSERT INTO `u_user_role` VALUES ('1', '1');

配置shiro相關檔案

ShiroConfiguration.java

package com.xm.shiro.config;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.cache.ehcache.EhCacheManager;
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.boot.autoconfigure.condition.ConditionalOnMissingBean;
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配置項
 * Created by Lucare.Feng on 2017/3/6.
 */
@Configuration
public class ShiroConfiguration {

    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    //處理認證匹配處理器:如果自定義需要實現繼承HashedCredentialsMatcher
    //指定加密方式方式,也可以在這裡加入快取,當用戶超過五次登陸錯誤就鎖定該使用者禁止不斷嘗試登陸
//    @Bean(name = "hashedCredentialsMatcher")
//    public HashedCredentialsMatcher hashedCredentialsMatcher() {
//        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
//        credentialsMatcher.setHashAlgorithmName("MD5");
//        credentialsMatcher.setHashIterations(2);
//        credentialsMatcher.setStoredCredentialsHexEncoded(true);
//        return credentialsMatcher;
//    }

    @Bean(name = "shiroRealm")
    @DependsOn("lifecycleBeanPostProcessor")
    public ShiroRealm shiroRealm() {
        ShiroRealm realm = new ShiroRealm();
//        realm.setCredentialsMatcher(hashedCredentialsMatcher());
        return realm;
    }

    @Bean(name = "ehCacheManager")
    @DependsOn("lifecycleBeanPostProcessor")
    public EhCacheManager ehCacheManager(){
        EhCacheManager ehCacheManager = new EhCacheManager();
        return ehCacheManager;
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroRealm());
        securityManager.setCacheManager(ehCacheManager());//使用者授權/認證資訊Cache, 採用EhCache 快取
        return securityManager;
    }

    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager  securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

//        Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
//        LogoutFilter logoutFilter = new LogoutFilter();
//        logoutFilter.setRedirectUrl("/login");
//        filters.put("logout", logoutFilter);
//        shiroFilterFactoryBean.setFilters(filters);
        System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");
        Map<String, String> filterChainDefinitionManager = new LinkedHashMap<>();
        filterChainDefinitionManager.put("/logout", "logout");
        filterChainDefinitionManager.put("/user/**", "authc,roles[user]");
        filterChainDefinitionManager.put("/shop/**", "authc,roles[shop]");
        filterChainDefinitionManager.put("/admin/**", "authc,roles[admin]");
        filterChainDefinitionManager.put("/login", "anon");//anon 可以理解為不攔截
        filterChainDefinitionManager.put("/ajaxLogin", "anon");//anon 可以理解為不攔截
        filterChainDefinitionManager.put("/statistic/**",  "anon");//靜態資源不攔截
        filterChainDefinitionManager.put("/**",  "authc,roles[user]");//其他資源全部攔截
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionManager);

        shiroFilterFactoryBean.setLoginUrl("/login");
        shiroFilterFactoryBean.setSuccessUrl("/");
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        return shiroFilterFactoryBean;
    }

    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator();
        daap.setProxyTargetClass(true);
        return daap;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
        aasa.setSecurityManager(securityManager);
        return aasa;
    }

    //thymeleaf模板引擎和shiro整合時使用
    /*@Bean(name = "shiroDialect")
    public ShiroDialect shiroDialect(){
        return new ShiroDialect();
    }*/

}

ShiroRealm.java

package com.xm.shiro.config;

import java.util.ArrayList;
import java.util.List;

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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.xm.shiro.admin.dao.UPermissionDao;
import com.xm.shiro.admin.dao.URoleDao;
import com.xm.shiro.admin.dao.UUserDao;
import com.xm.shiro.admin.entity.UPermission;
import com.xm.shiro.admin.entity.URole;
import com.xm.shiro.admin.entity.UUser;


/**
 * 獲取使用者的角色和許可權資訊
 * Created by bamboo on 2017/5/10.
 */
public class ShiroRealm extends AuthorizingRealm {

    private Logger logger = LoggerFactory.getLogger(ShiroRealm.class);

    //一般這裡都寫的是servic,我省略了service的介面和實現方法直接呼叫的dao
    @Autowired
    private UUserDao uUserDao;
    @Autowired
    private URoleDao uRoleDao;
    @Autowired
    private UPermissionDao uPermissionDao;

    /**
     * 登入認證
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        logger.info("驗證當前Subject時獲取到token為:" + token.toString());
        //查出是否有此使用者
        UUser hasUser = uUserDao.findByName(token.getUsername());
//        String md5Pwd = new Md5Hash("123", "lucare",2).toString();
        if (hasUser != null) {
            // 若存在,將此使用者存放到登入認證info中,無需自己做密碼對比,Shiro會為我們進行密碼對比校驗
            List<URole> rlist = uRoleDao.findRoleByUid(hasUser.getId());//獲取使用者角色
            List<UPermission> plist = uPermissionDao.findPermissionByUid(hasUser.getId());//獲取使用者許可權
            List<String> roleStrlist=new ArrayList<String>();////使用者的角色集合
            List<String> perminsStrlist=new ArrayList<String>();//使用者的許可權集合
            for (URole role : rlist) {
                roleStrlist.add(role.getName());
            }
            for (UPermission uPermission : plist) {
                perminsStrlist.add(uPermission.getName());
            }
            hasUser.setRoleStrlist(roleStrlist);
            hasUser.setPerminsStrlist(perminsStrlist);
//            Session session = SecurityUtils.getSubject().getSession();
//            session.setAttribute("user", hasUser);//成功則放入session
         // 若存在,將此使用者存放到登入認證info中,無需自己做密碼對比,Shiro會為我們進行密碼對比校驗
            return new SimpleAuthenticationInfo(hasUser, hasUser.getPswd(), getName());
        }
        return null;
    }

    /**
     * 許可權認證
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.info("##################執行Shiro許可權認證##################");
        //獲取當前登入輸入的使用者名稱,等價於(String) principalCollection.fromRealm(getName()).iterator().next();
//        String loginName = (String) super.getAvailablePrincipal(principalCollection);
        UUser user = (UUser) principalCollection.getPrimaryPrincipal();
//        //到資料庫查是否有此物件
//        User user = null;// 實際專案中,這裡可以根據實際情況做快取,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重複執行該方法
//        user = userMapper.findByName(loginName);
        if (user != null) {
            //許可權資訊物件info,用來存放查出的使用者的所有的角色(role)及許可權(permission)
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            //使用者的角色集合
            info.addRoles(user.getRoleStrlist()); 
            //使用者的許可權集合
            info.addStringPermissions(user.getPerminsStrlist()); 

            return info;
        }
        // 返回null的話,就會導致任何使用者訪問被攔截的請求時,都會自動跳轉到unauthorizedUrl指定的地址
        return null;
    }


}

涉及到的兩個方法

  • doGetAuthenticationInfo
    獲取使用者的許可權資訊,這是為下一步的授權做判斷,獲取當前使用者的角色和這些角色所擁有的許可權資訊。

  • doGetAuthorizationInfo
    根據使用者的許可權資訊做授權判斷,這一步是以doGetAuthenticationInfo為基礎的,只有在有使用者資訊後才能根據使用者的角色和授權資訊做判斷是否給使用者授權,因此這裡的Roles和Permissions是使用者的兩個重點判斷依據

本樣例原始碼