Shiro 核心功能案例講解 基於SpringBoot 有源碼__shrio

來源:互聯網
上載者:User
Shiro 核心功能案例講解 基於SpringBoot 有源碼

從實戰中學習Shiro的用法。本章使用SpringBoot快速搭建項目。整合SiteMesh架構布局頁面。整合Shiro架構實作用中身分識別認證,授權,資料加密功能。通過本章內容,你將學會使用者權限的分配規則,SpringBoot整合Shiro的配置,Shiro自訂Realm的建立,Shiro標籤式授權和註解式授權的使用情境,等實戰技能,還在等什麼,快來學習吧。

技術:SpringBoot,Shiro,SiteMesh,Spring,SpringDataJpa,SpringMVC,Bootstrap-sb-admin-1.0.4
說明:前端使用的是Bootstrap-sb-admin模版。注意文章貼出的代碼可能不完整,請以github上源碼為主,謝謝。
源碼:https://github.com/ITDragonBlog/daydayup/tree/master/Shiro 喜歡的朋友可以鼓勵(star)下。
效果圖:

Shiro 功能介紹

四個核心:登入認證,許可權驗證,會話管理,資料加密。
六個支援:支援WEB開發,支援緩衝,支援線程並發驗證,支援測試,支援使用者切換,支援”記住我”功能。

• Authentication :身份認證,也可以理解為登入,驗證使用者身份。
• Authorization :許可權驗證,也可以理解為授權,驗證使用者是否擁有某個許可權;即判斷使用者是否能進行什麼操作。
• Session Manager :會話管理,使用者登入後就是一次會話,在退出前,使用者的所有資訊都在會話中。
• Cryptography :資料加密,保護資料的安全性,常見的有密碼的加鹽加密。
• Web Support :支援Web開發。
• Caching :緩衝,Shiro將使用者資訊、擁有的角色/許可權資料緩衝,以提高程式效率。
• Concurrency :支援多線程應用的並發驗證,即在一個線程中開啟另一個線程,Shiro能把許可權自動傳播過去。
• Testing :提供測試支援。
• Run As :允許一個使用者以另一個使用者的身份進行訪問;前提是兩個使用者運行切換身份。
• Remember Me :記住我,常見的功能,即登入一次後,在指定時間內免登入。

Shiro 架構介紹

三個角色:目前使用者 Subject,安全管理器 SecurityManager,許可權佈建網域 Realm。

• Subject :代表目前使用者,提供了很多方法,如login和logout。Subject 只是一個門面,與Subject的所有互動都會委託給SecurityManager,SecurityManager才是真正的執行者;
• SecurityManager :安全管理器;Shiro的核心,它負責與Shiro的其他組件進行互動,即所有與安全有關的操作都會與SecurityManager 互動;且管理著所有的 Subject;
• Realm :Shiro 從 Realm 擷取安全資料(如使用者、角色、許可權),SecurityManager 要驗證使用者身份,必需要從 Realm 擷取相應的使用者資訊,判斷使用者身份是否合法,判斷使用者角色或許可權是否授權。 SpringBoot 整合SiteMesh

SiteMesh 是一個網頁布局和修飾的架構,利用它可以將網頁的內容和頁面結構分離,以達到頁面結構共用的目的。

SiteMesh 統一了頁面的風格,減少了重複代碼,提高了頁面的複用率,是一款值得我們去學習的架構(也有很多坑)。當然,今天的主角是Shiro,這裡只介紹它的基本用法。
SpringBoot 整合SiteMesh只需二個步驟:
第一步:配置攔截器FIlter,並在web中註冊bean。
第二步:建立裝飾頁面,引入常用的css和js檔案,統一系統樣式。 配置攔截器FIlter

指定攔截的URL請求路徑,指定裝飾頁面的檔案全路徑,指定不需要攔截的URL請求路徑。這裡攔截所有請求到裝飾頁面,只有登入頁面和靜態資源不攔截。

import org.sitemesh.builder.SiteMeshFilterBuilder;import org.sitemesh.config.ConfigurableSiteMeshFilter;/** * 配置SiteMesh攔截器FIlter,指定裝飾頁面和不需要攔截的路徑 * @author itdragon */public class WebSiteMeshFilter extends ConfigurableSiteMeshFilter{      @Override      protected void applyCustomConfiguration(SiteMeshFilterBuilder builder) {          builder.addDecoratorPath("/*", "/WEB-INF/layouts/default.jsp")  // 配置裝飾頁面               .addExcludedPath("/static/*")    // 靜態資源不攔截               .addExcludedPath("/login**");    // 登入頁面不攔截    }  }
import org.springframework.boot.web.servlet.FilterRegistrationBean;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;/** * web.xml 配置 * @author itdragon */@Configurationpublic class WebConfig {    @Bean   // 配置siteMesh3    public FilterRegistrationBean siteMeshFilter(){        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();        WebSiteMeshFilter siteMeshFilter = new WebSiteMeshFilter();        filterRegistrationBean.setFilter(siteMeshFilter);        return filterRegistrationBean;    }}
建立裝飾頁面

SiteMesh文法
<sitemesh:write property='title'/> : 被修飾頁面title的內容會在這裡顯示。
<sitemesh:write property='head'/> : 被修飾頁面head的內容會在這裡顯示,除了title。
<sitemesh:write property='body'/> : 被修飾頁面body的內容會在這裡顯示。
需要注意的是:SiteMesh的jar有OpenSymphony(最新版是2009年)和Apache(最新版是2015年),兩者用法是有差異的。筆者選擇的是Apache版本的jar。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html><html>    <head>        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">        <meta name="viewport" content="initial-scale=1.0, width=device-width, user-scalable=no" />        <title>ITDragon系統-<sitemesh:write property='title'/></title>        <link type="image/x-icon" href="images/favicon.ico" rel="shortcut icon">        <c:set var="ctx" value="${pageContext.request.contextPath}" />        <link href="${ctx}/static/sb-admin-1.0.4/css/bootstrap.min.css" rel="stylesheet">        <link href="${ctx}/static/sb-admin-1.0.4/css/sb-admin.css" rel="stylesheet">        <sitemesh:write property='head'/>    </head>    <body>        <div id="wrapper">            <%@ include file="/WEB-INF/layouts/header.jsp"%>            <div class='mainBody'>              <sitemesh:write property='body'/>            </div>        </div>        <script src="${ctx}/static/sb-admin-1.0.4/js/jquery.js"></script>        <script src="${ctx}/static/sb-admin-1.0.4/js/bootstrap.min.js"></script>    </body></html>
SpringBoot 整合Shiro

這是本章的核心知識點,SpringBoot 整合Shiro 有三個步驟:
第一步:建立實體類:使用者,角色,許可權。確定三者關係,以方便Realm的授權工作。
第二步:建立自訂安全資料來源Realm:負責使用者登入認證,使用者操作授權。
第三步:建立Spring整合Shiro配置類:配置攔截規則,生命週期,安全管理器,安全資料來源,等。 建立實體類

實體類:User,SysRole,SysPermission。
許可權設計思路:
1). 角色表確定系統功能表資源,許可權表確定菜單操作資源。
2). 使用者主要通過角色來擷取許可權,且一個使用者可以擁有多個角色(不推薦,但必須支援該功能)。
3). 一個角色可以擁有多個許可權,同時也可以有用多個使用者。
4). 一個許可權可以被多個角色使用。
5). 工作都是從易到難,我們可以先從“一個使用者擁有一個角色,一個角色擁有多個許可權”開始。
有了上面的分析,三個實體類代碼如下,省略了get/set方法。

import java.util.List;import javax.persistence.Entity;import javax.persistence.FetchType;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.JoinColumn;import javax.persistence.JoinTable;import javax.persistence.ManyToMany;import javax.persistence.Table;import javax.persistence.Transient;/** * 使用者實體類 * @author itdragon */@Table(name="itdragon_user_shiro")@Entitypublic class User {    @Id    @GeneratedValue(strategy=GenerationType.AUTO)    private Long id;                        // 自增長主鍵,預設ID為1的帳號為超級管理員    private String account;                 // 登入的帳號    private String userName;                // 註冊的暱稱    @Transient    private String plainPassword;           // 登入時的密碼,不持久化到資料庫    private String password;                // 加密後的密碼    private String salt;                    // 用於加密的鹽    private String iphone;                  // 手機號    private String email;                   // 郵箱    private String platform;                // 使用者來自的平台    private String createdDate;             // 使用者註冊時間    private String updatedDate;             // 使用者最後一次登入時間    @ManyToMany(fetch=FetchType.EAGER)    @JoinTable(name = "SysUserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "roleId") })    private List<SysRole> roleList;         // 一個使用者擁有多個角色    private Integer status;                 // 使用者狀態,0表示使用者已刪除}
import java.util.List;import javax.persistence.Entity;import javax.persistence.FetchType;import javax.persistence.GeneratedValue;import javax.persistence.Id;import javax.persistence.JoinColumn;import javax.persistence.JoinTable;import javax.persistence.ManyToMany;import javax.persistence.Table;/** * 角色表,決定使用者可以訪問的頁面 * @author itdragon */@Table(name="itdragon_sysrole")@Entitypublic class SysRole {    @Id    @GeneratedValue    private Integer id;     private String role;        // 角色    private String description; // 角色描述    private Boolean available = Boolean.FALSE; // 預設不可用    //角色 -- 許可權關係:多對多關係; 取出這條資料時,把它關聯的資料也同時取出放入記憶體中    @ManyToMany(fetch=FetchType.EAGER)    @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")})    private List<SysPermission> permissions;    // 使用者 - 角色關係:多對多關係;    @ManyToMany    @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")})    private List<User> users;}
import java.util.List;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.Id;import javax.persistence.JoinColumn;import javax.persistence.JoinTable;import javax.persistence.ManyToMany;import javax.persistence.Table;/** * 許可權表,決定使用者的具體操作 * @author itdragon */@Table(name = "itdragon_syspermission")@Entitypublic class SysPermission {    @Id    @GeneratedValue    private Integer id;    private String name;        // 名稱    private String url;         // 資源路徑    private String permission;  // 許可權字串 如:employees:create,employees:update,employees:delete    private Boolean available = Boolean.FALSE; // 預設不可用    @ManyToMany    @JoinTable(name = "SysRolePermission", joinColumns = { @JoinColumn(name = "permissionId") }, inverseJoinColumns = {@JoinColumn(name = "roleId") })    private List<SysRole> roles;}
建立自訂安全資料來源Realm

Shiro 從 Realm 擷取安全資料(如使用者、角色、許可權),SecurityManager 身份認證和許可權認證都是從Realm中擷取相應的使用者資訊,然後做比較判斷是否有身份登入,是否有許可權操作。
Shiro 支援多個Realm。同時也有不同的認證策略:
• FirstSuccessfulStrategy : 只要有一個Realm成功就返回,後面的忽略;
• AtLeastOneSuccessfulStrategy : 只要有一個Realm成功就通過,返回所有認證成功的資訊,預設;
• AllSuccessfulStrategy : 必須所有Realm都成功才算通過

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.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.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import com.itdragon.pojo.SysPermission;import com.itdragon.pojo.SysRole;import com.itdragon.pojo.User;import com.itdragon.service.UserService;/** * 自訂安全資料Realm,重點 * @author itdragon */public class ITDragonShiroRealm extends AuthorizingRealm {    private static final transient Logger log = LoggerFactory.getLogger(ITDragonShiroRealm.class);    @Autowired    private UserService userService;    /**     * 授權     */    @Override    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {        log.info("^^^^^^^^^^^^^^^^^^^^ ITDragon 配置目前使用者許可權");        String username = (String) principals.getPrimaryPrincipal();        User user = userService.findByAccount(username);        if(null == user){            return null;        }        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();        for (SysRole role : user.getRoleList()) {            authorizationInfo.addRole(role.getRole());  // 添加角色            for (SysPermission permission : role.getPermissions()) {                authorizationInfo.addStringPermission(permission.getPermission());  // 添加具體許可權            }        }        return authorizationInfo;    }    /**     * 身份認證     */    @Override    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token)            throws AuthenticationException {        log.info("^^^^^^^^^^^^^^^^^^^^ ITDragon 認證使用者身份資訊");        String username = (String) token.getPrincipal(); // 擷取使用者登入帳號        User userInfo = userService.findByAccount(username); // 通過帳號查加密後的密碼和鹽,這裡一般從緩衝讀取        if(null == userInfo){            return null;        }        // 1). principal: 認證的實體資訊. 可以是 username, 也可以是資料表對應的使用者的實體類對象.         Object principal = username;        // 2). credentials: 加密後的密碼.         Object credentials = userInfo.getPassword();        // 3). realmName: 當前 realm 對象的唯一名字. 調用父類的 getName() 方法        String realmName = getName();        // 4). credentialsSalt: 鹽值. 注意類型是ByteSource        ByteSource credentialsSalt = ByteSource.Util.bytes(userInfo.getSalt());        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);        return info;    }}
建立Spring整合Shiro配置類

第一步:配置Shiro攔截器,指定URL請求的許可權。首先靜態資源和登入請求匿名訪問,然後是使用者登出操作,最後是所有請求都需身份認證。Shiro攔截器優先順序是從上到下,切勿將/**=authc,放在前面。
第二步:配置Shiro生命週期處理器,
第三步:配置自訂Realm,負責身份認證和授權。
第四步:配置安全管理器SecurityManager,Shiro的核心。

import java.util.LinkedHashMap;import java.util.Map;import org.apache.shiro.authc.credential.HashedCredentialsMatcher;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.slf4j.Logger;import org.slf4j.LoggerFactory;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;/** * Shiro 配置,重點 * @author itdragon */@Configurationpublic class ShiroSpringConfig {    private static final transient Logger log = LoggerFactory.getLogger(ShiroSpringConfig.class);    /**     * 配置攔截器     *       * 定義攔截URL許可權,優先順序從上到下      * 1). anon  : 匿名訪問,無需登入      * 2). authc : 登入後才能訪問      * 3). logout: 登出     * 4). roles : 角色過濾器     *      * URL 匹配風格     * 1). ?:匹配一個字元,如 /admin? 將匹配 /admin1,但不匹配 /admin 或 /admin/;     * 2). *:匹配零個或多個字串,如 /admin* 將匹配 /admin 或/admin123,但不匹配 /admin/1;     * 2). **:匹配路徑中的零個或多個路徑,如 /admin/** 將匹配 /admin/a 或 /admin/a/b     *      * 配置身分識別驗證成功,失敗的跳轉路徑     */    @Bean    public ShiroFilterFactoryBean shirFilter(DefaultWebSecurityManager securityManager) {        log.info("^^^^^^^^^^^^^^^^^^^^ ITDragon 配置Shiro攔截工廠");        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();        shiroFilterFactoryBean.setSecurityManager(securityManager);        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();        filterChainDefinitionMap.put("/static/**", "anon"); // 靜態資源匿名訪問        filterChainDefinitionMap.put("/employees/login", "anon");// 登入匿名訪問        filterChainDefinitionMap.put("/logout", "logout");  // 使用者退出,只需配置logout即可實現該功能        filterChainDefinitionMap.put("/**", "authc");       // 其他路徑均需要身份認證,一般位於最下面,優先順序最低        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);        shiroFilterFactoryBean.setLoginUrl("/login");       // 登入的路徑        shiroFilterFactoryBean.setSuccessUrl("/dashboard"); // 登入成功後跳轉的路徑        shiroFilterFactoryBean.setUnauthorizedUrl("/403");  // 驗證失敗後跳轉的路徑        return shiroFilterFactoryBean;    }    /**     * 配置Shiro生命週期處理器     */    @Bean    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){        return new LifecycleBeanPostProcessor();    }    /**     * 自動建立代理類,若不添加,Shiro的註解可能不會生效。     */    @Bean    @DependsOn({"lifecycleBeanPostProcessor"})    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();        advisorAutoProxyCreator.setProxyTargetClass(true);        return advisorAutoProxyCreator;    }    /**     * 開啟Shiro的註解     */    @Bean    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(){        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());        return authorizationAttributeSourceAdvisor;    }    /**     * 配置加密匹配,使用MD5的方式,進行1024次加密     */    @Bean    public HashedCredentialsMatcher hashedCredentialsMatcher() {        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();        hashedCredentialsMatcher.setHashAlgorithmName("MD5");        hashedCredentialsMatcher.setHashIterations(1024);        return hashedCredentialsMatcher;    }    /**     * 自訂Realm,可以多個     */    @Bean    public ITDragonShi

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.