-
Notifications
You must be signed in to change notification settings - Fork 5
IBM API Connect #179
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
示例应用(Protected Resource)https://mybank.au-syd.mybluemix.net/ Portal配置Portal, URL: https://sandbox-orgccs-spacefd.developer.au.apiconnect.ibmcloud.com/ 得到Token使用在bluemix api connect中生成的api key和secret curl -s -X POST \
-uclient_id:client_secret \
https://api.au.apiconnect.ibmcloud.com/orgccs-spacefd/sandbox/oauth-endpoint-api/oauth2/token\?grant_type\=client_credentials\&scope\=read | json 响应 {
"token_type": "bearer",
"access_token": "AAEkODFiZGFhMjMtYTdjMy00ZmRjLWFjMjEtZjMwOGRkNWM3NzAz5yRSbw4TblxEJv8WgjwuYDVtfuW3rjh9rcWZkB0fxTEewqi7mI5IDkQ05AkywdSeQkUIewPfn34wYcsIiNJuNfhwPSpvcgx05aoPI__j5Bg6GmlTcaeMzkGdk8EcAx-w",
"expires_in": 3600,
"scope": "read"
} 以下尝试在Portal中注册app时生成的client id和secret curl -s -X POST \
-uclient_id_in_portal:mX7xL2mJ5vP7vQ7qV6yT0yU1fT1yK2oY5yD1pX0qK8sH8cS5uU \
https://api.au.apiconnect.ibmcloud.com/orgccs-spacefd/sandbox/oauth-endpoint-api/oauth2/token\?grant_type\=client_credentials\&scope\=read | json 初次尝试报如下错误: {
"error": "invalid_client",
"error_description": "client_id unauthorized"
} 这是因为需要在portal上相应的API Product Detail中点击subscribe按钮, 订阅相应的API才可以使用portal中生成的app id和app secret来得到access token. 在Subscribe成功后, 不再出现上面的错误. 使用tokencurl -s -H 'Accept: application/json' \
-H 'Authorization: Bearer AAEkODFiZGFhMjMtYTdjMy00ZmRjLWFjMjEtZjMwOGRkNWM3NzAzQ8gbam97a-BBO83uXRKh3l7xWqxgNFMGmjkVESF78hYGL8uzk-VpfYvXqyldQ8n-e1Z_aiMdGBwfU-ktCZ0SOseC7Z_z8IEOxPJIqR-4_VvxQoerIiOsa_T6nPiOSOFo' \
https://api.au.apiconnect.ibmcloud.com/orgccs-spacefd/sandbox/user-operation/users | json |
application对应client credentials对应2-leggedYou're mixing up client and user credentials here. Client in the context of OAuth always refers to the application that gets authorized. Thus in the Client Credentials Flow an application directly authorizes itself with the provider without any input from a user (also called 2-legged flow as only two parties are involved). The Username and Password Flow is a 3-legged-flow. A user provides his username and password to an application, the application then requests data from the provider using these credentials. 见: OAuth 2 - What is the difference between 'Username and Password Flow' vs 'Client Credential Flow node取authorization的代码http.createServer(function(req,res){
var header=req.headers['authorization']||'', // get the header
token=header.split(/\s+/).pop()||'', // and the encoded auth token
auth=new Buffer(token, 'base64').toString(), // convert from base64
parts=auth.split(/:/), // split on colon
username=parts[0],
password=parts[1];
res.writeHead(200,{'Content-Type':'text/plain'});
res.end('username is "'+username+'" and password is "'+password+'"');
}).listen(1337,'127.0.0.1'); 见: Basic HTTP authentication in Node.JS? 书中Node取access_token的代码var getAccessToken = function (req, res, next) {
// check the auth header first
var auth = req.headers['authorization'];
var inToken = null;
if (auth && auth.toLowerCase().indexOf('bearer') == 0) {
inToken = auth.slice('bearer '.length);
} else if (req.body && req.body.access_token) {
// not in the header, check in the form body
inToken = req.body.access_token;
} else if (req.query && req.query.access_token) {
inToken = req.query.access_token;
}
console.log('Incoming token: %s', inToken);
// nosql.one(function(token) {
// if (token.access_token == inToken) {
// return token;
// }
// }, function(err, token) {
// if (token) {
// console.log("We found a matching token: %s", inToken);
// } else {
// console.log('No matching token was found.');
// }
// req.access_token = token;
if (inToken != null) {
req.access_token = inToken;
next();
} else {
var error = new Error("token not found.");
error.status = 401;
next(error);
}
return;
}; |
在API中得到client idIBM API Connect中有一些context variable, 在Assembly的时候, 如果Policy选择的是 但是我们目前用的policy是proxy, 我在想是不是可以在proxy前面加个gateway script. 然后在script里添加一个header用来保存client id. 在抓图的过程中, 又发现了一个叫 但是我突然间完全放弃了上面的想法. gateway在向实际的api发起invoke/proxy请求的时候, 肯定会加一些自己特有的header, 我想在API中打印所有的header, 看看有没有我想要的, 代码如下: router.get('/', function(req, res, next) {
res.json(req.headers);
}); 使用CURL测试: ➜ ~ curl -i -s -H 'Accept: application/json' \
-H 'Authorization: Bearer AAEkMjUyMjMwOTUtNWQ4MC00YzY5LTk5MDAtNTdlNzE5M2VlMjM4mAKFYfFUImTln1ti673yxGj7mdVnuVFH1r6rAmLNj4a1jTJoruOnUBwDrK9LfVa7ua_BL2AmHr2WITLxSnzVdL4U_h0QBa3ljwtnewK5swk9NGzR-p5qMW6cyoZsT9SH' \
https://api.au.apiconnect.ibmcloud.com/orgccs-spacefd/sandbox/user-operation/users | json
➜ ~ 响应: HTTP/1.1 200 OK
X-Backside-Transport: OK OK
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Date: Tue, 16 Jan 2018 02:23:25 GMT
Etag: W/"3a6-I2LqYyt1WnLws2m4s0K7+gLqPP4"
X-Powered-By: Express
X-Global-Transaction-ID: 10706149
Access-Control-Expose-Headers: APIm-Debug-Trans-Id, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-Global-Transaction-ID
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET
Access-Control-Allow-Credentials: false
X-RateLimit-Limit: name=rate-limit,100;
X-RateLimit-Remaining: name=rate-limit,97;
{
"host": "mybank.au-syd.mybluemix.net",
"user-agent": "curl/7.54.0",
"$wscs": "ECDHE-RSA-AES256-GCM-SHA384",
"$wsis": "true",
"$wsra": "168.1.38.46",
"$wssc": "https",
"$wssn": "mybank.au-syd.mybluemix.net",
"$wssp": "443",
"accept": "application/json",
"cache-control": "no-transform",
"ibm-app-user": "xxxxx-5d80-4c69-9900-57e7193ee238",
"via": "1.1 AQAAAOPLJyM-,1.1 AQAAABQK4yg-",
"x-archived-client-ip": "168.1.17.11",
"x-b3-spanid": "xx",
"x-b3-traceid": "xxxxx",
"x-cf-applicationid": "xxxxx-ec5e-4482-a4cc-dd435edf3bfa",
"x-cf-instanceid": "7xxxxx5a4-e7d9-46df-57e5-8a11",
"x-cf-instanceindex": "0",
"x-client-ip": "168.1.17.11,168.1.38.46",
"x-forwarded-for": "168.1.17.11,168.1.38.46, 168.1.35.172",
"x-forwarded-host": "api.au.apiconnect.ibmcloud.com",
"x-forwarded-port": "443",
"x-forwarded-proto": "https",
"x-global-transaction-id": "10706149",
"x-request-start": "1516069405383",
"x-vcap-request-id": "7xxxx6-b42a-4048-4a24-aeee4ee240bc"
} 哇,.. 其中的 |
invalid_grant 的原因在调用POST /oauth2/client的时候, ibm api gateway返回401和下面的错误
Error: invalid_request - invalid redirect_uriOAuth Error
Error: invalid_request - missing redirect_uriOAuth Error
Error: Bad Request在Get Token时出现Bad Request错误, 可能是Access Code复制的不对, 可能是Client Secret没有填写. Custom Form Error: Error retrieving the custom form你的form不是xml格式的, 比如img标签没有闭合, 等. 可以在线校验一下: https://www.freeformatter.com/xml-validator-xsd.html, 通过xml校验后再进行测试. 相关的java代码TODO |
portal的使用使用admin登录 添加block: menu > content > add block > popular api或popular product 此时页面上并不会有任何的block出现. 一旦添加了任意一个block, 鼠标浮在home page的featured APIs区域, 右上区域会出现configure mini panels, 点击后可以在弹层的相应区域(左/中/右)添加block API对应的图片需要到API Detail page > Edit中设置. |
保护后台API阅读Three approaches to securing access to Bluemix applications with IBM API Connect 第一种是配置TLS证书, 第二种是使用Basic认证, 第三种是由gateway秘密的向后台API传递一个口令. 我选择了第三种, 步骤如下: 在IBM API Connect的Assemble中添加一个 一百个注意: message.headers.x-app-sharedsecret 不要漏掉了前面 Approach 1: Mutual TLS authenticationClick here for a detailed set of instructions that follow the process of configuring TLS mutual authentication between API Connect and your Bluemix application. Configure the Bluemix application to present a server certificate, and require a specific client certificate to be presented Approach 2: Basic AuthenticationClick here for a detailed set of instructions that follow the process of Basic Authentication. Configuring a Bluemix application to use Basic Authentication is achieved using different steps depending on which type of runtime your application is written in. In some cases Basic Authentication can be configured by making declarative changes to the deployment of the application itself, for example as described here for WebSphere Liberty, however for other runtimes it is necessary to make changes in the application code itself such as with Node.js here. Having secured our application with Basic Auth we must then configure API Connect to present the appropriate “Authorization” header that will allow the incoming request to be authenticated. This could take one of two forms; Use a hardcoded credential that identifies the API implementation in API Connect Approach 3: Custom HTTP headerClick here for a detailed set of instructions that follow the process of configuring use of a custom HTTP header. In this case the developer of the Bluemix application must define a custom HTTP header and fixed value that the caller must provide in order to be permitted to use the application – the enforcement of that header will be different depending on the specific runtime that the application is deployed in – but the basic steps are the same across every runtime; Configure the Bluemix application to require a specific HTTP header and value be presented by the caller |
配置自动化我用node.js写得根据swagger ui自动生成IBM API Connect yaml文件的工具 /**
* IBM API Connect YAML Tool
* =====
*
* Convert swagger-ui json file to IBM API Connect yaml file.
*
* The following features will be added by this tool:
*
* 1. x-ibm-name
* 2. x-ibm-configuration: assemble
* 3. securityDefinitions: oauth
* 4. security: oauth
* 5. schemes: https
*
* Please refer to ./data/template/api-connect.yaml for more details, this tool will use that template file as input.
*
* @author cyper
*
*/
const jsyaml = require('js-yaml'),
fs = require('fs'),
path = require('path'),
request = require('request');
const FILE_ENCODING = 'utf8';
const VERSION = '2018';
const SERVER_URL = 'https://xxxx.mybluemix.net';
const groupsOfApplication = ['Bank Information', 'Market Information', 'Appointments'];
const IBM_TEMPLATE_FILE = path.resolve(__dirname, './data/template/api-connect.yaml');
const outputDir = path.resolve(__dirname, './output');
// create output dir
if (!fs.existsSync(outputDir)) {
fs.mkdirSync(outputDir);
}
// 从swagger-ui上抓取api definition.
request(SERVER_URL + '/swagger-resources', {json: true}, function (err, res, data) {
if (err) {
return console.log(err);
}
let groups = data;
groups.forEach(group => {
let groupName = group.name;
let apiDocsUrl = SERVER_URL + group.location;
console.log(apiDocsUrl);
request(apiDocsUrl, {json: true}, function (err, res, data) {
if (err) {
return console.log(err);
}
let jsonFileName = groupName.toLowerCase().replace(/\s/g, '-') + ".json";
let isApplicationType = groupsOfApplication.some(name => name.toLowerCase() === groupName.toLowerCase());
processJson(jsonFileName, data, isApplicationType);
});
});
});
function processJson(fileName, json, isApplicationType) {
// insert other info such as assembly
let paths = json['paths'];
let template = jsyaml.safeLoad(fs.readFileSync(IBM_TEMPLATE_FILE, FILE_ENCODING));
// ['assembly']['execute'][0] is set-variable, 1 is operation-switch.
let caseTemplate = template['x-ibm-configuration']['assembly']['execute'][1]['operation-switch']['case'][0];
let cases = [];
// loop through each API defined in the json file
Object.keys(paths).forEach(key => {
let api = json['paths'][key];
let operations = [];
let targetUrl = `${SERVER_URL}${key}`;
// if it's GET request
if (api.get) {
console.log(`GET ${key}`);
operations.push(api.get.operationId);
}
// if it's POST request
if (api.post) {
console.log(`POST ${key}`);
operations.push(api.post.operationId);
}
// if it's DELETE request
if (api.delete) {
console.log(`DELETE ${key}`);
operations.push(api.delete.operationId);
}
let oneCase = JSON.parse(JSON.stringify(caseTemplate));
oneCase['operations'] = operations;
oneCase['execute'][0]['proxy']['target-url'] = targetUrl;
cases.push(oneCase);
});
template['x-ibm-configuration']['assembly']['execute'][1]['operation-switch']['case'] = cases;
// Object.assign(json, template);
// 增加OAuth2后变化的地方: 5大处.
json['x-ibm-configuration'] = template['x-ibm-configuration'];
json['securityDefinitions'] = template['securityDefinitions'];
json['security'] = template['security'];
json['schemes'] = template['schemes'];
json['info']['version'] = VERSION;
json['info']['x-ibm-name'] = json['info']['title'].toLowerCase().replace(/\s/g, '-');
// application 和 accessCode的不同之处.
if (isApplicationType) {
json['securityDefinitions']['OAuth2']['flow'] = 'application';
delete json['securityDefinitions']['OAuth2']['authorizationUrl'];
}
let result = jsyaml.dump(json);
let outputFile = path.resolve(outputDir, fileName.slice(0, -5) + '.yaml');
console.log(`output: ${outputFile}\n`);
fs.writeFileSync(outputFile, result, FILE_ENCODING);
}
|
Uh oh!
There was an error while loading. Please reload this page.
总结
Citi的portal: https://sandbox.developerhub.citi.com/
几个坑点
/oauth2/authorize
, 实际上要写全路径(包含endpoint)才可以, 不然在authorize的时候会报404错误. 其中<API_END_POINT>
的地址, 在Settings > Gateways中可以找到, 比如https://api.au.apiconnect.ibmcloud.com/orgccs-spacefd/sandbox
还有一个更深的坑, 这个API_END_POINT最后一部分
/sandbox
是可以修改的, 值必须和Settings>Overview中的Name保持一致, 不然在authorize的时候也会报404错误任何一个Product都先要在Portal中Subscribe才可使用(只要用到的product都要分别Subscribe), 并且 每个developer自己注册的app,都要分别subscribe.. (共需要点m*n次subscribe) -- Citi的portal消除了这个坑, 完全无需subscribe. 规避方法之一是将所有的API都放在一个product里面, 这样只要订阅一次.
Bluemix上的文档在publish product的时候, 每个product里面都包含了两组API, 一组是业务相关的, 一组是OAuth Provider API, 这样在developer订阅了这个product后, 就能使用/token和相应的api服务. 但是每个product里边都要包含一个OAuth Provider API, 是不是太过折腾.我试了一下, 在product中根本不需要包含OAuth Provider API. Citi的做法是, 将authorize和token api单独打包成一个叫Authentication的product, 并publish, 这样developer只要订阅一次这个Authentication的product, 就可以在任意Product里面使用这个auth/token API.
直接拿通过client_credentials得到的token,调用受
auth code
保护的API, 竟然也能通过权限认证. 对于这个漏洞, 目前的做法是在后台增加SecurityInterceptor, 从header中取ibm-app-user
, 前一种认证方式ibm api connect会自动将client key放进去, 后一种是自己放入的customer信息. 通过对这个值进行判断, 能得到当前developer传的token的类型. 如果类型不匹配, throw 401 error.定义和发布API的流程
↑
Stage to Sandbox的按钮 (小心这个按钮会把已经处于publish状态的product变成 stage状态)>>
图标切换, 所谓的navigation panel..一句话: 在Drafts-APIs页面配置API, 在Assemble中做API映射以及测试, 在Drafts-Products-Product页面Stage, 在Dashboard-Products页面Publish.
API Security
在API Designer左边的Security Definition中定义要使用哪种Security(一共有API Key, xx和OAuth)三种, 中间那个叫什么名字, 我忘记了(说明不重要:lol)
同时必定要在API Designer左边的Security中勾选上一个Section中定义的Definition, 又是这种毫无意义的操作.
即stage和publish总是成对出现, Security Definition和Security总是成对出现.
添加OAuth 2.0 Provider API
如果要在自定义的API中使用类型为
OAuth
的Security Definition, 则必须先添加另外一组内置的API, 名字叫OAuth 2.0 Provider API. 这组内置的API和你自己定义的API有很多相同之处,比如base path, paths, operations. 它在OAuth体系中相当于Authorization Server的角色, 因此它预定了在两个path, 分别是/oauth2/authorize
和/oauth2/token
, 并且规定了各自要求的参数类型(注意第二个Operation: /oauth2/token要求参数必须以Form Data的方式提交).OAuth 2区块
这个是相比自定义API多出来的一块, 也是最难懂的一块.
Client type
: Public/Confidential(这个选项真的是多此一举,分别对应OAUTH中的2-legged和3-legged)..Scopes
: 对应OAuth中的scope. 比如一个精典的app可以有read, write, delete三种scope. (其它API中的scope必须是这里定义的scopes的子集, 然后在其它自定义API中定义的scope description会覆盖这里写的scope description, 我们的项目并没有区分scope(目前仅定义了一个唯一的scope)Grants
: IBM又一个增加产品复杂度的设定内容, 表示你的Authorization Server支持哪些grant types. 它把OAuth中的Client Credentials叫做Application, 把Authorization Code叫Access Code..你妈, 无谓的多出两个要记的名词. 我们的产品目前使用的grant type是Password.Identity extraction
- Collect credentials using选择Basic, 表示IBM API Connect将会把client传过来的username和password值, 以Basic Auth的方式传给Authentication URL.Authenticate application users using
: 选择Authentication URLAuthentication URL
: 填你的后台API专门做authenticate的API的路径, 只要response code为200, 就表示认证通过, 其它状态码表示认证失败. 可以在你的Authenticate API的response header中添加一个叫``的key. IBM API Connect会把这个key的内容添加到它生成的access_token中. 在它内部解析access_token的时候会把这个值取出来以一个叫ibm-app-user
的值通过request header传给你的API.(妈呀, 文档中只字未提ibm-app-user, 通过在expressjs中打印req.headers我才知道有这么个东西.)Difference between the “Resource Owner Password Flow” and the “Client Credentials Flow”
使用OAuth Provider API
就是用上面配置的
OAuth 2.0 Provider API
来保护你自己的API(需要在你所有的API中都设置一遍), 套路如下:1. Security Definitions
添加
OAuth
,Name写一个有意义的名字比如OAuth吧 (这个Name会出现在API Product Detail页面上做为帮助文档的一部分)
Flow选择 implicit/password/application/access code之一,
Token URL输入:
<API_END_POINT>/<OAUTH2_PROVIDER_API_BASE_PATH>/oauth2/token
Flow必须是在Provider中声明的grants type之一, 我们用的Password.
Scopes: 添加一个或多个当前API用到的Scope, 必须是在Provider中定义的Scopes的子集.
2. Security
勾上上一步创建的OAuth和scope
References
The text was updated successfully, but these errors were encountered: