[新新增] 本文對應的原始碼 (多個flow, clients, 呼叫python api): https://github.com/solenovex/Identity-Server-4-Python-Hug-Api-Jwks
目前正在使用asp.net core 2.0 (主要是web api)做一個專案, 其中一部分功能需要使用js客戶端呼叫python的pandas, 所以需要建立一個python 的 rest api, 我暫時選用了hug, 官網在這: http://www.hug.rest/.
目前專案使用的是identity server 4, 還有一些web api和js client.
專案的早期後臺原始碼: https://github.com/solenovex/asp.net-core-2.0-web-api-boilerplate
下面開始配置identity server 4, 我使用的是windows.
新增ApiResource:
在 authorization server專案中的配置檔案新增紅色部分, 這部分就是python hug 的 api:
public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource(SalesApiSettings.ApiName, SalesApiSettings.ApiDisplayName) { UserClaims = { JwtClaimTypes.Name, JwtClaimTypes.PreferredUserName, JwtClaimTypes.Email } }, new ApiResource("purchaseapi", "採購和原料庫API") { UserClaims = { JwtClaimTypes.Name, JwtClaimTypes.PreferredUserName, JwtClaimTypes.Email } }, new ApiResource("hugapi", "Hug API") { UserClaims = { JwtClaimTypes.Name, JwtClaimTypes.PreferredUserName, JwtClaimTypes.Email } } }; }
修改js Client的配置:
// Sales JavaScript Client new Client { ClientId = SalesApiSettings.ClientId, ClientName = SalesApiSettings.ClientName, AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, AccessTokenLifetime = 60 * 10, AllowOfflineAccess = true, RedirectUris = { $"{Startup.Configuration["MLH:SalesApi:ClientBase"]}/login-callback", $"{Startup.Configuration["MLH:SalesApi:ClientBase"]}/silent-renew.html" }, PostLogoutRedirectUris = { Startup.Configuration["MLH:SalesApi:ClientBase"] }, AllowedCorsOrigins = { Startup.Configuration["MLH:SalesApi:ClientBase"] }, AlwaysIncludeUserClaimsInIdToken = true, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.Email, SalesApiSettings.ApiName, "hugapi" } }
修改js客戶端的oidc client配置選項:
新增 hugapi, 與authorization server配置對應.
{ authority: 'http://localhost:5000', client_id: 'sales', redirect_uri: 'http://localhost:4200/login-callback', response_type: 'id_token token', scope: 'openid profile salesapi hugapi email', post_logout_redirect_uri: 'http://localhost:4200', silent_redirect_uri: 'http://localhost:4200/silent-renew.html', automaticSilentRenew: true, accessTokenExpiringNotificationTime: 4, // silentRequestTimeout:10000, userStore: new WebStorageStateStore({ store: window.localStorage }) }
建立Python Hug api
(可選) 安裝virtualenv:
pip install virtualenv
然後在某個地方建立一個目錄:
mkdir hugapi && cd hugapi
建立虛擬環境:
virtualenv venv
啟用虛擬環境:
venv\Scripts\activate
然後大約這樣顯示:
安裝hug:
pip install hug
這時, 參考一下hug的文件. 然後建立一個簡單的api. 建立檔案main.py:
import hug @hug.get('/home') def root(): return 'Welcome home!'
執行:
hug -f main.py
結果好用:
然後還需要安裝這些:
pip install cryptography pyjwt hug_middleware_cors
其中pyjwt是一個可以encode和decode JWT的庫, 如果使用RS256演算法的話, 還需要安裝cryptography.
而hug_middleware_cors是hug的一個跨域訪問中介軟體(因為js客戶端和這個api不是在同一個域名下).
新增需要的引用:
import hug import jwt import json import urllib.request from jwt.algorithms import get_default_algorithms from hug_middleware_cors import CORSMiddleware
然後正確的做法是通過Authorization Server的discovery endpoint來找到jwks_uri,
identity server 4 的discovery endpoint的地址是:
http://localhost:5000/.well-known/openid-configuration, 裡面能找到各種節點和資訊:
但我還是直接寫死這個jwks_uri吧:
response = urllib.request.urlopen('http://localhost:5000/.well-known/openid-configuration/jwks') still_json = json.dumps(json.loads(response.read())['keys'][0])
identity server 4的jwks_uri, 裡面是public key, 它的結構是這樣的:
而我使用jwt庫, 的引數只能傳入一個證照的json, 也可就是keys[0].
所以上面的最後一行程式碼顯得有點.......
如果使用python-jose這個庫會更簡單一些, 但是在我windows電腦上總是安裝失敗, 所以還是湊合用pyjwt吧.
然後讓hug api使用cors中介軟體:
api = hug.API(__name__) api.http.add_middleware(CORSMiddleware(api))
然後是hug的authentication部分:
def token_verify(token): token = token.replace('Bearer ', '') rsa = get_default_algorithms()['RS256'] cert = rsa.from_jwk(still_json) try: result = jwt.decode(token, cert, algorithms=['RS256'], audience='hugapi') print(result) return result except jwt.DecodeError: return False token_key_authentication = hug.authentication.token(token_verify)
通過rsa.from_jwk(json) 就會得到key (certificate), 然後通過jwt.decode方法可以把token進行驗證並decode, 演算法是RS256, 這個方法要求如果token裡面包含了aud, 那麼方法就需要要指定audience, 也就是hugapi.
最後修改api 方法, 加上驗證:
@hug.get('/home', requires=token_key_authentication) def root(): return 'Welcome home!'
最後執行 hug api:
hug -f main.py
埠應該是8000.
執行js客戶端,登陸, 並呼叫這個hug api http://localhost:8000/home:
(我的js客戶端是angular5的, 這個沒法開源, 公司財產, 不過配置oidc-client還是很簡單的, 使用)
返回200, 內容是:
看一下hug的log:
token被正確驗證並解析了. 所以可以進入root方法了.
其他的python api框架, 都是同樣的道理.
[新新增] 本文對應的原始碼 (多個flow, clients, 呼叫python api): https://github.com/solenovex/Identity-Server-4-Python-Hug-Api-Jwks
可以使用這個例子自行搭建 https://github.com/IdentityServer/IdentityServer4.Samples/tree/release/Quickstarts/7_JavaScriptClient
官方還有一個nodejs api的例子: https://github.com/lyphtec/idsvr4-node-jwks
今日修改後的程式碼:
import json import hug import jwt import requests from jwt.algorithms import get_default_algorithms from hug_middleware_cors import CORSMiddleware api = hug.API(__name__) api.http.add_middleware(CORSMiddleware(api)) def token_verify(token): access_token = token.replace('Bearer ', '') token_header = jwt.get_unverified_header(access_token) res = requests.get( 'http://localhost:5000/.well-known/openid-configuration') jwk_uri = res.json()['jwks_uri'] res = requests.get(jwk_uri) jwk_keys = res.json() rsa = get_default_algorithms()['RS256'] key = json.dumps(jwk_keys['keys'][0]) public_key = rsa.from_jwk(key) try: result = jwt.decode(access_token, public_key, algorithms=[ token_header['alg']], audience='api1') return result except jwt.DecodeError: return False token_key_authentication = hug.authentication.token(token_verify) @hug.get('/identity', requires=token_key_authentication) def root(user: hug.directives.user): print(user) return user
我的部落格即將搬運同步至騰訊雲+社群,邀請大家一同入駐:https://cloud.tencent.com/developer/support-plan