OAuth2应用集成
本文介绍OAuth2应用如何与MaxKey进行集成。
认证流程
采用Authorization Code获取Access Token的授权验证流程又被称为Web Server Flow,适用于所有Server端的应用。其调用流程示意图如下:

对于应用而言,其流程由获取Authorization Code和通过Authorization Code获取Access Token这2步组成。
1.引导需要授权的用户到如下地址:
https://sso.maxkey.org/maxkey/oauth/v20/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI
2.页面跳转至
YOUR_REGISTERED_REDIRECT_URI/?code=CODEsss
3.换取Access Token
https://sso.maxkey.org/maxkey/oauth/v20/token?client_id=YOUR_CLIENT_ID&client_secret=YOUR _SECRET&grant_type=authorization_code&redirect_uri=YOUR_REGISTERED_REDIRECT_URI&code=CODE
返回值
        
{ "access_token":"SlAV32hkKG", "remind_in ":3600, "expires_in":3600 }
应用注册
应用在MaxKey管理系统进行注册,注册的配置信息如下

API接口标准
| 接口 | 说明 | 详细说明 | 调用方法 | 
|---|---|---|---|
| /oauth/v20/authorize | 请求用户授权Token | https://sso.maxkey.org/maxkey接收app sso认证请求, client_id为需要认证的应用的id; | APP | 
| /oauth/v20/token | 获取授权过的 Access Token | 后台应用获取 tokencode ,调用接口进行 tokencode 校验; 校验成功获取访问 token | APP | 
| /api/oauth/v20/me | 授权用户信息查询接口 | 通过访问 token 获取登录用户信息 | APP | 
1)/oauth/v20/authorize接口
请求用户授权Token
| 接口名称 | 请求用户授权Token | 
|---|---|
| url | https://sso.maxkey.org/maxkey/oauth/v20/authorize | 
| 请求方式 | http get/post | 
请求参数
| 参数 | 说明 | 
|---|---|
| client_id | 注册应用时分配的client_id。 | 
| redirect_uri | 应用回调地址,注册时需要配置 | 
| grant_type | 授权类型。 | 
| etc param | 其他参数。 | 
| 响应返回app应用程序,包含请求参数如下: | |
| http://app.maxkey.org/app/callback?tokencode =PQ7q7W91a-oMsCeLvIaQm6bTrgtp7 | |
| tokencode | 用于调用oauth/token,接口获取授权后的访问token。 | 
2 /oauth/v20/token接口
通过/oauth/v20/token用tokencode换取访问token
| 接口名称 | token 接口 | 
|---|---|
| url | https://sso.maxkey.org/maxkey/oauth/v20/token | 
| 请求方式 | http get/post | 
请求参数
| 参数 | 说明 | 
|---|---|
| client_id | 注册应用时分配的client_id。 | 
| client_secret | 注册应用时分配的client_secret | 
| redirect_uri | 应用回调地址,注册时需要配置 | 
| tokencode | 调用/oauth/v20/authorize获得的tokencode值。 | 
| grant_type | 授权类型。Grant type | 
| username | 当grant_type=password时,此参数表示直接认证用户名。 | 
| password | 当grant_type=password时,此参数表示直接认证用户密码。 | 
| etc param | 其他参数 | 
| 实际请求如下:  | |
| 返回数据 | |
| A successful response to this request contains the following fields: | |
| access_token | 用该token能调用SSO的API | 
| 成功返回JSON数据,如下:  | |
3)用户属性接口/api/oauth/v20/me
| 接口名称 | token 接口 | 
|---|---|
| url | https://sso.maxkey.org/maxkey/api/oauth/v20/me | 
| 请求方式 | http get/post | 
请求参数
| 参数 | 说明 | 
|---|---|
| access_token | 调用sso/ token获得的token值。 | 
| 实际请求如下:  | |
| 返回数据/ response data | |
| 成功返回JSON数据,如下: zhangs是认证的用户ID | |
OAuth认证接口属性列表
| 属性名(Attribute) | 描述 | 数据类型 | 
|---|---|---|
| uid | uid | 字符串 | 
OAuth2.0 错误码
MaxKey OAuth2.0实现中,授权服务器在接收到验证授权请求时,会按照OAuth2.0协议对本请求的请求头部、请求参数进行检验,若请求不合法或验证未通过,授权服务器会返回相应的错误信息,包含以下几个参数:
error: 错误码
error_description: 错误的描述信息
错误信息的返回方式有两种:
当请求授权Endpoint:https://sso.maxkey.org/maxkey/oauth/v20/authorize 时出现错误,返回方式是:跳转到redirect_uri,并在uri 的query parameter中附带错误的描述信息。
当请求access token endpoint:https://sso.maxkey.org/maxkey/oauth/v20/token 时出现错误,返回方式:返回JSON文本。
例如:
 
{
	"error":"unsupported_response_type",
	"error_description":"不支持的 ResponseType."
}
OAuth2.0错误响应中的错误码定义如下表所示:
| 编号 | 错误码(error) | 描述(error_description) | 
|---|---|---|
| 1 | empty_client_id | 参数client_id为空 | 
| 2 | empty_client_secret | 参数client_secret为空 | 
| 3 | empty_redirect_uri | 参数redirect_uri为空 | 
| 4 | empty_response_type | 参数response_type为空 | 
| 5 | empty_code | code为空 | 
| 6 | app_unsupport_sso | 应用不支持sso登录 | 
| 7 | app_unsupport_oauth | 应用不支持OAuth认证 | 
| 8 | invalid_client_id | 非法的client_id | 
| 9 | invalid_response_type | 非法的response_type | 
| 10 | invalid_scope | 非法的scope | 
| 11 | invalid_grant_type | 非法的grant_type | 
| 12 | redirect_uri_mismatch | 非法的redirect_uri | 
| 13 | unsupported_response_type | 不支持传递的response_type | 
| 14 | invalid_code | 非法的code | 
| 15 | unsupported_refresh_token | 不支持refresh_token的方式 | 
| 16 | access_token_exprise | access_token过期 | 
| 17 | invalid_access_token | 非法的access_token | 
| 18 | invalid_refresh_token | 非法的refresh_token | 
| 19 | refresh_token_exprise | refresh_token过期 | 
OAuth2客户端集成
本文使用JAVA WEB程序为例
第一步,引入客户端所需包
gson-2.2.4.jar
maxkey-client-sdk.jar
nimbus-jose-jwt-8.10.jar
commons-codec-1.9.jar
commons-io-2.2.jar
commons-logging-1.1.1.jar
第二步,认证跳转
<%@ page language="java" import="java.util.*" pageEncoding="ISO-8859-1"%>
<%@ page language="java" import="org.maxkey.client.oauth.oauth.*" %>
<%@ page language="java" import="org.maxkey.client.oauth.builder.*" %>
<%@ page language="java" import="org.maxkey.client.oauth.builder.api.MaxkeyApi20" %>
<%@ page language="java" import="org.maxkey.client.oauth.model.Token" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+path+"/";
String callback="http://oauth.demo.maxkey.top:8080/demo-oauth/oauth20callback.jsp";
OAuthService service = new ServiceBuilder()
     .provider(MaxkeyApi20.class)
     .apiKey("b32834accb544ea7a9a09dcae4a36403")
     .apiSecret("E9UO53P3JH52aQAcnLP2FlLv8olKIB7u")
     .callback(callback)
     .build();
Token EMPTY_TOKEN = null;
String authorizationUrl = service.getAuthorizationUrl(EMPTY_TOKEN);
request.getSession().setAttribute("oauthv20service", service);
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    
    <title>OAuth 2.0 SSO</title>
	<meta http-equiv="pragma" content="no-cache">
	<meta http-equiv="cache-control" content="no-cache">
	<meta http-equiv="expires" content="0">    
	<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
	<meta http-equiv="description" content="This is my page">
  </head>
  
  <body>
    <a href="<%=authorizationUrl%>&approval_prompt=auto">oauth 2.0 sso</a>
  </body>
</html>
第三步,获取令牌及用户信息
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ page language="java" import="org.maxkey.client.oauth.oauth.*" %>
<%@ page language="java" import="org.maxkey.client.oauth.builder.*" %>
<%@ page language="java" import="org.maxkey.client.oauth.builder.api.MaxkeyApi20" %>
<%@ page language="java" import="org.maxkey.client.oauth.model.*" %>
<%@ page language="java" import="org.maxkey.client.oauth.*" %>
<%@ page language="java" import="org.maxkey.client.oauth.domain.*" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
OAuthService service = (OAuthService)request.getSession().getAttribute("oauthv20service");
if(service==null){
	String callback="http://oauth.demo.maxkey.top:8080/demo-oauth/oauth20callback.jsp";
	service = new ServiceBuilder()
     .provider(MaxkeyApi20.class)
     .apiKey("b32834accb544ea7a9a09dcae4a36403")
     .apiSecret("E9UO53P3JH52aQAcnLP2FlLv8olKIB7u")
     .callback(callback)
     .build();
}
Token EMPTY_TOKEN = null;
Verifier verifier = new Verifier(request.getParameter("code"));
Token accessToken = service.getAccessToken(EMPTY_TOKEN, verifier);
 
OAuthClient restClient=new OAuthClient("https://sso.maxkey.top/maxkey/api/oauth/v20/me");
 
 UserInfo userInfo=restClient.getUserInfo(accessToken.getAccess_token());
 
 
%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
  <head>
    <base href="<%=basePath%>">
    
   <title>OAuth V2.0 Demo</title>
	<meta http-equiv="pragma" content="no-cache">
	<meta http-equiv="cache-control" content="no-cache">
	<meta http-equiv="expires" content="0">    
	<meta http-equiv="keywords" content="keyword1,keyword2,keyword3">
	<meta http-equiv="description" content="OAuth V2.0 Demo">
	<link rel="shortcut icon" type="image/x-icon" href="<%=basePath %>/images/favicon.ico"/>
	<script type="text/javascript" src="<%=basePath %>/jquery-3.5.0.min.js"></script>
	<script type="text/javascript" src="<%=basePath %>/jsonformatter.js"></script>
	<link   type="text/css" rel="stylesheet"  href="<%=basePath %>/demo.css"/>
	
  </head>
  
  <body>
  		<div class="container">
	  		<table class="datatable">
	  			<tr>
	  				
	  				<td colspan="2" class="title">OAuth V2.0 Demo</td>
	  			</tr>
	  			
	  			<tr>
	  				<td width="50%">OAuth V2.0 Logo</td>
	  				<td width="50%"> <img src="<%=basePath %>/images/oauth-2-sm.png"  width="124px" height="124px"/></td>
	  			</tr>
	  			<tr>
	  				<td>Login</td>
	  				<td><%=userInfo.getUsername() %></td>
	  			</tr>
	  			<tr>
	  				<td>DisplayName</td>
	  				<td><%=userInfo.getDisplayName() %></td>
	  			</tr>
	  			<tr>
	  				<td>Department</td>
	  				<td><%=userInfo.getDepartment() %></td>
	  			</tr>
	  			<tr>
	  				<td>JobTitle</td>
	  				<td><%=userInfo.getJobTitle() %></td>
	  			</tr>
	  			<tr>
	  				<td>email</td>
	  				<td><%=userInfo.getEmail() %></td>
	  			</tr> 
	  			<tr>
	  				<td>ResponseString</td>
	  				<td  style="word-wrap: break-word;">
						<textarea cols="68" rows="20" v-model="text2"><%=userInfo.getResponseString() %></textarea>
					</td>
	  			</tr>
	  		</table>
			<script type="text/javascript">
				FormatTextarea();
			</script>
  		</div>
  </body>
</html>
