javascript - 将 Sails / Node.js 发布到 Kongregate
问题描述
所以,最后我建立在 Sails 上的 MMORTS 游戏将登陆 Kongregate。几乎没有障碍,比如连接 websocket,但现在解决了。可能最后一个障碍是保持经过身份验证的会话。我到处都在使用框架,但我不知道身份验证会话是如何在幕后工作的。
主要问题可能是 CSRF 或 CORS。
我正在使用 Sails v1.0。所以,我从上传到 kongregate 的 HTML 开始。我正在举一个最简单的例子:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta charset="utf-8">
<script src="jquery.js"></script>
<script src='https://cdn1.kongregate.com/javascripts/kongregate_api.js'></script>
<script src="sails.io.js"
autoConnect="false"
environment="production"
headers='{ "x-csrf-token": "" }'
></script>
<script type="text/javascript">
io.sails.url = 'https://my-secret-game.com'; // or where you want
</script>
</head>
<script src="main.js"></script>
</html>
这是 main.js ,我也上传到 kongregate
kongregateAPI.loadAPI(function(){
window.kongregate = kongregateAPI.getAPI();
var username = kongregate.services.getUsername();
var id = kongregate.services.getUserId();
var token = kongregate.services.getGameAuthToken();
$.get("https://my-secret-game.com/csrfToken", function (data, jwres) {
var params = {
username: username,
id: id,
token: token,
_csrf: data._csrf
};
$.post("https://my-secret-game.com/kong", params, function(data, jwr, xhr){
// cant set the cookie - because of chrome. this doesnt work
document.cookie = document.cookie + ';authenticated=true;sails.sid=' + data.id;
$.get("https://my-secret-game.com/csrfToken", function (data, jwres) {
var msg = {
testing_authentication: true,
_csrf: data._csrf
};
$.post("https://my-secret-game.com/test", msg, function(data, status){
// getting the 403 Forbidden, CSRF mismatch. trying to access
// the play/test route, which is protected by sessionAUTH
console.log('data.response', data.response)
});
});
});
});
});
问题是,每当我尝试使用 sessionAUTH 发布我的 Sails 后端时,我都会收到 403 Forbidden。我也无法设置 cookie - 可能是因为 Chrome。我能做些什么?当我获得 CSRF 令牌时,在下一个请求中,我的 Sails 应用程序会响应 CSRF 不匹配。它变得错误。
这是我的 Sails 后端服务器上的控制器
module.exports = {
kong: function (req, res, next) {
var url = 'https://api.kongregate.com/api/authenticate.json';
var kong_api_key = 'my-secred-api-key';
var params = req.allParams();
var request = require('request');
var req_form = {
"api_key": kong_api_key,
"user_id": params.id,
"game_auth_token": params.token
};
request({
url: url,
method: "POST",
json: true,
body: req_form,
timeout: 5000
}, function (err, response, body){
if(err) { console.log(err, 'ERR43'); return res.ok(); }
else {
if(!response.body.success) {
console.log('unsuccessful login from kongregate')
return res.ok();
}
// trying to use a existing user and authenticate to it
User.find({username: 'admin-user'}).exec(function(err, users) {
var user = users[0];
req.session.authenticated = true;
req.session.user = { id: user.id };
// trying to send session_id, so that i could hold it on kongregates cookies as `sid`
return res.send({ user: user, id: req.session.id });
});
}
});
},
somoene 可以帮助修复我的应用程序的身份验证和 CSRF 吗?
如果需要有关我的配置的更多信息,这是 config/session.js
var prefixes = 'dev';
module.exports.session = {
secret: 'my-secret',
cookie: {
secure: false
},
adapter: 'redis',
host: 'localhost',
port: 6379,
ttl: 3000000,
db: 0,
prefix: prefixes + 'sess:',
};
配置/policies.js
module.exports.policies = {
user: {
'new': 'flash',
'create': 'flash',
'edit': 'rightUser',
'update': 'rightUser',
'*': 'sessionAuth'
},
play: {
'*': 'sessionAuth'
}
};
api/policies/sessionAuth.js
module.exports = function(req, res, next) {
if (req.session.authenticated) {
return next();
} else {
var requireLoginErr = [
{ name: 'requireLogin', message: 'You must be signed in' }
];
req.session.flash = {
err: requireLoginErr
};
res.redirect('/');
return;
}
};
配置/安全.js
module.exports.security = {
csrf: true,
cors: {
allowRequestMethods: 'GET,PUT,POST,OPTIONS,HEAD',
allowRequestHeaders: 'content-type,Access-Token',
allowResponseHeaders: '*',
allRoutes: true,
allowOrigins: '*',
allowCredentials: false,
},
};
解决方案
好吧,因为我没有答案(显然 - 问题很糟糕),所以用我自己解决的解决方案来回答 - 所以下次我可以阅读我自己。不确定它有多好,但至少它有效。
Sails CORS 采用 Express.js 格式,如果我允许在配置中使用,则允许将套接字连接到 kongregate。但它不允许通过cookies发送sails.sid(身份验证令牌)以正常方式进行身份验证。
由于安全原因,Chrome 根本不允许使用 javascript(我在 Kongregate 上没有后端)将 cookie 设置为标头。因此,如果我无法发送带有标头的 cookie,Sails 就无法以正常方式对请求进行身份验证。即使我允许 CORS 接受“cookie”标头 - 浏览器也不允许使用 javascript 设置 cookie 标头。
我可以制作一些独特的标题,如“身份验证”并在那里设置sails.sid,扩展Sails的一些核心功能以采用这个新标题而不是cookie标题。但问题是 - 在支持 Sails 的情况下,我根本无法获得这些sails.sid 并将其发送到我的外部前端。它是在哪里创建的?如何在 Sails 后端获取sails.sid?不确定 - 不能用谷歌搜索它。
所以,我只是以最简单的方式进行身份验证 - 在帐户登录/注册时,我只是自己创建一个会话密钥 - 使用 bcrypt 散列 user_id+secret_token (取自sails config secrets)。并发送到前端 { user_id: 'abcd', secret_token: 'a2dw412515...' }
我在 Sails 中制定了对每个 POST/GET 请求进行身份验证的策略——获取请求的 session_id 和 user_id,并使用 bcrypt 进行比较,session_id 是否与加密的 user_id+secret_token 相同。我希望它足够安全。
所以,它奏效了。我只需要禁用 CSRF。也许有一天我会再次实现它,我只需要以我的方式编写它,而不是保留 Sails 的默认值。
工作代码:
前端
// you upload this HTML file to kongregate
// also you upload additionally ZIP named "kongregate_uploaded_folder" with libraries like sails.io, jquery
<!DOCTYPE html>
<html>
<head>
<script src="kongregate_uploaded_folder/jquery.js"></script>
<script src='https://cdn1.kongregate.com/javascripts/kongregate_api.js'></script>
<script src="kongregate_uploaded_folder/sails.io.js"
autoConnect="false"
environment="production"
></script>
</head>
<body style="padding:0; margin:0; overflow:hidden;">
<div style="position:absolute; margin: 0px; padding: 0px;">
<canvas id="main_canvas" style="position:absolute;" width="640" height="480" >Best initial resolution to have in Kongregate</canvas>
</div>
<script>
// the first thing happends - you try to connect your frontend with Kongregate
kongregateAPI.loadAPI(function(){
window.kongregate = kongregateAPI.getAPI();
if(!kongregate.services.isGuest()) {
var params = {
username: kongregate.services.getUsername(),
id: kongregate.services.getUserId(),
token: kongregate.services.getGameAuthToken(),
};
// call your backend to create a new session and give you session_id
$.post("https://your_game_server.com/kong", params, function(data, jwr, xhr){
var kong_session_id = data.kong_session_id;
var kong_user_id = data.kong_user_id;
var user = data.user;
// connect your sockets with the server in this way
io.socket = io.sails.connect("https://your_game_server.com", { useCORSRouteToGetCookie: false, reconnection: true });
// subscribe to the global sockets channel. You have to make this route and code, but here is a example
io.socket.get('/subscribe', { kong_session_id: kong_session_id, kong_user_id: kong_user_id }, function(data, jwr){
if (jwr.statusCode == 200){
io.socket.on(data.room, function(event){
// on any server-side event, you will get this "event" message. At this part you decide what to do with this data
incoming_socket_event(event); // i wont write this function
});
// your game continues here:
$.get("https://your_game_server.com/player_data?kong_session_id=" + kong_session_id + "&kong_user_id=" + kong_user_id, params, function(data, jwr, xhr){
// you will get authenticated "current_user"
}
});
})
}
});
</script>
</html>
后端
// SAILS BACKEND: home_controller.js
module.exports = {
kong: function (req, res, next) {
// player has already opened your game in kongregate.com and frontend requests this endpoint POST /kong { id: 84165456, token: 'as54..' }
// you need to create a new session for this player, or register this player. This is your own session creation, since default sails session wont work with external website frontend.
var req_params = {
url: "https://api.kongregate.com/api/authenticate.json", // default URL to validate kongregate user
method: "POST",
json: true,
body: {
api_key: 'gg000g00-c000-4c00-0000-c00000f2de8', // kongregate will provide this api-key for server-side connection (this one has some letters replaced)
user_id: 84165456, // when frontend requests POST /kong { id=84165456 } , this 84165456 is provided by kongregate in the frontend
game_auth_token: "as54a45asd45fs4aa54sf" // when frontend requests POST /kong { token = 'as54..' }, this is provided by kongregate in the frontend
},
timeout: 20000
}
// request kongregate that this is the real player and you will get a username
request(req_params, function (err, response, body){
var response_params = response.body; // response from kongregate API
// search for user with this kongregate_id inside your database, maybe he is already registered, and you need just to create a new session.
User.find({ kongregate_id: response_params.user_id }).exec(function(err, usr) {
var user = usr[0]
// if player already has an account inside your online game
if(user) {
// create new session for this user.
require('bcryptjs').hash("your_own_random_secret_key" + user.id, 10, function sessionCreated(err, kong_session_id) {
// send this info to frontend, that this player has been connected to kongregate
return res.send({
user: user,
kong_session_id: kong_session_id,
kong_user_id: user.id
});
});
//if this is new user, you need to register him
} else {
var allowedParams = {
username: response_params.username, // kongregate will give you this player username
email: 'you_have_no_email@kong.com', // kongregate does not provide email
password: 'no_password_from_kongregate',
kongregate_id: response_params.user_id // kongregate will give you this player id
};
User.create(allowedParams, function(err, new_user) {
// create new session for this user.
require('bcryptjs').hash("your_own_random_secret_key" + new_user.id, 10, function sessionCreated(err, kong_session_id) {
// send this info to frontend, that this player has been connected to kongregate
return res.send({
user: new_user,
kong_session_id: kong_session_id,
kong_user_id: new_user.id
});
});
});
}
});
});
},
};
路线
// config/routes.js
module.exports.routes = {
'GET /player_data': 'PlayController.player_data',
'GET /subscribe': 'PlayController.subscribe',
'POST /kong': {
action: 'home/kong',
csrf: false // kongregate is a external website and you will get CORS error without this
},
};
安全
// config/security.js
module.exports.security = {
csrf: false,
cors: {
allowOrigins: ['https://game292123.konggames.com'], // your game ID will be another, but in this style
allowRequestMethods: 'GET,POST',
allowRequestHeaders: 'Content-Type',
allowResponseHeaders: '',
allRoutes: true,
allowCredentials: false,
},
};
插座
// config/sockets.js
module.exports.sockets = {
grant3rdPartyCookie: true,
transports: ["websocket"],
beforeConnect: function(handshake, cb) { return cb(null, true); },
};
配置策略
// /config/policies.js
module.exports.policies = {
play: {'*': 'sessionAuth'},
};
API 政策
// /app/sessionAuth.js
module.exports = function(req, res, next) {
var params = req.allParams();
// your own session handling way to get the user from session token
require('bcryptjs').compare("your_own_random_secret_key" + params.kong_user_id, params.kong_session_id, function(err, valid) {
req.session.authenticated = true;
req.session.user_id = params.kong_user_id;
return next();
});
};
控制器
// /api/controllers/PlayController.js
module.exports = {
player_data: async function (req, res, next) {
var users = await User.find(req.session.user_id);
return res.send({ current_user: users[0] });
},
subscribe: async function (req, res, next) {
var users = await User.find(req.session.user_id);
var roomName = String(users[0].id);
sails.sockets.join(req.socket, roomName);
res.json({ room: roomName });
},
}
推荐阅读
- google-app-engine - gcloud - 无法配置我的 VPC 连接器以使用我的 Redis 实例
- unity3d - 如何使动画中的最后一个精灵播放超过 1 帧?
- debugging - 将 VS Code 调试器用于无服务器 lambda flask 应用程序
- python - 像属性一样访问字典“值”?
- swift - 如何让继承作用于闭包内的参数?
- sql - 计算订阅者的调用频率 SQL Netezza
- mysql - mysql db的分片配置
- python - Python SpeechRecognition 逐字逐句?连续输出?
- c - 使用 << 和 + 重写 C 表达式
- javascript - 如何使用外部js文件在表单提交上调用函数