客户端扫描二维码来实现用户登陆的场景,非常普遍,也是目前比较方便的登陆方式之一。
本篇博客就来说一说实现的思路,以及后端、前端、客户端(Android)的协同,以及一些代码。这篇博客不对ZuulFilter以及token的相关知识做补充说明,如果有疑问,请留下评论。
如何理解这种登陆方式的实现?可以看作是三端借助一个随机字符串 randomStr , 来实现用户信息token的精确传递。
前端 or 后端 生成一个随机数randomStr ,后端根据包含randomStr 的数据(数据内容完全根据系统的需要去设置) e.g.
{randomStr : **************** , auth_path : /app/verify , remark : app need take this random_num and access_token to request auth_path}
然后由此生成二维码(github有相应的工具类 com.google.zxing),并返回给前端。此时后端还需要在redis中新建一个k-v键值对,以randomStr 为key,并设置一个默认值和有效时间。
前端在接受到图片之后,需要做三件事。
1. 展示 ; 2.定时器,过了一定时间提示该二维码失效,然后自动刷新重新获取,或者隐藏(根据需求来定) , 3 .定时器,每秒检查一下后端的redis中,k-v是否有值。
客户端的只需要扫描之后,获取到二维码中包含的信息,携带自身已经获取的token以及二维码中的randomStr 请求接口,在该接口中,将用户信息保存在k-v中即可。
下面是主要代码。
后端提供的接口
- 生成二维码接口
- 检测redis中是否有值的接口
- app端的认证接口
1 生成二维码接口
@GetMapping("/produce-qrcode")
@Override
public void produceQrCode(String randomStr) throws IOException {
Map<String , String> payloadOnQrCode = new HashMap<>();
payloadOnQrCode.put("randomStr" , randomStr);
payloadOnQrCode.put("authApi" , "/identity/verify-on-app");
payloadOnQrCode.put("remark" , "携带客户端token以及randomStr参数,请求authApi接口即可完成登陆验证.二维码有效期 2 分钟");
//将qrCode_randomStr作为key , value = "" 保存在redis
String key = "qrCode_" + randomStr;
RedisUtil.set(redisTemplate , key , "for record" , 60 * 2);
String jsonString = JSON.toJSONString(payloadOnQrCode);
// 生成二维码
QRCodeUtil.outPutQRCodeByImgUrl(jsonString , httpServletResponse);
}
2 检测redis中是否有值的接口
@PostMapping("/check-qrcode-condition")
@ResponseBody
@Override
public Result checkQrCodeCondition(String randomStr) {
String key = "qrCode_" + randomStr;
String redis_value = RedisUtil.get(redisTemplate , key);
if(StringUtils.isBlank(redis_value)){
return new Result("qrCode has expired,please refresh");
}
if(StringUtils.isNotEmpty(redis_value) && !redis_value.equals("for record")){
RedisUtil.del(redisTemplate , key);
// redis_value 即为用户的ID
User user = userService.findOneById(redis_value);
return new Result((Object) produceTokenByUser(user));
}else{
return new Result();
}
}
3 app端的认证接口
@PostMapping("/verify-on-app")
@ResponseBody
@Override
public Result verifyOnApp(@RequestBody Map<String , Object> body) {
String randomStr = body.get("randomStr").toString();
String token_userId = body.get("token_userId").toString();
String key = "qrCode_" + randomStr;
String redis_value = RedisUtil.get(redisTemplate , key);
if(StringUtils.isEmpty(redis_value)){
return new Result("二维码已过期,请重新获取二维码");
}else if(redis_value.equals("for record")){
// 二维码在有效期之内
RedisUtil.set(redisTemplate , key , token_userId , 5);// 浏览器轮询 有效时间可以设置的小一点
}
return new Result();
}
app(Android)中,主要代码(如何扫描二维码 并获取数据,不做说明)
1 发送认证请求
@Override
protected void setListener() {
authorize_button.setOnClickListener((v) ->{
// 发送验证信息
new Thread(this::sendVerify).start();
});
}
private void sendVerify(){
try {
Map<String , String> param = new HashMap<>();
param.put("randomStr" , randomStr);
URL url = new URL(HttpContents.ZUUL_PATH + auth_path);
HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8");
httpURLConnection.setRequestMethod("POST");
httpURLConnection.addRequestProperty(ACCESS_TOKEN_KEY , SharePreUtils.getAccessToken(context));// 登陆成功时,保存的用户信息
httpURLConnection.setUseCaches(false);
httpURLConnection.setDoInput(true);
httpURLConnection.setDoOutput(true);
httpURLConnection.setConnectTimeout(10000);
httpURLConnection.setReadTimeout(10000);
DataOutputStream dos = new DataOutputStream(httpURLConnection.getOutputStream());
dos.write(JSON.toJSONString(param).getBytes(StandardCharsets.UTF_8));
dos.flush();
dos.close();
} catch (Exception e) {
e.printStackTrace();
}finally {
finish();
}
}
前端
检查redis的代码
function startCheckQrCodeConditionInterval(){
// 开启定时器
vg_interval_checkQrCodeCondition = window.setInterval(function(){
var current_ms = Date.now();
if(current_ms - vg_lastGetQrCodeTime_ms < 2 * 60 * 1000){
// 小于两分钟 持续的检查二维码状态
var check_url = zuul_url + "/identity/check-qrcode-condition";
var param = {randomStr : vg_qrCode_uuid};
$.post(check_url , param , function(res){
if(res.state === 1 && null !== res.data && "" !== res.data){
//停止定时器
stopRefQrCodeInterval();
// 保存获取到的token信息
saveToken(res.data);
startCheckQrCodeConditionInterval();
$.tooltip("扫码成功" , tipShow.normal , true);
window.location = "index.html";
}else if(res.state === 0 && "qrCode has expired,please refresh" === res.message){
stopRefQrCodeInterval();
stopCheckQrCodeConditionInterval();
$.tooltip("二维码已过期,请重新获取!");
}
});
}else{
clearInterval(vg_interval_checkQrCodeCondition);
vg_interval_checkQrCodeCondition = null;
}
} , delay.short);
}
以上是扫码登陆的简单过程,任何问题,可以留在评论中