【Blog.Core开源】网关自定义认证鉴权与传参
書接上文,上回咱們說到了《【Blog.Core開源】網(wǎng)關(guān)統(tǒng)一集成下游服務(wù)文檔》,已經(jīng)將多個下游服務(wù)統(tǒng)一集成到了網(wǎng)關(guān)里,并且也把接口文檔Swagger給集成了,那今天就說一下認(rèn)證和鑒權(quán)相關(guān)的話題。
繼續(xù)說下故事背景
在平時開發(fā)的時候,特別是有網(wǎng)關(guān)的情況下,經(jīng)常會遇到一個不可避免的話題,就是網(wǎng)關(guān)到底要不要幫下游處理某些業(yè)務(wù)邏輯的問題,比如說認(rèn)證鑒權(quán)、審計日志、當(dāng)前用戶信息獲取,白名單等等。
這里其實見仁見智,同時也要考慮各個項目的具體架構(gòu)設(shè)計和需要,我個人的習(xí)慣還是網(wǎng)關(guān)要輕一些,什么叫輕一些呢,拿BlogCore舉例,認(rèn)證走的是Ids4的統(tǒng)一認(rèn)證平臺,從平臺那里得到令牌Token,然后經(jīng)過網(wǎng)關(guān)走到BlogCore,解析,并走具體的自定義授權(quán)邏輯,因為這里涉及到動態(tài)菜單權(quán)限配置,所以很少會放到網(wǎng)關(guān)里處理,畢竟每個下游服務(wù)都可能會有自己的那部分邏輯。其實除了授權(quán)這塊,還有一些數(shù)據(jù),比如當(dāng)前用戶的私密信息,例如手機(jī)號之類的,這個phone肯定不能放到token里的,因為token雖然有過期時間,但是就算是失效,還是可以解密出來的,放到公網(wǎng)上的令牌基本都是只放一些非私密的個人信息,比如uid或者是roleId,實在有需要也可以在token里放部門的id的,這也無可厚非,但是phone和address是萬萬不能放到token里的。
那么問題來了,phone和address我們到底應(yīng)該從哪里獲取?上邊的菜單權(quán)限大家已經(jīng)達(dá)成共識,就是放到下游,讓下游服務(wù)自己來處理,那根據(jù)token中的uid來獲取phone信息,就需要考慮下了,很多人說放網(wǎng)關(guān)唄,每次請求查庫等操作,然后放到header里傳遞給下游,這也是一個方案,今天也會給大家講講怎么獲取,怎么傳。
當(dāng)然我個人的意見還是網(wǎng)關(guān)僅僅是解析token里有的,傳遞給下游,至于查庫的那些,還是下游獲取吧,這是我的個人意見,并不是完全正確。為什么呢,大家想想,咱們在網(wǎng)關(guān)里寫攔截器或者中間件,每次接口請求,都根據(jù)header中的token來查庫,這樣不管下游需不需要,不管下游接口是不是匿名都去查庫一下,會造成資源浪費,比如我就想搜索下list,每次都查詢下當(dāng)前人的user信息,似乎沒那么必要,特別是list頁面高并發(fā)的時候,是不是不太好,當(dāng)然這樣的好處就是對下游方便且能做詳細(xì)的審計日志。
今天咱們就說下如何自定義攔截器傳遞自定義claim信息給下游。
01
PART
網(wǎng)關(guān)自定義認(rèn)證處理器
在網(wǎng)關(guān)中注冊認(rèn)證服務(wù),并設(shè)計處理器,實現(xiàn)認(rèn)證授權(quán)攔截,比如說token是否可以正常的解密等,用來判斷token的有效性等,也可以查詢數(shù)據(jù)庫,獲取私密信息:
services.AddAuthentication().AddScheme<AuthenticationSchemeOptions,?CustomAuthenticationHandler>(Permissions.GWName,?_?=>?{?});然后具體的處理器,大家根據(jù)需求自定義即可,注意把信息放到Claims里,不僅可以在當(dāng)前網(wǎng)關(guān)的其他地方獲取,從而減少二次請求的情況。也可以傳遞給下游服務(wù)。
public class CustomAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions> {public CustomAuthenticationHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,ILoggerFactory logger,UrlEncoder encoder,ISystemClock clock) : base(options, logger, encoder, clock){}protected override async Task<AuthenticateResult> HandleAuthenticateAsync(){// 可以查詢數(shù)據(jù)庫等操作// 獲取當(dāng)前用戶不能放到token中的私密信息var userPhone = "15010000000";var claims = new List<Claim>(){new Claim("user-phone", userPhone),new Claim("gw-sign", "gw")};var principal = new ClaimsPrincipal(new ClaimsIdentity(claims, Scheme.Name));var ticket = new AuthenticationTicket(principal, Scheme.Name);await Task.CompletedTask;return AuthenticateResult.Success(ticket);} }?內(nèi)容很簡單,就是一個普通的處理器,那接下來就是看如何把Claim給傳給下游服務(wù)了。
02
PART
對下游服務(wù)開啟認(rèn)證處理器
Ocelot已經(jīng)做好了配置,就像是自定義響應(yīng)處理器一樣,認(rèn)證的也可以直接配置:
也可以有更多的參數(shù)配置,具體可以參考官網(wǎng):
https://ocelot.readthedocs.io/en/latest/features/configuration.html?highlight=AuthenticationOptions#configuration
03
PART
Ocelot將Claim傳遞下游
還是在Ocelot的官網(wǎng)上可以看到很多Demo,我只配置三項,1、分別是動態(tài)從Claim中獲取并用Request的Header傳值,2、直接在Request中傳遞固定Header值,3、獲取下游服務(wù)的Response的Header給上游網(wǎng)關(guān)。
其中第三點還是很有用的,比如我們以后的Skywalking中,如果某次鏈路請求報錯了,但是又想快速的定位,所以就需要用戶給我們提供當(dāng)前操作的標(biāo)識,有時候是uid,有時候是url,這兩個都不是很直觀。通過配置Ocelot,正好可以從下游服務(wù)的response的header中返給前端,用戶就能提供了,更加快速方便的定位問題。
// blog-svc {"UpstreamPathTemplate": "/svc/blog/{url}","UpstreamHttpMethod": [ "Get", "Post", "Put", "Delete" ],"LoadBalancerOptions": {"Type": "RoundRobin"},"DownstreamPathTemplate": "/svc/blog/{url}","DownstreamScheme": "http","DownstreamHostAndPorts": [{"Host": "localhost","Port": 9291}],//?添加到headers// 從claims中獲取"AddHeadersToRequest": {"user-phone": "Claims[user-phone] > value","gw-sign": "Claims[gw-sign] > value"},//?從上游網(wǎng)關(guān)的request的header中"UpstreamHeaderTransform": {"custom-key": "blog.gateway"},// 從下游服務(wù)的response的header中"DownstreamHeaderTransform":?{"down-app":?"{para-down-app}","trace-id":?"Trace-Id"},"AuthenticationOptions": {"AuthenticationProviderKey": "GW"} },在上邊注釋的三塊,就是常見的三種方案。
04
PART
下游服務(wù)查看具體效果
在BlogCore服務(wù)中,valueController中測試下是否傳遞了具體的參數(shù):
其中獲取Claim方法,也獲取了下header中其他的參數(shù):
public IEnumerable<Claim> GetClaimsIdentity(){var claims = _accessor.HttpContext.User.Claims.ToList();var headers = _accessor.HttpContext.Request.Headers;foreach (var header in headers){claims.Add(new Claim(header.Key, header.Value));}return claims;}這里有一個小注意事項:
如果下游服務(wù)是加權(quán)的,可以直接通過swagger添加token的方式,獲取claims信息,但是接口是匿名的,那swagger是不會傳遞token信息的,我們可以用postman測試,一樣的效果,畢竟前端Vue.js也是我們手動傳遞的。
關(guān)于swagger不加權(quán)就不傳遞token這個問題,以后我會優(yōu)化下,寫個擴(kuò)展中間件。
查看下具體的情況:
攜帶上token以后,發(fā)起請求,無論是自定義固定的參數(shù)還是Claims中的變量都傳給了下游服務(wù),并且下游的Response的Header也有了值。
好啦,網(wǎng)關(guān)系列的分享就先到這里了,咱們下次再見,說說注冊中心集成功能。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的【Blog.Core开源】网关自定义认证鉴权与传参的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一起来庆祝 .NET 20 周年!
- 下一篇: 如何通过 HttpWebRequest