如何在rails应用程序上创建动态QR码,以便在扫描和成功处理时,带有QR码的打开页面可以重定向到成功页面。
这类似于whatsapp web实现,其中android app扫描QR码的那一刻,页面加载消息。
更感兴趣的是会话的管理。扫描QR时,可以重新加载显示的页面,然后重定向到另一页。任何想法?
答案 0 :(得分:3)
您可以更新用户模型,以便能够存储要在QR码中使用的唯一标记值; e.g。
$ rails generate migration add_token_to_user token:string
或单独的相关模型
$ rails generate model Token value:string user:belongs_to
然后生成可在URL中使用并对其进行编码的唯一标记值 进入QRCode
# Gemfile
gem "rqrcode"
# app/models/token.rb
require "securerandom"
class User < ActiveRecord::Base
def generate_token
begin
self.token = SecureRandom.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg"
end while self.class.exists?(token: token)
end
def qr_code
RQRCode::QRCode.new(
Rails.application.routes.url_helpers.url_for(
controller: "session",
action: "create",
email: email,
token: token
)
)
end
end
然后在您的应用程序中的某处显示此QRCode
# app/views/somewhere.html.erb
<%= @token.qr_code.as_html %>
然后连接应用程序的路由和控制器以处理生成的进程 和编码的QRCode URL
# config/routes.rb
Rails.application.routes.draw do
# ...
get "/login", to: "sessions#new"
end
# app/controller/sessions_controller.rb
class SessionsController < ApplicationController
def create
user = User.find_by(email: params[:email], token: params[:token])
if user
session[:user_id] = user.id # login user
user.update(token: nil) # nullify token, so it cannot be reused
redirect_to user
else
redirect_to root_path
end
end
end
<强>参考文献:强>
答案 1 :(得分:3)
我添加了一个新答案有两个原因:
1。 Acacia重新解决了这个问题,重点是什么是App的重定向 有QR码的页面正在查看,我在初始时没有提到 解决方案是由于对问题的误解,
2。有些人发现第一个答案很有帮助,这个新答案会有所帮助 改变它虽然相似,但不再相同
扫描QR时,我可以重新加载显示的页面 然后重定向到另一个页面
- 金合欢
为了达到这个目的,需要在某种程度上开放连接 显示QRCode的页面,解释说QRCode的内容可以 用它来实现它。但是,由于您试图模仿的应用程序 要求只有那个查看页面的用户才会生效,而不是 实际上已登录,需要页面中的某些内容是唯一的。
要解决此问题,您需要做一些事情:
用于识别未登录用户的唯一令牌可用于联系/ 受外部浏览器的影响
一种使用JavaScript登录的方法,以便将查看的页面更新为 在上一步的事件
某种身份验证令牌之间可以进行交换 应用程序和外部QRCode扫描仪应用程序,以便 将自己身份验证为特定用户
以下解决方案将以上第3步结果排除在外 演示这个想法,主要集中在服务器端 应用。话虽这么说,第三步的解决方案应该很简单 通过将知道用户身份验证令牌附加到其中的URL来传递 QRCode作为附加参数(并将其作为POST请求提交, 而不是在本演示中作为GET请求。)
您需要使用一些随机令牌来验证用户和 通过嵌入在QCcode中的URL进行交换; e.g。
$ rails generate model Token type:string value:string user:belongs_to
type
是Rails中的reserverd关键字,用于单表继承。
它将用于特定的不同种类/专用令牌
应用
生成可在URL中使用并对其进行编码的唯一标记值 进入QRCode,使用类似以下模型和代码的东西:
# Gemfile
gem "rqrcode" # QRCode generation
# app/models/token.rb
require "securerandom" # used for random token value generation
class Token < ApplicationRecord
before_create :generate_token_value
belongs_to :user
def generate_token_value
begin
self.value = SecureRandom.urlsafe_base64 #=> "b4GOKm4pOYU_-BOXcrUGDg"
end while self.class.exists?(value: value)
end
def qr_code(room_id)
RQRCode::QRCode.new(consume_url(room_id))
end
def consume_url(room_id)
Rails.application.routes.url_helpers.url_for(
host: "localhost:3000",
controller: "tokens",
action: "consume",
user_token: value,
room_id: room_id
)
end
end
# app/models/external_token.rb
class ExternalToken < Token; end
# app/models/internal_token.rb
class InternalToken < Token; end
InternalTokens
将仅在应用程序本身中使用,并且是
短命
ExternalTokens
将仅用于与应用程序进行交互
外;喜欢你的移动QRCode扫描仪应用程序;用户在哪里
先前已注册或已登录以允许
此身份验证令牌将生成并存储在外部应用程序中
然后在您的应用程序中的某处显示此QRCode
# e.g. app/views/tokens/show.html.erb
<%= @external_token.qr_code(@room_id).as_html.html_safe %>
我还会隐藏应用程序的@room_id
标记内的当前<head>
使用以下内容:
# e.g. app/views/tokens/show.html.erb
<%= content_for :head, @room_id.html_safe %>
# app/views/layouts/application.html.erb
<!DOCTYPE html>
<html>
<head>
<title>QrcodeApp</title>
<!-- ... -->
<%= tag("meta", name: "room-id", content: content_for(:head)) %>
<!-- ... -->
</head>
<body>
<%= yield %>
</body>
</html>
然后连接应用程序的路由和控制器以处理生成的路由 和编码的QRCode URL。
对于我们需要的路线:
"token#show"
"token#consume"
"sessions@create"
我们还需要一些在显示令牌页面中打开连接的方法 可以与之互动以强制它登录,因为我们需要:
mount ActionCable.server => "/cable"
这将需要Rails 5和ActionCable来实现,否则需要另外一个 发布/子解决方案;像Faye;将需要使用旧版本。
所有路线看起来都像这样:
# config/routes.rb
Rails.application.routes.draw do
# ...
# Serve websocket cable requests in-process
mount ActionCable.server => "/cable"
get "/token-login", to: "tokens#consume"
post "/login", to: "sessions#create"
get "/logout", to: "sessions#destroy"
get "welcome", to: "welcome#show"
root "tokens#show"
end
然后,这些操作的控制器如下:
# app/controller/tokens_controller.rb
class TokensController < ApplicationController
def show
# Ignore this, its just randomly, grabbing an User for their Token. You
# would handle this in the mobile application the User is logged into
session[:user_id] = User.all.sample.id
@user = User.find(session[:user_id])
# @user_token = Token.create(type: "ExternalToken", user: @user)
@user_token = ExternalToken.create(user: @user)
# keep this line
@room_id = SecureRandom.urlsafe_base64
end
def consume
room_id = params[:room_id]
user_token = params[:user_token] # This will come from the Mobile App
if user_token && room_id
# user = Token.find_by(type: "ExternalToken", value: user_token).user
# password_token = Token.create(type: "InternalToken", user_id: user.id)
user = ExternalToken.find_by(value: user_token).user
password_token = InternalToken.create(user: user)
# The `user.password_token` is another random token that only the
# application knows about and will be re-submitted back to the application
# to confirm the login for that user in the open room session
ActionCable.server.broadcast("token_logins_#{room_id}",
user_email: user.email,
user_password_token: password_token.value)
head :ok
else
redirect_to "tokens#show"
end
end
end
令牌控制器show
操作主要为@room_id
生成show
值
在视图模板中重用。 consume
中的其余代码仅用于
证明了这种应用。
令牌控制器room_id
操作需要user_token
和InternalToken
继续,否则将用户重定向回QRCode登录页面。当它们是
然后,它会生成与用户关联的ExternalToken
然后它将用于将通知/事件推送到的room_id
所有房间都有InternalToken
(其中只有一个是独一无二的
用户查看生成此URL的QRCode页面,同时提供
用户必需的身份验证信息(或者在这种情况下我们的
应用程序)快速登录没有密码的应用程序
生成# app/controller/sessions_controller.rb
class SessionsController < ApplicationController
def create
user = User.find_by(email: params[:user_email])
internal_token = InternalToken.find_by(value: params[:user_password_token])
# Token.find_by(type: "InternalToken", value: params[:user_password_token])
if internal_token.user == user
session[:user_id] = user.id # login user
# nullify token, so it cannot be reused
internal_token.destroy
# reset User internal application password (maybe)
# user.update(password_token: SecureRandom.urlsafe_base64)
respond_to do |format|
format.json { render json: { success: true, url: welcome_url } }
format.html { redirect_to welcome_url }
end
else
redirect_to root_path
end
end
def destroy
session.delete(:user_id)
session[:user_id] = nil
@current_user = nil
redirect_to root_path
end
end
代替使用。
如果是外部应用程序,您也可以将用户电子邮件作为参数传递 知道它,而不是在这个演示示例中假设它是正确的。
对于会话控制器,如下:
user_email
此会话控制器接收user_password_token
和session[:user_id]
在继续之前,请确保这两个用户在内部匹配相同的用户
登录。然后使用internal_token
创建用户会话并销毁
create
,因为它只是一次性使用,仅在内部使用
在这种认证申请中。
以及会话# app/controller/welcome_controller.rb
class WelcomeController < ApplicationController
def show
@user = current_user
redirect_to root_path unless current_user
end
private
def current_user
@current_user ||= User.find(session[:user_id])
end
end
操作的某种欢迎控制器
登录后重定向到
/cable
由于此应用程序使用
ActionCable,我们
已经安装了room_id
路径,现在我们需要设置一个频道
给定用户特有的。但是,由于用户尚未登录,我们使用
先前由令牌控制器show
生成的# app/channels/tokens_channel.rb
# Subscribe to `"tokens"` channel
class TokensChannel < ApplicationCable::Channel
def subscribed
stream_from "token_logins_#{params[:room_id]}" if params[:room_id]
end
end
值
行动,因为它随机而独特。
room_id
<head>
也嵌入了<div>
(尽管它可能是隐藏的
id
元素或QRCode的// app/assets/javascripts/channels/tokens.js
var el = document.querySelectorAll('meta[name="room-id"]')[0];
var roomID = el.getAttribute('content');
App.tokens = App.cable.subscriptions.create(
{ channel: 'TokensChannel', room_id: roomID }, {
received: function(data) {
this.loginUser(data);
},
loginUser: function(data) {
var userEmail = data.user_email;
var userPasswordToken = data.user_password_token; // Mobile App's User token
var userData = {
user_email: userEmail,
user_password_token: userPasswordToken
};
// `csrf_meta_tags` value
var el = document.querySelectorAll('meta[name="csrf-token"]')[0];
var csrfToken = el.getAttribute('content');
var xmlhttp = new XMLHttpRequest();
// Handle POST response on `onreadystatechange` callback
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE ) {
if (xmlhttp.status == 200) {
var response = JSON.parse(xmlhttp.response)
App.cable.subscriptions.remove({ channel: "TokensChannel",
room_id: roomID });
window.location.replace(response.url); // Redirect the current view
}
else if (xmlhttp.status == 400) {
alert('There was an error 400');
}
else {
alert('something else other than 200 was returned');
}
}
};
// Make User login POST request
xmlhttp.open(
"POST",
"<%= Rails.application.routes.url_helpers.url_for(
host: "localhost:3000", controller: "sessions", action: "create"
) %>",
true
);
// Add necessary headers (like `csrf_meta_tags`) before sending POST request
xmlhttp.setRequestHeader('X-CSRF-Token', csrfToken);
xmlhttp.setRequestHeader("Content-Type", "application/json");
xmlhttp.send(JSON.stringify(userData));
}
});
属性,由您决定),这意味着
它可以在我们的JavaScript中用于接收传入的电路板广播
到那个房间/ QRCode; e.g。
received
此ActionCable订阅中只有两个操作;
loginUser
来处理传入的请求/事件,loginUser
我们的自定义功能 userData
执行以下操作:
处理传入数据以构建新的数据对象user_email
以POST回
我们的应用程序,包含用户信息; user_password_token
&amp;
new XMLHttpRequest()
;需要使用身份验证登录AJAX
令牌作为密码(因为它有点不安全,密码通常是
散列;意思是他们不知道,因为他们无法逆转)
在没有jQuery的情况下创建一个userData
对象POST,发送一个
使用xmlhttp.onreadystatechange
作为登录信息的JSON登录URL的POST请求,
同时还附加当前的HTML页面CSRF令牌; e.g。
否则JSON请求将在没有它的情况下失败
在a上执行的xmlhttp.send(...)
回调函数
从{{1}}函数调用回复。它将取消订阅
来自当前房间的用户,因为它不再需要,并重定向
当前页面到&#34; Welcomw页面&#34;它在回复中收到了回复。除此以外
它会警告用户出现故障或出错的情况
这将产生以下类型的应用
您可以通过以下网址访问我所处理项目的副本:
唯一没有解决的问题是滚动室令牌生成, 哪个要么需要JavaScript库来生成/重新生成 具有Room Token的URL或返回重新生成的Controller Action QRCode可以是图像或HTML,可以立即显示在 页。这两种方法仍然需要你有一些JavaScript关闭 当前连接并打开一个新的房间/会话令牌,可以 使用时只能在一定时间后才能收到消息。
<强>参考文献:强>