个人开发者实现微信扫码登录
使用码上登录中转微信扫码登录
使用之前最好有一个公网服务器,能够公网访问的 redis
和 mysql
数据库,并且能够部署公网访问的服务
码上登录是一个小程序,对个体开发者提供了免费的微信扫一扫登录入口:官网 http://login.vicy.cn/
针对微信扫码登录需要进行企业认证,个人开发者无法使用的问题,我们可以使用码上登录进行桥接,将微信扫码登录的信息转发给我们的程序
在开发之前,我们需要有一个能被公网访问的地址,用来接收码上登录回调给我们的用户数据
更多信息参考码上登录 Api 文档:http://login.vicy.cn/apiWord.html
使用步骤
进入码上登录官网创建我们的应用:http://login.vicy.cn/
微信扫码登录,进入首页,点击
马上创建应用
点击创建应用,输入网站名称和回调 URL ,填写完成后创建回得到
secretKey
网站名称就是用户扫码后看到的你的网站名称
回调 URL 就是用户确认登录后,接收用户信息的
controller
,可以先随便写,再后期修改
创建后台应用
先创建一个
web
模块service-ucenter
用于编写代码,需要整合mybatis
和redis
,我使用SpringBoot
的web
项目导入
commons
和对应的数据库依赖,该依赖的作用是让我们发送HTTP
请求,获取二维码信息<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId> </dependency> <dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId> </dependency> <dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId> </dependency> <!--http相关依赖--> <dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpclient</artifactId> </dependency> <dependency><groupId>org.apache.httpcomponents</groupId><artifactId>httpcore</artifactId> </dependency> <!-- redis --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--mybatis-plus--> <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId> </dependency> <!--mysql--> <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId> </dependency>
配置文件如下,拿去使用记得修改对应的数据库配置
server:port: 8085servlet:application-display-name: service-ucenter spring:datasource:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://IP:3306/guli_college?useUnicode=true&characterEncoding=utf8&useSSL=falseusername: rootpassword: root# 配置 druid 连接池type: com.alibaba.druid.pool.DruidDataSourceredis:host: IPport: 6379database: 0timeout: 1800000mvc:pathmatch:matching-strategy: ant_path_matcher# 开启rest风格hiddenmethod:filter:enabled: true
编写
HttpClientUtils
工具类,直接粘过去用package com.zym.utils;import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.http.Consts; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.config.RequestConfig.Builder; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.conn.ConnectTimeoutException; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLContextBuilder; import org.apache.http.conn.ssl.TrustStrategy; import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.message.BasicNameValuePair;import javax.net.ssl.SSLContext; import javax.net.ssl.SSLException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import java.io.IOException; import java.net.SocketTimeoutException; import java.security.GeneralSecurityException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set;/*** 依赖的jar包有:commons-lang-2.6.jar、httpclient-4.3.2.jar、httpcore-4.3.1.jar、commons-io-2.4.jar* @author zhaoyb**/ public class HttpClientUtils {public static final int connTimeout=10000;public static final int readTimeout=10000;public static final String charset="UTF-8";private static HttpClient client = null;static {PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();cm.setMaxTotal(128);cm.setDefaultMaxPerRoute(128);client = HttpClients.custom().setConnectionManager(cm).build();}public static String postParameters(String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, Exception{return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);}public static String postParameters(String url, String parameterStr,String charset, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception{return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);}public static String postParameters(String url, Map<String, String> params) throws ConnectTimeoutException,SocketTimeoutException, Exception {return postForm(url, params, null, connTimeout, readTimeout);}public static String postParameters(String url, Map<String, String> params, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,SocketTimeoutException, Exception {return postForm(url, params, null, connTimeout, readTimeout);}public static String get(String url) throws Exception {return get(url, charset, null, null);}public static String get(String url, String charset) throws Exception {return get(url, charset, connTimeout, readTimeout);}/*** 发送一个 Post 请求, 使用指定的字符集编码.** @param url* @param body RequestBody* @param mimeType 例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3* @param charset 编码* @param connTimeout 建立链接超时时间,毫秒.* @param readTimeout 响应超时时间,毫秒.* @return ResponseBody, 使用指定的字符集编码.* @throws ConnectTimeoutException 建立链接超时异常* @throws SocketTimeoutException 响应超时* @throws Exception*/public static String post(String url, String body, String mimeType,String charset, Integer connTimeout, Integer readTimeout)throws ConnectTimeoutException, SocketTimeoutException, Exception {HttpClient client = null;HttpPost post = new HttpPost(url);String result = "";try {if (StringUtils.isNotBlank(body)) {HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));post.setEntity(entity);}// 设置参数Builder customReqConf = RequestConfig.custom();if (connTimeout != null) {customReqConf.setConnectTimeout(connTimeout);}if (readTimeout != null) {customReqConf.setSocketTimeout(readTimeout);}post.setConfig(customReqConf.build());HttpResponse res;if (url.startsWith("https")) {// 执行 Https 请求.client = createSSLInsecureClient();res = client.execute(post);} else {// 执行 Http 请求.client = HttpClientUtils.client;res = client.execute(post);}result = IOUtils.toString(res.getEntity().getContent(), charset);} finally {post.releaseConnection();if (url.startsWith("https") && client != null&& client instanceof CloseableHttpClient) {((CloseableHttpClient) client).close();}}return result;}/*** 提交form表单** @param url* @param params* @param connTimeout* @param readTimeout* @return* @throws ConnectTimeoutException* @throws SocketTimeoutException* @throws Exception*/public static String postForm(String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,SocketTimeoutException, Exception {HttpClient client = null;HttpPost post = new HttpPost(url);try {if (params != null && !params.isEmpty()) {List<NameValuePair> formParams = new ArrayList<NameValuePair>();Set<Entry<String, String>> entrySet = params.entrySet();for (Entry<String, String> entry : entrySet) {formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));}UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);post.setEntity(entity);}if (headers != null && !headers.isEmpty()) {for (Entry<String, String> entry : headers.entrySet()) {post.addHeader(entry.getKey(), entry.getValue());}}// 设置参数Builder customReqConf = RequestConfig.custom();if (connTimeout != null) {customReqConf.setConnectTimeout(connTimeout);}if (readTimeout != null) {customReqConf.setSocketTimeout(readTimeout);}post.setConfig(customReqConf.build());HttpResponse res = null;if (url.startsWith("https")) {// 执行 Https 请求.client = createSSLInsecureClient();res = client.execute(post);} else {// 执行 Http 请求.client = HttpClientUtils.client;res = client.execute(post);}return IOUtils.toString(res.getEntity().getContent(), "UTF-8");} finally {post.releaseConnection();if (url.startsWith("https") && client != null&& client instanceof CloseableHttpClient) {((CloseableHttpClient) client).close();}}}/*** 发送一个 GET 请求** @param url* @param charset* @param connTimeout 建立链接超时时间,毫秒.* @param readTimeout 响应超时时间,毫秒.* @return* @throws ConnectTimeoutException 建立链接超时* @throws SocketTimeoutException 响应超时* @throws Exception*/public static String get(String url, String charset, Integer connTimeout,Integer readTimeout)throws ConnectTimeoutException,SocketTimeoutException, Exception {HttpClient client = null;HttpGet get = new HttpGet(url);String result = "";try {// 设置参数Builder customReqConf = RequestConfig.custom();if (connTimeout != null) {customReqConf.setConnectTimeout(connTimeout);}if (readTimeout != null) {customReqConf.setSocketTimeout(readTimeout);}get.setConfig(customReqConf.build());HttpResponse res = null;if (url.startsWith("https")) {// 执行 Https 请求.client = createSSLInsecureClient();res = client.execute(get);} else {// 执行 Http 请求.client = HttpClientUtils.client;res = client.execute(get);}result = IOUtils.toString(res.getEntity().getContent(), charset);} finally {get.releaseConnection();if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {((CloseableHttpClient) client).close();}}return result;}/*** 从 response 里获取 charset** @param ressponse* @return*/@SuppressWarnings("unused")private static String getCharsetFromResponse(HttpResponse ressponse) {// Content-Type:text/html; charset=GBKif (ressponse.getEntity() != null && ressponse.getEntity().getContentType() != null && ressponse.getEntity().getContentType().getValue() != null) {String contentType = ressponse.getEntity().getContentType().getValue();if (contentType.contains("charset=")) {return contentType.substring(contentType.indexOf("charset=") + 8);}}return null;}/*** 创建 SSL连接* @return* @throws GeneralSecurityException*/private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {try {SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {@Overridepublic boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {return true;}}).build();SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {@Overridepublic boolean verify(String arg0, SSLSession arg1) {return true;}@Overridepublic void verify(String host, SSLSocket ssl)throws IOException {}@Overridepublic void verify(String host, X509Certificate cert)throws SSLException {}@Overridepublic void verify(String host, String[] cns,String[] subjectAlts) throws SSLException {}});return HttpClients.custom().setSSLSocketFactory(sslsf).build();} catch (GeneralSecurityException e) {throw e;}} }
编写
controller
和service
代码,处理前端操作,获取二维码信息Controller
代码/*** 微信扫码登录* @author zym*/ @RestController @CrossOrigin @RequestMapping("/wx") public class WxLogin {@Resourceprivate MemberService memberServiceImpl;@Resourceprivate RedisTemplate redisTemplate;/*** 向码上登录服务器请求二维码信息* @return*/@GetMapping("/login")public String login(HttpSession session) throws GuLiError {return memberServiceImpl.getQrCode();} }
编写
Service
代码,通过HttpClentUtils
发送HTTP
请求,得到用户扫码登录的二维码信息请求地址为:
https://server01.vicy.cn/8lXdSX7FSMykbl9nFDWESdc6zfouSAEz/wxlogin/wxLogin/tempUserId?secretKey=你的secretKey
将
secretKey
更改为自己刚刚在码上登录创建应用得到的secretKey
返回值
codeUrl
里面存放了二维码信息,具体参数如下:{"errcode":0,"data":{"qrCodeReturnUrl":"http://login.vicy.cn?tempUserId=18d55e78f66848e781bc7ce65a1fe5b4", "tempUserId":"18d55e78f66848e781bc7ce65a1fe5b4"},"message":"成功"}
属性 类型 说明 qrCodeReturnUrl string 把qrCodeReturnUrl发送给前端生成二维码 tempUserId string 该次登录会话的凭证,一个二维码的 tempUserId 在回调的时候会传递给后端 /*** @author zym*/ @Service public class MemberServiceImpl extends ServiceImpl<MemberMapper, Member> implements MemberService {@Resourceprivate RedisTemplate redisTemplate;/*** 通过码上登录获取二维码信息* @return*/@Overridepublic String getQrCode() {String codeUrl = null;try {codeUrl = HttpClientUtils.get("https://server01.vicy.cn/8lXdSX7FSMykbl9nFDWESdc6zfouSAEz/wxlogin/wxLogin/tempUserId?secretKey=06a0b50645544c58ba32cb475ae6330a");} catch (Exception e) {e.printStackTrace();}return codeUrl;} }
前端展示二维码
使用 vue-qriously
利用二维码信息 qrCodeReturnUrl
生成二维码,展示给用户,代码如下
<template><div class="main"><div v-show="!showform"><qriously :value="qrCodeReturnUrl" :size="250"/><font aligen="center">请使用微信扫码登录</font><!-- initQCode: 是你在你的vue实例中定义好的变量 size:是这个Canvas的大小,这里要根据你的视觉稿来决定--></div><!-- 更多登录方式 --><div class="more-sign"><h6>社交帐号登录</h6><ul><li><a id="weixin" class="weixin" target="_blank" @click="getQrCodeReturnUrl()"><iclass="iconfont icon-weixin" /></a></li></ul></div></div></div>
</template><script>import '~/assets/css/sign.css'import '~/assets/css/iconfont.css'import cookie from 'js-cookie'import register from '@/api/register'import Qriously from 'vue-qriously'export default {layout: 'sign',data () {return {loginInfo:{},qrCodeReturnUrl:'',tempUserId:'',showform:true}},methods: {// 获取扫码登录信息getQrCodeReturnUrl(){// 请求刚刚写的控制器 http://localhost:8085/wx/login,可以直接 ajax 异步调用,这里使用 vue-admin 模板作了封装register.getQrCodeReturnUrl().then(response => {if (response.data.errcode === 0){console.log(response.data.data);this.qrCodeReturnUrl = response.data.data.qrCodeReturnUrl;this.tempUserId = response.data.data.tempUserIdthis.showform = false;}})}}
</script>
<style>.el-form-item__error{z-index: 9999999;}
</style>
编写码上登录传递信息给我们的地址
同样在上面的 controller
中添加回调方法,此方法必须是 post
请求类型,接收如下五个参数
属性 | 类型 | 说明 |
---|---|---|
userId | string | 用户唯一ID |
tempUserId | string | 该次会话的凭证 |
nickname | string | 登录用户的昵称(用户授权后,可获得,可能为空) |
avatar | string | 登录用户的头像url(用户授权后,可获得,可能为空) |
ipAddr | string | 登录地点的ip |
该方法还需要返回扫码登录状态,码上登录会根据返回的信息在用户手机上提示是否登录成功,返回信息如下,使用 map 封装这两个属性,需要将其转换成 json 格式进行返回
属性 | 类型 | 说明 |
---|---|---|
errcode | number | 错误码(0:请求成功) |
message | string | 接口调用情况描述,如:”成功” |
详细代码如下:注意:此方法需要解决跨域问题,我使用了 Getway 网关解决,也可以加上 @CrossOrigin 注解或者其他方式
/**
* 微信登录回调
* @return
*/
@PostMapping("/callback")
@ResponseBody
public String callback(@RequestParam(value = "userId",required = false) String userId,@RequestParam(value = "tempUserId",required = false) String tempUserId,@RequestParam(value = "nickname",required = false) String nickname,@RequestParam(value = "avatar",required = false) String avatar,@RequestParam(value = "ipAddr",required = false) String ipAddr,HttpServletResponse httpServletResponse) {try {// 判断用户是否第一次登录Member memberById = memberServiceImpl.getById(userId);if (memberById == null) {// 第一次登录,创建用户Member member = new Member();member.setId(userId);member.setNickname(nickname);member.setAvatar(avatar);memberServiceImpl.save(member);memberById = memberServiceImpl.getById(userId);}// 使用 JwtUtils 生成 tokenString token = JwtUtils.getJwtToken(memberById.getId(), memberById.getNickname());// 将用户 token 存放进 redis,键是 tempUserIdredisTemplate.opsForValue().set(tempUserId, token, 1, TimeUnit.MINUTES);HashMap<String, Object> map = new HashMap<>(1);if (userId != null) {map.put("errcode", 0);map.put("message", "登录成功");}Gson gson = new Gson();return gson.toJson(map);} catch (Exception e) {HashMap<String, Object> map = new HashMap<>(1);map.put("errcode", 1);map.put("message", "登录失败");Gson gson = new Gson();return gson.toJson(map);}
}
JwtUtils
如下:
package com.zym.utils;import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.util.StringUtils;import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Optional;/*** JWT工具类* @author zym*/
public class JwtUtils {public static final long EXPIRE = 1000 * 60 * 60 * 24;public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO";public static String getJwtToken(String id, String nickname){String JwtToken = Jwts.builder().setHeaderParam("typ", "JWT").setHeaderParam("alg", "HS256").setSubject("guli-user").setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + EXPIRE)).claim("id", id).claim("nickname", nickname).signWith(SignatureAlgorithm.HS256, APP_SECRET).compact();return JwtToken;}/*** 判断token是否存在与有效* @param jwtToken* @return*/public static boolean checkToken(String jwtToken) {if(StringUtils.isEmpty(jwtToken)) {return false;}try {Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);} catch (Exception e) {e.printStackTrace();return false;}return true;}/*** 判断token是否存在与有效* @param request* @return*/public static boolean checkToken(HttpServletRequest request) {try {String jwtToken = request.getHeader("token");if(StringUtils.isEmpty(jwtToken)) {return false;}Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);} catch (Exception e) {e.printStackTrace();return false;}return true;}/*** 根据token获取会员id* @param request* @return*/public static String getMemberIdByJwtToken(HttpServletRequest request) {String jwtToken = request.getHeader("guli_token");if(!Optional.ofNullable(jwtToken).isPresent()) {return "";}Jws<Claims> claimsJws =Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken);Claims claims = claimsJws.getBody();return (String)claims.get("id");}
}
将本项目打包放到公网服务器上运行
登录码上登录官网,修改之前创建应用的回调 URL
为 callback
方法的公网请求路径
前端获取登录状态
在之前创建的 controller
中新增 getToken
方法,根据 tempUserId
循环查询 redis
中是否有这个用户的信息,如果有,就将其返回给前端
一段时间后,redis
都没有查询到用户的 token
则视为登录过期,提示用户刷新二维码后重新登录
/*** 微信登录回调,根据用户 tempUserId 从 redis 中获取 token* @return*/@GetMapping("/getToken/{tempUserId}")public R getToken(@PathVariable String tempUserId) {System.out.println("tempUserId = " + tempUserId);boolean flag = false;for (int i = 0; i < 60; i++) {String token = (String) redisTemplate.opsForValue().get(tempUserId);if (token != null) {return R.ok().setData("token", token);}else {try {sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}}}return R.error().setData("message", "登录超时,点击微信图标刷新二维码后重试");}
前端编写 getToken
函数,调用这个 controller
查询登录状态,这里只展示 js
代码
<script>import '~/assets/css/sign.css'import '~/assets/css/iconfont.css'import cookie from 'js-cookie'import register from '@/api/register'import Qriously from 'vue-qriously'export default {layout: 'sign',data () {return {loginInfo:{},qrCodeReturnUrl:'',tempUserId:'',showform:true}},methods: {// 获取扫码登录信息getQrCodeReturnUrl(){register.getQrCodeReturnUrl().then(response => {if (response.data.errcode === 0){console.log(response.data.data);this.qrCodeReturnUrl = response.data.data.qrCodeReturnUrl;this.tempUserId = response.data.data.tempUserIdthis.showform = false;// 调用 getToken 函数this.getToken();}})},// 获取扫码登录用户tokengetToken(){// 请求后端刚刚写的控制器 http://localhost:8085/wx/getToken/这里是tempUserId ,查询这个二维码的登录信息register.getToken(this.tempUserId).then(response => {// 成功获取到登录信息,则将用户 token 保存到 cookie 中,并跳转到网站首页if (response.data.status === true) {console.log(response.data.data);cookie.set("token", response.data.data.token);this.$router.push("/");} else {// 获取用户信息超时的逻辑请自己编写}})}},}
</script>
注意:这里使用jwt做单点登录,你也可以直接存储用户信息
个人开发者实现微信扫码登录相关推荐
- 微信开放平台开发——网页微信扫码登录(OAuth2.0)
1.OAuth2.0 OAuth(开放授权)是一个开放标准,允许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用. 允许用户提供 ...
- SpringBoot整合微信扫码登录
SpringBoot整合微信扫码登录 准备工作 基本思路流程 搭建SpringBoot 引入依赖 加入配置文件 代码实现 工具类 controller层 结果 准备工作 1.登录官网了解到,学习者想本 ...
- 项目整合微信扫码登录功能
项目整合微信登录功能 一.准备工作 https://open.weixin.qq.com 1.注册 2.邮箱激活 3.完善开发者资料 4.开发者资质认证 准备营业执照,1-2个工作日审批.300元 5 ...
- Spring学习笔记(二十三)——实现网站微信扫码登录获取微信用户信息Demo
目录 微信扫码登录介绍 开发步骤 微信扫码登录示例 微信开放文档 遇到的问题 使用第三方工具实现网站微信扫码登录 开发前介绍 开发步骤 微信扫码登录获取微信用户信息Demo实现流程 实现效果 实现过程 ...
- 通过微信扫码登录剖析 oauth2 认证授权技术
本文目录 前言 趣味解读oauth2 oauth2精髓 oauth2核心概念 结合微信登录深刻理解oauht2 本文小结 前言 相信很多小伙伴在学习 JAVA 的过程中或多或少接触或者开发过类似于 x ...
- 【vue+pc端】实现微信扫码登录pc端,后端通过微信开发平台,前端生成二维码(仅供参考)
这两周的需求是通过微信扫码登录pc端,刚定下需求原型图还没出来前,后端特意发了微信开发平台的链接给我,关于如何生成二维码的文档,以及扫码跳转后如何传code给他. 请戳这里准备工作|微信开放文档 我最 ...
- 微信扫码登录功能实现
原因:很简单,公司的账号登录需要用到微信扫码登录与QQ的登录功能,所以,在做好了微信的扫码登录之后,本人就写这篇微信扫码登录功能实现的教程 教程开始 需要用到的网站: https://open.wei ...
- 基于Springboot2.x+vue3.x整合实现微信扫码登录
第1章 准备工作 1.1 微信开放平台 微信扫码登录,需要在微信开放平台注册账号被认证为开发者才能接入官网地址:https://open.weixin.qq.com/ 1.1.1 注册账号并认证成为开 ...
- 使用码上登录实现微信扫码登录
现如今使用微信的人越来越多,很多网站都实现了让用户直接扫码就能登录网站,正是这种方式给用户带来了极多的好处,最重要的是用户不用担心自己总记不住账户密码,从而登录不了网站. 为了方便用户登录,我也想接入 ...
最新文章
- Python标准库介绍
- msbuild 语法_用于删除文件的MSBuild Task语法
- Windows 8下看漫画的程序发布
- CV之OD:计算机视觉之目标检测(Object Detection)方向的简介、使用方法、案例应用之详细攻略
- 一个BADI中实施多个Implementation
- 管家病毒查杀模块逆向分析
- Spring Security——简单第三方OAuth2登录自动配置——GitHub登录DEMO
- 对知识图谱的告白:斯坦福大学CS520课程介绍
- winfrom软件开发汽车测试_从事汽车电子软件开发岗,我们最近还没那么愁
- 二十四、Java集合框架(一)
- 微软.Net RIA Services项目前景简评
- python第三方模块下载方法(最详最细)
- 【Java编程】模拟帐户存取和转账操作
- ATECLOUD智能云测试平台,中国人自己的“LABVIEW”-测试测量软件
- 易宝支付 java_易宝支付工具类
- 未来计算机手抄报图片,小学生科技创造未来手抄报图片简单又漂亮
- assoc 和 ftype
- gpu服务器压力测试方法,如何用Furmark对GPU进行压力测试?
- centos 6.6 安装图形界面 X server gnome Xwindows
- 苹果收取30%过路费_你是顶是踩?
热门文章
- 基础类与基础算法学习
- Springboot 2.6.1 + Nacos 2.0.3 + Dubbo 3.0.2.1
- freeswitch控制台常用命令
- 5 Pandas数据库
- android 状态栏挡住app,【已解决】react-navigation导航栏被状态栏遮盖挡住了一部分...
- 2021年上海值得去的66家规模互联网大厂公司全名简称
- matlab中如何读写txt,Matlab中读取txt文件的几种方法
- 个人学习笔记——庄懂的技术美术入门课(美术向)01
- python识别手写数字knn_机器学习-kNN实现简单的手写数字识别系统
- 骨传导为什么比气传导好,骨传导耳机还有这么多优势!