北極圈漏油環保浩劫 俄羅斯拘捕電廠3人_台中搬家公司

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

摘錄自2020年6月11日中央社報導

西伯利亞工業城諾里爾斯克附近電廠柴油外洩,造成北極圈環保大災難。俄羅斯調查人員今(10日)拘捕電廠主管與工程人員共三人。

俄羅斯礦業集團諾里爾斯克鎳業公司(Norilsk Nickel)子公司在諾里爾斯克(Norilsk)經營的一座電廠5月29日發生貯油槽塌陷意外,導致超過2萬1000公噸柴油外洩。

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

參與清理工作的西伯利亞石油運輸公司(Transneft Siberia)總指揮布隆尼科夫(Viktor Bronnikov)表示,現場工作人員已見到漏油對當地生態的第一波衝擊,包括麝鼠與野鴨死亡。

事件調查委員會表示,已拘留3名涉嫌觸犯環境保護法規的人士,包括電廠廠長史米爾諾夫(Pavel Smirnov)與2名工程人員。3人若被定罪,最高可處5年有期徒刑。

生態保育
公害污染
污染治理
生物多樣性
國際新聞
俄羅斯
北極圈
漏油事件
漏油污染
河川污染
水污染

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

OPPO Find X3 Lite 完整實機開箱照洩漏!只是外觀有點眼熟?_網頁設計公司

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

前段時間 OPPO 正式在台灣推出 Reno5 系列新機,在 2021 年也傳聞將在第一季 Find X 系列就會推出最新的 Find X3 系列新機。不過在稍早有一款 OPPO Find X3 Lite 的實機外觀提前被曝光!從這些提前被洩漏的完整盒裝配件和標籤,都能肯定這就是 Find X3 Lite 國外市售版的實機。不過眼尖的人可能會發現,這款手機其實有些眼熟。

▲圖片來源:Sudhanshu(Twitter/@Sudhanshu1414)

OPPO Find X3 Lite 完整實機開箱照洩漏!只是外觀有點眼熟?

稍早在 Twitter 由 Sudhanshu 釋出了一批 OPPO Find X3 Lite 的完整實機開箱照片,從中我們可見到 Find X3 Lite 採用四鏡頭主相機、支持螢幕指紋辨識並附贈了軟質保護套和 SuperVOOC 快速充電器。

▲圖片來源:Sudhanshu(Twitter/@Sudhanshu1414)

從其他張照片能清楚看到 Find X3 Lite 的機身正反面,其螢幕採用平面的 OLED 挖孔全螢幕,在機身背面配備四鏡頭主相機。從這些照片各位想必已經覺得它有些眼熟吧?其實這款 OPPO Find X3 Lite 5G 應是針對特定市場推出的更名機型,而它的「本體」正是兩週前才剛在台灣正式發表的 OPPO Reno5 。

▲圖片來源:Sudhanshu(Twitter/@Sudhanshu1414)

OPPO Reno5(Find X3 Lite)與 Reno5 Pro 單從機身背面很難分辨,但從機身正面的螢幕則可區分兩者之間的不同。首先, OPPO Reno5 配備 6.43 吋 FHD+ 解析度 OLED 螢幕,螢幕為平面。至於 Reno5 Pro 則配備更大的 6.55 吋 FHD+ 解析度 OLED 曲面螢幕,兩者皆支援 90Hz 螢幕更新率和 180Hz 觸控採樣率。

▲Reno5(圖左)|Reno5 Pro(圖右)

然而,其實最早提出這則爆料的事 Evan Blass(@evleaks),他在兩週前就已經於 Voice 預告 OPPO 將以 Reno5 推出換名在其他市場上市的 Find X3 Lite ,當時也釋出官方渲染圖(如下):

▲圖片來源:Evan Blass(Voice/@evleaks)

這類將同款產品,針對不同銷售市場更換新的名稱在當地上市是相當常見的。例如之前部分 Redmi 和小米的手機、智慧手錶在引進台灣後,可能會採用不同的名稱。另外,前陣子 realme 在中國市場推出 realme V15 ,近期也將在印度市場更名為 realme X7 在印度上市。

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

消息來源:Sudhanshu(Twitter/@Sudhanshu1414)|Evan Blass(Voice/@evleaks)

延伸閱讀:
OPPO Reno5 、Reno5 Pro 正式在台發表:全新升級 AI 錄影、獨家 Reno Glow 2.0 星鑽工藝打造精緻外觀

realme Watch 2 智慧手錶通過 FCC 認證,外觀、規格提前曝光!電池容量增加、續航翻倍

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

官網釋出多張iPhone12的全球風景照 全部出自使用者之手_如何寫文案

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

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

對於智慧型手機的用戶而言,照片拍得好不好看,很多時候都是選擇手機的標準之一。而近日蘋果就在官方網站上一口氣貼出許多風景照片,來強調iPhone在拍照上的優異性能。據悉這些照片都是使用iPhone 12系列手機拍攝完成,而且投稿的人都是世界各地iPhone的使用者喔。

↑一片水面映照出上方廣闊的天空。 以 iPhone 12 Pro Max 拍攝;拍攝者:Pieter de Vries,澳洲。

蘋果在官方網站上表示,iPhone 12 與 iPhone 12 mini 的雙相機系統,配備大尺寸超廣角相機和全新廣角相機,具有 ƒ/1.6 光圈,可提供更多 27% 的光線,有效提升在低光源環境中所拍攝的照片與影片。

↑用來遮陽的紅色防水布呈現出小孩嬉戲的倒影。 以 iPhone 12 mini 拍攝,拍攝者:Joe Panpiansin,泰國。

 

↑一個人的藍色眼睛特寫鏡頭。 以 iPhone 12 mini 拍攝,拍攝者:Andrey Glazunov,俄羅斯。

 

↑一個人在城市的街道上映射出長長的倒影。 以 iPhone 12 mini 拍攝,拍攝者:Matti Haapoja,加拿大。

 

↑在敞開的門外,坐在廢墟與陰影中的人物。 以 iPhone 12 拍攝,拍攝者:Bo Liu,中國。

 

↑帶有交錯格子派皮的派。以 iPhone 12拍攝,拍攝者:Nilay Örnek,土耳其。

 

↑站在對著水的碼頭末端的女子。以 iPhone 12拍攝,拍攝者:Bahar Akıncı,土耳其。

 

iPhone 12 Pro 與 iPhone 12 Pro Max 的相機系統配備超廣角、廣角與望遠相機,iPhone 12 Pro Max 配備 65 mm焦距的望遠相機,並提供 5 倍光學變焦範圍,以及廣角鏡頭,而放大 47% 的感光元件,具有 1.7 微米像素,在低光源環境中拍攝表現可大幅提升 87%。

 

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

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

↑夜間拍攝在沙漠中坐在營火前的一名男子。以 iPhone 12 Pro 拍攝,拍攝者:Abdullah Shaijie,科威特。

↑杜拜天際線景觀。以 iPhone 12 Pro 拍攝,拍攝者:Abdullah Shaijie,科威特。

↑前景的紅葉刻劃出遠處的瀑布。以 iPhone 12 Pro 拍攝,拍攝者:Ikuchika Aoyama,日本。

↑雙紅橋的尖頂直達天際。以 iPhone 12 Pro 拍攝,拍攝者:Ikuchika Aoyama,日本。

 

↑一個人在黃色牆壁樓梯間的平台上擺姿勢。以 iPhone 12 Pro 拍攝;拍攝者:Sarah M. Lee,英國。

 

看了世界各地的照片,是不是又燃起你想要出國的念頭呢?去年由於疫情的關係,大家幾乎都沒法出門,就先看些照片過過乾癮吧!而最後也提醒一下,對於拍照而言,器材固然重要,但很多時候,如果你想要拍攝出好的照片,其實腦袋與想法會比器材還要重要喔。

 

↑一名女子的人像照,頭頂懸掛著成串發光紅色紙燈籠的女子。以 iPhone 12 Pro Max 拍攝,拍攝者:NKCHU,中國。

↑街上的一大片水坑映射出周遭環境與上方的天空。以 iPhone 12 Pro Max 拍攝;拍攝者:Neal Kumar,美國。

↑義大利的雨中街景。以 iPhone 12 Pro Max 拍攝,拍攝者:Calogero Agrò,義大利。

↑站在海灘上的黑白人物照片,可透過浮木的格子窺見。以 iPhone 12 Pro Max 拍攝,拍攝者:Hélène Hadjiyianni,法國。

↑光影灑落在男孩的臉上。以 iPhone 12 Pro Max 拍攝,拍攝者:Rohit Vohra,印度。

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

專業的才看的到!美國白宮新官網暗藏玄機 原始碼竟然有「招募啟示」_網頁設計公司

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

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

美國新任總統拜登於20日正式宣布就職,而美國白宮網站也在當天進行了改版。這次的改版除了新增時下流行的「深色模式」外,同時也有字體放大、縮小的功能。而有媒體發現,其實在網站的原始碼中,竟然暗藏一個不為人知的彩蛋。

有國外媒體發現,在白宮官方網站的原始碼中,竟然暗藏了一個非常特別的彩蛋,如果仔細鑽研這些原始碼,可以發現工程師在原始碼中寫下「If you’re reading this, we need your help building back better. 」(如果你正在閱讀這段文字,我們需要你的協助來進行更好的重建),同時還附上一個網址「https://usds.gov/apply」 ,而這段網址則是指向「美國數位服務小組(U.S. Digital Service,USDS)」的官方網站。這個非常特別的招募啟示,真的不是專業的高手根本不會注意到。

 

美國數位服務小組成立於2014年,在歐巴馬任期內成立的單位。由於當時美國政府各個單位、各地方政府都在進行數位化,觀念與知識彼此之間有嚴重落差不說,彼此之間的資料甚至還無法共通,於是歐巴馬就成立數為服務小組,主要工作就是由小組內經驗老道的工程師領導,協助美國政府的各個部門進行數位化的工作。

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

 

就過往來講,這次白宮網頁在原始碼中藏彩蛋,也並非是首例,就過往來講,也有非常多案例可以查詢,只是為何工程師們都習慣在原始罵內搞鬼呢?小編曾經詢問過業界相關人員,就有工程師朋友表示,其實身為一個專業的工程師,平時真的會習慣動不動就打開原始碼頁面來觀看,所以對於一般民眾覺得很難發現的原始碼,其實工程師們真的天天打開,一點都不困難,對他們就跟吃飯喝水一樣簡單。

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

api.versioning 版本控制 自動識別最高版本_網頁設計公司

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

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

Microsoft.AspNetCore.Mvc.Versioning //引入程序集

.net core 下面api的版本控製作用不需要多說,可以查閱https://www.cnblogs.com/dc20181010/p/11313738.html

普通的版本控制一般是通過鏈接、header此類方法進行控制,對ApiVersionReader進行設置,例如

services.AddApiVersioning(o => {
                //o.ReportApiVersions = true;//返回版本可使用的版本
                o.ApiVersionReader = ApiVersionReader.Combine(new HeaderApiVersionReader("api-version"), new QueryStringApiVersionReader("api-version"));//通過Header或QueryString進行傳值來判斷api的版本
//o.DefaultApiVersion
= new ApiVersion(1, 0);//默認版本號
});

或者使用https://www.cnblogs.com/tdfblog/p/asp-net-core-api-versioning.html這種方式

這兩種方式都需要傳遞api的版本信息,如果不傳遞將會報錯

{"error":{"code":"ApiVersionUnspecified","message":"An API version is required, but was not specified.","innerError":null}}

如果我們不想傳遞api的版本信息時,可以將

o.AssumeDefaultVersionWhenUnspecified = true; //此選項將用於在沒有版本的情況下提供請求
o.DefaultApiVersion = new ApiVersion(1, 0); //設置默認Api版本是1.0

打開,這個我們每次請求如果不傳遞版本信息也不會報錯了,但我們的請求將會指向1.0版本,那麼我想讓默認版本指向我寫的api裏面的最高版本怎麼做?

我們將默認版本修改為最高版本可以嗎?

這裏將會出現一個問題,我的api版本可能由於各種各樣原因造成最高版本不一致的問題

所以我們不能採用指定默認版本是最高版本的方法來解決,這個最高版本還必須要是動態的,通過翻閱https://github.com/microsoft/aspnet-api-versioning/wiki/API-Version-Selector#current-implementation-api-selector可以得知

The CurrentImplementationApiVersionSelector selects the maximum API version available which does not have a version status. 
If no match is found, it falls back to the configured DefaultApiVersion. For example, if the versions "1.0", "2.0", and "3.0-Alpha" are available,
then "2.0" will be selected because it's the highest, implemented or released API version. CurrentImplementationApiVersionSelector選擇不具有版本狀態的最大可用API版本。 如果找不到匹配項,它將回退到配置的DefaultApiVersion。
例如,如果提供版本“
1.0”,“ 2.0”和“ 3.0-Alpha”,則將選擇“ 2.0”,因為它是最高,已實施或已發布的API版本。

services.AddApiVersioning( options => options.ApiVersionSelector = new CurrentImplementationApiVersionSelector( options ) );

通過這個版本選擇器,我們可以將最大版本得出,修改上面services.AddApiVersioning

services.AddApiVersioning(o => {
                o.ReportApiVersions = true;//返回版本可使用的版本
                //o.ApiVersionReader = new UrlSegmentApiVersionReader();
                //o.ApiVersionReader = ApiVersionReader.Combine(new HeaderApiVersionReader("api-version"), new QueryStringApiVersionReader("api-version"));
                //o.ApiVersionReader = ApiVersionReader.Combine(new QueryStringApiVersionReader("api-version"));
                o.ApiVersionReader = ApiVersionReader.Combine(new HeaderApiVersionReader("api-version"));//版本號以什麼形式,什麼字段傳遞
                o.AssumeDefaultVersionWhenUnspecified = true;//此選項將用於在沒有版本的情況下提供請求
                o.DefaultApiVersion = new ApiVersion(1, 0);//默認版本號
                o.ApiVersionSelector = new CurrentImplementationApiVersionSelector(o);//默認以當前最高版本進行訪問
            });

舉個栗子

namespace Default.v1.Controllers
{
    [ApiVersion("1.0")]
    [Route("[controller]/[action]")]
    [ApiController]
    public class HomeController : Controller, IBaseController
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController (ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        public JsonResult GetJson()
        {
            return Json("Home 1.0");
        }
}

Default.v1.Controllers.Home

namespace Default.v2.Controllers
{
    [ApiVersion("2.0")]
    [Route("[controller]/[action]")]
    [ApiController]
    public class HomeController : Controller, IBaseController
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController (ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        public JsonResult GetJson()
        {
            return Json("Home 2.0");
        }
}

Default.v2.Controllers.Home

namespace Default.v1.Controllers
{
    [ApiVersion("1.0")]
    [Route("[controller]/[action]")]
    [ApiController]
    public class TestController : Controller, IBaseController
    {
        private readonly ILogger<HomeController> _logger;

        public TestController (ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        public JsonResult GetJson()
        {
            return Json("Test 1.0");
        }
}

Default.v1.Controllers.Test

 

 

 

 我們在

請求/home/getjson 時返回“Home 2.0”

請求/test/getjson 時返回“Test 1.0”

這樣就可以動態的請求最高版本了

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

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

 

但是還是會有問題的,比如,在我添加了Area和User區域下的HomeController,且User區域下的HomeController增加了1.0和3.0版本之後,神奇的一幕出現了

我的HomeController進不去了。。。

{"error":{"code":"UnsupportedApiVersion","message":"The HTTP resource that matches the request URI 'https://localhost:44311/home/getjson' is not supported.","innerError":null}}

這個時候去google都查不到原因。。。

查看api-supported-versions,返回的是1.0,2.0,3.0。。。我的api版本控制被污染了3.0版本從哪裡來的哪?第一反應是從User區域來的

我現在在User區域下添加一個除了Home和Test以外Name的Controller就可以請求成功,這個讓我懷疑到是不是api.versioning本身的問題,首先懷疑的是Controller的Name問題,源碼拉取下來,從添加版本控制的地方(services.AddApiVersioning)開始找

 

 

 

最後終於在ApiVersionCollator中找到了蛛絲馬跡

///https://github.com/microsoft/aspnet-api-versioning/blob/master/src/Microsoft.AspNetCore.Mvc.Versioning/Versioning/ApiVersionCollator.cs

namespace Microsoft.AspNetCore.Mvc.Versioning
{
    using Microsoft.AspNetCore.Mvc.Abstractions;
    using Microsoft.AspNetCore.Mvc.Controllers;
    using Microsoft.Extensions.Options;
    using System;
    using System.Collections.Generic;
    using System.Linq;

    /// <summary>
    /// Represents an object that collates <see cref="ApiVersion">API versions</see> per <see cref="ActionDescriptor">action</see>.
    /// </summary>
    [CLSCompliant( false )]
    public class ApiVersionCollator : IActionDescriptorProvider
    {
        readonly IOptions<ApiVersioningOptions> options;

        /// <summary>
        /// Initializes a new instance of the <see cref="ApiVersionCollator"/> class.
        /// </summary>
        /// <param name="options">The current <see cref="ApiVersioningOptions">API versioning options</see>.</param>
        public ApiVersionCollator( IOptions<ApiVersioningOptions> options ) => this.options = options;

        /// <summary>
        /// Gets the API versioning options associated with the collator.
        /// </summary>
        /// <value>The current <see cref="ApiVersioningOptions">API versioning options</see>.</value>
        protected ApiVersioningOptions Options => options.Value;

        /// <inheritdoc />
        public int Order { get; protected set; }

        /// <inheritdoc />
        public virtual void OnProvidersExecuted( ActionDescriptorProviderContext context )
        {
            if ( context == null )
            {
                throw new ArgumentNullException( nameof( context ) );
            }

            foreach ( var actions in GroupActionsByController( context.Results ) )
            {
                var collatedModel = CollateModel( actions );

                foreach ( var action in actions )
                {
                    var model = action.GetProperty<ApiVersionModel>();

                    if ( model != null && !model.IsApiVersionNeutral )
                    {
                        action.SetProperty( model.Aggregate( collatedModel ) );
                    }
                }
            }
        }

        /// <inheritdoc />
        public virtual void OnProvidersExecuting( ActionDescriptorProviderContext context ) { }

        /// <summary>
        /// Resolves and returns the logical controller name for the specified action.
        /// </summary>
        /// <param name="action">The <see cref="ActionDescriptor">action</see> to get the controller name from.</param>
        /// <returns>The logical name of the associated controller.</returns>
        /// <remarks>
        /// <para>
        /// The logical controller name is used to collate actions together and aggregate API versions. The
        /// default implementation uses the "controller" route parameter and falls back to the
        /// <see cref="ControllerActionDescriptor.ControllerName"/> property when available.
        /// </para>
        /// <para>
        /// The default implementation will also trim trailing numbers in the controller name by convention. For example,
        /// the type "Values2Controller" will have the controller name "Values2", which will be trimmed to just "Values".
        /// This behavior can be changed by using the <see cref="ControllerNameAttribute"/> or overriding the default
        /// implementation.
        /// </para>
        /// </remarks>
        protected virtual string GetControllerName( ActionDescriptor action )
        {
            if ( action == null )
            {
                throw new ArgumentNullException( nameof( action ) );
            }

            if ( !action.RouteValues.TryGetValue( "controller", out var key ) )
            {
                if ( action is ControllerActionDescriptor controllerAction )
                {
                    key = controllerAction.ControllerName;
                }
            }

            return TrimTrailingNumbers( key );
        }

        IEnumerable<IEnumerable<ActionDescriptor>> GroupActionsByController( IEnumerable<ActionDescriptor> actions )
        {
            var groups = new Dictionary<string, List<ActionDescriptor>>( StringComparer.OrdinalIgnoreCase );

            foreach ( var action in actions )
            {
                var key = GetControllerName( action );

                if ( string.IsNullOrEmpty( key ) )
                {
                    continue;
                }

                if ( !groups.TryGetValue( key, out var values ) )
                {
                    groups.Add( key, values = new List<ActionDescriptor>() );
                }

                values.Add( action );
            }

            foreach ( var value in groups.Values )
            {
                yield return value;
            }
        }

        static string TrimTrailingNumbers( string? name )
        {
            if ( string.IsNullOrEmpty( name ) )
            {
                return string.Empty;
            }

            var last = name!.Length - 1;

            for ( var i = last; i >= 0; i-- )
            {
                if ( !char.IsNumber( name[i] ) )
                {
                    if ( i < last )
                    {
                        return name.Substring( 0, i + 1 );
                    }

                    return name;
                }
            }

            return name;
        }

        static ApiVersionModel CollateModel( IEnumerable<ActionDescriptor> actions ) => actions.Select( a => a.GetApiVersionModel() ).Aggregate();
    }
}

View Code

 

其中GroupActionsByController將Controller按照Controller的名字進行分組,再看看內部,分組的時候將GetControllerName( action )作為key,那麼GetControllerName是幹嘛的,

protected virtual string GetControllerName( ActionDescriptor action )
        {
            if ( action == null )
            {
                throw new ArgumentNullException( nameof( action ) );
            }

            if ( !action.RouteValues.TryGetValue( "controller", out var key ) )
            {
                if ( action is ControllerActionDescriptor controllerAction )
                {
                    key = controllerAction.ControllerName;
                }
            }

            return TrimTrailingNumbers( key );
        }

這個方法原本是沒有問題的,但是牽扯到Area的時候就會出問題了。。它將根目錄下的HomeController和User.HomeController視為同一類的Controller然後去做版本的屬性注入,造成CurrentImplementationApiVersionSelector選擇器選不到正確的版本,所以返回了上面的錯誤,我們將GetControllerName內部修改為

protected virtual string GetControllerName( ActionDescriptor action )
        {
            if ( action == null )
            {
                throw new ArgumentNullException( nameof( action ) );
            }

            if ( !action.RouteValues.TryGetValue( "controller", out var key ) )
            {
                if ( action is ControllerActionDescriptor controllerAction )
                {
                    key = controllerAction.ControllerName;
                }
            }

            if ( !action.RouteValues.TryGetValue( "area", out var area ) )
            {
            }

            return TrimTrailingNumbers( area + key );
        }

這樣就可以走通了

 

我們有兩種解決辦法,一個是把源碼拉取下來,方法修改掉,項目的依賴項替換為自己修改的Microsoft.AspNetCore.Mvc.Versioning,另一種辦法是將services.AddApiVersioning重寫。。。請相信我,拉取修改替換依賴比重寫services.AddApiVersioning快且簡便。。。

issue:https://github.com/microsoft/aspnet-api-versioning/issues/630

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

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

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

Physic Design:Floorplan算法概覽_如何寫文案

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

僅用於學習交流,轉載請聯繫本人。

 

1 floorplan是什麼

floorplan常被翻譯成布圖規劃,是指在芯片級別上對模塊進行布局,也就是哪個單元放在什麼地方,但是單元內部的具體布局並不關心。該步驟為芯片版圖設計中的關鍵步驟之一,因為在這一步會影響芯片的面積、能否成功布線以及布線線長等諸多關鍵指標和步驟。

圖1. 布圖規劃就是確定這些Block的形狀和相對位置,但是對Block內部不關注,一個Block可能是一個加法器或者其它什麼。至於圖中所示的三種類型(臨接,通道、臨界+通道),目前是不重要的。圖片引用自 http://www.signoffsemi.com/floorplan-placement-2/

 

2 目標是什麼

floorplan的首要目標就是給模塊指定一個最佳的形狀(如果需要指定的話),給出模塊間的最佳的相對位置關係。這裏需要先區分一個概念,module和block(這裏對應的中文我記成模塊磚塊)。一個加法器的原理圖是一個模塊,也就是我們知道一個加法器是由一個半加器和進位電路組成的,但是在版圖中這個加法器長寬分別是多少,我們是不知道的。當其長寬確定下來之後,就稱成為一個磚塊(Block),就相當於磚,和建房子的磚沒啥區別,就是說可以用這個磚塊來構建版圖啦,所以翻譯成磚塊。在設計好芯片的邏輯關係后,更具輸入的是磚塊還是模塊可能面臨三種狀況:

(1)輸入的對象都是磚塊(block)。也就是說,構建版圖時,使用的基本構圖單元(加法器)已經被規定好了,只能使用,不能改變形狀和大小。但是這些磚塊間的位置是floorplan需要去確定的。

(2)輸入的對象是模塊(module)。也就是說,構建版圖時,使用的基本構圖單元(加法器)沒有被規定死,可以指定其長寬比例(面積基本上變不了,因為加法器里的東西需要地方放)。這種情況下,floorplan需要為這些模塊指定長寬比例使得模塊變成磚塊,然後還要指出這些磚塊之間的相對位置。

(3)當然是混合型,輸入既有磚塊又有模塊。略。

 

但是這裡有一個問題就是什麼樣算是最佳?或者換一種說法,顯然floorplan是一個優化問題,那麼優化的目標是什麼呢?一般來說有一下幾點:

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

銷售文案是什麼?A文案是廣告用的文字。舉凡任何宣傳、行銷、販賣商品時所用到的文字都是文案。在網路時代,文案成為行銷中最重要的宣傳方式,好的文案可節省大量宣傳資源,達成行銷目的。

(1)使芯片面積最小;

(2)使得模塊之間的連線最短,這也就意味着延遲小;

(3)模塊之間擁塞盡可能小,也就是說,模塊之間有足夠的空間讓互連線通過;

優化目標可能是其中一個也可能是其中的很多,如果要同時滿足多個優化目標的話,可以為這些目標分配權重,形成一個綜合的優化目標函數。(但筆者認為,這實在是一種有些天真但在一定程度上可接受的做法,相當於超參數)

f=a*area+b*wirelength+c*congestion (a+b+c=1)

3 常見的算法有哪些

在這裏,再重述一遍floorplan的主要目標,(1)指定模塊的形狀,(2)給出模塊間的相對位置。目前幾乎所有的数字芯片設計都會使用物理設計EDA工具,其中自然就包含floorplan工具,既然是自動化工具,就比然有相應的算法。總體上來說,floorplan可以分為三類(Susmita Sur-Koay):

(1)構造型算法。這一類算法旨在指出磚塊之間的相對位置。這一類算法有slicing embedding,hierarchical enumeration和dual graph等。

(2)迭代型算法。這一類算法旨在將模塊變成磚塊,也就是為模塊確定長寬比。這一類算法有模擬退火,遺傳算法,力導向等。

(3)基於特定知識的算法。這一類算法企圖同時完成這兩件事情。

 

參考文獻

Susmita Sur-Koay. Handbook of Algorithms for Physical Design Automation. p140.

 

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

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

什麼是銷售文案服務?A就是幫你撰寫適合的廣告文案。當您需要販售商品、宣傳活動、建立個人品牌,撰寫廣告文案都是必須的工作。

踏上嵌入式之路,一去不復返!_網頁設計公司

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

大家好,昨天又有一批新的小夥伴加入,這裏重新簡單介紹一下自己的情況。和很多應屆生一樣,剛畢業有着找工作的迷茫和擔憂(這裏不包含已經工作的前輩),這是我去年也有過這樣的經歷;今天有一個網友加我微信,說在b站上看了我的介紹(b站ID:TXP嵌入式),學習經歷很像他,說到這裏,我也不由噓噓的感慨以前的時光,確實我大學裏面不是本專業的,是學汽車的,在學校里的時光,和朋友搞過一年多的汽車保養和維修的創業(其實也不算創業,當時想着提高自己的技術,這算是第一次體驗過如何去經營一個組織的經驗,確實當老闆沒有想象的那麼簡單,要有各方面的抗壓技能等等!),下面我就簡單說一下,我是如何從零基礎到linux開發崗位的一個經驗介紹:

 

正如上面所說,我是跨專業來學电子的,在幹了一年左右的汽車保養和維修,我果斷的放棄了汽車行業(其實最為直接的原因,就是出來上班工資太低,而且以我的體質不適合干汽車維修,為啥這樣說:舉個簡單,在進學院的實驗室管理員,這個過程中要經過大概三個月的魔鬼訓練,每天跑步大概6公里左右,然後跑完做各種難度的動作,到後面舉汽車的輪胎也是有的,說實話,這個我真舉不起,有些輪胎太大了,實在沒有力氣舉(我本身人又瘦小,所以你懂的當時是有多麼難的),後面我挺過來了,進了實驗室(這裏我很羡慕學电子或者計算機的網友能夠進入本學院的實驗室,順便再插一句,我學歷沒有很多大佬那麼高,我只是一名非常普通的大專生),這個過程我不展開詳細講了,今天的核心內容是如何從零基礎到linux開發崗位。

 

我是大二下學期快要結束的時候開始正式學編程(其實大二也有學,但是中途參加過兩次國家行業大賽:汽車新能源服務大賽和智能交通大賽,其中一次獲得三等獎(這裏非常感謝當時學院和老師的指導以及搭檔老王,這兩次比賽經歷算是我大學裏面最難忘的記憶了)。其實我學編程說實話,我的大學班主任算是我的啟蒙老師(這裏真的非常感謝,不然現在我自己也不知道做啥行業,沒有方向感),他給我推薦了一本c語言書籍和野火的開發板來學習,後面我就一發不可收拾了,一直自學堅持到現在,這其中走的彎路太多,比如:在我自己建的一個交流群裏面,經常有網友問,到底學哪個好,其實當初我也是這樣,在入門嵌入式過程中,會發現真的太多東西要學,苦於當時身邊沒有人指導一下方向(我的班主任他是研究生畢業,一出來就來到我們學校當老師了,當時也有找老師談過,但是自己還是沒有找到方向),所以學的過程中非常累,抓不住重點,看到那樣好,就馬上跟風去學,其實自己內心沒有去真正思考過這個技術到底是不是自己喜歡和以後吃飯的傢伙(這是站在我今天已經工作的角度簡單分析的。),因為嵌入式太廣了,甚至很多人連嵌入式到底是啥也不知道(所謂“嵌入式”,名如其文,指在很多芯片中,其所包含的處理器就像嵌入在裏面不為人知一樣,嵌入式領域的應用非常之廣泛,是處理器除了服務器和PC領域之外的主要應用領域:實時(Real Time)嵌入式領域(也就是我們常說的RTOS)、移動(Mobile)領域(比如要搞複雜的系統:linux、安卓等)、傳統嵌入式領域(一般就是單片機裸機了)),其實這段話,我是在網絡上看到一個前輩總結出的,結合自身體驗,確實是分為這三大領域。

 

回到剛才學嵌入式過程的哪個話題,其實在過程中最大的難處在於:缺乏鋼鐵一般的毅力(如果真喜歡嵌入式這個行業的話,一定要堅持下去,會有意想不到的結果)、良好的學術氛圍(我指的是自學的人沒有好學習的環境和交流,全靠一個人走“夜路”,走到哪裡算哪裡,也不知道到底是對還是錯)、缺乏項目經驗;第三點是很多人最為缺乏的(包括我自己也是這樣,其實有的網友說,學校做的畢設和企業裏面做的項目差別太大了,要考慮到很多方面,這個說的確實是這樣,但是我要說的是,先把學校畢設和一些小項目,認真做好,技術經驗和解決問題的能力就是在一個一個的小項目中成長的,帶領你如何走進嵌入式開發的大門,所以在學校裏面的實戰,一定要認真做好,有機會和對嵌入式感興趣的,可以去實驗室,不是說非要進入實驗室,只是說實驗室的環境稍微可能要好一點;把學校裏面的項目做好,後面有機會可以擴展到企業項目,只要你有能力,機會總會來的,就怕你沒有實力和準備,不然就算有機會來的,你也只能幹瞪眼和干著急,沒啥用啊,因為你不會啊,這就是現實;以後出來上班也是這樣,你憑什麼拿高薪,只有有能力給公司產生利潤,老闆就願意給你高薪,因為你有能力,值得這個價。)這裏可能又扯外了,哈哈,不過現實就是如此,只有自己有能力,資源和一切等,你都有機會得到。

 

這學習嵌入式過程,我自己走的彎路確實走的太多,而且很多東西又沒有學會,比如說一開始學過51單片機,後面看到別人玩pic、avr單片機,我也跟着去玩,最後發現自己還是沒有學會pic和avr,就是因為自己學的雜亂無比,其實這裏我建議在學的過程中,最好去網絡上找小項目練手,這樣會學的比較好,不要只看視頻教程學,這樣是學不到啥東西的,只是說帶你走了一個過程,熟悉了一下這種類型的開發以及一些協議和外設等等,實際你自己真要動手去做的話,你會發現不知道從哪裡下手,所以說要多練,養成一個良好的編程習慣以及當拿到一個小項目的時候,我該怎麼去入手,做好大體規劃,然後再去深入細節(這裏模塊化思維很重要,真的);然後後面,發現python和opencv好火,又去跟風學這個,又走了彎路,又沒有學會,這裏你可以發現我,不知道自己真正要學什麼,以後往哪裡走,只是一味的跟風(說的難聽一點,就是在裝逼);後面臨近畢業我真正開始意識到這一點,我得先深入一個方向去學,然後再去擴展,於是乎,我最開始是從pcb去深入的,我報了一個培訓班,認真學了三個月,確實出去能夠幹活了,這裏非常感謝凡億的鄭老師很助教的辛苦付出。在我學完pcb就直接去了一個非常小的公司裏面實習,軟件和硬件都要做,那時候我軟件很差勁,就畫板好一點,畫兩層板和四層還行,六層的不行,沒有實戰過,學的時候,跟實戰還是有差別的,而且小公司裏面不做那麼高層的板子,六層板的話,成本還是蠻高的,更別說8層、12層那種板子了。

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

 

那我是如何接觸到linux的呢?沒錯,我在那家小公司實習了兩個月,我就走了,為啥,因為我認識到了自己到底要啥了,這個時候我無意之間買了朱有鵬老師的嵌入式linux核心課程,對於我這種小白來說,這課程可以改變了我職業發展,這是我真心話,因為我出來上班,直接跳過做單片機,直接上手linux,其實這裏我也是比較幸運的,自己也比較大膽,在今天特殊情況下,一來深圳就辭職了上家公司(我正式上班的第一家公司是做手機屏幕,不算是驅動,只是簡單調試显示和觸摸,也不是我喜歡的;當然這裏我是有準備跳槽的。)在上家公司我又待了兩個月左右,這兩個月,我也不知道自己是怎麼走過來的,我拼了命在學朱老師課程,從最基本的linux命令(之前在學校的時候我接觸過,但是太零散了。)、以及基本的c語言基礎、c語言提高、linux應用編程,到現在的linux內核和驅動,在那兩個月里,我每天下班都會去看課程去學(晚上下班7:30.有時候晚一點,早上上班9點,然後周末是單休),每天晚上我都學到凌晨兩點左右,這和我學習pcb的時候,一樣,這種感覺現在回憶起來,讓人噓噓。期間我也開始寫公眾號了(把自己學的知識點,總結出來,並分享出去,讀者也可以發現,我寫的文章並不好,沒有深度,但是這是一個人成長的過程),就是我現在這個公眾號,這期間,我改變了自己的學習方式,養成了喜歡和別人交流技術,也認識了很多前輩和網友,這裏他們的幫助對我影響也非常大(過程中得到了很多指導以及一些網友提的不足之處,讓我糾正了很多誤區。)。

 

其實我去找linux崗位的時候,內心也是非常害怕,怕找不到工作的,因為我linux應用,只是學完了課程內容知識,並沒去找項目練手,只是想快點上手linux;於是乎,我在過年期間就開始頭簡歷,這過程確實難了,和現在有網友找工作的感受一樣;我投了很多簡歷,但是只收到的回復不是很多,因為缺乏項目經驗以及學歷的原因,沒有達到要求,但是我明白,這個過程是自己成長的過程,並沒有放棄對linux崗位的追求,終於後面經過在假期的磨鍊,我又投了幾家,終於收到了offer(這其中我自己又去認真的把課程中的一個網絡編程小項目又熟悉了一邊,以及c語言的基礎,即使是到現在我依然會去買一些書籍,繼續去鞏固基礎):

 

 

後面通過了面試,就順利拿到了linux崗位開發了。這裏我可能我沒有說的很具體,但是這其中的艱辛只有我自己知道,多少次想過放棄從事這個行業,但是又一次次我跌倒了,又重新站起來了,因為我奶奶經常告誡我:年輕的時候要多吃點苦,先苦后甜的道理,我聽過我奶奶講她以前的故事,那時候的苦,我的苦比起她吃的苦,簡直不能比)。

 

好了,今天就分享到這裏了,這就是我的簡單故事,平常和普通,只有靠自己的努力和行動才能得到你想要的。後面我會繼續分享技術文章;如果有網友想進交流群的話,可以加我微信,回復技術,我會拉您進群,之所以這樣,是為了防止有些人,進了交流群裏面,老是發一些與技術無關的鏈接和廣告,這樣的話,就失去了交流群的意義了。最後,說一句:感謝各位網友的支持。

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

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

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

面試問題—JAVA程序CPU佔用過高怎麼定位_網頁設計

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

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

今天一個電話面試問了這個問題。回來查了下答案,自己也順帶操作一遍,做個記錄。之前只知道jstack工具可以查看線程狀態這些。比如死鎖這些,主要是之前不知道top -H -p pid這個命令的使用,這命令可以看到進程下麵線程信息,拿到線程ID,然後再結合jstack命令使用就可以解決這個問題了。下面記錄一下具體的操作步驟:

1.打個jar包丟到機器上運行

package com.nijunyang.test;



public class TestApplication {

    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            new Thread(()->test()).start();
        }

    }

    public static void test() {
        while (true) {
            int a = 1  + 6;
            System.out.println(a);
        }
    }
}

使用這個maven插件 打包jar

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-assembly-plugin</artifactId>
                <configuration>
                    <archive>
                        <manifest>
                            <mainClass>com.nijunyang.test.TestApplication</mainClass>
                        </manifest>
                    </archive>
                    <descriptorRefs>
                        <descriptorRef>jar-with-dependencies</descriptorRef>
                    </descriptorRefs>
                </configuration>
                <executions>
                    <execution>
                        <id>make-assembly</id>
                        <phase>package</phase>
                        <goals>
                            <goal>single</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>

2. java -jar test-0.0.1-SNAPSHOT-jar-with-dependencies.jar  運行程序

 

 一直在輸出

3.top |grep java  或者 jps指令找到java進程的pid(6167)

 

 

4. top -H -p pid   以線程的形式查看該進程 top -H -p 6167

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

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

 

 因為我們程序是起了50個線程 所以這裏就會展示這個進程中的所有線程呢

5.前面的線程ID是10進制的,,需要轉換成16進制,,因為等下在jstack命令取出來的線程ID是16進制的:這裏就隨便選一個線程ID 去轉換了,真實環境肯定是選擇CPU佔用率最高的那個線程,echo “obase=16;6219” | bc

 

 

6.jstack 6167 >threadInfo.txt   信息輸出到文件 然後查看。也可以直接在命令裏面查看

 

7.文件中查找184b的線程ID信息,就可以找到是哪個線程導致的內存佔用過高,同時也能看到具體的代碼位置

 

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

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

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

SpringSecurity(1)—認證+授權代碼實現_貨運

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

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

認證+授權代碼實現

Spring Security是 一種基於 Spring AOP 和 Servlet 過濾器的安全框架。它提供全面的安全性解決方案,同時在 Web 請求級和方法調用級處理身份確認和授權。

有關認證和授權的理論知識,之前有寫過相關博客。了解權限管理

一、SpringSceurity工作流程

網上找一張圖,覺得畫的挺好的,比較容易理解。不然換的是源碼流程圖很難去理解。

圖片地址 : 地址 可以單機放大看更加清楚

要想理解這張圖建議看下這篇博客,因為這張圖中需要自定義的My…類,在文章中都有說明,所以更好理解點。

Spring Boot Security 詳解

二、認證+授權代碼

這裏只展示一些核心代碼,具體完整代碼放在github上。

1、UserDetails接口

Security 中的用戶接口,我們自定義用戶類要實現該接口, 用於向security中注入當前用戶的姓名密碼,和擁有的角色。同時也包含一些其它信息,比如當前用戶是否過期,

賬號是否鎖定等等。

自己定義User實現這個接口

public class User implements UserDetails {
    private String username;
    private String password;
    private List<Role> roles;
    /**
     * 獲取用戶名
     */
    @Override
    public String getUsername() {
        return username;
    }
    /**
     * 獲取密碼
     */
    @Override
    public String getPassword() {
        return password;
    }
    /**
     * 用戶的權限集, 默認需要添加ROLE_ 前綴
     */
    @Override
    @JsonIgnore
    public List<GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (Role role : roles) {
            authorities.add(new SimpleGrantedAuthority("ROLE_" + role.getName()));
        }
        return authorities;
    }
    /**
     * 賬戶是否過期
     */
    @Override
    @JsonIgnore
    public boolean isAccountNonExpired() {
        return true;
    }
    /**
     * 賬戶是否鎖定
     */
    @Override
    @JsonIgnore
    public boolean isAccountNonLocked() {
        return true;
    }
    /**
     * 憑證是否過期
     */
    @Override
    @JsonIgnore
    public boolean isCredentialsNonExpired() {
        return true;
    }
    /**
     * 用戶是否可用
     */
    @Override
    public boolean isEnabled() {
        return true;
    }  
}

2、UserDetailsService

Security 中的用戶 Service,自定義用戶服務類需要實現該接口。這個接口只有一個方法需要我們去實現,那就是通過用戶名去獲取用戶信息。這裏也是和數據庫交互獲取

用戶認證和授權信息的地方。

@Service
@Slf4j
public class UserService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        //TODO 正常應該查詢數據庫獲取用戶和用戶的權限
//        User user = userMapper.loadUserByUsername(userName);
//        List<Role> roles = rolesMapper.getRolesByUid(user.getId());
//        user.setRoles(roles);
        log.info("登陸用戶名: {}", userName);
        //通過用戶名查詢到的密碼 密碼肯定是加密過的 這裏明文密碼是 123456
        String password = "e10adc3949ba59abbe56e057f20f883e";
        //用戶對應權限
        List<Role> roles = Lists.newArrayList(new Role(1L, "教師"), new Role(2L, "學生"));
        User user = new User(userName, password, roles);
        return user;
    }
}

注意 這裏的明文密碼是 123456,也就是用戶輸入這個才能完成認證。授權的話當前用戶有兩個角色 教師學生。在下面測試的時候會用到。

3、WebSecurityConfigurerAdapter

它是Spring Security的Java 配置類。創建類SecurityConfiguration繼承 WebSecurityConfigurerAdapter,來對我們應用中所有的安全相關的事項(

所有url,驗證用戶名密碼,表單重定向等)進行控制。

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 1、配置的是認證信息, AuthenticationManagerBuilder 這個類,就是AuthenticationManager的建造者, 我們只需要向這個類中, 配置用戶信息,
     *    就能生成對應的AuthenticationManager, 這個類也提過,是用戶身份的管理者, 是認證的入口, 因此,我們需要通過這個配置,想security提供真實的用戶身份。
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    }
    /**
     * 2、配置Security的認證策略, 每個模塊配置使用and結尾。這個也是最複雜的
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    }
    /**
     * 3、這個配置方法用於配置靜態資源的處理方式,可使用 Ant 匹配規則。就是可以不用認證就可以直接訪問的接口
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
    }
}

完整示例

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserService userService;
    /**
     * 密碼驗證器
     */
    @Autowired
    private PassWordEncorder passWordEncorder;
    /**
     * 成功處理器
     */
    @Autowired
    private AuthenctiationSuccessHandler authenctiationSuccessHandler;

    /**
     * 失敗處理器
     */
   @Autowired
   private AuthenctiationFailHandler authenctiationFailHandler;
   /**
    * 向Security注入用戶信息
    */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService).passwordEncoder(passWordEncorder);
    }
    /**
     * 配置規則
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //開啟登陸配置
        http.authorizeRequests()
                // 登錄之後就能訪問
                .antMatchers("/no-authorize").authenticated()
                // 登陸后 需要校長角色權限
                .antMatchers("/need-authorize").hasRole("校長")
                // 其他的路徑都是登錄后即可訪問
                .anyRequest().authenticated()
                .and().formLogin()
                // 定義登錄頁面,未登錄時,訪問一個需要登錄之後才能訪問的接口,會自動跳轉到該頁面
                .loginPage("/login_page")
                //登錄成功的處理器
                .successHandler(authenctiationSuccessHandler)
                //登錄失敗的處理器
                .failureHandler(authenctiationFailHandler)
                // 登錄處理接口
                .loginProcessingUrl("/login")
                // 定義登錄時,用戶名的 key,默認為 username
                .usernameParameter("username")
                //定義登錄時,用戶密碼的 key,默認為 password
                .passwordParameter("password").permitAll()
                .and().logout()
                ////和表單登錄相關的接口統統都直接通過
                .permitAll()
                .and().csrf().disable().exceptionHandling().accessDeniedHandler(getAccessDeniedHandler());
    }

    /**
     * 對於/static/  下的路徑都不用認證
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/no-login");
    }

    /**
     * 用戶未認證異常攔截
     */
    @Bean
    AccessDeniedHandler getAccessDeniedHandler() {
        return new AuthenticationAccessDeniedHandler();
    }
}

注意 這裏一共配置了三個路徑用於測試。

1、/no-login 接口不需要認證就可以直接訪問
2、/no-authorize 需要認證 但不需要授權就可以訪問
3、/need-authorize 首先需要認證 認證通過還需要授權 這裏需要校長的角色才可以訪問該接口 但是我們測試用戶只有教師和學生所以沒有權限訪問該接口

下面會針對這個個接口分別進行測試。

三、測試

1、接口提供

@RestController
public class TestController {

    /**
     * 1、不需要登陸就可以訪問
     */
    @RequestMapping(value = "/no-login")
    public ServiceResponse noLogin() {
        return ServiceResponse.success("歡迎訪問不需要登陸接口");
    }
    /**
     * 2、只登陸,不許認證接口
     */
    @RequestMapping(value = "/no-authorize")
    public ServiceResponse needAuthorize(){
        return ServiceResponse.success("登陸了 不用授權");
    }
    /**
     * 3、登陸 + 相關認證接口
     */
    @RequestMapping(value = "/need-authorize")
    public ServiceResponse noAuthorize() {
        return ServiceResponse.success("登陸+授權成功");
    }
    /**
     * @Description: 如果自動跳轉到這個頁面,說明用戶未登錄,返回相應的提示即可
     */
    @RequestMapping("/login_page")
    public ServiceResponse loginPage() {
        return  ServiceResponse.failure("001", "尚未登錄,請登錄!");
    }
}

2、未登錄訪問 no-login 和 no-authorize 接口

no-login接口

很明顯沒有登陸 請求該接口成功!

no-authorize接口

沒有登陸訪問失敗,在上面配置了如果用戶沒有認證的話跳轉到login_page接口,所以這裏返回 ‘尚未登錄,請登錄!’

3、登陸后訪問 no-authorize 和 need-authorize 接口

先登陸

根據上面配置登陸的路徑為 /login 請求參數包括 usernamepassword

注意 這裏需要post請求。

no-authorize 接口

登陸就可以訪問了。

need-authorize 接口

雖然登陸成功了,但是因為該接口需要校長角色,之前給該用戶只配置了教師和學生的角色所以訪問失敗。

參考

1、SpringSide 3 中的安全框架

2、Spring Security 工作原理概覽

3、Spring Boot Security 詳解 很贊

別人罵我胖,我會生氣,因為我心裏承認了我胖。別人說我矮,我就會覺得好笑,因為我心裏知道我不可能矮。這就是我們為什麼會對別人的攻擊生氣。
攻我盾者,乃我內心之矛(17)

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

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

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

SpringBoot學習筆記(十五:OAuth2 )_網頁設計公司

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

@

目錄

  • 一、OAuth 簡介
    • 1、什麼是OAuth
    • 2、OAuth 角色
    • 3、OAuth 授權流程
    • 4、OAuth授權模式
      • 4.1、授權碼
      • 4.2、隱藏式
      • 4.3、密碼式
      • 4.4、憑證式
  • 二、實踐
    • 1、密碼模式
      • 1.1、授權服務器
        • 1.1.1、依賴
        • 1.1.2、授權服務器配置
        • 1.1.3、Spring Security配置
      • 1.2、資源服務器
        • 1.2.1、資源服務器配置
        • 1.2.2、資源服務接口
      • 1.3、測試
        • 1.3.1、獲取token
        • 1.3.2、使用獲取到的token訪問資源接口
    • 2、授權碼模式
      • 2.1、應用註冊
      • 2.2、具體代碼
      • 2.3、測試

一、OAuth 簡介

1、什麼是OAuth

開放授權(Open Authorization,OAuth)是一種資源提供商用於授權第三方應用代表資源所有者獲取有限訪問權限的授權機制。由於在整個授權過程中,第三方應用都無須觸及用戶的密碼就可以取得部分資源的使用權限,所以OAuth是安全開放的。

例如,用戶想通過 QQ 登錄csdn,這時csdn就是一個第三方應用,csdn要訪問用戶的一些基本信息就需要得到用戶的授權,如果用戶把自己的 QQ 用戶名和密碼告訴csdn,那麼csdn就能訪問用戶的所有數據,井且只有用戶修改密碼才能收回授權,這種授權方式安全隱患很大,如果使用 OAuth ,就能很好地解決這一問題。

OAuth第一個版本誕生於2007年12月,並於2010年4月正式被IETF作為標準發布(編號RFC 5849)。由於OAuth1.0複雜的簽名邏輯以及單一的授權流程存在較大缺陷,隨後標準工作組又推出了 OAuth2.0草案,並在2012年10月正式發布其標準(編號RFC 6749)。OAuth2.0放棄了OAuth1.0中讓開發者感到痛苦的数字簽名和加密方案,使用已經得到驗證並廣泛使用的HTTPS技術作為安全保障手 段。OAuth2.0與OAuth1.0互不兼容,由於OAuth1.0已經基本退出歷史舞台,所以下面提到的OAuth都是指OAuth2.0。

2、OAuth 角色

想要理解OAuth的運行流程,則必須要認識4個重要的角色。

  • Resource Owner:資源所有者,通常指用戶,例如每一個QQ用戶。
  • Resource Server:資源服務器,指存放用戶受保護資源的服務器,通常需要通過Access Token(訪問令牌)才能進行訪問。例如,存儲QQ用戶基本信息的服務器,充當的便是資源服務器的 角色。
  • Client:客戶端,指需要獲取用戶資源的第三方應用,如CSDN網站。
  • Authorization Server:授權服務器,用於驗證資源所有者,並在驗證成功之後向客戶端發放相關訪問令牌。

3、OAuth 授權流程

這是 個大致的流程,因為 OAuth2 中有 種不同的授權模式,每種授權模式的授權流程又會有差異,基本流程如下:

  • 客戶端(第三方應用)向資源所有者請求授權。
  • 服務端返回一個授權許可憑證給客戶端。
  • 客戶端拿着授權許可憑證去授權服務器申請令牌。
  • 授權服務器驗證信息無誤后,發放令牌給客戶端。
  • 客戶端拿着令牌去資源服務器訪問資源。
  • 資源服務器驗證令牌無誤后開放資源。

4、OAuth授權模式

OAuth 協議的授權模式共分為4種。

4.1、授權碼

授權碼(authorization code)方式,指的是第三方應用先申請一個授權碼,然後再用該碼獲取令牌。

這種方式是最常用的流程,安全性也最高,它適用於那些有後端的 Web 應用。授權碼通過前端傳送,令牌則是儲存在後端,而且所有與資源服務器的通信都在後端完成。這樣的前後端分離,可以避免令牌泄漏。

  • 第一步,A 網站提供一個鏈接,用戶點擊后就會跳轉到 B 網站,授權用戶數據給 A 網站使用。下面就是 A 網站跳轉 B 網站的一個示意鏈接。
https://b.com/oauth/authorize?
  response_type=code&
  client_id=CLIENT_ID&
  redirect_uri=CALLBACK_URL&
  scope=read

上面 URL 中,response_type參數表示要求返回授權碼(code),client_id參數讓 B 知道是誰在請求,redirect_uri參數是 B 接受或拒絕請求后的跳轉網址,scope參數表示要求的授權範圍(這裡是只讀)。

  • 第二步,用戶跳轉后,B 網站會要求用戶登錄,然後詢問是否同意給予 A 網站授權。用戶表示同意,這時 B 網站就會跳回redirect_uri參數指定的網址。跳轉時,會傳回一個授權碼,就像下面這樣。
https://a.com/callback?code=AUTHORIZATION_CODE

上面 URL 中,code參數就是授權碼。

  • 第三步,A 網站拿到授權碼以後,就可以在後端,向 B 網站請求令牌。
https://b.com/oauth/token?
 client_id=CLIENT_ID&
 client_secret=CLIENT_SECRET&
 grant_type=authorization_code&
 code=AUTHORIZATION_CODE&
 redirect_uri=CALLBACK_URL

上面 URL 中,client_id 參數和 client_secret 參數用來讓 B 確認 A 的身份(client_secret參數是保密的,因此只能在後端發請求),grant_type參數的值是 AUTHORIZATION_CODE,表示採用的授權方式是授權碼,code參數是上一步拿到的授權碼,redirect_uri 參數是令牌頒發后的回調網址。

  • 第四步,B 網站收到請求以後,就會頒發令牌。具體做法是向redirect_uri指定的網址,發送一段 JSON 數據。

    {    
      "access_token":"ACCESS_TOKEN",
      "token_type":"bearer",
      "expires_in":2592000,
      "refresh_token":"REFRESH_TOKEN",
      "scope":"read",
      "uid":100101,
      "info":{...}
    }

上面 JSON 數據中,access_token字段就是令牌,A 網站在後端拿到了。

4.2、隱藏式

有些 Web 應用是純前端應用,沒有後端。這時就不能用上面的方式了,必須將令牌儲存在前端。RFC 6749 就規定了第二種方式,允許直接向前端頒發令牌。這種方式沒有授權碼這个中間步驟,所以稱為(授權碼)”隱藏式”(implicit)。

  • 第一步,A 網站提供一個鏈接,要求用戶跳轉到 B 網站,授權用戶數據給 A 網站使用。
https://b.com/oauth/authorize?
  response_type=token&
  client_id=CLIENT_ID&
  redirect_uri=CALLBACK_URL&
  scope=read

上面 URL 中,response_type參數為token,表示要求直接返回令牌。

  • 第二步,用戶跳轉到 B 網站,登錄后同意給予 A 網站授權。這時,B 網站就會跳回redirect_uri參數指定的跳轉網址,並且把令牌作為 URL 參數,傳給 A 網站。
https://a.com/callback#token=ACCESS_TOKEN

上面 URL 中,token參數就是令牌,A 網站因此直接在前端拿到令牌。

注意,令牌的位置是 URL 錨點(fragment),而不是查詢字符串(querystring),這是因為 OAuth 2.0 允許跳轉網址是 HTTP 協議,因此存在”中間人攻擊”的風險,而瀏覽器跳轉時,錨點不會發到服務器,就減少了泄漏令牌的風險。

這種方式把令牌直接傳給前端,是很不安全的。因此,只能用於一些安全要求不高的場景,並且令牌的有效期必須非常短,通常就是會話期間(session)有效,瀏覽器關掉,令牌就失效了。

4.3、密碼式

如果你高度信任某個應用,RFC 6749 也允許用戶把用戶名和密碼,直接告訴該應用。該應用就使用你的密碼,申請令牌,這種方式稱為”密碼式”(password)。

  • 第一步,A 網站要求用戶提供 B 網站的用戶名和密碼。拿到以後,A 就直接向 B 請求令牌。
https://oauth.b.com/token?
  grant_type=password&
  username=USERNAME&
  password=PASSWORD&
  client_id=CLIENT_ID

上面 URL 中,grant_type參數是授權方式,這裏的password表示”密碼式”,username和password是 B 的用戶名和密碼。

  • 第二步,B 網站驗證身份通過後,直接給出令牌。注意,這時不需要跳轉,而是把令牌放在 JSON 數據裏面,作為 HTTP 回應,A 因此拿到令牌。

4.4、憑證式

最後一種方式是憑證式(client credentials),適用於沒有前端的命令行應用,即在命令行下請求令牌。

  • 第一步,A 應用在命令行向 B 發出請求。
https://oauth.b.com/token?
  grant_type=client_credentials&
  client_id=CLIENT_ID&
  client_secret=CLIENT_SECRET

上面 URL 中,grant_type參數等於client_credentials表示採用憑證式,client_id和client_secret用來讓 B 確認 A 的身份。

  • 第二步,B 網站驗證通過以後,直接返回令牌。

這種方式給出的令牌,是針對第三方應用的,而不是針對用戶的,即有可能多個用戶共享同一個令牌。

二、實踐

1、密碼模式

如果是自建單點服務,一般都會使用密碼模式。資源服務器和授權服務器
可以是同一台服務器,也可以分開。這裏我們學習分佈式的情況。

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

授權服務器和資源服務器分開,項目結構如下:

1.1、授權服務器

授權服務器的職責:

  • 管理客戶端及其授權信息
    * 管理用戶及其授權信息
    * 管理Token的生成及其存儲
    * 管理Token的校驗及校驗Key

1.1.1、依賴

        <!--security-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <!--oauth2-->
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.6.RELEASE</version>
        </dependency>

1.1.2、授權服務器配置

授權服務器配置通過繼承AuthorizationServerConfigurerAdapter的配置類實現:

/**
 * @Author 三分惡
 * @Date 2020/5/20
 * @Description  授權服務器配置
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;//密碼模式需要注入認證管理器

    @Autowired
    public PasswordEncoder passwordEncoder;

    //配置客戶端
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client-demo")
                .secret(passwordEncoder.encode("123"))
                .authorizedGrantTypes("password") //這裏配置為密碼模式
                .scopes("read_scope");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager);//密碼模式必須添加authenticationManager
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients()
                .checkTokenAccess("isAuthenticated()");
    }
}

  • 客戶端的註冊:這裏通過inMemory的方式在內存中註冊客戶端相關信息;實際項目中可以通過一些管理接口及界面動態實現客戶端的註冊
  • 校驗Token權限控制:資源服務器如果需要調用授權服務器的/oauth/check_token接口校驗token有效性,那麼需要配置checkTokenAccess(“isAuthenticated()”)
  • authenticationManager配置:需要通過endpoints.authenticationManager(authenticationManager)將Security中的authenticationManager配置到Endpoints中,否則,在Spring Security中配置的權限控制將不會在進行OAuth2相關權限控制的校驗時生效。

1.1.3、Spring Security配置

通過Spring Security來完成用戶及密碼加解密等配置:

/**
 * @Author 三分惡
 * @Date 2020/5/20
 * @Description SpringSecurity 配置
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("fighter")
                .password(passwordEncoder().encode("123"))
                .authorities(new ArrayList<>(0));
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //所有請求必須認證
        http.authorizeRequests().anyRequest().authenticated();
    }
}

1.2、資源服務器

資源服務器的職責:

  • token的校驗
  • 給與資源

1.2.1、資源服務器配置

資源服務器依賴一樣,而配置則通過繼承自ResourceServerConfigurerAdapter的配置類來實現:

/**
 * @Author 三分惡
 * @Date 2020/5/20
 * @Description
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Bean
    public RemoteTokenServices remoteTokenServices() {
        final RemoteTokenServices tokenServices = new RemoteTokenServices();
        tokenServices.setClientId("client-demo");
        tokenServices.setClientSecret("123");
        tokenServices.setCheckTokenEndpointUrl("http://localhost:8090/oauth/check_token");
        return tokenServices;
    }

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.stateless(true);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        //session創建策略
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
        //所有請求需要認證
        http.authorizeRequests().anyRequest().authenticated();
    }
}

主要進行了如下配置:

  • TokenService配置:在不採用JWT的情況下,需要配置RemoteTokenServices來充當tokenServices,它主要完成Token的校驗等工作。因此需要指定校驗Token的授權服務器接口地址
  • 同時,由於在授權服務器中配置了/oauth/check_token需要客戶端登錄后才能訪問,因此也需要配置客戶端編號及Secret;在校驗之前先進行登錄
  • 通過ResourceServerSecurityConfigurer來配置需要訪問的資源編號及使用的TokenServices

1.2.2、資源服務接口

接口比較簡單:

/**
 * @Author 三分惡
 * @Date 2020/5/20
 * @Description
 */
@RestController
public class ResourceController {

    @GetMapping("/user/{username}")
    public String user(@PathVariable String username){
        return "Hello !"+username;
    }
}

1.3、測試

授權服務器使用8090端口啟動,資源服務器使用默認端口。

1.3.1、獲取token

訪問/oauth/token端點,獲取token:

  • url:   http://localhost:8090/oauth/token?username=fighter&password=123&scope=read_scope&grant_type=password
  • 請求頭:
  • 返回的token

1.3.2、使用獲取到的token訪問資源接口

  • 使用token調用資源,訪問http://localhost:8080/user/fighter,注意使用token添加Bearer請求頭

相當於在Headers中添加 Authorization:Bearer 4a3c351d-770d-42aa-af39-3f54b50152e9。

OK,可以看到資源正確返回。

這裏僅僅是密碼模式的精簡化配置,在實際項目中,某些部分如:

  • 資源服務訪問授權服務去校驗token這部分可能會換成Jwt、Redis等tokenStore實現,
  • 授權服務器中的用戶信息與客戶端信息生產環境從數據庫中讀取,對應Spring Security的UserDetailsService實現類或用戶信息的Provider

2、授權碼模式

很多網站登錄時,允許使用第三方網站的身份,這稱為”第三方登錄”。所謂第三方登錄,實質就是 OAuth 授權。

例如用戶想要登錄 A 網站,A 網站讓用戶提供第三方網站的數據,證明自己的身份。獲取第三方網站的身份數據,就需要 OAuth 授權。

以A網站使用GitHub第三方登錄為例,流程示意如下:

接下來,簡單地實現GitHub登錄流程。

2.1、應用註冊

在使用之前需要先註冊一個應用,讓GitHub可以識別。

  • 訪問地址:https://github.com/settings/applications/new,填寫註冊表

應用的名稱隨便填,主頁 URL 填寫http://localhost:8080,回調地址填寫 http://localhost:8080/oauth/redirect。

  • 提交表單以後,GitHub 應該會返回客戶端 ID(client ID)和客戶端密鑰(client secret),這就是應用的身份識別碼

2.2、具體代碼

  • 只需要引入web依賴:
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
  • GitHub相關配置
github.client.clientId=29d127aa0753c12263d7
github.client.clientSecret=f3cb9222961efe4c2adccd6d3e0df706972fa5eb
github.client.authorizeUrl=https://github.com/login/oauth/authorize
github.client.accessTokenUrl=https://github.com/login/oauth/access_token
github.client.redirectUrl=http://localhost:8080/oauth/redirect
github.client.userInfoUrl=https://api.github.com/user

  • 對應的配置類
@Component
@ConfigurationProperties(prefix = "github.client")
public class GithubProperties {
    private String clientId;
    private String clientSecret;
    private String authorizeUrl;
    private String redirectUrl;
    private String accessTokenUrl;
    private String userInfoUrl;
    //省略getter、setter
}    
  • index.html:首頁比較簡單,一個鏈接向後端發起登錄請求
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>網站首頁</title>
</head>
<body>
    <div style="text-align: center">
        <a href="http://localhost:8080/authorize">Login in with GitHub</a>
    </div>
</body>
</html>
  • GithubLoginController.java:
     * 使用RestTemplate發送http請求
     * 使用Jackson解析返回的json,不用引入更多依賴
     * 快捷起見,發送http請求的方法直接寫在控制器中,實際上應該將工具方法分離出去
     * 同樣是快捷起見,返回的用戶信息沒有做任何解析
@Controller
public class GithubLoginController {
    @Autowired
    GithubProperties githubProperties;


    /**
     * 登錄接口,重定向至github
     *
     * @return 跳轉url
     */
    @GetMapping("/authorize")
    public String authorize() {
        String url =githubProperties.getAuthorizeUrl() +
                "?client_id=" + githubProperties.getClientId() +
                "&redirect_uri=" + githubProperties.getRedirectUrl();
        return "redirect:" + url;
    }

    /**
     * 回調接口,用戶同意授權后,GitHub會將授權碼傳遞給此接口
     * @param code GitHub重定向時附加的授權碼,只能用一次
     * @return
     */
    @GetMapping("/oauth/redirect")
    @ResponseBody
    public String redirect(@RequestParam("code") String code) throws JsonProcessingException {
        System.out.println("code:"+code);
        // 使用code獲取token
        String accessToken = this.getAccessToken(code);
        // 使用token獲取userInfo
        String userInfo = this.getUserInfo(accessToken);
        return userInfo;
    }


    /**
     * 使用授權碼獲取token
     * @param code
     * @return
     */
    private String getAccessToken(String code) throws JsonProcessingException {
        String url = githubProperties.getAccessTokenUrl() +
                "?client_id=" + githubProperties.getClientId() +
                "&client_secret=" + githubProperties.getClientSecret() +
                "&code=" + code +
                "&grant_type=authorization_code";
        // 構建請求頭
        HttpHeaders requestHeaders = new HttpHeaders();
        // 指定響應返回json格式
        requestHeaders.add("accept", "application/json");
        // 構建請求實體
        HttpEntity<String> requestEntity = new HttpEntity<>(requestHeaders);
        RestTemplate restTemplate = new RestTemplate();
        // post 請求方式
        ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
        String responseStr = response.getBody();
        // 解析響應json字符串
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(responseStr);
        String accessToken = jsonNode.get("access_token").asText();
        System.out.println("accessToken:"+accessToken);
        return accessToken;
    }

    /**
     *
     * @param accessToken 使用token獲取userInfo
     * @return
     */
    private String getUserInfo(String accessToken) {
        String url = githubProperties.getUserInfoUrl();
        // 構建請求頭
        HttpHeaders requestHeaders = new HttpHeaders();
        // 指定響應返回json格式
        requestHeaders.add("accept", "application/json");
        // AccessToken放在請求頭中
        requestHeaders.add("Authorization", "token " + accessToken);
        // 構建請求實體
        HttpEntity<String> requestEntity = new HttpEntity<>(requestHeaders);
        RestTemplate restTemplate = new RestTemplate();
        // get請求方式
        ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
        String userInfo = response.getBody();
        System.out.println("userInfo:"+userInfo);
        return userInfo;
    }

}

2.3、測試

  • 訪問localhost:8080,點擊鏈接,重定向至GitHub
  • 在GitHub中輸入賬號密碼,登錄
  • 登錄成功后,GitHub 就會跳轉到redirect_uri指定的跳轉網址,並且帶上授權碼
http://localhost:8080/oauth/redirect?code=d45683eded3ac7d4e6ed

OK,用戶信息也一併返回了。

本文為學習筆記類博客,學習資料見參考!

參考:

【1】:《SpringSecurity 實戰》
【2】:《SpringBoot Vue全棧開發實戰》
【3】:理解OAuth 2.0
【4】:OAuth 2.0 的一個簡單解釋
【5】:OAuth 2.0 的四種方式
【6】:這個案例寫出來,還怕跟面試官扯不明白 OAuth2 登錄流程?
【7】:做微服務繞不過的 OAuth2,松哥也來和大家扯一扯
【8】:GitHub OAuth 第三方登錄示例教程
【9】:OAuth 2.0 認證的原理與實踐
【10】:Spring Security OAuth2 Demo —— 密碼模式(Password)
【11】:Spring Security OAuth專題學習-密碼模式及客戶端模式實例
【12】:Spring Boot and OAuth2
【13】:Spring Boot+OAuth2使用GitHub登錄自己的服務

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

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單