常见的微信扫码登录有两种

这两种方式都需要提交企业资料认证和300元年费,有些想要学习或者自己的网站没有盈利的,其实不舍得花这个钱,特别是个人开发者,没有企业资料去做认证。

既然没法做企业认证,那我们就把矛头指向微信小程序了。

微信小程序无论是个人还是企业的,都开放了获取用户的基本信息,无须认证,不收费。而且,还提供了 1 个可以生成带参数的,数量暂无限制小程序码接口,所以我们就可以通过这个接口实现扫码登录了。

实现原理

登录页面从服务端获取一个带uuid参数的小程序码,然后创建一个websocket并带上这个uuid参数(用于网页端和小程序的通信绑定)

用户通过微信扫码授权后把登录code、用户信息和uuid参数提交到服务端

服务端根据登录code获取openId,然后在根据openId创建用户,最后生成user token广播给前端(通过uuid找到对应的soket链接并发送)

前端接收到token后,auth 登录,页面再重载一下,流程完毕

实战

获取小程序码

前端获取小程序码并创建websocket

import { Form, Tabs, Input, Button, Checkbox, Spin, Icon, message } from 'antd';

import React, { Component } from 'react';

import { FormComponentProps } from 'antd/es/form';

import { connect } from 'dva';

import { Link } from 'umi';

import { ConnectState, ConnectProps } from '@/models/connect';

import { getLoginCode } from './service';

import styles from './style.less';

const FormItem = Form.Item;

const { TabPane } = Tabs;

interface LoginProps extends ConnectProps, FormComponentProps {

submitting: boolean;

}

interface LoginState {

base64Img: string;

codeExpired: boolean;

codeLoading: boolean;

}

@connect(({ loading }: ConnectState) => ({

submitting: loading.effects['authLogin/login'],

}))

class Login extends Component {

static socketTimeout = 120000;

state: LoginState = {

base64Img: '',

codeExpired: false,

codeLoading: false,

};

ws: any;

timer: any;

componentDidMount() {

this.createWebSocket();

}

componentWillUnmount() {

clearTimeout(this.timer);

if (this.ws && this.ws.readyState === 1) {

this.ws.close();

}

}

createWebSocket = async () => {

clearTimeout(this.timer);

if (this.ws && this.ws.readyState === 1) {

this.ws.close();

}

this.setState({ codeExpired: false, codeLoading: true });

const { data: { base64_img: base64Img, token } } = await getLoginCode();

const socketUrl = `wss://${window.location.host}/wss?token=${token}`;

this.ws = new WebSocket(socketUrl);

this.ws.addEventListener('message', (e: any) => {

const { data: msg } = e;

const { event, data } = JSON.parse(msg);

/* eslint no-case-declarations:0 */

switch (event) {

case 'App\\Events\\WechatScanLogin':

const { token, permissions } = data;

// 获取到token后Auth登录

this.props.dispatch({

type: 'authLogin/loginSuccess',

payload: { token, permissions },

callback: () => {

message.success('登录成功!');

clearTimeout(this.timer);

if (this.ws && this.ws.readyState === 1) {

this.ws.close();

}

},

});

break;

default:

break;

}

});

this.setState({ base64Img, codeExpired: false, codeLoading: false });

this.timer = setTimeout(this.handleWebSocketTimeout, Login.socketTimeout);

};

handleWebSocketTimeout = () => {

if (this.ws && this.ws.readyState === 1) {

this.ws.close();

}

this.setState({ codeExpired: true });

};

handleSubmit = (e: React.FormEvent) => {

e.preventDefault();

const { form } = this.props;

form.validateFields({ force: true }, (err: any, values: object) => {

if (!err) {

const { dispatch } = this.props;

dispatch({

type: 'authLogin/login',

payload: values,

});

}

});

};

renderCode = () => {

const { base64Img, codeExpired, codeLoading } = this.state;

if (codeExpired) {

return (

<>

小程序码已失效

className={styles.noticeBtn}

type="primary"

size="large"

block

onClick={this.createWebSocket}

>

刷新小程序码

>

);

}

return (

<>

微信扫码后点击“登录”,

即可完成账号绑定及登录。

{

codeLoading

? } tip="正在加载..." />

:

}

>

);

};

render() {

const { form, submitting } = this.props;

const { getFieldDecorator } = form;

return (

{this.renderCode()}

{getFieldDecorator('account', {

rules: [{ required: true, message: '请输入账户名称!' }],

})()}

{getFieldDecorator('password', {

rules: [{ required: true, message: '请输入账户密码!'}],

})()}

{getFieldDecorator('remember')(

自动登录,

)}

忘记密码

登录

);

}

}

export default Form.create()(Login);

服务端生成小程序码逻辑

namespace App\Http\Controllers\V2;

use EasyWeChat;

use Illuminate\Support\Str;

use EasyWeChat\Kernel\Http\StreamResponse;

use JWTFactory;

use JWTAuth;

class WechatController extends Controller

{

public function loginCode()

{

$uuid = Str::random(16);

$miniProgram = EasyWeChat::miniProgram();

$response = $miniProgram->app_code->getUnlimit('scan-login/' . $uuid, [

'page' => 'pages/auth/scan-login',

'width' => 280,

]);

if ($response instanceof StreamResponse) {

$payload = JWTFactory::setTTL(2)->sub($uuid)->make();

$token = (string)JWTAuth::encode($payload);

$response->getBody()->rewind();

$base64_img = base64_encode($response->getBody()->getContents());

$data = compact('token', 'base64_img');

return compact('data');

}

return $response;

}

}

小程序扫码处理逻辑

// pages/auth/scan-login.js

import regeneratorRuntime from '../../utils/runtime'

const { setToken } = require('../../utils/authority');

const { login } = require('../../utils/helpers')

Page({

data: {

uuid: '',

},

async onGetUserInfo(e) {

if (e.detail.userInfo) { // 用户按了允许授权按钮

setToken();

await login({ uuid: this.data.uuid });

wx.reLaunch({

url: '/pages/user/index'

})

wx.showToast({

title: '登录成功',

icon: 'none',

});

}

},

async onLoad(query) {

const scene = decodeURIComponent(query.scene);

const uuid = scene.split('/')[1];

this.setData({ uuid });

},

})

WEB 端登录确认

style='width: 400rpx'

class="weui-btn"

type="primary"

open-type="getUserInfo"

bindgetuserinfo="onGetUserInfo"

>

登录

取消登录

扫码授权后服务端处理逻辑

public function login(Request $request)

{

$this->validate($request, [

'code' => 'required',

]);

$code = $request->input('code');

$miniProgram = EasyWeChat::miniProgram();

$miniProgramSession = $miniProgram->auth->session($code);

$openId = $miniProgramSession->openid;

$sessionKey = $miniProgramSession->session_key;

$lockName = self::class . "@store:$openId";

$lock = Cache::lock($lockName, 60);

abort_if(!$lock->get(), 422, '操作过于频繁,请稍后再试!');

$userInfo = $request->input('userInfo');

$rawData = $request->input('rawData');

$signature = $request->input('signature');

$signature2 = sha1($rawData . $sessionKey);

abort_if($signature !== $signature2, 403, '数据不合法!');

$user = User::where('we_chat_openid', $openId)->first();

if (!$user) {

$user = new User;

// $user->name = Arr::get($userInfo, 'nickName', '');

$user->we_chat_openid = $openId;

$user->user_info = $userInfo;

$user->save();

}

$token = Auth::login($user);

$data = [

'access_token' => $token,

'token_type' => 'Bearer',

'expires_in' => Carbon::now()->addMinutes(config('jwt.ttl'))->toDateTimeString(),

'unread_count' => $user->unreadNotifications()->count(),

];

$lock->release();

$uuid = $request->input('uuid');

if ($uuid) {

$permissions = $user->getAllPermissions()->pluck('name');

// 把token广播给前端

event(new WechatScanLogin($uuid, "Bearer $token", $permissions));

}

return compact('data');

}

WechatScanLogin 事件

namespace App\Events;

use Illuminate\Broadcasting\PrivateChannel;

use Illuminate\Queue\SerializesModels;

use Illuminate\Foundation\Events\Dispatchable;

use Illuminate\Broadcasting\InteractsWithSockets;

use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class WechatScanLogin implements ShouldBroadcast

{

use Dispatchable, InteractsWithSockets, SerializesModels;

protected $uuid;

public $token;

public $permissions;

public function __construct($uuid, $token, $permissions)

{

$this->uuid = $uuid;

$this->token = $token;

$this->permissions = $permissions;

}

public function broadcastOn()

{

return new PrivateChannel('scan-login.' . $this->uuid);

}

}

websocket server

const WebSocket = require('ws'); // socket.io 支持的协议版本(4)和 微信小程序 websocket 协议版本(13)不一致,所以选用ws

const Redis = require('ioredis');

const fs = require('fs');

const ini = require('ini');

const jwt = require('jsonwebtoken');

const url = require('url');

const config = ini.parse(fs.readFileSync('./.env', 'utf8')); // 读取.env配置

const redis = new Redis({

port: env('REDIS_PORT', 6379), // Redis port

host: env('REDIS_HOST', '127.0.0.1'), // Redis host

// family: 4, // 4 (IPv4) or 6 (IPv6)

password: env('REDIS_PASSWORD', null),

db: 0,

});

const wss = new WebSocket.Server({

port: 6001,

clientTracking: false,

verifyClient({req}, cb) {

try {

const urlParams = url.parse(req.url, true);

const token = urlParams.query.token || req.headers.authorization.split(' ')[1];

const jwtSecret = env('JWT_SECRET');

const algorithm = env('JWT_ALGO', 'HS256');

const {sub, nbf, exp} = jwt.verify(token, jwtSecret, {algorithm});

if (Date.now() / 1000 > exp) {

cb(false, 401, 'token已过期.')

}

if (Date.now() / 1000 < nbf) {

cb(false, 401, 'token未到生效时间.')

}

if (!sub) {

cb(false, 401, '无法验证令牌签名.')

}

cb(true)

} catch (e) {

console.info(e);

cb(false, 401, 'Token could not be parsed from the request.');

}

},

});

const clients = {};

wss.on('connection', (ws, req) => {

try {

const urlParams = url.parse(req.url, true);

const token = urlParams.query.token || req.headers.authorization.split(' ')[1];

const jwtSecret = env('JWT_SECRET');

const algorithm = env('JWT_ALGO', 'HS256');

const {sub} = jwt.verify(token, jwtSecret, {algorithm});

const uuid = sub;

ws.uuid = uuid;

if (!clients[uuid]) {

clients[uuid] = [];

}

clients[uuid].push(ws);

} catch (e) {

ws.close();

}

ws.on('message', message => { // 接收消息事件

if (ws.uuid) {

console.info('[%s] message:%s %s', getNowDateTimeString(), ws.uuid, message);

}

});

ws.on('close', () => { // 关闭链接事件

if (ws.uuid) {

console.info('[%s] closed:%s', getNowDateTimeString(), ws.uuid);

const wss = clients[ws.uuid];

if (wss instanceof Array) {

const index = wss.indexOf(ws);

if (index > -1) {

wss.splice(index, 1);

if (wss.length === 0) {

delete clients[ws.uuid];

}

}

}

}

})

});

// redis 订阅

redis.psubscribe('*', function (err, count) {

});

redis.on('pmessage', (subscrbed, channel, message) => { // 接收 laravel 推送的消息

console.info('[%s] %s %s', getNowDateTimeString(), channel, message);

const {event} = JSON.parse(message);

const uuid = channel.split('.')[1];

const wss = clients[uuid];

switch (event) {

case 'Illuminate\\Notifications\\Events\\BroadcastNotificationCreated':

case 'App\\Events\\WechatScanLogin':

if (wss instanceof Array) {

wss.forEach(ws => {

if (ws.readyState === 1) {

ws.send(message);

}

});

}

break;

}

});

function env(key, def = '') {

return config[key] || def

}

function getNowDateTimeString() {

const date = new Date();

return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;

}

本作品采用《CC 协议》,转载必须注明作者和本文链接

PHP小程序码扫码登录网站,微信扫小程序码实现网页端登录相关推荐

  1. Spring学习笔记(二十三)——实现网站微信扫码登录获取微信用户信息Demo

    目录 微信扫码登录介绍 开发步骤 微信扫码登录示例 微信开放文档 遇到的问题 使用第三方工具实现网站微信扫码登录 开发前介绍 开发步骤 微信扫码登录获取微信用户信息Demo实现流程 实现效果 实现过程 ...

  2. 基于微信小程序springboot粤味早茶店微信扫码点餐系统源码和论文

    这是一个功能简单的微信点餐小程序. 当今社会的发展,日新月异,发生了翻天覆地的变化,尤其是在智能方面,发展的更加迅速,随之带来的就是各个产业的智能化.军工业,化工,当然还有餐饮业,都在逐渐向智能化进发 ...

  3. 应用网站微信扫码登录处理逻辑解析

    应用网站微信扫码登录处理逻辑解析 近日刚刚进行了网站应用微信扫码登录的功能开发.开发前看了不少文档,但感觉都很偏技术术语,太过于专业.对于刚刚想涉及这方面的菜鸟来说,看的一脸懵逼. 趁着自己刚刚动手的 ...

  4. 网站微信扫码登录总结

    文章目录 微信扫码 前端操作 后端操作 企业微信扫码 总结 补充 网站微信扫码登录从最初的惊艳四座到如今在各大网站普及已经过了7年的时间,如今网站微信扫码登录靠着便捷.安全的特性成为了网站与用户的首选 ...

  5. 网站微信扫码支付流程

    网站微信扫码支付流程 一.申请微信公众号 1.首先去微信公众平台申请微信公众号(小程序的也可以),APP_ID是要用的 二.注册商户平台(个体目前不能申请) 1.注册申请通过后,商户号(MCH_ID) ...

  6. PC网站微信扫码支付,Native支付,“当前商户号暂不支持关联该类型的appid“,“签名错误,请检查后再试““springBoot 微信支付“

    springBoot 微信支付 PC网站微信扫码支付-Native支付 一.采坑大合集 1.当前商户号暂不支持关联该类型的appid 2.签名错误,请检查后再试 二.springboot集成微信支付D ...

  7. 通用权限管理系统组件 中集成多个子系统的单点登录(网站入口方式)附源码

    通用权限管理系统组件 (GPM - General Permissions Manager) 中集成多个子系统的单点登录(网站入口方式)附源码 上文中实现了直接连接数据库的方式,通过配置文件,自定义的 ...

  8. 网站微信扫码支付java开发

    网站微信扫码支付java开发 一.网站微信扫码支付开发并没有现成的java示例,总结一下自己微信扫码支付心得 二.首先去微信公众平台申请账户 https://mp.weixin.qq.com ** 三 ...

  9. PC网站微信扫码支付之Native支付(模式二)

    简介 Native支付是指商户系统按微信支付协议生成支付二维码,用户再用微信"扫一扫"完成支付的模式.该模式适用于PC网站.实体店单品或订单.媒体广告支付等场景. Native支付 ...

最新文章

  1. linux中awk下 gsub函数用法
  2. python ftp文件夹文件递归上传推送
  3. codefirst数据库迁移
  4. mysql客户端连接hive_连接Hive的客户端界面工具–SQuirrel SQL Client
  5. 蓝牙地址的name为null_蓝牙, enable协议栈流程
  6. js数组遍历、对象遍历、字符串遍历
  7. java scanner 类_Java Scanner类
  8. VTK修炼之道28:图像统计_灰度直方图计算
  9. SpringBoot中使用Redis数据库
  10. 强制转换const 引用
  11. kail中tools的安装和第一个php学习笔记
  12. MOON.ORM 3.5 MYSQL的配置及使用方法(最新版免费下载使用.欢迎加盟)
  13. Python 文档学习
  14. linux鸟叔的私房菜txt,鸟哥的Linux私房菜(pdf+epub+mobi+txt+azw3)
  15. 带经纬度的水印相机_经纬度生成小工具(仿水印相机)
  16. python刷步数程序设计_乐心健康间接修改微信步数-Docker持久运行python脚本
  17. D3临摹作业_分词与词云可视化(西安交大国家艺术基金数据可视化培训第28天)
  18. 地球系统模式(CESM)
  19. 华为交换机如何查看端口所联设备的MACIP
  20. 【Matlab】Matlab基础入门

热门文章

  1. ZooKeeper知识点整理
  2. 小程序文档整理之 -- API(媒体)
  3. 深度学习驱动智能搜索引擎,RankBrain革了SEO的命
  4. 从编译器源码中提取ARMv8的指令编码
  5. 吴恩达机器学习笔记——含一个隐藏层的神经网络
  6. python定义整数_Python | 程序定义一个整数值并打印
  7. 5G工业路由器的工厂自动化应用
  8. python numpy是什么_Python库Numpy里ndarray.ndim 是什么意思?
  9. 关于office2021升级
  10. 二维码制作方法分享,学会这招轻松制作二维码