Spring Boot+mysql+Vue.js最小子系统骨架Demo
本Demo实现了一个登录页面。具体功能如下:
- 用户可以在网页的输入框中输入用户名和密码
- 之后前端Vue.js使用axios发送get请求,将用户名和密码传送给后端
- 后端Controller将获取到的用户名和密码与数据库(使用jdbc)中的查询结果比较,并将结果发送request回前端
- 前端根据返回值,保存登录状态到会话Cookie
- 用户在关闭浏览器(区分于关闭页面)前,会话Cookie均有效,登录状态可以保持。这样,页面间的跳转等操作均不会导致登录状态失效
具体的实现如下。
加入依赖(Maven)
首先创建一个Spring项目(可以参考之前的博客)。之后,配置Maven。Maven大部分应该由IDEA自动生成。只需要管dependency就好。之后,IDEA窗口上会有一个Maven图标+一个看上去像刷新的图标。点击一下,即可让Maven自动配置依赖。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>XXXX</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>XXXX</name>
<description>XXXX</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
程序入口
package com.example.xxxx;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
public class XXXXApplication {
public static void main(String[] args) {
SpringApplication.run(XXXXApplication.class, args);
}
@GetMapping("/hello")
public String sayHello(@RequestParam(value = "myName", defaultValue = "World") String name) {
return String.format("Hello %s!", name);
}
}
创建Model类(Java – Spring)
创建以下类,作为MVC中M的部分。
大概就是一个User类吧。具体的业务逻辑用的到。这里懒得写了。登录功能比较简单,所以不写这个也行。
public class User{
private String username;
private String password;
set...;
get...;
func1...;
}
配置数据库(JDBC)
resources/application.properties中,加入这一行,避免启动时配置数据源失败问题【重要】
spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
使用xml配置jdbc。resources/myjdbc.xml如下所示。url、username、password记得换成自己的。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="url" value="jdbc:mysql://localhost:3306/DATABASENAME?useSSL=false" />
<property name="username" value="username" />
<property name="password" value="YOURPASSWORD" />
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
读取数据库信息
CheckLoginFromDB.java用于在数据库中查找指定用户名的密码,并判断提供的密码是否正确。
package com.example.xxxx;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
public class CheckLoginFromDB {
public static boolean login(String userID, String password) {
ApplicationContext context = new ClassPathXmlApplicationContext("myjdbc.xml");
JdbcTemplate jdbcTemplate = (JdbcTemplate) context.getBean("jdbcTemplate");
String sql = "Select password from RegisteredUser where userID = \"" + userID + "\"";
List<String> results = jdbcTemplate.query(sql, new MyStringRowMapper());
if(results.size() != 1) return false;
if(password.equals(results.get(0))){
return true;
}
return false;
}
private static class MyStringRowMapper implements RowMapper<String> {
@Override
public String mapRow(ResultSet rs, int rowNum) throws SQLException {
return rs.getString("password");
}
}
}
控制类
LogInController.java是一个控制类(MVC中的C),将从前端的http的get请求中获取两个参数,并调用CheckLoginFromDB进行用户名、密码检查。之后,如果为真,则返回用户名(字符串)。否则返回"Failed"。
package com.example.xxxx;
import com.example.XXXXXXX.getfromdb.CheckLoginFromDB;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class LogInController {
@GetMapping("/loginget")
public String login(@RequestParam("username") String username, @RequestParam("password") String password){
System.out.println("Calling login, username = " + username);
if(CheckLoginFromDB.login(username, password) == true){
System.out.println("Success Login");
return username;
}
else return "Failed";
}
}
写好前端页面(html – Vue.js)
在src/main/resources/static下建立login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cookie Example</title>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<input type="text" v-model="usernameInput">
<input type="text" v-model="passwordInput">
<button @click="login">LOGIN</button>
<p>Value from Cookie: {{ cookieValue }}</p>
<!-- 假装userID就是session的值,把这个值放到会话cookie中。会话cookie在页面关闭、页面跳转后仍然存在,但是在浏览器关闭后就消失了-->
</div>
<script>
new Vue({
el: '#app',
data() {
return {
session: 'Failed',
usernameInput: '',
passwordInput: '',
cookieValue: 'Not Logged In'
};
},
created() {
this.readFromCookie();
},
methods: {
login() {
axios.get('/loginget', {
params: {
username: this.usernameInput,
password: this.passwordInput
}
})
.then(response => {
this.session = response.data;
if(this.session != 'Failed'){
console.log("login success"); //异步
this.saveToCookie();
this.readFromCookie();
}
})
.catch(error => {
console.error(error);
});
// 异步。没等到request的时候,这里的代码(如果有)就可能已经执行了。
},
saveToCookie() {
// 将session的值存储到会话 Cookie
document.cookie = `session=${encodeURIComponent(this.session)};`;
console.log('Value saved to Cookie:', this.session);
},
readFromCookie() {
// 获取所有的 Cookie
const cookies = document.cookie.split(';');
// 遍历每个 Cookie,找到名为 "savedText" 的 Cookie
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.startsWith('session=')) {
// 提取保存的值并解码
this.cookieValue = decodeURIComponent(cookie.substring('session='.length));
console.log('Value from Cookie:', this.cookieValue);
break;
}
}
}
}
});
</script>
</body>
</html>
如何Demo
在浏览器中输入localhost:8080/login.html即可进入页面。
需要先保证数据库中有对应的数据。如果没有,可以先造一个“桩模块”,永远返回一个true。
public class CheckLoginFromDB {
public static boolean login(String userID, String password) {
return true;
}
}
页面效果:有两个输入框,第一个用来输入用户名,第二个输入密码。之后有一个LOGIN按钮,以及下面一个字符串。未登录时是Not Logged In。
之后点击LOGIN按钮,如果密码正确,下面的字符串会变成用户名。刷新、关闭此页面但浏览器有其他页面的情况下再进入该页面,登录状态都会保持。
TODO
数据库不要明文存储密码
数据库中明文存储密码肯定是不好的。因此,可以将密码Hash过后存在数据库中;之后,每次获取密码之后,都先对字符串进行Hash之后,再去数据库中查询。以下是一个Java对字符串进行MD5运算的样例。需要注意的是,MD5其实在很多场景下,安全性并不够高。有需要的话,应该采用更好的Hash方法。
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class StrMD5 {
public static void main(String[] args){
String password = "123456789a";
try {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] messageDigest = md.digest(password.getBytes());
BigInteger no = new BigInteger(1, messageDigest);
String hashText = no.toString(16);
System.out.println(hashText);
} catch (NoSuchAlgorithmException e){
System.out.println("NoSuchAlgorithmException");
}
return;
}
}
Session ID而不是UserID
在这里为了偷懒,直接使用UserID作为“SessionID”使用了。事实上,后端应该为每个会话建立一个Session ID,并做出对应的维护、管理。