-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' of github.com:Ikki-Dai/blogs
- Loading branch information
Showing
3 changed files
with
137 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
# 扫码登陆实战方案 | ||
|
||
## 需求 | ||
|
||
- 用户 使用手机扫描 浏览器登录页的二维码 | ||
- 用户手机上显示 用户许可页面,同时浏览器显示扫码成功,处于等待确认状态 | ||
- 用户在手机上确认登陆,浏览器显示确认成功并跳转登陆后页面 | ||
|
||
## 分析 | ||
|
||
1. 浏览器请求服务器 并生成二维码,显然二维码内含有一些信息能被对应的手机客户端识别 | ||
- 二维码内的信息不能被其他软件识别,否则的话,浏览器会被其他人的客户端识别并发起登陆请求 | ||
- 二维码被扫描后不能再次被识别,有可能被其他账号扫描并登陆 | ||
2. 浏览器二维码被展示出来后,需要实时监听到二维码的消费情况。 | ||
- 浏览器轮询显然是个不错的选择,但是我们可以使用**`Server Sent Event` **会是更好的选择 | ||
3. 二维码在一定时间内没有被消费,应当过期处理 | ||
- 断网从连不能影响消息的消费 | ||
|
||
## 代码实战 | ||
|
||
### 浏览器 获取二维码等待响应 | ||
|
||
```java | ||
@GetMapping("/qr-code/{client_id}") | ||
public ResponseEntity<SseEmitter> QRLogin(@PathVariable("client_id") String clientId) { | ||
String authcode = UUID.randomUUID().toString(); | ||
SseEmitter sseEmitter = new SseEmitter(90 * 1000L); //90s 内没有完成 SSE会超时 | ||
String id = clientId + '@' + authcode; | ||
BlockingQueue<String> queue = redisson.getBlockingQueue("SSE_QUE:" + id); // 生成通道 | ||
localCachedMap.put(id, true); | ||
|
||
threadPoolTaskExecutor.submit(() -> { | ||
Map<String, String> body = new HashMap<>(); | ||
body.put("clientId", clientId); | ||
body.put("authCode", authcode); | ||
body.put("redirectUrl", "immigrant.com/login/qr"); | ||
|
||
try { | ||
sseEmitter.send(body, MediaType.APPLICATION_JSON); | ||
while (true) { | ||
String s = queue.take(); | ||
log.info(s); | ||
if ("END".equals(s)) { | ||
localCachedMap.put(id, false); //完成后会关闭通道, 二维码泄露也不会发送消息到服务端 | ||
sseEmitter.complete(); | ||
} else { | ||
sseEmitter.send(s); | ||
} | ||
} | ||
|
||
} catch (InterruptedException | IOException e) { | ||
e.printStackTrace(); | ||
} | ||
|
||
|
||
}); | ||
// SSE 链接销毁时,要设置通道关闭 | ||
sseEmitter.onCompletion(() -> { | ||
queue.clear(); | ||
log.info("sse {} completed", id); | ||
}); | ||
|
||
|
||
return ResponseEntity.ok(sseEmitter); | ||
``` | ||
|
||
|
||
### 客户端扫描二维码并发送请求 | ||
|
||
```java | ||
@PostMapping("/qr-code/{client_id}") | ||
public ResponseEntity<SseEmitter> QRLogin(@PathVariable("client_id") String clientId,@RequestBody ScanRequest request) { | ||
BlockingQueue<String> queue; | ||
if (Boolean.TRUE.equals(localCachedMap.get(clientId))) { | ||
queue = redisson.getBlockingQueue("SSE_QUE:" + clientId); | ||
} else { | ||
return new ResponseEntity<>(HttpStatus.GONE); | ||
} | ||
|
||
try { | ||
queue.offer(objectMapper.writeValueAsString(scanRequest)); | ||
} catch (JsonProcessingException e) { | ||
e.printStackTrace(); | ||
} | ||
|
||
return ResponseEntity.noContent().build(); | ||
} | ||
``` | ||
|
||
|
||
|