Hand – to – hand sweep a code login example project
I don’t know if it is wechat, now there are more and more scenes of scanning code login, as a new four good code farmers with pursuit and ideal, of course, have to keep up with the trend of The Times, have to hand off a example
This sample project mainly uses the following technology stack
qrcode-plugin
: Open source QR code generation toolkit, project link:Github.com/liuyueyi/qu…SpringBoot
: Basic project environmentthymeleaf
: page rendering engineSSE/ asynchronous request
: Event pushed by the serverjs
: Basic operations of native JS
I. Principle analysis
According to the previous plan, should give priority to write the file download related blog, however, saw a blog about the principle of scanning code login, found that it can be combined with the previous asynchronous request /SSE, do a practical application, so there is this blog
For the principle of scanning code login, please check: chat about the principle of two-dimensional code scanning login
1. Scenario description
In order to take care of the scanning code login may not understand the students, here is a simple introduction to what it is
Generally speaking, scan code login, involving both ends, three steps
- On the PC, you can log in to a website. The login method of this website is different from the traditional user name/password (mobile phone number/verification code), but a TWO-DIMENSIONAL code is displayed
- App side, use the app of this website, first make sure you are logged in, then scan the TWO-DIMENSIONAL code, pop up a page of login authorization, click Authorization
- If the login succeeds, the home page is automatically displayed
2. Brief introduction of principle and process
In the design of the whole system, the most core point is the mobile phone terminal scan code, PC login success, what is the principle of this?
- We assume that app and backend are identified by token
- App scans code for authorization and transfers token to the back end. The back end can determine who initiates the login request on the PC side according to the token
- The backend will write the successful login status back to the PC requester and jump to the home page (this is equivalent to the normal process after the successful login of the user, you can choose session, cookie or JWT).
With the help of the above principle, step by step analysis of key points
- Log in to the PC and generate a QR code
- The TWO-DIMENSIONAL code must be unique, and bind the identity of the requestor (otherwise assume that the two-dimensional code of two people is the same, one person scan the code to log in, the other one is also logged in?)
- The client keeps the connection with the server, so as to receive the subsequent login success and adjust the home page event (there are many options, such as polling, long connection push).
- App scans code and authorizes login
- After scanning the code, jump to the authorization page (so the two-dimensional code should be a URL)
- Authorization (identification, binding of identity information to THE PC requestor, and redirecting to the home page)
Finally, we selected the business process relationship as follows:
II. The implementation
Then enter the project development stage, for the above flow chart one by one implementation
1. Project environment
First of all, there is a common SpringBoot project. Select version 2.2.1.release
Pom dependencies are as follows
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1. RELEASE</version>
<relativePath/> <! -- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.github.hui.media</groupId>
<artifactId>qrcode-plugin</artifactId>
<version>2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</pluginManagement>
</build>
<repositories>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/libs-release-local</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>yihui-maven-repo</id>
<url>https://raw.githubusercontent.com/liuyueyi/maven-repository/master/repository</url>
</repository>
</repositories>
Copy the code
Key dependency description
qrcode-plugin
: Not to be bluster, this is probably the most useful, flexible, and cool QR code generation toolkit on the Java side, the latest version2.2
When importing dependencies, specify the repository addresshttps://raw.githubusercontent.com/liuyueyi/maven-repository/master/repository
spring-boot-starter-thymeleaf
: The template rendering engine we chose, where there is no front and back end separation, contains all the function points in one project
The configuration fileapplication.yml
server:
port: 8080
spring:
thymeleaf:
mode: HTML
encoding: UTF-8
servlet:
content-type: text/html
cache: false
Copy the code
Obtain the local IP address
Provide a utility class to get the native IP, avoid hard-coded urls, resulting in ungeneric
import java.net.*;
import java.util.Enumeration;
public class IpUtils {
public static final String DEFAULT_IP = "127.0.0.1";
/** * Uses the address of the first nic as the Intranet ipv4 address instead of returning 127.0.0.1 **@return* /
public static String getLocalIpByNetcard(a) {
try {
for (Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); e.hasMoreElements(); ) {
NetworkInterface item = e.nextElement();
for (InterfaceAddress address : item.getInterfaceAddresses()) {
if(item.isLoopback() || ! item.isUp()) {continue;
}
if (address.getAddress() instanceof Inet4Address) {
Inet4Address inet4Address = (Inet4Address) address.getAddress();
returninet4Address.getHostAddress(); }}}return InetAddress.getLocalHost().getHostAddress();
} catch (SocketException | UnknownHostException e) {
returnDEFAULT_IP; }}private static volatile String ip;
public static String getLocalIP(a) {
if (ip == null) {
synchronized (IpUtils.class) {
if (ip == null) { ip = getLocalIpByNetcard(); }}}returnip; }}Copy the code
2. Login interface
The @crossorigin annotation supports cross-domain, since we will use localhost to access the login interface later in the test; But SSE registrations use native IP, so there are cross-domain issues that may not exist in real projects
Login page logic, a two-dimensional code returned after access, two-dimensional code content is login authorization URL
@CrossOrigin
@Controller
public class QrLoginRest {
@Value(("${server.port}"))
private int port;
@GetMapping(path = "login")
public String qr(Map<String, Object> data) throws IOException, WriterException {
String id = UUID.randomUUID().toString();
// IpUtils is a tool class to obtain the local IP address. If you use 127.0.0.1, localhost in the local test, then the app will have a problem accessing the code
String ip = IpUtils.getLocalIP();
String pref = "http://" + ip + ":" + port + "/";
data.put("redirect", pref + "home");
data.put("subscribe", pref + "subscribe? id=" + id);
String qrUrl = pref + "scan? id=" + id;
// The following line generates a 200-by-200 red, dotted QR code encoded in Base64
// One line, simple and easy, strong Amway
String qrCode = QrCodeGenWrapper.of(qrUrl).setW(200).setDrawPreColor(Color.RED)
.setDrawStyle(QrCodeOptions.DrawStyle.CIRCLE).asString();
data.put("qrcode", DomUtil.toDomSrc(qrCode, MediaType.ImageJpg));
return "login"; }}Copy the code
Notice that in the implementation above, we return a view and pass three pieces of data
- Redirect: Redirect to the url (after the app is authorized)
- Subscribe: a url that the user accesses, opens a long connection, and receives the scan and login events pushed by the server.
- Qrcode: qrcode image in base64 format
Note:subscribe
andqrcode
Globally unique IDS are used, and this parameter will be important in future operations
Add the file login. HTML to the corresponding HTML page in the Resources /templates file
<! DOCTYPEhtml>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="SpringBoot thymeleaf"/>
<meta name="author" content="YiHui"/>
<meta name="viewport" content="Width = device - width, initial - scale = 1.0"/>
<title>Two-dimensional code interface</title>
</head>
<body>
<div>
<div class="title">Please scan code to log in</div>
<img th:src="${qrcode}"/>
<div id="state" style="display: none"></div>
<script th:inline="javascript">
var stateTag = document.getElementById('state');
var subscribeUrl = [[${subscribe}]];
var source = new EventSource(subscribeUrl);
source.onmessage = function (event) {
text = event.data;
console.log("receive: " + text);
if (text == 'scan') {
stateTag.innerText = 'Scanned';
stateTag.style.display = 'block';
} else if (text.startsWith('login#')) {
// the login format is login#cookie
var cookie = text.substring(6);
document.cookie = cookie;
window.location.href = [[${redirect}]]; source.close(); }}; source.onopen =function (evt) {
console.log("Start subscribing");
}
</script>
</div>
</body>
</html>
Copy the code
Note that in the HTML implementation above, the tag id state is not visible by default; SSE is implemented through EventSource (with the advantage of real-time and retry capability) and formatted for the returned results
- If received from the server
scan
Message, modify the state tag text and set it to visible - If received from the server
login#cookie
Format data, indicating that the login is successful.#
Set the local cookie, then redirect to the home page, and close the long connection
Second, in the script tag, if you need access to passed parameters, note the following two points
- Need to be added on the script tag
th:inline="javascript"
[[${}]]
Get transfer parameters
3. The sse interface
In the previous login interface, a SSE registration interface is returned. The client will access this interface when accessing the login page. According to our previous SSE tutorial document, this interface can be implemented as follows
private Map<String, SseEmitter> cache = new ConcurrentHashMap<>();
@GetMapping(path = "subscribe", produces = {org.springframework.http.MediaType.TEXT_EVENT_STREAM_VALUE})
public SseEmitter subscribe(String id) {
// Set a five-minute timeout
SseEmitter sseEmitter = new SseEmitter(5 * 60 * 1000L);
cache.put(id, sseEmitter);
sseEmitter.onTimeout(() -> cache.remove(id));
sseEmitter.onError((e) -> cache.remove(id));
return sseEmitter;
}
Copy the code
4. Scan interface
Next is to scan the TWO-DIMENSIONAL code into the authorization page interface, this logic is relatively simple
@GetMapping(path = "scan")
public String scan(Model model, HttpServletRequest request) throws IOException {
String id = request.getParameter("id");
SseEmitter sseEmitter = cache.get(request.getParameter("id"));
if(sseEmitter ! =null) {
// Tell the PC that the code has been scanned
sseEmitter.send("scan");
}
// The authorized URL
String url = "http://" + IpUtils.getLocalIP() + ":" + port + "/accept? id=" + id;
model.addAttribute("url", url);
return "scan";
}
Copy the code
After scanning codes to access the page, the user locates the PC client based on the transmitted ID and sends scan information
The authorization page is easier to implement, adding an authorization hyperlink is good, and then adding user tokens according to the actual situation (since there is no independent APP and user system, the following is a demonstration to randomly generate a token to replace it).
<! DOCTYPEhtml>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="SpringBoot thymeleaf"/>
<meta name="author" content="YiHui"/>
<meta name="viewport" content="Width = device - width, initial - scale = 1.0"/>
<title>Scan the login page</title>
</head>
<body>
<div>
<div class="title">Are you sure you're logged in?</div>
<div>
<a id="login">The login</a>
</div>
<script th:inline="javascript">
// Generate a UUID to simulate the transfer of the user token
function guid() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g.function (c) {
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
// Get the actual token, complete the parameter, here is a simple simulation
var url = [[${url}]];
document.getElementById("login").href = url + "&token=" + guid();
</script>
</div>
</body>
</html>
Copy the code
5. Authorize interfaces
After clicking the authorization hyperlink above, the login is successful. Our back-end implementation is as follows
@ResponseBody
@GetMapping(path = "accept")
public String accept(String id, String token) throws IOException {
SseEmitter sseEmitter = cache.get(id);
if(sseEmitter ! =null) {
// Send a successful login event with the user's token. We use cookies to save the token here
sseEmitter.send("login#qrlogin=" + token);
sseEmitter.complete();
cache.remove(id);
}
return "Login successful:" + token;
}
Copy the code
6. The home page
After the user authorization is successful, it will automatically jump to the home page. We can simply make a welcome copy on the home page
@GetMapping(path = {"home", ""})
@ResponseBody
public String home(HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies == null || cookies.length == 0) {
return "Not logged in!";
}
Optional<Cookie> cookie = Stream.of(cookies).filter(s -> s.getName().equalsIgnoreCase("qrlogin")).findFirst();
return cookie.map(cookie1 -> "Welcome to home page:" + cookie1.getValue()).orElse("Not logged in!");
}
Copy the code
7. The measured
Login to this a complete authorization is done, can the practical exercise, is a complete demo screenshot below (although I didn’t really use app to scan log in, but the recognition of qr code address, in the browser for authorization, actually does not affect the whole process, you use 2 d scan authorization effect is the same)
Notice a few key points in the screenshot above
- After scanning, the qr code will be displayed on the login page
scanned
The copy of - After the authorization is successful, the login page will jump to the home page and show “welcome XXX”. Note that the users are the same
Nodule 8.
Real business development scheme selection may and proposed in this paper is not quite same, there may be a more elegant way (please have the experience of bosses sermons), just as a reference, this paper does not represent the standard, not entirely accurate, if the people into the pit, please leave a message (of course I am not responsible for ð)
The above demonstrates the example of a two-dimensional code login by hand, mainly using the following technical points
qrcode-plugin
: Generate two-dimensional code, once again strong amway a private Java ecosystem best use two-dimensional code generation toolkitGithub.com/liuyueyi/qu…(Although the boast is fierce, but I did not charge for advertising, because this is also written by me ð)SSE
: The server pushes events. The server communicates with each other in a single channel to achieve message pushSpringBoot/Thymeleaf
: Demonstrate the basic project environment
Finally, feel good can like it, add a friend something nothing to talk about, pay attention to a wechat public number to support one or two, it is ok
III. The other
0. Project
Related blog
As for this blog post, some knowledge points can be completed by referring to the following articles
- ãSpringBoot WEB series ãSSE server send event details
- ãSpringBoot WEB series ã Asynchronous request knowledge and use posture summary
- ãSpringBoot WEB series ãThymeleaf environment setup
- Project: github.com/liuyueyi/sp…
- Project source: github.com/liuyueyi/sp…
1. An ashy Blog
As far as the letter is not as good, the above content is purely one’s opinion, due to the limited personal ability, it is inevitable that there are omissions and mistakes, if you find bugs or have better suggestions, welcome criticism and correction, don’t hesitate to appreciate
Below a gray personal blog, record all the study and work of the blog, welcome everyone to go to stroll
- A grey Blog Personal Blog blog.hhui.top
- A Grey Blog-Spring feature Blog Spring.hhui.top