Springboot-配置MySQL和注册登录模块
MysQL
配置环境变量
将C:\Program Files\MySQL\MySQL Server 8.0\bin
(如果安装到了其他目录,填写相应目录的地址即可)添加到环境变量PATH
中,这样就可以在任意目录的终端中执行mysql
命令了。
MySQL服务
(默认开机自动启动,如果想手动操作,可以参考如下命令)
- 关闭:
net stop mysql80
- 启动:
net start mysql80
MySQL常用操作
连接用户名为root
,密码为123456
的数据库服务:mysql -uroot -p123456
show databases;
:列出所有数据库create database kob
;:创建数据库drop database kob;
:删除数据库use kob;
:使用数据库kobshow tables;
:列出当前数据库的所有表create table user(id int, username varchar(100))
:创建名称为user的表,表中包含id和username两个属性。drop table user;
:删除表insert into user values(1, 'hong');
:在表中插入数据select * from user;
:查询表中所有数据delete from user where id = 2;
:删除某行数据
IDEA关联


此时MySQL连接成功,并可以在IDEA中通过图形界面修改
配置SpringBoot
添加依赖
在Maven仓库地址中搜索相关依赖
然后在pom.xml
文件中添加依赖:
Spring Boot Starter JDBC
Project Lombok
自动资源管理、自动生成 getter、setter、equals、hashCode 和 toString 等
MySQL Connector/J
mybatis-plus-boot-starter
mybatis-plus-generator
spring-boot-starter-security
(暂时不装)jjwt-api
(暂时不装)
以为例Spring Boot Starter JDBC
为例,复制依赖代码
1 | <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jdbc --> |
复制到pom.xml
中的dependencies
模块下:
最终添加的dependency
如下
1 | <!--Spring Boot Starter JDBC--> |
重新加载所有maven
项目
加载完成之后,就能看到依赖项
数据库配置
在application.properties
中添加数据库配置:
1 | spring.datasource.username=root |
此时运行项目即可
SpringBoot中的常用模块
pojo
层:将数据库中的表对应成Java
中的Class
mapper
层(也叫Dao
层):将pojo
层的class
中的操作(CRUD),映射成sql
语句Create => insert
Retrieve=> select
service
层:写具体的业务逻辑,组合使用mapper
中的操作controller
层:负责请求转发,接受前端页面过来的参数,传给相应Service
处理,接到返回值,再传给页面
依次实现这些模块
pojo层
首先创建com/kob/backend/pojo
包,然后在其中创建User.java
负责实现与User
表想对应的User
类
这里的关键是三个注解
从编译后的class
文件中,也能证明这一点。
添加注解之前:
添加注解之后
mapper层
首先创建com/kob/backend/mapper
包,然后在其中创建UserMapper.java
添加@Mapper
注解并且继承mybatisplus
中的BaseMapper
,传入<User>
目的是将pojo
层的User
中的操作(CRUD),映射成sql
语句
controller层
为方便调试,在当前阶段,先讲service
与controller
写在一块(后期具体业务需要分开,controller
调用sevice
中的接口)
创建com/kob/backend/controller
包
然后针对User
表创建/user/UserController.java
添加@RestController
注解
1 |
在这里我们可以实现与User
表相关的业务逻辑(正常应该在service
层 这里为了方便调试 暂时写在一块了)
@RequestMapping
将所有请求类型全部接收过来
- 如果只处理
post
类型的请求@PostMapping
- 如果只处理
get
类型的请求@GetMapping
1)实现查询当前所有用户
在controller
中如何调用数据库的接口
首先引入刚刚定义的UserMapper接口
1 |
|
UserMapper接口由mybatisplus来实现
1 |
|
继承了mybatisplus中的BaseMapper
可以通过Mybatis-Plus官网来查看所有API的具体用法
如果我们希望查询所有用户,就要借助selectList
这个api
1 |
|
结果运行如下:
与实际数据表中结果一致
2)指定ID查询用户
使用selectById
api根据ID来查询用户
1 |
|
结果如下
同时也可以借助Mybatis-Plus
中的条件构造器,来构造一些自定义的条件,通过对条件进行筛选的方式来过滤出结果。
1 |
|
结果与selectById
一致。
这里代码中的返回值中的User
就是所得到的符合条件pojo
中User
类的对象。
实际上就是返回数据表中的一行数据。
3)区间查询
同样可以通过ge
和le
来进行区间查询
1 |
|
可以无限追加条件,通过
.condition
的方式
例如查询id>=1
&& id <=4
的用户
4)插入数据
1 |
|
5)删除记录
1 |
|
集成Spring Security
借助Spring Security
来实现登录认证,再没有判断登录认证的情况下,访问任意界面,均无法访问,并弹出登录界面
在pom.xml
文件中添加依赖:
spring-boot-starter-security
1
2
3
4
5<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.7.1</version>
</dependency>

此时再进行访问任意界面,均无法访问,并弹出登录界面
该页面是Spring Security
是自己实现的。
login
默认用户名为user
动态生成密码如下:
输入之后就能成功访问
并且在之后的访问过程中,均不需要重新登录。
授权验证原理
这涉及到授权验证方式:session
(之后会使用jjwt
,session
是传统的授权与验证方式)
1)登录阶段
登录成功以后,后端生成SessionID
,将其同时保存在后端数据库与浏览器的Cookie
中
2)在每次向后端Springboot
发送请求的同时,会将SessionID
从Cookie
中取出同样传送给后端Springboot
。然后Springboot
通过向数据库查询判断当前SessionID
是否存在以及是否过期,如果存在,将有关SessionID
的信息(包括对应的用户名、过期时间)从数据库中取出,判断是否过期,如果当前SessionID
没有过期,表示登录成功。如果发现SessionID
过期或者根本不存在,则返回给用户登录页重新登录。
例如:登录成功后会在Cookie
中存入Session
信息
每次向后端请求时,都会取出
如果对Session信息进行篡改或者删除
再次请求时,由于后端在数据库中找不到对应的SessionID
,返回登录页面
注意:SessionID
相当于给浏览器颁发的一张临时身份证,之后浏览器在执行业务操作的时候,都要随身携带这个身份证。
logout
此外,Spring Security
还自己实现了logout
界面
可以理解为自己是新的controller
退出之后继续回到了最初的界面
修改Spring Security
此时登录还只是通过Spring Security提供的默认用户名和随机生成密码,如何通过数据库判断一个其中存储的用户是否登录成功呢?需要修改Spring Security
UserDetailsServiceImpl
实现service.impl.UserDetailsServiceImpl
类,继承自UserDetailsService
接口,用来接入数据库信息
该UserDetailsService
api接收一个username,通过该username返回包含用户的用户名和密码的UserDetails接口的实现类的对象(简称UserDetails的实现对象)
首先考虑根据username去数据库中查询对应的user
在处理User是否存在之前,先创建一个UserDetails的实现类:
UserDetailsImpl
- 创建
service.impl.utils.UserDetailsImpl
实现如下:
1 | public class UserDetailsImpl implements UserDetails { |
如果不加显示构造函数,也可以下面这样,自动添加相关的属性和构造函数。
现在继续回到service.impl.UserDetailsServiceImpl
,填上最后这段代码
此时就可以实现根据数据库中User信息来进行登录,即根据用户的用户名,去查询用户信息,再根据输入的密码判断是否匹配,而不是使用默认的用户名和密码。
不过此时登录会报错
如果直接用明文密码来存储,需要在数据库中加上{noop}标记,代表不需要加密直接判断,就不用到PasswordEncoder
此时再来登录
就成功了

并且可以访问所有的API
密码的加密存储
上述可以看出,如果密码使用明文,必须声明!
如果我们需要对密码进行加密,实现config.SecurityConfig
类,用来实现用户密码的加密存储
1 |
|
BCryptPasswordEncoder的测试
现在需要将密码改成对应的加密形式,否则的话没法验证通过
可以借助BCryptPasswordEncoder
的encode()
方法更新之前数据库中的密码
此时再次登录,就可以成功。
同时,要在添加阶段就直接存储加密阶段的代码
结果如下:
集成jwt验证
原理
默认情况下,使用session进行身份验证。但对于前后端分离的情况,可能会出现跨域问题,使用session会变得不方便,用jwt验证会更加容易。
对于给定的url,可以分为两大类:
- 公开可以访问
- login页面
- 需要授权才能访问
先来看下传统使用session进行身份验证的方式:

(1)用户进行登录时,登录成功以后,后端生成SessionID
,将其同时保存在后端数据库或者内存,和浏览器的Cookie
中。(后端同样保存了SessionID
与用户信息userInfo
的映射关系)
(2)在每次向后端Springboot
发送请求的同时,会将SessionID
从Cookie
中取出同样传送给后端。
(3)对于需要授权访问的url,Springboot
通过向数据库or内存查询判断当前SessionID
是否存在以及是否过期,如果存在,将有关SessionID
的信息(包括对应的用户名、过期时间)从数据库中取出,判断是否过期,如果发现SessionID
过期或者根本不存在,则返回给用户登录页重新登录。
(4)若当前SessionID
没有过期,则通过SessionID
与用户信息userInfo
的映射关系,将对应的User
提取到上下文中(在Contoller中就可以通过一些API来拿到User
),成功进行授权页面的访问。
为了解决跨域的情景,使用Jwt验证。
优势如下:
容易实现跨域
不需要在服务器端存储
对于有多个服务器的情况,就可以实现用一个令牌来登录多个服务器
配置
1)在pom.xml文件配置相关依赖
jjwt-api
jjwt-impl
jjwt-jackson
2)添加相关类
实现
utils.JwtUtil
类,为jwt
工具类,用来创建、解析jwt token
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
public class JwtUtil {
// 有效期14天
public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14;
//秘钥
public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";
public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid)
.setSubject(subject)
.setIssuer("sg")
.setIssuedAt(now)
.signWith(signatureAlgorithm, secretKey)
.setExpiration(expDate);
}
public static SecretKey generalKey() {
byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
}
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(jwt)
.getBody();
}
}实现
config.filter.JwtAuthenticationTokenFilter
类,用来验证jwt token
是否合法有效,如果验证成功,则将User
信息注入上下文中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private UserMapper userMapper;
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
token = token.substring(7);
String userid;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
throw new RuntimeException(e);
}
User user = userMapper.selectById(Integer.parseInt(userid));
if (user == null) {
throw new RuntimeException("用户名未登录");
}
UserDetailsImpl loginUser = new UserDetailsImpl(user);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
}配置
config.SecurityConfig
类,放行登录、注册等接口1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44import com.kob.backend.config.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/user/account/token/", "/user/account/register/").permitAll()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}也就是对于登录和注册的URL,变为公开可以访问:(未来需要放行其他URL,也在这个地方继续添加即可)
后端API实现
在具体的API实现之前,先来更新下数据库。将数据库中的id域变为自增
在数据库中将id列变为自增
在
pojo.User
类中添加注解:@TableId(type = IdType.AUTO)
下面是具体的API编写。在Springboot中实现API一共需要实现三个地方:
controller
service
service.impl
sevice
在sevice
中写接口
RegisterService
LoginService
InfoService
service.impl
在service.impl
实现接口
LoginServiceImpl
1 |
|
InfoServiceImpl
1 |
|
RegisterServiceImpl
主要是加入了一些规则判断,若不符合规则,返回相应的错误信息,若符合规则,则在数据库中添加一个新的User并且将成功的信息返回。
1 |
|
controller
LoginController
1 |
|
注意,对于登录而言,一般是post
请求,如果是get
请求,会将用户名和密码参数放在url
链接中,明文传输,而post
请求看不到明文,所以使用@PostMapping
注解;
将post
请求中的参数,放在Map
中,需要用到注解@RequestParam
如何调试这段代码的功能呢?
由于是Post请求,所以没法从浏览器输入URL的方式进行访问,因为浏览器中对应的Get请求,不能在浏览器中调试。有两种调试方法
1)前端框架中调试
2)使用postman(更方便 推荐)
对于返回的token
1 | "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI5OWE5ZThjZDNlZDI0OTI3YTZiMWMzNDk5MDU1ZDljMyIsInN1YiI6IjEiLCJpc3MiOiJzZyIsImlhdCI6MTY1ODk3MjYzMCwiZXhwIjoxNjYwMTgyMjMwfQ.iioSQLuAyzpYLPzTgGuhs1ODb6mYIzpqnz6K8VQqbWc" |
使用https://jwt.io/中提供的工具进行解析,可以看出对应的userID
InfoController
1 |
|
一般而言,获取信息对应的get,修改,删除和添加对应的是post
调试:
使用哪个用户的token,就可以生成哪个用户的信息。至此,我们实现了用户的登录和授权认证。
RegisterController
1 |
|
调试如下:
1)失败案例
2)成功案例
到此为止,后端登录和注册模块的API就全部实现。
登录界面
配置路由
新增两个页面:
src\views\user\account\UserAccountLoginView.vue
src\views\user\account\UserAccountRegisterView.vue
并在src\router\index.js
中为其注册路由
验证:
登录基本样式
样式改造:
借助bootstrap
中的Grid system
,一个用户布局的工具。
Grid
将每行分为 12 个模板列,允许您创建跨越任意数量列的不同元素组合。列类指示要跨越的模板列的数量(例如,col-4
跨越四个)我们的登录窗口设置为跨越三个col-3
,并设置为居中。
效果如下:
借助bootstrap
中的Form controls
,负责处理表单样式。
此时还缺少一个登录按钮,用到bootstrap
提供的Buttons
1 | <template> |
全局信息vuex
对于每个页面而言,都需要存储当前登录的用户信息,也就是需要将用户信息设置为全局存储。
需要用到vue
的其中一个特性vuex
创建src\store\user.js
将用户信息(id,username,is_login)
以及负责授权用的jwt-token
保存在该文件中

并导入到全局module
中
然后在action
中编写辅助函数,现在需要发生login
请求并且获取token

登录实现
现在在登录的主页面views\user\account\UserAccountLoginView.vue
负责实现登录功能。
UserAccountLoginView.vue
script
部分:
其中:
- 借助
ref
定义变量 router.push({name:'home'})
表示如果登录成功 跳转到name为home的页面中
template
部分
其中
@submit.prevent="login"
表示submit
时触发login
函数 并阻止默认行为v-model
将输入的值,与script
部分使用ref
定义的变量绑定{{ error_message }}
表示直接取出变量error_message
的值
此时,实现了成功登录
如果输出错误:
如果输出正确:
动态显示信息
在登录完成之后,我们希望在前端页面中,动态显示出用户信息(也就是用户名、头像、ID)。因此需要在登录成功之后,再次向后端发送请求来获取当前用户的用户信息。
因此需要在src\store\user.js
中增加辅助函数

并更新UserAccountLoginView.vue
如下:
也就是登录成功之后,进行获取信息,如果获取成功,就在控制台输出相应的用户信息。
现在如何显示到导航栏上去呢?
需要在components\NavBar.vue
中修改下面代码:
此时登录成功:
这里有一个bug,登录成功之后刷新页面变为未登录。此时的Jwt-token存放在浏览器的内存中,会因刷新而清空,需要将Jwt-token存放在浏览器的local Storage中,即使用户关闭或者刷新浏览器,都不会退出登录状态。
这个后面会给出解决方法,暂时按下不表。
未登录:
退出logout
用户登录之后如何退出呢?
对于整个认证机制,Jwt-token完全存在于用户本地。
Jwt-token中除了存放user Id之外,还存放一个过期时间,服务器验证的时候可以判断是否过期。
所以用户退出的逻辑很简单,那就是用户自己删掉Jwt-token,这件事前端就可以完成。
同样是在src\store\user.js
中写入相关辅助函数
然后在src\components\NavBar.vue
中,添加一个退出的事件。

在点击”退出”时触发logout
函数,同时跳转到登录页面即可。
前端页面授权
实现前端页面授权,也就是判断jwt-token不合法的时候,自动退出到登录界面。
可以在src\router\index.js
实现
在routes
中,为某一个route
新增一个判断是否需要授权的信息,true
表示需要授权才能访问。
引入store
,来判断用户是否登录
1 | import store from "../store/index" |
同时增加beforeEach
函数,则进入某个页面之前,执行该函数。
这样就可以实现,在未登录时,访问to.meta.requestAuth
为true
的页面,会自动重定向到登录页面。登录之后,才能正常访问。
注册页面
views\user\account\UserAccountRegisterView.vue
,实现的逻辑与登录页面一致。


注意:
注册阶段的ajax
请求直接放在了UserAccountRegisterView.vue
而登录阶段的ajax
请求则是:
之所以会将操作放到user.js
中,原因是需要修改store.state
值
区分一个概念:store.state
和store.state.user
打印store.state
1 | { |
打印store.state.user
1 | { |
登录状态持久化
对于前面提到的bug,也就是登录成功之后刷新页面变为未登录。此时的Jwt-token存放在浏览器的内存中,具体来说是存储在store.state.user
中的token
变量中,会因刷新而清空,需要将Jwt-token存放在浏览器的local Storage
中,即使用户关闭或者刷新浏览器,都不会退出登录状态。
1、在登录成功时,存储到local Storage
中,在退出时,从local Storage
中删除
store\user.js

2、每次刷新页面时,变为未登录状态,经过router\index.js
写入的逻辑,会重定向到登录页面
然后我们添加相关的判断逻辑,在每次刷新页面时进入到登录页面之后,先判断local Storage
是否有jwt-token
,如果存在,将jwt-token
取出验证是否有效,如果有效,则不需要重新登录,跳转到首页(home)即可
views\user\account\UserAccountLoginView.vue
此时可以初步实现效果。只不过还有瑕疵。也就是刷新之后:由于经过—–>登录页面—–>首页。因此,登录页面会一闪而过,有一种”白影”效果。可以先让登录页面默认不展示,在判断结束之后再展示。
3、处理“白影”
新增一个变量pulling_info
表示当前是否正在从服务器获取信息中,如果正在拉取信息,则不展示登录页面
pulling_info
为ture
表示正在拉取信息,为false
表示已经拉取完毕
当拉取信息结束之后,再显示对应的页面
当判断完jwt-token
是否存在和有效后,更新pulling_info
为false
,表示拉取结束。
注意:当验证有效时,先进行的跳转页面,再进行的更新pulling_info
,所以看不到“白影”
至此,登录和注册模块完成!