现如今使用微信的人越来越多,很多网站都实现了让用户直接扫码就能登录网站,正是这种方式给用户带来了极多的好处,最重要的是用户不用担心自己总记不住账户密码,从而登录不了网站。
为了方便用户登录,我也想接入微信扫码登录。但因为微信开发者需要企业认证,所以没办法接入个人网站实现微信扫码登录。最近我发现了一个小程序“码上登录”,对个人开发者提供了免费的微信扫一扫登录入口,其实就是一个桥接入口,为我们去获取用户扫码之后的信息,从而在发给我们,我们接收再返回信息即可。
好了,撸起袖子直接干吧!
准备工作
在码上登录注册,然后创建自己的应用并指定回调地址,会生成一个secretKey,后面获取二维码链接用得到
微信登录时序图
首先我们向码上登录的服务器发送请求获取二维码信息,然后用户扫码,用户跳转到登录的小程序,用户点击登录,授权,码上登录服务器向自己的服务器回调入口返回数据,然后我们返回登录状态信息。
后台开发
向码上登录服务器发送请求,返回二维码数据信息
secretKey(即我们创建应用时生成的secretKey)调用码上登录服务器时候需要进行验证的凭据
@FeignClient(name = "wx", url = "https://server01.vicy.cn")
public interface UserFeignClient {
/**
* 向码上登录服务器请求二维码
*/
@GetMapping(value = "/8lXdSX7FSMykbl9nFDWESdc6zfouSAEz/wxLogin/tempUserId")
String getQrCode(@RequestParam(required = true, value = "secretKey") String secretKey);
}
@FeignClient标签的常用属性如下:
- name:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现
- url: url一般用于调试,可以手动指定@FeignClient调用的地址
- decode404:当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException
- configuration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract
- fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口
- fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码
- path: 定义当前FeignClient的统一前缀
发送请求
获取登录二维码信息
@Value(value = "${justAuth.clientSecret.wechat}")
private String wechatClientSecret;
@ApiOperation(value = "获取码上登录微信二维码链接", notes = "获取码上登录微信二维码链接")
@GetMapping("/getWechatQrCodeReturnUrl")
public String getWechatQrCodeReturnUrl () {
// 将传递过来的转换成大写
String source = SysConf.WECHAT;
Boolean isOpenLoginType = webConfigService.isOpenLoginType(source.toUpperCase());
if (!isOpenLoginType) {
return ResultUtil.result(SysConf.ERROR, "后台未开启该登录方式!");
}
return ResultUtil.result(SysConf.SUCCESS,userService.getWechatQrCodeReturnUrl(wechatClientSecret));
}
实现类
@Service
public class UserServiceImpl implements UserService {
@Resource
private UserFeignClient userFeignClient;
@Override
public String getWechatQrCodeReturnUrl(String wechatClientSecret) {
String result = userFeignClient.getQrCode(wechatClientSecret);
return result;
}
}
返回数据
{"errcode":0,"data":{"qrCodeReturnUrl":"http://login.vicy.cn?tempUserId=18d55e78f66848e781bc7ce65a1fe5b4", "tempUserId":"18d55e78f66848e781bc7ce65a1fe5b4"},"message":"成功"}
errcode:错误码
message:描述信息
data:存放二维码信息集合
tempUserId:登录会话临时凭证
qrCodeReturnUrl:发送给前端用于生成二维码
回调地址
@ApiOperation(value = "码上登录回调地址", notes = "码上登录回调地址")
@PostMapping(value = "/callback")
public JSONObject backUserScanedInfo(
@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) throws IOException {
log.info("用户信息============\r\n"+ "【userId】"+userId+ " 【tempUserId】 "+tempUserId+ " 【nickname】 "+nickname+ " 【headimgurl】 "+avatar+ " 【ipAddr】"+ipAddr);
// TODO 写自己的逻辑 保存用户或更新用户信息到数据库
// 返回状态码
JSONObject jsonObject = new JSONObject();
if (userId != null){
jsonObject.put("errcode",0);
jsonObject.put("message","success");
}else {
jsonObject.put("errcode",1);
jsonObject.put("message","error");
}
return jsonObject;
}
前端实现
<template>
<div>
<div v-if="showPasswordLogin == false" style="text-align: center" class="block">
<el-image :src="qrcode" style="width: 250px;" id="qrcode" ref="qrCodeUrl">
<div slot="error" class="image-slot" @click="getWechatQrCodeReturnUrl">
<i class="el-icon-refresh" style="height: 250px; line-height: 250px; cursor: pointer">点击刷新</i>
</div>
</el-image>
</div>
</div>
</template>
<script>
import {getWechatQrCodeReturnUrl} from '@/api/user'
import QRCode from "qrcode";
export default {
name: 'share',
data () {
return {
url: "http://www.baidu.com", //识别二维码后跳转的地址
qrcode:'' //存储二维码地址
}
},
components: {},
created () {
},
methods: {
// 获取码上登录返回的数据生成二维码
getWechatQrCodeReturnUrl: function () {
let that = this
getWechatQrCodeReturnUrl().then(response => {
// eslint-disable-next-line eqeqeq
if (response.code == this.$ECode.SUCCESS) {
this.showPasswordLogin = false
let dataz = JSON.parse(response.data);
this.url = dataz.data.qrCodeReturnUrl
QRCode.toDataURL(this.url,{
version: 7,
errorCorrectionLevel: 'Q',
width: 280,
height: 280,
margin: 0,
}).then(url => {
// console.log(url);
this.qrcode = url
}).catch(err => {
// console.error(err)
})
this.tempUserId = dataz.data.tempUserId
let count = 0
let interval = setInterval(() => {
count++
if (count > 5) {
this.qrcode = ''
clearInterval(interval)
}
let params = new URLSearchParams()
params.append('ticket', this.tempUserId)
getUserLoginStatus(params).then(response => {
console.log('获取用户登录状态', response)
if (response.code == that.$ECode.SUCCESS) {
let token = this.tempUserId
if (token != undefined) {
setCookie('token', token, 7)
}
this.setUserInfo(response.data)
this.setLoginState(true)
// location.reload()
// 跳转到首页
location.replace(this.vueXqCodingWebUrl + '/#/?token=' + token)
//window.location.reload()
}
})
}, 3000)
} else {
this.$message.error(response.message)
}
})
}
}
}
</script>
效果