【发布时间】:2020-05-22 14:14:19
【问题描述】:
我有以下设置:
资源服务器:它是一个用 PHP (Slim, Doctrine) 编写的基本 RESTful API
Auth Server:我自己用 PHP 实现的 OAuth 2 服务器(Slim,Doctrine)
Public Client:用 VueJS 编写的 SPA,可在网络上公开获取。
此外,我想使用授权代码或客户端凭据授予类型授权更多客户端。
我已经阅读了大部分 official OAuth 2 Framework 规范以及其他几个“简单指南”和实现它的库。 我想我理解了授权代码授权类型的流程如何工作的理论,但是在尝试实现它时,所有重定向都有很多问题。 我知道有一个可行的解决方案,但感觉很老套,我确信必须有一个更简单更好的解决方案。此外,由于我不是安全专家,我很担心 我的解决方案容易受到攻击。
因此,我的问题是:在不依赖许多第三方库的情况下,OAuth 2 授权代码授权类型的简单且安全的实现会是什么样子。
在这里,我提供了我如何理解流程的图像,我将描述并提供我的解决方案的代码 sn-ps。
- (Step 1 to Step 4) 用户点击网站上的登录按钮。 Vuex 操作
loginOAuth被触发,它要求 Auth 服务器提供登录表单并在新窗口中显示。
Vuex 行动loginOAuth
loginOAuth({commit}){
let oauth_axios = axios.create({
baseURL: '/php-own-oauth-server/API/public/api/v1/',
timeout: 1000
});
oauth_axios({
url: 'auth-form',
method: 'GET'
})
.then(resp => {
console.log(resp);
let login_window = window.open('', 'My Name', 'left=0,top=0,width=800,height=500,toolbar=0,scrollbars=0,status=0');
login_window.document.write(resp.data);
login_window.document.close();
})
.catch(err => {
commit('set_error_message', err);
});
},
- (第 5 步到第 7 步)用户输入他们的用户凭据并单击表单上的提交,这会将 POST 调用发送到 Auth 服务器。如果凭据正确,服务器会响应
a 代码,在我的例子中是一个短暂的 JWT。响应是通过
window.opener.postMessage(xhr.responseText, '*');提供给主窗口的,我觉得这很hacky。我还没有找到 执行“使用代码重定向到应用程序”步骤的更好方法。为了签署代表授权类型代码的 JWT,我使用了一个简单的字符串秘密,因为它只需要在 相同的身份验证服务器。
HTML 登录表单,由 OAuth 服务器返回 .../auth-form
<head>
<script>
window.onload = function() {
var form = document.querySelector("form");
form.onsubmit = submitted.bind(form);
}
function submitted(event) {
event.preventDefault();
var xhr = new XMLHttpRequest();
xhr.open("POST", "https://localhost/php-own-oauth-server/API/public/api/v1/auth-form-validate-user-credentials");
xhr.onreadystatechange = function(event) {
if (xhr.readyState == XMLHttpRequest.DONE) {
if (xhr.status == 200) {
window.opener.postMessage(xhr.responseText, '*');
window.close();
}
}
};
var formData = new FormData(document.getElementById("myForm"));
xhr.send(formData);
}
</script>
</head>
<form id="myForm">
<div class="form-group">
<label for="email">Email address</label>
<input type="email" class="form-control" id="email" name="email" aria-describedby="emailHelp">
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password" class="form-control" name="password" id="password">
</div>
<div class="form-group form-check">
<input type="checkbox" class="form-check-input" id="exampleCheck1">
<label class="form-check-label" for="exampleCheck1">Remember me</label>
</div>
<button type="submit" class="btn btn-primary" value="Submit">Login</button>
<button class="btn btn-link">Password forgotten</button>
</form>
在 VueJS 应用程序中,我注册了以下事件侦听器以接收登录表单发送的消息。 它正在获取来自 Auth Server 的响应,该响应主要是 JWT 令牌形式的代码和 通过调度一个动作将其放回 Vuex 上下文中。
JavaScript 事件监听器从登录表单获取响应
window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
if (event.data) {
try {
store.dispatch('Auth/requestAccessToken', JSON.parse(event.data));
} catch (e) {
}
}
}
- (第 8 步到第 9 步)该代码与 client_id 和 client_secret 一起使用(是的,我知道 client_secret 对于公共 SPA 客户端没有多大意义)从 Auth 服务器请求访问令牌。
Vuex 操作requestAccessToken
requestAccessToken({commit}, payload){
let oauth_axios = axios.create({
baseURL: '/php-own-oauth-server/API/public/api/v1/',
timeout: 1000
});
oauth_axios({
url: 'request-access-token',
method: 'POST',
data: {
client_id: 'frontend_app',
client_secret: 'test123!',
code: payload.data.code
}
})
.then(resp => {
console.log(resp);
// resp.data.data.access_token
})
.catch(err => {
commit('set_error_message', err);
});
}
从这一点开始,流程是直截了当的。我只需将访问令牌作为标头添加到可以验证它的资源服务器的每个请求中。 此验证使用私有和公共 RSA 密钥对。 Auth Server 使用私钥对 JWT 进行签名,Resource Server 使用公钥验证签名。
对我来说,这个解决方案感觉不对。是否有更好/更通用的解决方案来实现 OAuth 2 授权代码授权类型?我也怀疑我的解决方案是否可以被非 Web 应用程序客户端使用。
感谢您阅读并帮助我。 干杯,
拉斐尔·希佩
【问题讨论】:
标签: php vue.js authentication oauth-2.0 authorization