搭建好了基于 OWIN 的 OAuth2 服务器之后, 接下来就是如何从服务器取得授权了, 下面就介绍如何实现 OAuth2 定义的四种授权方式。
授权码授权针对机密的客户端优化, 可以同时获取访问凭据 (access token) 和刷新凭据 (refresh token) , 因为是基于 HTTP 重定向的方式, 所以客户端必须能够操纵资源所有者的用户代理(通常是浏览器)并且能够接收从授权服务器重定向过来的请求。
在实现上使用开源的 DotNetOpenAuth 来简化实现代码, DotNetOpenAuth 可以通过 NuGet 获取, 示例代码如下:
// init a new oauth web server client;
var authServer = new AuthorizationServerDescription {
AuthorizationEndpoint = new Uri(Paths.AuthorizePath),
TokenEndpoint = new Uri(Paths.TokenPath)
};
var webServerClient = new WebServerClient(authServer, clientId, clientSecret);
// redirect user user-agent to authorization endpoint;
var userAuthorization = webServerClient.PrepareRequestUserAuthorization(new[] { "bio", "notes" });
userAuthorization.Send(HttpContext);
Response.End();
// get access token from request (redirect from oauth server)
var authorizationState = webServerClient.ProcessUserAuthorization(Request);
if (authorizationState != null) {
ViewBag.AccessToken = authorizationState.AccessToken;
ViewBag.RefreshToken = authorizationState.RefreshToken;
ViewBag.Action = Request.Path;
}
//refresh token
var state = new AuthorizationState {
AccessToken = Request.Form["AccessToken"],
RefreshToken = Request.Form["RefreshToken"]
};
if (webServerClient.RefreshAuthorization(state)) {
ViewBag.AccessToken = state.AccessToken;
ViewBag.RefreshToken = state.RefreshToken;
}
// call protected user resource
var client = new HttpClient(webServerClient.CreateAuthorizingHandler(accessToken));
var body = await client.GetStringAsync(new Uri(Paths.ResourceUserApiPath));
ViewBag.ApiResponse = body;
隐式授权为已知的公开客户端优化, 用于客户端操作一个特定的重定向地址, 只能获取访问凭据 (access token) , 不支持刷新凭据 (refresh token) 。 客户端通常在浏览器内用 Javascript 实现。
因为是基于 HTTP 重定向的方式, 所以客户端必须能够操纵资源所有者的用户代理(通常是浏览器)并且能够接收从授权服务器重定向过来的请求。
与授权码授权方式不同的是, 客户端不需要为授权和访问凭据分别发送单独的请求, 可以直接从授权请求获取访问凭据。
隐式授权不包括客户端授权, 依赖资源所有者(用户)的现场判断以及客户端重定向地址, 由于访问凭据是在 URL 中编码的, 所以有可能会暴漏给用户或客户端上的其它应用。
由于这种授权方式一般是通过浏览器实现的, 所以就不用依赖 DotNetOpenAuth 了, 只需要 Javascript 就行了, 示例代码如下:
// index.html
var authorizeUri = '@(Paths.AuthorizePath)';
var tokenUri = '@(Paths.TokenPath)';
var apiUri = '@Paths.ResourceUserApiPath';
var clientId = '@clientId';
var returnUri = '@clientRedirectUrl';
var nonce = 'my-nonce';
$('#authorize').click(function () {
// build redirect url
var uri = addQueryString(authorizeUri, {
'client_id': clientId,
'redirect_uri': returnUri,
'state': nonce,
'scope': 'bio notes',
'response_type': 'token'
});
// login callback
window.oauth = {};
window.oauth.signin = function (data) {
if (data.state !== nonce) {
return;
}
$('#accessToken').val(data.access_token);
}
// open login.html in a new window.
window.open(uri, 'authorize', 'width=640,height=480');
});
// add query string to uri
function addQueryString(uri, parameters) {
var delimiter = (uri.indexOf('?') == -1) ? '?' : '&';
for (var parameterName in parameters) {
var parameterValue = parameters[parameterName];
uri += delimiter + encodeURIComponent(parameterName) + '=' + encodeURIComponent(parameterValue);
delimiter = '&';
}
return uri;
}
// login.html
// get fragment and call opener's signin function.
var fragments = getFragment();
if (window.opener && window.opener.oauth && window.opener.oauth.signin) {
window.opener.oauth.signin(fragments);
}
window.close();
// get fragment from window uri
function getFragment() {
if (window.location.hash.indexOf("#") === 0) {
return parseQueryString(window.location.hash.substr(1));
} else {
return {};
}
}
// parse query string to object;
function parseQueryString(queryString) {
var data = {}, pairs, pair, separatorIndex, escapedKey, escapedValue, key, value;
if (queryString === null) {
return data;
}
pairs = queryString.split("&");
for (var i = 0; i < pairs.length; i++) {
pair = pairs[i];
separatorIndex = pair.indexOf("=");
if (separatorIndex === -1) {
escapedKey = pair;
escapedValue = null;
} else {
escapedKey = pair.substr(0, separatorIndex);
escapedValue = pair.substr(separatorIndex + 1);
}
key = decodeURIComponent(escapedKey);
value = decodeURIComponent(escapedValue);
data[key] = value;
}
return data;
}
资源所有者密码凭据授权适用于那些被充分信任的应用, 比如设备操作系统或者权限很高的应用。 授权服务器启用这类授权是要格外注意, 只能在其它授权方式不能用的时候才使用这种授权方式。
这种授权方式适用于能够取得用户的凭据 (通常是通过可交互的表单) 的应用, 也可以用于迁移现有的那些需要直接授权 (HTTP Basic 或 Digest ) 的应用, 将保存的用户凭据改为保存访问凭据 (access token) 。
对于 DotNetOpenAuth 来说, 这种授权也是十分容易实现的, 示例代码如下:
// create auth server description
var authServer = new AuthorizationServerDescription {
AuthorizationEndpoint = new Uri(Paths.AuthorizePath),
TokenEndpoint = new Uri(Paths.TokenPath)
};
// create web server client
var webServerClient = new WebServerClient(authServer, clientId, clientSecret);
// use user name and password to exchange access token;
var state = webServerClient.ExchangeUserCredentialForToken(
username, password,
new[] {"scope1", "scope2", "scope3"}
);
// get access token;
var token = state.AccessToken;
客户端凭据授权是指客户端可以只通过客户端自己的凭据 (client_id 和 client_secret) (或者其它方式的认证) 来获取访问凭据, 客户端可以根据自己的需要来访问受保护的资源, 或者资源所有者已经访问过认证服务器时, 才能使用这种授权方式。 只有对完全受信任的客户端才能使用这种授权方式, 因为对受保护的资源方来说, 认证信息的内容是客户端程序的凭据, 而不是资源所有者的凭据。
DotNetOpenAuth 也支持这种授权方式, 示例代码如下:
// create auth server description
var authServer = new AuthorizationServerDescription {
AuthorizationEndpoint = new Uri(Paths.AuthorizePath),
TokenEndpoint = new Uri(Paths.TokenPath)
};
// create web server client
var webServerClient = new WebServerClient(authServer, clientId, clientSecret);
// get client access token;
var state = webServerClient.GetClientAccessToken(
new[] { "test1", "test2", "test3" }
);
// get access token;
var token = state.AccessToken;
上面介绍的都是如何取得访问凭据 (access_token) , 拿到了访问凭据之后如何来使用呢? 对于使用微软的 OWIN 中间件 Microsoft.Owin.Security.OAuth 搭建的服务器来说, 需要设置 HTTP 请求的 Authorization 标头为 Bearer {access_token}
就可以了, 这个属于 OAuth 的规范之内了, 示例代码如下:
使用 jQuery 的 Ajax 请求时, 示例代码如下:
var accessToken = '@AccessToken';
$.ajax({
url: '@ResourcePath',
beforeSend: function(jqr) {
jqr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
}
})
.done(function (data) {
// other code here.
});
使用其它语言的代码与上面的 js 代码大同小异,上面只是一些代码片段, 在 github 上有完整的项目代码, 不清楚的地方可以直接查看源代码。