kong的安装和配置启动之前一篇博文已经讲过,这里推荐一个UI管理:kong-dashboard,功能还不是很强大,但是可以省去一些比较基础的配置(api,consumers,plugins的管理),安装配置地址如下: 官方链接
正文
1.oauth2.0插件使用配置的官方文档,但是做本地开发时还是踩了一些坑的,这里记录一下。首先使用oauth2.0需要kong配置https协议,由于是本地开发,随便自己生成就可以了,没有openssl的自己装
生成私钥:
1
| openssl genrsa -out server.key 1024
|
执行结果
1 2 3 4
| Generating RSA private key, 1024 bit long modulus .++++++ ...............++++++ e is 65537 (0x10001)
|
生成证书
1
| openssl req -new -x509 -key server.key -out server.pem -days 365
|
执行结果(这里是我的示例,要改成自己需要的,其实只有Common Name是重点,其它可有可无,这个表示的是域名,由于是本地开发,所以就用localhost)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| You are about to be asked to enter information that will be incorporated into your certificate request. What you are about to enter is what is called a Distinguished Name or a DN. There are quite a few fields but you can leave some blank For some fields there will be a default value, If you enter '.', the field will be left blank. ----- Country Name (2 letter code) [AU]:CN State or Province Name (full name) [Some-State]:Guangdong Locality Name (eg, city) []:Guangzhou Organization Name (eg, company) [Internet Widgits Pty Ltd]:Test Organizational Unit Name (eg, section) []: Common Name (e.g. server FQDN or YOUR name) []:localhost Email Address []:
|
至此,会在当前路径下生成两个文件,我的是在/usr/local/ssl下,根据官方文档配置ssl分别填写(注意版本)
1 2 3 4
| $ curl -i -X POST http://localhost:8001/certificates \ -F "cert=@/usr/local/ssl/server.pem" \ -F "key=@/usr/local/ssl/server.key" \ -F "localhost"
|
创建api添加ssl(注意是host模式,不能用uris(低于0.10版本的叫request_path)模式)
1 2 3 4
| curl -i -X POST http://localhost:8001/apis \ -d "name=sslapi" \ -d "upstream_url=http://www.xxx.com" \ -d "hosts=localhost"
|
至此配置完成,测试一下
1
| $ curl -i https://localhost:8443/
|
可以访问,ok,建议配置上把https_only属性也配置上,这样的话http形式就不能访问了,反正oauth一定要https,接下来创建一个oauth2.0插件
这里有四种模式一般来说都是用enable_authorization_code,但是这里测试起来复杂,就用password模式
1 2 3 4 5
| $ curl -X POST http://kong:8001/apis/{api}/plugins \ --data "name=oauth2" \ --data "config.enable_password_grant=true" \ --data "config.scopes=email,phone,address,password" \ --data "config.mandatory_scope=true"
|
这样sslapi就用上了oauth2.0插件,再次访问的话
1
| $ curl -i https://localhost:8443/
|
报如下错误:
1
| {"error_description":"The access token is missing","error":"invalid_request"}
|
所以,现在需要一个认证服务器,在本地机器上创建一个脚本,我用的是java,运行在tomcat(这里的坑可就多了,看注释),在此之前,还需要创建一个consumer,因为验证的时候有一些配置需要用到
1 2 3
| $ curl -X POST http://localhost:8001/consumers/ \ --data "username=user123" \ --data "custom_id=SOME_CUSTOM_ID"
|
为consumer创建应用来管理oauth和api(这里password模式不需要redirect_uri属性,但是又不能为空,随便填,比如code模式就需要用到)
1 2 3
| $ curl -X POST http://kong:8001/consumers/{consumer_id}/oauth2 \ --data "name=Testapplication" \ --data "redirect_uri=http://some-domain/endpoint/"
|
再创建一个api来转发到验证服务器
1 2 3 4
| curl -i -X POST http://localhost:8001/apis \ -d "name=auth" \ -d "upstream_url=http://www.xxx.com/auth" \ -d "uris=/auth"
|
开始写验证服务(用户填写用户名密码发到kong,kong转发到验证服务器)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| // 接收验证请求 @ResponseBody @RequestMapping("auth") public Result auth(@RequestParam("username") String username, @RequestParam("password") String password, @RequestParam("url") String url) { Result result = new Result(); try { // 这里写死验证条件,根据需要去验证 if (username.equals("zfs") && password.equals("123456")) { Map<String,Object> param = new HashMap<String,Object>(); // 这些在创建consumers的application的时候会生成 param.put("client_id", "9b120e441c664526a179e64f5fae9d61"); param.put("client_secret", "f272ad1b876e48358062893300b14d37"); // 这个可以看这里https://tools.ietf.org/html/rfc6749#section-7.1 param.put("grant_type", "password"); // 这些在创建oauth2.0插件的时候生成 param.put("scope", "password"); param.put("provision_key", "function"); // 这些是自己的用户信息 param.put("authenticated_userid", "233"); param.put("username", "zfs"); param.put("password", "123456"); // 必须是post请求 String res = sendHttps(url, param); result.setSuccessResult(res); } } catch (Exception e) { e.printStackTrace(); } return result; }
|
验证服务器验证通过后发送请求到kong的OAuth2 接口,kong会下发token给请求的用户
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| public static String sendHttps(String forwardUrl, Map<String, Object> param) throws Exception { // forwardUrl不能写死,要当成参数传进来,这个url是你要访问的api再加端点,如我这里要访问的是https://localhost:8443下的资源,所以这里url为https://localhost:8443/oauth2/token(kong提供了/oauth2/authorize和/oauth2/token两个,这里不是code模式,不需要/oauth2/authorize,这些都是需要用post访问的) HttpURLConnection conn = null; try { // 这里ssl要自己写,因为这是一个SSL版本问题。服务器只支持SSLv3,而Java将从v2开始,并尝试向上协商,但并非所有服务器都支持这种类型的协商。在网上找答案说使用SSLv3是目前唯一的解决方案,默认是TLS1,会报connect reast的错误,具体的tomcat对应关系见https://blogs.oracle.com/java-platform-group/diagnosing-tls,-ssl,-and-https,我这里是tomcat7,所以设置为TLSv1.2 System.setProperty("https.protocols", "TLSv1.2"); SSLContext sc = SSLContext.getInstance("TLSv1.2"); sc.init(null, new TrustManager[] { new MyX509TrustManager() }, new java.security.SecureRandom()); URL url = new URL(forwardUrl); HttpsURLConnection https = (HttpsURLConnection) url .openConnection(); if (url.getProtocol().toLowerCase().equals("https")) { // 这里信任所有证书 https.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String hostname, SSLSession session) { return false; } }); conn = https; } else { conn = (HttpURLConnection) url.openConnection(); } ((HttpsURLConnection) conn).setSSLSocketFactory(sc .getSocketFactory()); ((HttpsURLConnection) conn) .setHostnameVerifier(new TrustAnyHostnameVerifier()); // 设置请求的参数 String post = ""; if (null != param && param.size() > 0) { Set<Entry<String, Object>> entrys = param.entrySet(); for (Entry<String, Object> entry : entrys) { String key = StringUtil.objToStr(entry.getKey()); String val = StringUtil.objToStr(entry.getValue()); post += key + "=" + val + "&"; } post = post.substring(0, post.length() - 1); } conn.setRequestMethod("POST");// 提交模式 // 发送POST请求必须设置如下两行 conn.setDoOutput(true); conn.setDoInput(true); // 获取URLConnection对象对应的输出流 PrintWriter printWriter = new PrintWriter(conn.getOutputStream()); // 发送请求参数 printWriter.write(post);// post的参数 xx=xx&yy=yy // flush输出流的缓冲 printWriter.flush(); conn.connect(); // 开始获取数据 BufferedInputStream bis = new BufferedInputStream( conn.getInputStream()); ByteArrayOutputStream bos = new ByteArrayOutputStream(); int len; byte[] arr = new byte[1024]; while ((len = bis.read(arr)) != -1) { bos.write(arr, 0, len); bos.flush(); } bos.close(); return bos.toString("utf-8"); } catch (Exception e) { throw e; } } private static class MyX509TrustManager implements X509TrustManager { public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { } public X509Certificate[] getAcceptedIssuers() { return null; } } private static class TrustAnyHostnameVerifier implements HostnameVerifier { public boolean verify(String hostname, SSLSession session) { return true; } }
|
认证服务器写完,开始验证
1
| $ curl -i https://localhost:8443/auth?username=zfs&password=123456&url=https://localhost:8443/oauth2/token
|
返回结果(这是我自己的json结构,即上面的Result)
1
| {"data":"{\"refresh_token\":\"673871402d0f45fe962be3b9fa2eb68a\",\"token_type\":\"bearer\",\"access_token\":\"924e79bd9fa941d08cc5b96fe8eba44b\",\"expires_in\":7200}\n","err":0,"msg":"操作成功!"}
|
拿到access_token之后就可以访问https://localhost:8443下的资源了,试一下
1
| $ curl -k 'https://localhost:8443/test' -H 'Authorization: Bearer 924e79bd9fa941d08cc5b96fe8eba44b'
|
这样整个流程就走完了,其实这里因为用了虚拟机,传进去的url参数不是https://localhost:8443/oauth2/token,这个地址是不对的,因为在主机里访问虚拟机不能直接用localhost,而是得用ip+port,所以我传的是https://myip:8443/oauth2/token,这里就有一个问题了,证书绑的是域名,ip请求并不能被识别,所以再创了一个api,心好累
1 2 3 4
| curl -i -X POST http://localhost:8001/apis \ -d "name=zhuanfa" \ -d "upstream_url=https://localhost:8443/oauth2/token" \ -d "uris=/zhuanfa"
|
所以验证的请求就会变成
1
| $ curl -i https://localhost:8443/auth?username=zfs&password=123456&url=https://myip:8443/zhuanfa/oauth2/token
|