开源精选》是我们分享Github、Gitee等开源社区中优质项目的栏目,包括技术、学习、实用与各种有趣的内容。本期推荐的是一个通过Web访问设备终端的工具——rtty。
rtty由客户端和服务端组成。客户端采用纯C实现,服务端采用GO语言实现,前端界面采用vue实现。使用rtty可以在任何地方通过Web访问您的设备的终端,通过设备ID来区分您的不同的设备。rtty非常适合远程维护Linux设备。
不支持 SSL:rtty(32K) + libev(56K)
支持的 SSL:+ libmbedtls(88K) + libmbedcrypto(241K) + libmbedx509(48k)
1 从Release页面下载编译好的二进制文件或者自己编译
git clone https://github.com/zhaojh329/rttys
cd ui
npm install
npm run build
cd ..
./build.sh linux amd64
2 认证
生成一个 token:
$ rttys token
Please set a password:******
Your token is: 34762d07637276694b938d23f10d7164
使用 token:
$rttys -t 34762d07637276694b938d23f10d7164
您可以在配置文件中指定设备 CA 存储(有效文件)或在 CLI 中指定设备 CA 存储(参数 ssl-cacert) 来启用 mTLS。 存储中没有有效 CA 的设备将在 TLS 握手中断开连接。
3 运行服务
移动rttys可执行程序到/usr/local/bin/
sudo mv rttys /usr/local/bin/
拷贝配置文件到/etc/rttys/
sudo mkdir /etc/rttys
sudo cp rttys.conf /etc/rttys/
创建一个systemd单元文件: /etc/systemd/system/rttys.service
[Unit]
Description=rttys
After=network.target
[Service]
ExecStart=/usr/local/bin/rttys run -c /etc/rttys/rttys.conf
TimeoutStopSec=5s
[Install]
WantedBy=multi-user.target
要首次启动该服务,请执行通常的systemctl操作
sudo systemctl daemon-reload
sudo systemctl enable rttys
sudo systemctl start rttys
可以通过以下方式停止服务
sudo systemctl stop rttys
4 数据库准备(Sqlite、MySql 或者 Mariadb)
在数据库实例上,以root用户身份登录到数据库控制台
mysql -u root -p
创建将由 Rttys 使用的数据库用户,通过密码验证。本例使用 “rttys” 作为密码。请为您的实例使用安全密码
CREATE USER 'rttys' IDENTIFIED BY 'rttys';
使用 UTF-8 字符集和排序规则创建数据库。确保使用 utf8mb4 字符集而不是 utf8,因为前者支持基本多语言平面 之外的所有 Unicode字符(包括emojis)。另外,根据您期望的内容选择排序规则。如有疑问,请使用 unicode_ci 或general_ci
CREATE DATABASE rttys CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_unicode_ci';
将数据库上的所有权限授予上面创建的数据库用户
GRANT ALL PRIVILEGES ON rttys.* TO 'rttys';
FLUSH PRIVILEGES;
安装依赖
sudo apt install -y libev-dev libssl-dev # Ubuntu, Debian
sudo pacman -S --noconfirm libev openssl # ArchLinux
sudo yum install -y libev-devel openssl-devel # Centos
克隆rtty代码
git clone --recursive https://github.com/zhaojh329/rtty.git
编译
cd rtty && mkdir build && cd build
cmake .. && make install
将下面的参数替换为您自己的参数
sudo rtty -I 'My-device-ID' -h 'your-server' -p 5912 -a -v -d 'My Device Description'
如果您的 rttys 配置了一个 token,请加上如下参数(将下面的 token 替换为您自己生成的)
-t 34762d07637276694b938d23f10d7164
使用 Web 浏览器访问您的服务器: http://your-server-host:5913,然后点击连接按钮。
或者直接连接设备,无需 Web 登录(需要在服务端配置设备白名单)
http://your-server-host:5913/connect/devid1
http://your-server-host:5913/connect/devid2
从本地传输文件到远程设备
rtty -R
从远程设备传输文件到本地
rtty -S test.txt
—END—
开源协议:MIT
开源地址:https://github.com/zhaojh329/rtty
译自: https://nicolasparada.netlify.com/posts/passwordless-auth-client/
作者: Nicolás Parada
译者: qhwdw
我们继续 无密码验证 的文章。上一篇文章中,我们用 Go 写了一个 HTTP 服务,用这个服务来做无密码验证 API。今天,我们为它再写一个 JavaScript 客户端。
我们将使用 这里的 这个单页面应用程序(SPA)来展示使用的技术。如果你还没有读过它,请先读它。
记住流程:
对于根 URL(/),我们将根据验证的状态分别使用两个不同的页面:一个是带有访问表单的页面,或者是已验证通过的用户的欢迎页面。另一个页面是验证回调的重定向页面。
我们将使用相同的 Go 服务器来为客户端提供服务,因此,在我们前面的 main.go 中添加一些路由:
router.Handle("GET", "/...", http.FileServer(SPAFileSystem{http.Dir("static")}))
type SPAFileSystem struct {
fs http.FileSystem
}
func (spa SPAFileSystem) Open(name string) (http.File, error) {
f, err :=spa.fs.Open(name)
if err !=nil {
return spa.fs.Open("index.html")
}
return f, nil
}
这个伺服文件放在 static 下,配合 static/index.html 作为回调。
你可以使用你自己的服务器,但是你得在服务器上启用 CORS 。
我们来看一下那个 static/index.html 文件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Passwordless Demo</title>
<link rel="shortcut icon" href="data:,">
<script src="/js/main.js" type="module"></script>
</head>
<body></body>
</html>
单页面应用程序的所有渲染由 JavaScript 来完成,因此,我们使用了一个空的 body 部分和一个 main.js 文件。
我们将使用 上篇文章 中的 Router。
现在,我们使用下面的内容来创建一个 static/js/main.js 文件:
import Router from 'https://unpkg.com/@nicolasparada/router'
import { isAuthenticated } from './auth.js'
const router=new Router()
router.handle('/', guard(view('home')))
router.handle('/callback', view('callback'))
router.handle(/^\//, view('not-found'))
router.install(async resultPromise=> {
document.body.innerHTML=''
document.body.appendChild(await resultPromise)
})
function view(name) {
return (...args)=> import(`/js/pages/${name}-page.js`)
.then(m=> m.default(...args))
}
function guard(fn1, fn2=view('welcome')) {
return (...args)=> isAuthenticated()
? fn1(...args)
: fn2(...args)
}
与上篇文章不同的是,我们实现了一个 isAuthenticated() 函数和一个 guard() 函数,使用它去渲染两种验证状态的页面。因此,当用户访问 / 时,它将根据用户是否通过了验证来展示主页或者是欢迎页面。
现在,我们来编写 isAuthenticated() 函数。使用下面的内容来创建一个 static/js/auth.js 文件:
export function getAuthUser() {
const authUserItem=localStorage.getItem('auth_user')
const expiresAtItem=localStorage.getItem('expires_at')
if (authUserItem !==null && expiresAtItem !==null) {
const expiresAt=new Date(expiresAtItem)
if (!isNaN(expiresAt.valueOf()) && expiresAt > new Date()) {
try {
return JSON.parse(authUserItem)
} catch (_) { }
}
}
return null
}
export function isAuthenticated() {
return localStorage.getItem('jwt') !==null && getAuthUser() !==null
}
当有人登入时,我们将保存 JSON 格式的 web 令牌、它的过期日期,以及在 localStorage 上的当前已验证用户。这个模块就是这个用处。
在继续这个页面之前,我将写一些与服务器 API 一起使用的 HTTP 工具。
我们使用以下的内容去创建一个 static/js/http.js 文件:
import { isAuthenticated } from './auth.js'
function get(url, headers) {
return fetch(url, {
headers: Object.assign(getAuthHeader(), headers),
}).then(handleResponse)
}
function post(url, body, headers) {
return fetch(url, {
method: 'POST',
headers: Object.assign(getAuthHeader(), { 'content-type': 'application/json' }, headers),
body: JSON.stringify(body),
}).then(handleResponse)
}
function getAuthHeader() {
return isAuthenticated()
? { authorization: `Bearer ${localStorage.getItem('jwt')}` }
: {}
}
export async function handleResponse(res) {
const body=await res.clone().json().catch(()=> res.text())
const response={
statusCode: res.status,
statusText: res.statusText,
headers: res.headers,
body,
}
if (!res.ok) {
const message=typeof body==='object' && body !==null && 'message' in body
? body.message
: typeof body==='string' && body !==''
? body
: res.statusText
const err=new Error(message)
throw Object.assign(err, response)
}
return response
}
export default {
get,
post,
}
这个模块导出了 get() 和 post() 函数。它们是 fetch API 的封装。当用户是已验证的,这二个函数注入一个 Authorization: Bearer <token_here> 头到请求中;这样服务器就能对我们进行身份验证。
我们现在来到欢迎页面。用如下的内容创建一个 static/js/pages/welcome-page.js 文件:
const template=document.createElement('template')
template.innerHTML=`
<h1>Passwordless Demo</h1>
<h2>Access</h2>
<form id="access-form">
<input type="email" placeholder="Email" autofocus required>
<button type="submit">Send Magic Link</button>
</form>
`
export default function welcomePage() {
const page=template.content.cloneNode(true)
page.getElementById('access-form')
.addEventListener('submit', onAccessFormSubmit)
return page
}
这个页面使用一个 HTMLTemplateElement 作为视图。这只是一个输入用户 email 的简单表单。
为了避免干扰,我将跳过错误处理部分,只是将它们输出到控制台上。
现在,我们来写 onAccessFormSubmit() 函数。
import http from '../http.js'
function onAccessFormSubmit(ev) {
ev.preventDefault()
const form=ev.currentTarget
const input=form.querySelector('input')
const email=input.value
sendMagicLink(email).catch(err=> {
console.error(err)
if (err.statusCode===404 && wantToCreateAccount()) {
runCreateUserProgram(email)
}
})
}
function sendMagicLink(email) {
return http.post('/api/passwordless/start', {
email,
redirectUri: location.origin + '/callback',
}).then(()=> {
alert('Magic link sent. Go check your email inbox.')
})
}
function wantToCreateAccount() {
return prompt('No user found. Do you want to create an account?')
}
它对 /api/passwordless/start 发起了 POST 请求,请求体中包含 email 和 redirectUri。在本例中它返回 404 Not Found 状态码时,我们将创建一个用户。
function runCreateUserProgram(email) {
const username=prompt("Enter username")
if (username===null) return
http.post('/api/users', { email, username })
.then(res=> res.body)
.then(user=> sendMagicLink(user.email))
.catch(console.error)
}
这个用户创建程序,首先询问用户名,然后使用 email 和用户名做一个 POST 请求到 /api/users。成功之后,给创建的用户发送一个魔法链接。
这是访问表单的全部功能,现在我们来做回调页面。使用如下的内容来创建一个 static/js/pages/callback-page.js 文件:
import http from '../http.js'
const template=document.createElement('template')
template.innerHTML=`
<h1>Authenticating you</h1>
`
export default function callbackPage() {
const page=template.content.cloneNode(true)
const hash=location.hash.substr(1)
const fragment=new URLSearchParams(hash)
for (const [k, v] of fragment.entries()) {
fragment.set(decodeURIComponent(k), decodeURIComponent(v))
}
const jwt=fragment.get('jwt')
const expiresAt=fragment.get('expires_at')
http.get('/api/auth_user', { authorization: `Bearer ${jwt}` })
.then(res=> res.body)
.then(authUser=> {
localStorage.setItem('jwt', jwt)
localStorage.setItem('auth_user', JSON.stringify(authUser))
localStorage.setItem('expires_at', expiresAt)
location.replace('/')
})
.catch(console.error)
return page
}
请记住……当点击魔法链接时,我们会来到 /api/passwordless/verify_redirect,它将把我们重定向到重定向 URI,我们将放在哈希中的 JWT 和过期日期传递给 /callback。
回调页面解码 URL 中的哈希,提取这些参数去做一个 GET 请求到 /api/auth_user,用 JWT 保存所有数据到 localStorage 中。最后,重定向到主页面。
创建如下内容的 static/pages/home-page.js 文件:
import { getAuthUser } from '../auth.js'
export default function homePage() {
const authUser=getAuthUser()
const template=document.createElement('template')
template.innerHTML=`
<h1>Passwordless Demo</h1>
<p>Welcome back, ${authUser.username}
Nginx("engine x")是一款是由俄罗斯的程序设计师Igor Sysoev所开发高性能的 Web和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器。
在高连接并发的情况下,Nginx是Apache服务器不错的替代品。
Nginx 安装
系统平台:CentOS release 6.6 (Final) 64位。
一、安装编译工具及库文件
yum -y install make zlib zlib-devel gcc-c++ libtool openssl openssl-devel
二、首先要安装 PCRE
PCRE 作用是让 Nginx 支持 Rewrite 功能。
1、下载 PCRE 安装包,下载地址: http://downloads.sourceforge.net/project/pcre/pcre/8.35/pcre-8.35.tar.gz
[root@bogon src]# wget http://downloads.sourceforge.net/project/pcre/pcre/8.35/pcre-8.35.tar.gz
2、解压安装包:
[root@bogon src]# tar zxvf pcre-8.35.tar.gz
3、进入安装包目录
[root@bogon src]# cd pcre-8.35
4、编译安装
[root@bogon pcre-8.35]# ./configure[root@bogon pcre-8.35]# make && make install
5、查看pcre版本
[root@bogon pcre-8.35]# pcre-config --version
安装 Nginx
1、下载 Nginx,下载地址:http://nginx.org/download/nginx-1.6.2.tar.gz
[root@bogon src]# wget http://nginx.org/download/nginx-1.6.2.tar.gz
2、解压安装包
[root@bogon src]# tar zxvf nginx-1.6.2.tar.gz
3、进入安装包目录
[root@bogon src]# cd nginx-1.6.2
4、编译安装
[root@bogon nginx-1.6.2]# ./configure --prefix=/usr/local/webserver/nginx --with-http_stub_status_module --with-http_ssl_module --with-pcre=/usr/local/src/pcre-8.35[root@bogon nginx-1.6.2]# make[root@bogon nginx-1.6.2]# make install
5、查看nginx版本
[root@bogon nginx-1.6.2]# /usr/local/webserver/nginx/sbin/nginx -v
到此,nginx安装完成。
Nginx 配置
创建 Nginx 运行使用的用户 www:
[root@bogon conf]# /usr/sbin/groupadd www [root@bogon conf]# /usr/sbin/useradd -g www www
配置nginx.conf ,将/usr/local/webserver/nginx/conf/nginx.conf替换为以下内容
[root@bogon conf]# cat /usr/local/webserver/nginx/conf/nginx.conf user www www;worker_processes 2; #设置值和CPU核心数一致error_log /usr/local/webserver/nginx/logs/nginx_error.log crit; #日志位置和日志级别pid /usr/local/webserver/nginx/nginx.pid;#Specifies the value for maximum file descriptors that can be opened by this process.worker_rlimit_nofile 65535;events{ use epoll; worker_connections 65535;}http{ include mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" $http_x_forwarded_for'; #charset gb2312; server_names_hash_bucket_size 128; client_header_buffer_size 32k; large_client_header_buffers 4 32k; client_max_body_size 8m; sendfile on; tcp_nopush on; keepalive_timeout 60; tcp_nodelay on; fastcgi_connect_timeout 300; fastcgi_send_timeout 300; fastcgi_read_timeout 300; fastcgi_buffer_size 64k; fastcgi_buffers 4 64k; fastcgi_busy_buffers_size 128k; fastcgi_temp_file_write_size 128k; gzip on; gzip_min_length 1k; gzip_buffers 4 16k; gzip_http_version 1.0; gzip_comp_level 2; gzip_types text/plain application/x-javascript text/css application/xml; gzip_vary on; #limit_zone crawler $binary_remote_addr 10m; #下面是server虚拟主机的配置 server { listen 80;#监听端口 server_name localhost;#域名 index index.html index.htm index.php; root /usr/local/webserver/nginx/html;#站点目录 location ~ .*\.(php|php5)?$ { #fastcgi_pass unix:/tmp/php-cgi.sock; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; include fastcgi.conf; } location ~ .*\.(gif|jpg|jpeg|png|bmp|swf|ico)$ { expires 30d; # access_log off; } location ~ .*\.(js|css)?$ { expires 15d; # access_log off; } access_log off; }}
检查配置文件ngnix.conf的正确性命令:
[root@bogon conf]# /usr/local/webserver/nginx/sbin/nginx -t
启动 Nginx
Nginx 启动命令如下:
[root@bogon conf]# /usr/local/webserver/nginx/sbin/nginx
访问站点
从浏览器访问我们配置的站点ip:
Nginx 其他命令
以下包含了 Nginx 常用的几个命令:
/usr/local/webserver/nginx/sbin/nginx -s reload # 重新载入配置文件/usr/local/webserver/nginx/sbin/nginx -s reopen # 重启 Nginx/usr/local/webserver/nginx/sbin/nginx -s stop # 停止 Nginx
*请认真填写需求信息,我们会在24小时内与您取得联系。