主頁 > 後端開發 > ?圖例結合超硬核講解shiro?

?圖例結合超硬核講解shiro?

2021-09-11 10:25:17 後端開發

shiro目錄

  • 前言
  • 一、shiro簡介
  • 二、shiro簡單使用
  • 三、身份驗證
    • 3.1.環境準備
    • 3.2.登錄/退出
  • 四、shiro授權
    • 4.1.shiro授權的三種方式:
    • 4.2.實作
    • 4.3.自定義Realm進行授權
  • 五、與SpringBoot集成案例
  • 六、shiro各個攔截器對比及使用配置
    • 6.1.各種攔截器的特點
    • 6.2.INI檔案配置攔截器
    • 6.3.代碼形式配置攔截器
    • 6.4.簡單使用
  • 七、集成Shiro之Shiro標簽(.jsp和.ftl)
    • 7.1.JSP的Shiro標簽
    • 7.2.Freemark的Shiro標簽
  • 八、會話管理
    • 8.1.會話相關API
    • 8.2.會話監聽器(SessionListener介面)
    • 8.3.SessionDao(會話持久化、提供CRUD操作)
    • 8.4.會話驗證
  • 九、快取
    • 9.1. 集中式快取
    • 9.2. 本地快取
  • 十、shiro修仙進階


前言

目前,使用 Apache Shiro 的人越來越多,因為它相當簡單,對比 Spring Security,可能沒有 Spring Security 做的功能強大,但是在實際作業時可能并不需要那么復雜的東西,所以使用小而簡單的 Shiro 就足夠了,對于它倆到底哪個好,這個不必糾結,能更簡單的解決專案問題就好了


一、shiro簡介

Apache Shiro 是 Java 的一個安全框架,可以非常容易的開發出足夠好的應用,其不僅可以用在 JavaSE 環境,也可以用在JavaEE 環境,Shiro 可以幫助我們完成:認證、授權、加密、會話管理、與 Web 集成、快取等,這不就是我們想要的嘛,而且 Shiro 的 API 也是非常簡單;其基本功能點如下圖所示:
在這里插入圖片描述

Authentication :身份認證/登錄,驗證用戶是不是擁有相應的身份;
Authorization: :授權,即權限驗證,驗證某個已認證的用戶是否擁有某個權限;即判斷用
戶是否能做事情,常見的如:驗證某個用戶是否擁有某個角色,或者細粒度的驗證某個用
戶對某個資源是否具有某個權限;
Session Manager: :會話管理,即用戶登錄后就是一次會話,在沒有退出之前,它的所有信
息都在會話中;會話可以是普通 JavaSE 環境的,也可以是如 Web 環境的;
Cryptography :加密,保護資料的安全性,如密碼加密存盤到資料庫,而不是明文存盤;
Web Support :Web 支持,可以非常容易的集成到 Web 環境;
Caching:快取,比如用戶登錄后,其用戶資訊、擁有的角色/權限不必每次去查,這樣可以
提高效率;
Concurrency :shiro 支持多執行緒應用的并發驗證,即如在一個執行緒中開啟另一個執行緒,能
把權限自動傳播過去;
Testing :提供測驗支持;
Run As :允許一個用戶假裝為另一個用戶(如果他們允許)的身份進行訪問;
Remember Me: :記住我,這個是非常常見的功能,即一次登錄后,下次再來的話不用登錄
了,

注意:Shiro 不會去維護用戶、維護權限;這 些需要我們 自己去 設計/ 提供 ; 然后通過
相應的 介面注入給 給 Shiro 即可,

shiro外部及內部結構:
在這里插入圖片描述
在這里插入圖片描述
具體引數配置什么意思可查看此文:Shiro完整教程, 附帶各種配置

二、shiro簡單使用

1.Idea穿件一個Maven專案
引入pom依賴:

 <dependencies>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>1.2.3</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.6.1</version>
        </dependency>
    </dependencies>

2.任意創建一個包,在里面創建一個Tutorial類

package me.aihe;import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

public class Tutorial {
    private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);   

    public static void main(String[] args) {    
        log.info("My First Apache Shiro Application");       
        System.exit(0);    
    }
}

3.使用Shiro
Shiro提供了一個通用的方案通過 INI 進行配置 ,當然也可以通過XML,YMAL,JSON等進行配置,
在resource目錄下面,創建一個shiro.ini的檔案,內容如下:

# -----------------------------------------------------------------------------
# Users and their (optional) assigned roles
# username = password, role1, role2, ..., roleN
# -----------------------------------------------------------------------------
[users]
root = secret, admin
guest = guest, guest
presidentskroob = 12345, president
darkhelmet = ludicrousspeed, darklord, schwartz
aihe = aihe, goodguy, client

# -----------------------------------------------------------------------------
# Roles with assigned permissions
# roleName = perm1, perm2, ..., permN
# -----------------------------------------------------------------------------
[roles]
admin = *
client = look:*
goodguy = winnebago:drive:eagle5

4.參考Shiro.ini配置進行測驗
現在改變我們的Tutorial類檔案,內容如下

package me.aihe;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Created by aihe on 2017/6/14.
 */
public class Tutorial {

    private static final transient Logger log = LoggerFactory.getLogger(Tutorial.class);
    public static void main(String[] args) {
        log.info("My First Apache Shiro Application");

        //1. 這里的SecurityManager是org.apache.shiro.mgt.SecurityManager,而不是java.lang.SecurityManager
        // 加載組態檔
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");

        //2.決議組態檔,并且回傳一些SecurityManger實體
        SecurityManager securityManager = factory.getInstance();

        //3.設定SecurityManager到靜態記憶體區,單例模式
        SecurityUtils.setSecurityManager(securityManager);


        // 安全操作
        Subject currentUser = SecurityUtils.getSubject();

        // 在應用的當前會話中設定屬性
        Session session =  currentUser.getSession();
        session.setAttribute("key","value");

        //當前我們的用戶是匿名的用戶,我們嘗試進行登錄,
        if (!currentUser.isAuthenticated()){
            UsernamePasswordToken token = new UsernamePasswordToken("aihe", "aihe");

            //this is all you have to do to support 'remember me' (no config - built in!):
            token.setRememberMe(true);

            //嘗試進行登錄用戶,如果登錄失敗了,我們進行一些處理

            try{
                currentUser.login(token);

                //當我們獲登錄用戶之后
                log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");


                // 查看用戶是否有指定的角色
                if ( currentUser.hasRole( "client" ) ) {
                    log.info("Look is in your role" );
                } else {
                    log.info( "....." );
                }

                // 查看用戶是否有某個權限
                if ( currentUser.isPermitted( "look:desk" ) ) {
                    log.info("You can look.  Use it wisely.");
                } else {
                    log.info("Sorry, you can't look.");
                }

                if ( currentUser.isPermitted( "winnebago:drive:eagle5" ) ) {
                    log.info("You are permitted to 'drive' the 'winnebago' with license plate (id) 'eagle5'.  " +
                            "Here are the keys - have fun!");
                } else {
                    log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
                }

                //登出

                currentUser.logout();

            }
            catch ( UnknownAccountException uae ) {
                //賬戶不存在的操作
            } catch ( IncorrectCredentialsException ice ) {
                //密碼不正確
            } catch ( LockedAccountException lae ) {
                //用戶被鎖定了
            } catch ( AuthenticationException ae ) {
                //無法判斷的情形
            }

        }


        System.exit(0);
    }
}

這個相對來說是一個簡單的程式,但也證明了一些shiro的基本用法,我們可以通過shiro進行認證,權限控制等

三、身份驗證

身份驗證,即在應用中誰能證明他就是他本人,一般提供如他們的身份 ID 一些標識資訊來
表明他就是他本人,如提供身份證,用戶名/密碼來證明,
在 shiro 中,用戶需要提供 principals (身份)和 credentials(證明)給 shiro,從而應用能
驗證用戶身份:
principals身份,即主體的標識屬性,可以是任何東西,如用戶名、郵箱等,唯一即可,
一個主體可以有多個 principals,但只有一個 Primary principals,一般是用戶名/密碼/手機號,
credentials證明/憑證,即只有主體知道的安全值,如密碼/數字證書等,
最常見的 principals 和 credentials 組合就是用戶名/密碼了,接下來先進行一個基本的身份認
證,
另外兩個相關的概念是之前提到的 Subject 及 Realm,分別是主體及驗證主體的資料源,

3.1.環境準備

1.引入pom依賴( junit、common-logging 及 shiro-core 依賴)

 <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.9</version>
            </dependency>
            <dependency>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
                <version>1.1.3</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-core</artifactId>
                <version>1.2.2</version>
            </dependency>
        </dependencies>

3.2.登錄/退出

1.原理:

使用** FormAuthenticationFilter過濾器實作, 原理如下:
1.用戶沒有認證時, 就會請求 loginurl進行認證, 用戶身份和用戶密碼提交資料到loginurl地址.
2.資料提交到 loginurl 地址后, 由
FormAuthenticationFilter進行攔截, 并取出 request 中的username 和 password.
3.然后
FormAuthenticationFilter** 會呼叫 realm, 在進行呼叫時會傳入一個 token, 也就是會傳入username 和 password.
4.最后 realm 認真時根據 username 查詢用戶資訊.例如我們之前查詢了用戶選單和 url.
如果查詢不到, realm 回傳 null, ** FormAuthenticationFilter**向 request 域填充一個引數, 這個引數記錄了例外資訊.

2.實作

    //登陸提交地址,和applicationContext-shiro.xml中配置的loginurl一致
    @RequestMapping("login")
    public String login(HttpServletRequest request)throws Exception{
        
        //如果登陸失敗從request中獲取認證例外資訊,shiroLoginFailure就是shiro例外類的全限定名
        String exceptionClassName = (String) request.getAttribute("shiroLoginFailure");
        //根據shiro回傳的例外類路徑判斷,拋出指定例外資訊
        if(exceptionClassName!=null){
            if (UnknownAccountException.class.getName().equals(exceptionClassName)) {
                //最侄訓拋給例外處理器
                throw new CustomException("賬號不存在");
            } else if (IncorrectCredentialsException.class.getName().equals(
                    exceptionClassName)) {
                throw new CustomException("用戶名/密碼錯誤");
            } else if("randomCodeError".equals(exceptionClassName)){
                throw new CustomException("驗證碼錯誤 ");
            }else {
                throw new Exception();//最終在例外處理器生成未知錯誤
            }
        }
        //此方法不處理登陸成功(認證成功),shiro認證成功會自動跳轉到上一個請求路徑
        //登陸失敗還到login頁面
        return "login";
    }

這里一定要注意我們寫的這個 login() 方法, 只是負責處理認證失敗的結果, 如果用戶認證成功那么就會跳轉到上一個請求路徑.

什么是上一個請求路徑呢?
答: 如果你要訪問 xxxx.jsp 但是 shiro 發現你還沒有登錄, 就會進行攔截, 并跳轉到登錄界面例如login.jsp, 當認證成功后會跳轉到xxxx.jsp

3.退出
不用我們實作退出, 只要訪問一個退出 url, 由LogoutFilter攔截住, 清除 session.

/logout.action = logout

4.從 Shiro 的 session 中獲取認證資訊

//因為我們用過 Shiro 用戶認證后是存放在 Shiro 的 session 中.
Subject subject = SecurityUtils.getSubject();
//取出身份資訊
subject.getPrincipal();

四、shiro授權

4.1.shiro授權的三種方式:

1.編程式:通過寫if/else授權代碼塊完成:

Subject subject = SecurityUtils.getSubject();  
if(subject.hasRole(“admin”)) {  
    //有權限  
} else {  
    //無權限  
}

2.注解式:通過在執行的Java方法上放置相應的注解完成(沒有權限將拋出相應的例外):

@RequiresRoles("admin")  
public void hello() {  
    //有權限  
}

3.JSP/GSP標簽:在JSP/GSP頁面通過相應的標簽完成:

<shiro:hasRole name="admin">  
<!— 有權限 —>  
</shiro:hasRole> 

4.2.實作

1.在src包下創建一個shiro-permission.ini組態檔,用于模擬資料庫中的權限資料,內容如下:

#基于用戶的訪問控制
[users]
#用戶zhangsan的密碼是123,此用戶具有role1和role2兩個角色;用戶wang具有role2一個角色
zhangsan=123,role1,role2
wang=123,role2

#基于權限的訪問控制
[roles]
#角色role1對資源user擁有create、update權限
role1=user:create,user:update
#角色role2對資源user擁有create、delete權限
role2=user:create,user:delete
#角色role3對資源user擁有create權限
role3=user:create

對組態檔中的解釋如下:

權限識別符號號規則:資源:操作:實體(中間使用半角:分隔),如下:

user:create:01,表示對用戶資源的01實體進行create操作,
user:create,表示對用戶資源進行create操作,相當于user:create:,對所有用戶資源實體進行create操作,
user:
:01,表示對用戶資源實體01進行所有操作,

然后寫一個測驗類Authorization.java,代碼如下:

public class AuthorizationTest
{
    //角色授權測驗和資源授權測驗
    @Test
    public void testAuthorization()
    {
        //第一步,創建SecurityManager工廠
        Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:shiro-permission.ini");

        //第二步:創建SecurityManager
        SecurityManager securityManager=factory.getInstance();

        //第三步,將SecurityManager設定到系統運行環境,和spring整合后會將SecurityManager配置到spring容器中,一般單例管理
        SecurityUtils.setSecurityManager(securityManager);

        //第四步,創建subject
        Subject subject=SecurityUtils.getSubject();

        //創建token令牌,這里的用戶名和密碼以后由用戶輸入
        UsernamePasswordToken token=new UsernamePasswordToken("zhangsan","123");

        try {
            //執行認證,將用戶輸入的資訊同資料庫(即.ini組態檔)中資訊進行對比
            subject.login(token);
        }catch (AuthenticationException e)
        {
            e.printStackTrace();
        }

        System.out.println("認證狀態:"+subject.isAuthenticated());

        //認證通過后才能執行授權
        //第一種授權方式是基于角色的授權,hasRole傳入角色的標識
        boolean ishasRole=subject.hasRole("role1");//該用戶是否有role1這個角色
        System.out.println("單個角色判斷"+ishasRole);

        //hasAllRoles是否擁有多個角色
        boolean hasAllRoles=subject.hasAllRoles(Arrays.asList("role1","role2"));
        System.out.println("多個角色判斷"+hasAllRoles);//角色的就講到這里了,后面我們都是通過資源進行權限講解


        //使用check方法進行授權,如果授權不通過會拋出例外
        subject.checkRole("role3");

        //第二種授權方式是基于資源的授權,isPermitted傳入權限識別符號
        boolean isPermitted=subject.isPermitted("user:create");//該用戶是否有對user資源進行創建的權限
        System.out.println("單個權限判斷"+isPermitted);

        //多個權限判斷
        boolean isPermittedAll=subject.isPermittedAll("user:create:1","user:update");
        System.out.println("多個權限判斷:"+isPermittedAll);

    }
}

從測驗代碼中我們可以知道,只有當用戶資訊得到認證后才能對用戶進行授權操作,

上面只是一個簡單的入門程式,從代碼中直接讀取資料庫(即組態檔)中的權限資料,然后將其與代碼中的給定權限進行判斷看該用戶是否具有該權限,而實際操作中我們是通過Realm進行讀取資料庫中的資料的,所以接下來講自定義Realm進行授權,

4.3.自定義Realm進行授權

1.需求
上邊的程式通過shiro-permission.ini對權限資訊進行靜態配置,實際開發中從資料庫中獲取權限資料,就需要自定義realm,由realm從資料庫查詢權限資料,

realm根據用戶身份查詢權限資料,將權限資料回傳給authorizer(授權器),

2.自定義Realm
在上篇文章自定義的realm中即CustomRealm.java,修改doGetAuthorizationInfo()方法,代碼如下:


 //上邊是進行認證的方法,在上篇文章中已完成
 
 
 
 //用于授權,當然首先要實作認證
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        //從principals獲取主身份資訊
        //將getPrimaryPrincipal方法回傳值轉為真實身份型別(在上邊的goGetAuthenticationInfo認證通過填充到SimpleAuthenticationInfo)
        String userCode= (String) principals.getPrimaryPrincipal();

        //根據身份資訊獲取權限資訊,
        //模擬從資料庫中獲取到的動態權限資料
        List<String> permissions=new ArrayList<>();
        permissions.add("user:create");//模擬user的創建權限
        permissions.add("items:add");//模擬商品的添加權限

        //查到權限資料,回傳授權資訊(包括上邊的permissions)
        SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();

        //將上邊查詢到授權資訊填充到simpleAuthorizationInfo物件中
        simpleAuthorizationInfo.addStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }

然后要對該自定義Realm進行配置,即將自定義Realm放入SecurityManager中,上篇文章中的shiro-realm.ini組態檔中我們已實作,

然后便可以進行測驗了,測驗代碼如下:

 //自定義realm進行資源授權測驗
    @Test
    public void testAuthorizationCustomRealm()
    {
        //第一步,創建SecurityManager工廠
        Factory<SecurityManager> factory=new IniSecurityManagerFactory("classpath:config/shiro-realm.ini");

        //第二步:創建SecurityManager
        SecurityManager securityManager=factory.getInstance();

        //第三步,將SecurityManager設定到系統運行環境,和spring整合后會將SecurityManager配置到spring容器中,一般單例管理
        SecurityUtils.setSecurityManager(securityManager);

        //第四步,創建subject
        Subject subject=SecurityUtils.getSubject();

        //創建token令牌,這里的用戶名和密碼以后由用戶輸入
        UsernamePasswordToken token=new UsernamePasswordToken("zhangsan","111111");

        try {
            //執行認證,將用戶輸入的資訊同資料庫(即.ini組態檔)中資訊進行對比
            subject.login(token);
        }catch (AuthenticationException e)
        {
            e.printStackTrace();
        }

        System.out.println("認證狀態:"+subject.isAuthenticated());



        //基于資源的授權,呼叫isPermitted方法會呼叫CustomRealm從資料庫查詢正確的權限資料
        // isPermitted傳入權限識別符號,判斷user:create:1是否在CustomRealm查詢到的權限資料之內
        boolean isPermitted=subject.isPermitted("user:create:1");//該用戶是否有對user的1資源進行創建的權限
        System.out.println("單個權限判斷"+isPermitted);

        //多個權限判斷
        boolean isPermittedAll=subject.isPermittedAll("user:create:1","user:create");
        System.out.println("多個權限判斷:"+isPermittedAll);
    }

授權流程講解:

1.對subject進行授權,呼叫方法isPermitted(“permission串”),
2.SecurityManager執行授權,通過ModularRealmAuthorizer執行授權,3.
3.ModularRealmAuthorizer執行realm(自定義的CustomRealm)從資料庫查詢權限資料,即呼叫realm的授權方法:doGetAuthorizationInfo()
4.realm從資料庫查詢權限資料,回傳ModularRealmAuthorizer
5.ModularRealmAuthorizer呼叫PermissionResolver進行權限串比對,
6.如果比對后,isPermitted中"permission串"在realm查詢到權限資料中,說明用戶訪問permission串有權限,否則 沒有權限,拋出例外,

五、與SpringBoot集成案例

SpringBoot整合Shiro(Java安全框架)案例(含原始碼)

六、shiro各個攔截器對比及使用配置

6.1.各種攔截器的特點

簡寫(加粗為常用)名稱優先級(1為最高)說明對應Java類
anon匿名攔截器1不需要登錄就能訪問,一般用于靜態資源,或者移動端介面org.apache.shiro.web.filter.authc.AnonymousFilter
authc登錄攔截器2需要登錄認證才能訪問的資源org.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasicHttp攔截器3Http身份驗證攔截器,非常用型別,不太了解org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
logout登出攔截器4用戶登出攔截器,主要屬性:redirectURL退出登錄后重定向的地址org.apache.shiro.web.filter.authc.LogoutFilter
noSessionCreation不創建會話攔截器5呼叫 subject.getSession(false) 不會有什么問題,但是如果 subject.getSession(true) 將拋出 DisabledSessionException 例外org.apache.shiro.web.filter.authc.NoSessionCreationFilter
prems權限攔截器6驗證用戶是否擁有資源權限org.apache.shiro.web.filter.authc.PermissionsAuthorizationFilter
port埠攔截器7其主要屬性: port(80) 如果用戶訪問該頁面是非 80,將自動將請求埠改為 80 并重定向到該 80 埠org.apache.shiro.web.filter.authc.PortFilter
restrest風格攔截器8rest 風格攔截器,自動根據請求方法構建權限字串構建權限字串;非常用型別攔截器org.apache.shiro.web.filter.authc.HttpMethodPermissionFilter
roles角色攔截器9驗證用戶是否擁有資源角色org.apache.shiro.web.filter.authc.RolesAuthorizationFilter
sslSSL攔截器10只有請求協議是https才能通過,否則你會自動跳轉到https埠(443)org.apache.shiro.web.filter.authc.SslFilter
user用戶攔截器11用戶攔截器,用戶已經身份驗證 / 記住我登錄的都可;org.apache.shiro.web.filter.authc.UserFilter

6.2.INI檔案配置攔截器

1.配置之前,我們還需要講一下 攔截器的通配符的寫法,如下所示:

?:匹配一個字符
*:匹配零個或多個字符
**:匹配零個或多個路徑

2.然后,我們看一下[urls]模塊的示例.這里我只用到了一些常用的攔截器:

[urls]
#不需要登錄
/login=anon
/static/**=anon
#需要登錄
/home=authc
#需要角色
/deleteUser = roles["superAdmin"]
#需要用戶權限
/addUser = perms["user:create"]

3.但是,我們發現一個問題,假設用戶沒有該權限和沒有該角色,或者沒有登錄的時候,還有登出的時候,我們都需要給他們配置對應的重定向路徑.這時候,我們就不能在[urls]模塊中使用了,我們需要在[main]模塊中進行各個情況路徑的重定向設定了.示例代碼如下所示:

[main]
#用戶登錄的地址
authc.loginUrl = /login
#用戶沒有對應角色的跳轉重指向
roles.unauthorizedUrl = /login
#用戶沒有對應權限的跳轉重指向
perms.unauthorizedUrl = /login
#用戶登出的跳轉重指向
logout.redirectUrl = /login

注意:INI檔案中 [urls] 模塊攔截順序是從上往下依次執行.

6.3.代碼形式配置攔截器

使用INI檔案配置攔截器是較為簡潔的配置形式,其實質就是通過Bean注入的形式配置攔截器 接下來我們看一下我們如何通過代碼形式配置攔截器.

配置了核心安全事務管理器 和自定義的權限登錄器.代碼如下所示:

@Configuration
public class ShiroConfiguration {
    //配置核心安全事務管理器
    @Bean(name="securityManager")
    public DefaultWebSecurityManager securityManager(@Qualifier("myShiroRealm") MyShiroRealm myShiroRealm) {
        DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
        manager.setRealm(myShiroRealm);
        return manager;
    }
    //配置自定義的權限登錄器
    @Bean(name="myShiroRealm")
    public MyRealm authRealm() {
        MyRealm myShiroRealm=new MyRealm();
        return myShiroRealm;
    }
}

我們繼續配置攔截器模塊的代碼.整體代碼如下所示:

    @Bean(name="shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager manager) {
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        bean.setSecurityManager(manager);
        //配置登錄的url和登錄成功的url以及驗證失敗的url
        bean.setLoginUrl("/login");
        bean.setSuccessUrl("/home");
        bean.setUnauthorizedUrl("/login");
        //配置訪問權限
        LinkedHashMap<String, String> filterChainDefinitionMap=new LinkedHashMap<>();
        filterChainDefinitionMap.put("/loginUser", "anon");
        filterChainDefinitionMap.put("/static/*", "anon");
        filterChainDefinitionMap.put("/logout*","anon");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }

比較過來,依然發現,使用INI檔案形式配置攔截器要比代碼形式更加簡潔.

6.4.簡單使用

現在我們來拿/html/login = anon和/html/home = authc以及設定重定向的authc.loginUrl = /html/login來簡單說明一下,具體應該如何使用.

我們想讓SpringBoot專案支持Html格式的訪問,那么我們需要在pom.xml檔案中加入如下的Maven依賴.

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

同時,我們要在application.properties檔案中配置如下資訊.設定HTML的存放路徑

spring.thymeleaf.prefix=classpath:/templates/
spring.mvc.view.suffix=.html

目錄結構如下:
在這里插入圖片描述

然后,我們把ShiroConfiguration組態檔進行如下的配置:

@Configuration
public class ShiroConfiguration {

    @Bean(name="shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager manager) {
        ShiroFilterFactoryBean bean=new ShiroFilterFactoryBean();
        bean.setSecurityManager(manager);
        //配置登錄的url和登錄成功的url以及驗證失敗的url
        bean.setLoginUrl("/index");
        bean.setSuccessUrl("/home");
        bean.setUnauthorizedUrl("/index");
        //配置訪問權限
        LinkedHashMap<String, String> filterChainDefinitionMap=new LinkedHashMap<>();
        filterChainDefinitionMap.put("/index", "anon");
        filterChainDefinitionMap.put("/home", "authc");
        filterChainDefinitionMap.put("/**","anon");
        bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return bean;
    }

    //配置核心安全事務管理器
    @Bean(name="securityManager")
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
        return manager;
    }

}

我們在resources目錄下創建兩個html頁面,一個login.html,一個home.html,其中代碼如下所示.

login:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>用戶登錄</title>
</head>
<body>

<div style="text-align: center;margin-top: 100px; font-size: 20px;">用戶登錄界面</div>

</body>
</html>

home:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>主頁面</title>
</head>
<body>

<div style="text-align: center;margin-top: 100px; font-size: 20px;">Home界面</div>

</body>
</html>

然后我們需要撰寫兩個介面來訪問login.html和home.html.這里我創建了一個名為HtmlController的控制器.整體代碼較為簡單,這里就直接黏貼出所有代碼了.如下所示:

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.Map;

@Controller
public class HtmlController {

    @RequestMapping(value = "/index",method = RequestMethod.GET)
    public String userLoginHtmlAction (){
        return "index";
    }

    @RequestMapping(value = "/home",method = RequestMethod.GET)
    public String userHomeHtmlAction (){
        return "home";
    }

}

然后我們就會猜想有如下情況出現,假設我們可以直接通過瀏覽器訪問/html/login,那么我們會直接訪問到login.html ,但是當我們訪問/html/home的時候,由于我們沒有進行用戶登錄認證(根本就沒有做登錄介面.😁),所以我們不可能通過認證,通過登錄重指向,瀏覽器仍然會展示login.html 頁面,下面我們就啟動專案來驗證一下.如下所示,攔截成功.
在這里插入圖片描述
在這里插入圖片描述

七、集成Shiro之Shiro標簽(.jsp和.ftl)

7.1.JSP的Shiro標簽

在JSP中使用Shiro標簽比較簡單,我們只需要注意用法即可.下面我就把所以Shiro標簽放在下面了,各位看官自行查考:

<shiro:guest>
    游客訪問 <a href = "login.jsp"></a>
</shiro:guest>
 
user 標簽:用戶已經通過認證\記住我 登錄后顯示回應的內容
<shiro:user>
    歡迎[<shiro:principal/>]登錄 <a href = "logout">退出</a>
</shiro:user>
 
authenticated標簽:用戶身份驗證通過,即 Subjec.login 登錄成功 不是記住我登錄的
<shiro:authenticted>
    用戶[<shiro:principal/>] 已身份驗證通過
</shiro:authenticted>
 
notAuthenticated標簽:用戶未進行身份驗證,即沒有呼叫Subject.login進行登錄,包括"記住我"也屬于未進行身份驗證
<shiro:notAuthenticated>
    未身份驗證(包括"記住我")
</shiro:notAuthenticated>
 
 
principal 標簽:顯示用戶身份資訊,默認呼叫
Subjec.getPrincipal()獲取,即Primary Principal
<shiro:principal property = "username"/>
 
hasRole標簽:如果當前Subject有角色將顯示body體內的內容
<shiro:hashRole name = "admin">
    用戶[<shiro:principal/>]擁有角色admin
</shiro:hashRole>
 
hasAnyRoles標簽:如果Subject有任意一個角色(或的關系)將顯示body體里的內容
<shiro:hasAnyRoles name = "admin,user">
    用戶[<shiro:pricipal/>]擁有角色admin 或者 user
</shiro:hasAnyRoles>
 
lacksRole:如果當前 Subjec沒有角色將顯示body體內的內容
<shiro:lacksRole name = "admin">
    用戶[<shiro:pricipal/>]沒有角色admin
</shiro:lacksRole>
 
hashPermission:如果當前Subject有權限將顯示body體內容
<shiro:hashPermission name = "user:create">
    用戶[<shiro:pricipal/>] 擁有權限user:create
</shiro:hashPermission>
 
lacksPermission:如果當前Subject沒有權限將顯示body體內容
<shiro:lacksPermission name = "org:create">
    用戶[<shiro:pricipal/>] 沒有權限org:create
</shiro:lacksPermission>

7.2.Freemark的Shiro標簽

1.在SpringBoot里面并不是直接支持JSP檔案的,然后我就在專案中使用了Freemark,但是Shiro標簽并不能直接支持.ftl檔案,所以我們需要先引入一個Maven依賴:


<dependency>
    <groupId>net.mingsoft</groupId>
    <artifactId>shiro-freemarker-tags</artifactId>
    <version>0.1</version>
</dependency>

2.然后,我們寫一個名為ShiroTagsFreeMarkerCfg配置類來對Freemark使用Shiro標簽進行配置.當然了,要確定配置類能被正確注入到Bean中,代碼如下所示:

import com.jagregory.shiro.freemarker.ShiroTags;
import freemarker.template.TemplateModelException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;

import javax.annotation.PostConstruct;

@Component
public class ShiroTagsFreeMarkerCfg {

    @Autowired
    private FreeMarkerConfigurer freeMarkerConfigurer;

    @PostConstruct
    public void setSharedVariable() throws TemplateModelException {
        freeMarkerConfigurer.getConfiguration().setSharedVariable("shiro", new ShiroTags());
    }
}

3.配置類搞好之后,我們就可以在.ftl檔案中使用Shiro的權限標簽了,標簽格式如下所示:

<@shiro.guest>  
游客訪問 <a href = "login.jsp"></a>
</@shiro.guest> 

user 標簽:用戶已經通過認證\記住我 登錄后顯示回應的內容
<@shiro.user>  
歡迎[<@shiro.principal/>]登錄,<a href="/logout.html">退出</a>  
</@shiro.user>   

 authenticated標簽:用戶身份驗證通過,即 Subjec.login 登錄成功 不是記住我登錄的
<@shiro.authenticated>  
    用戶[<@shiro.principal/>]已身份驗證通過  
</@shiro.authenticated>   
 
notAuthenticated標簽:用戶未進行身份驗證,即沒有呼叫Subject.login進行登錄,包括"記住我"也屬于未進行身份驗證
<@shiro.notAuthenticated>
    當前身份未認證(包括記住我登錄的)
</@shiro.notAuthenticated> 
 
 
principal 標簽:顯示用戶身份資訊,默認呼叫
Subjec.getPrincipal()獲取,即Primary Principal
<@shiro.principal property="username"/>

 
hasRole標簽:如果當前Subject有角色將顯示body體內的內容
<@shiro.hasRole name="admin">  
    用戶[<@shiro.principal/>]擁有角色admin<br/>  
</@shiro.hasRole> 
 
hasAnyRoles標簽:如果Subject有任意一個角色(或的關系)將顯示body體里的內容
<@shiro.hasAnyRoles name="admin,user,member">  
用戶[<@shiro.principal/>]擁有角色admin或user或member<br/>  
</@shiro.hasAnyRoles>   
 
lacksRole:如果當前 Subjec沒有角色將顯示body體內的內容
<@shiro.lacksRole name="admin">  
用戶[<@shiro.principal/>]不擁有admin角色
</@shiro.lacksRole>   
 
hashPermission:如果當前Subject有權限將顯示body體內容
<@shiro.hasPermission name="user:add">  
    用戶[<@shiro.principal/>]擁有user:add權限
</@shiro.hasPermission>   

lacksPermission:如果當前Subject沒有權限將顯示body體內容
<@shiro.lacksPermission name="user:add">  
    用戶[<@shiro.principal/>]不擁有user:add權限
</@shiro.lacksPermission> 

八、會話管理

會話管理器管理著應用中所有 Subject 的會話的創建、維護、洗掉、失效、驗證等作業,
Shiro 的核心組件
,頂層組件 SecurityManager 直接繼承了 SessionManager,且提供了
SessionsSecurityManager 實 現 直 接 把 會 話 管 理 委 托 給 相 應 的 SessionManager
DefaultSecurityManagerDefaultWebSecurityManager 默認 SecurityManager 都繼承了
SessionsSecurityManager

8.1.會話相關API

Subject.getSession()
獲取會話,等價于Subject.getSession(true),即如果當前沒有創建session物件會創建一個;Subject.getSession(false),如果當前沒有創建session物件則回傳null,
Subject.getSession(true)
session.getId()
獲取當前會話的唯一標識,
session.getHost()
獲取當前會話的主機地址,
session.getTimeout() & session.setTimeout(毫秒)
設定/獲取當前Session的過期時間,
session.getStartTimestamp() & session.getLastAccessTime()
獲取會話的啟動時間及最后訪問時間;如果是J2SE環境需要自己定期呼叫session.touch()去更新最后訪問時間;如果是Web環境,每次進入ShiroFilter都會自動呼叫session.touch()來更新最后訪問時間,
session.touch() & session.stop()
更新會話最后訪問時間以及銷毀會話;Subject.logout()會自動呼叫session.stop(),在Web應用中,呼叫HttpSession.invalidate()也會自動呼叫session.stop()來銷毀shiro的會話,
session.setAttribute(key,val) & session.getAttribute(key) & session.removeAttribute(key)
設定/獲取/洗掉 會話屬性,

8.2.會話監聽器(SessionListener介面)

①onStart(Session)
監聽會話創建事件
②onStop(Session)
監聽會話銷毀事件
③onExpiration(Session)
監聽會話過期事件

8.3.SessionDao(會話持久化、提供CRUD操作)

在這里插入圖片描述

AbstractSessionDAO 提供了 SessionDAO 的基礎實作,如生成會話ID等,
CachingSessionDAO 提供了對開發者透明的會話快取的功能,需要設定相應的 CacheManager,
MemorySessionDAO 直接在記憶體中進行會話維護,
EnterpriseCacheSessionDAO 提供了快取功能的會話維護,默認情況下使用 MapCache 實作,內部使用 ConcurrentHashMap 保存快取的會話,
在這里插入圖片描述

tips:在實際開發中,如果要用到SessionDAO組件,可以自定義類實作自EnterpriseCacheSessionDAO類,為其注入sessionIdGenerator屬性,如果用到快取的話還可以注入一個快取的名字,最后將這個SesionDAO組件注入給SessionManager(會話管理器),最后將SessionManager配置給SecurityManager,下圖是一個完整的配置細節,

8.4.會話驗證

①Shiro提供了會話驗證調度器,用于定期的驗證會話是否已過期,如果過期將停止會話,
②出于性能考慮,一般情況下都是獲取會話的同時來驗證會話是否過期并停止會話的;但是如果在Web環境中,如果用戶不主動退出是不知道會話是否過期的,因此需要定義的檢測會話是否過期,Shiro提供了會話驗證調度器來定期檢查會話是否過期,SessionValidationScheduler
③Shrio也提供了使用Quartz會話驗證調度器 QuartzSessionValidationScheduler

九、快取

方法一:

在securityManager配置中添加cacheManager配置項,會注入到realm中,
在這里插入圖片描述

方法二:在realm中配置,

realm本身實作了CacheManagerAware介面

public interface CacheManagerAware {

    /**
     * Sets the available CacheManager instance on this component.
     *
     * @param cacheManager the CacheManager instance to set on this component.
     */
    void setCacheManager(CacheManager cacheManager);
}

securityManager不只會幫realm注入cacheManager,還會幫sessionManager注入cacheManager

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="redisCacheManager" />
        <property name="realm" ref="myRealm" />
        <property name="sessionManager" ref="sessionManager"/>
    </bean>

這樣的話,realm和sessionManager就不用再配快取

9.1. 集中式快取

基于Redis的集中式快取方案:https://github.com/alexxiyang/shiro-redis

基于Memcached的集中式快取方案:https://github.com/mythfish/shiro-memcached

基于Ehcache集群模式的存放方案:添加鏈接描述

9.2. 本地快取

本地快取的實作有幾種方式:

(1)直接存放到JVM堆記憶體
(2)使用NIO存放在堆外記憶體,自定義實作或者借助于第三方快取組件,

不論是采用集中式快取還是使用本地快取,shiro的權限資料本身都是直接存放在本地的,不同的是快取標志的存放位置,采用本地快取方案是,我們將快取標志也存放在本地,這樣就避免了查詢快取標志的網路請求,能更進一步提升快取效率,

十、shiro修仙進階

本篇對于shiro的學習到這就結束了 如果有不全或者不足的地方

說明網址
shiro視頻教程shiro視頻教程
shiro系列博客shiro系列博客
shiro具體操作shiro具體操作
shiro官方檔案shiro官方檔案

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/299174.html

標籤:java

上一篇:2021最全演算法和資料結構學習路線——java大廠面試必備??持續更新,建議收藏

下一篇:我看了1000+招聘啟事,原來大廠偏愛這種程式員……

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more