学习平台
环境配置
安装配置MySQL
这里安装配置MySQL以及设置用户名密码就不再赘述了。
配置SpringBoot
在后端项目的
pom.xml
中导入依赖:Spring Boot Starter JDBC
Project Lombok
MySQL Connector/J
mybatis-plus-boot-starter
mybatis-plus-generator
spring-boot-starter-security
jjwt-api
jjwt-impl
jjwt-jackson
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<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-jdbc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
<version>2.7.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.5.3</version>
</dependency>在
application.properties
中添加数据库配置1
2
3
4spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.url=jdbc:mysql://localhost:3306/kob?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=utf-8
spring.datasource.driver-class-name=com.mysql.cj.jdbc.DriverSpringBoot
中的常用模块pojo
层:将数据库中的表对应成Java
中的Class
mapper
层(也叫Dao
层):将pojo
层的class
中的操作,映射成sql
语句service
层:写具体的业务逻辑,组合使用mapper
中的操作controller
层:负责请求转发,接受页面过来的参数,传给Service
处理,接到返回值,再传给页面
配置Spring Security
Spring Security可以实现页面拦截效果,以前的项目大多需要编写一个过滤器,才能实现一些页面的拦截。
实现步骤
导入Spring Security的依赖包
1
2
3
4
5
6<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.7.1</version>
</dependency>实现
service.impl.UserDetailsServiceImpl
类,继承自UserDetailsService
接口,用来接入数据库信息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
37package com.kob.backend.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
/**
* @author xzt
* @version 1.0
*/
public class UserDetailServiceImpl implements UserDetailsService {
private UserMapper userMapper;
/**
* 通过传过来的用户名来返回对应的用户的详细信息
* @param username 用户名
* @return 对应的用户的详细信息
* @throws UsernameNotFoundException
*/
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
User user = userMapper.selectOne(queryWrapper);
if(user == null) throw new RuntimeException("用户不存在");
return new UserDetailsImpl(user); // 需要编写UserDetailImpl来实现UserDetails接口
}
}实现
service.impl.utils.UserDetailImpl
实现UserDetails
接口。用来返回查询到的用户相关信息.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
55package com.kob.backend.service.impl.utils;
import com.kob.backend.pojo.User;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
/**
* @author xzt
* @version 1.0
*/
public class UserDetailsImpl implements UserDetails {
private User user;
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
public String getPassword() {
return user.getPassword();
}
public String getUsername() {
return user.getUsername();
}
public boolean isAccountNonExpired() {
return true;
}
public boolean isAccountNonLocked() {
return true;
}
public boolean isCredentialsNonExpired() {
return true;
}
public boolean isEnabled() {
return true;
}
}
注意:如果数据库中的密码没有加密,并且想要进行登录验证,则需要在数据库中的密码前面加上{noop}
实现密码加密处理
实现config.SecurityConfig
类,用来实现用户密码的加密存储。
1 |
|
使用的是BCryptPasswordEncoder()
进行加密,主要有如下几个方法:
encode(password)
可以将密码明文转为密文,每次加密之后的结果不一样matches(password, encodedPassword)
判断明文和密文是否匹配.
集成JWT验证
在基于令牌的身份验证中,我们使用JWT(JSON Web Tokens)进行身份验证。
传统的登录验证模式:使用session
来进行判断.不能解决跨域判断.
JWT验证模式
实现步骤
导入
Jwt
相关依赖1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-api -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
实现
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
62
63
64
65
66
67package com.kob.backend.utils;
import 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;
/**
* @author xzt
* @version 1.0
* 用来创建、解析jwt
*/
public class JwtUtil {
public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14; // 有效期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
58
59
60
61
62
63
64package com.kob.backend.filter;
import 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;
/**
* @author xzt
* @version 1.0
*/
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
44
45
46
47
48
49
50
51
52
53
54package com.kob.backend.config;
import com.kob.backend.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.WebSecurityConfiguration;
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;
/**
* @author xzt
* @version 1.0
* 实现用户密码的加密存储, 放行登录,注册等接口
*/
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);
}
}
数据库修改
将数据库中的id
域变为自增
- 在数据库中将
id
列变为自增 - 在
pojo.User
类中添加注解:@TableId(type = IdType.AUTO)
1 | package com.kob.backend.pojo; |
后端实现
编写API需要分别在controller
、Service
、Mapper
实现.这里主要编写了注册、登录、以及从jwt token中获取用户信息。
登录功能
实现/user/account/token/
(链接):验证用户名密码,验证成功后返回jwt token
(令牌)
实现
service/user/account/LoginService
1
2
3
4
5
6
7
8
9
10
11
12
13package com.kob.backend.service.user.account;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* @author xzt
* @version 1.0
*/
public interface LoginService {
public Map<String, String> getToken(String username, String password);
}实现
service/impl/user/account/LoginServiceImpl
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
50package com.kob.backend.service.impl.user.account;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.service.user.account.LoginService;
import com.kob.backend.utils.JwtUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Service;
import java.security.PrivateKey;
import java.util.HashMap;
import java.util.Map;
/**
* @author xzt
* @version 1.0
*/
public class LoginServiceImpl implements LoginService {
private AuthenticationManager authenticationManager; // 用来验证用户是否能登录成功
/**
* 实现用户的登录
* @param username 用户名
* @param password 密码
* @return
*/
public Map<String, String> getToken(String username, String password) {
// 将用户名密码进行封装,将密码加密
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(username, password);
Authentication authenticate = authenticationManager.authenticate(authenticationToken); // 登录失败,会自动处理
// 登录成功,取出用户信息
UserDetailsImpl loginUser = (UserDetailsImpl) authenticate.getPrincipal();
User user = loginUser.getUser();
String jwt = JwtUtil.createJWT(user.getId().toString());
Map<String, String> map = new HashMap<>();
map.put("error_msg", "success");
map.put("token", jwt);
return map;
}
}实现
controller/user/account/LoginController
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
26package com.kob.backend.controller.user.account;
import com.kob.backend.service.user.account.LoginService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* @author xzt
* @version 1.0
*/
public class LoginController {
private LoginService loginService;
public Map<String, String> getToken( { Map<String, String> map)
String username = map.get("username");
String password = map.get("password");
return loginService.getToken(username, password);
}
}使用PostMan进行测试结果
获得用户信息功能
实现/user/account/info/
:根据令牌返回用户信息
实现
service/user/account/InfoService
1
2
3
4
5
6
7
8
9
10
11
12
13package com.kob.backend.service.user.account;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* @author xzt
* @version 1.0
*/
public interface InfoService {
public Map<String, String> getInfo();
}实现
Service/user/account/InfoServiceImpl
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
35package com.kob.backend.service.impl.user.account;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.service.user.account.InfoService;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
/**
* @author xzt
* @version 1.0
*/
public class InfoServiceImpl implements InfoService {
public Map<String, String> getInfo() {
UsernamePasswordAuthenticationToken authentication =
(UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
UserDetailsImpl loginUser = (UserDetailsImpl) authentication.getPrincipal();
User user = loginUser.getUser();
Map<String, String> map = new HashMap<>();
map.put("error_msg", "success");
map.put("id", user.getId().toString());
map.put("username", user.getUsername());
map.put("password", user.getPassword());
map.put("photo", user.getPhoto());
return map;
}
}实现
controller/user/account/InfoController
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
26package com.kob.backend.controller.user.account;
import com.kob.backend.service.user.account.InfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* @author xzt
* @version 1.0
*/
public class InfoController {
private InfoService infoService;
public Map<String, String> getInfo(){
return infoService.getInfo();
}
}使用PostMan测试结果
注册功能
实现/user/account/register/
:注册账号
实现
/service/user/account/RegisterService
1
2
3
4
5
6
7
8
9
10
11
12
13package com.kob.backend.service.user.account;
import org.springframework.stereotype.Service;
import java.util.Map;
/**
* @author xzt
* @version 1.0
*/
public interface RegisterService {
public Map<String, String> register(String username, String password, String confirmPassword);
}实现
/service/impl/user/account/RegisterServiceImpl
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77package com.kob.backend.service.impl.user.account;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.user.account.RegisterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.parameters.P;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author xzt
* @version 1.0
*/
public class RegisterServiceImpl implements RegisterService {
private UserMapper userMapper;
private PasswordEncoder passwordEncoder;
public Map<String, String> register(String username, String password, String confirmPassword) {
Map<String, String> map = new HashMap<>();
if(username == null) {
map.put("error_msg", "用户名不能为空");
return map;
}
if(password == null || confirmPassword == null) {
map.put("error_msg", "密码不能为空");
return map;
}
username = username.trim();
if(username.length() == 0) {
map.put("error_msg", "用户名不能为空");
return map;
}
if(password.length() == 0 || confirmPassword.length() == 0) {
map.put("error_msg", "密码不能为空");
return map;
}
if(username.length() > 100) {
map.put("error_msg", "用户名长度不能大于100");
return map;
}
if(password.length() > 100 || confirmPassword.length() > 100) {
map.put("error_msg", "密码长度不能大于100");
return map;
}
if(!password.equals(confirmPassword)){
map.put("error_msg", "两次输入的密码不一致");
return map;
}
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("username", username);
List<User> users = userMapper.selectList(queryWrapper);
if(!users.isEmpty()) {
map.put("error_msg", "用户名已存在");
return map;
}
String encodedPassword = passwordEncoder.encode(password); // 密码加密
String photo = "https://cdn.acwing.com/media/user/profile/photo/72309_lg_e1afa7c633.jpg";
User user = new User(null, username, encodedPassword, photo);
userMapper.insert(user);
map.put("error_msg", "success");
return map;
}
}实现
controller/user/account/RegisterController
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
31package com.kob.backend.controller.user.account;
import com.kob.backend.service.user.account.RegisterService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* @author xzt
* @version 1.0
*/
public class RegisterController {
private RegisterService registerService;
public Map<String, String> register( { Map<String, String> map)
String username = map.get("username");
String password = map.get("password");
String confirmPassword = map.get("confirmPassword");
return registerService.register(username, password, confirmPassword);
}
}使用PostMan进行测试
前端实现
页面创建
这里主要实现登录页面和注册页面的前端样式。
页面创建,需要在
views/user/account/
下创建两个页面UserAccountLoginView.vue
和UserAccountRegisterView.vue
将创建的页面加入路径中
router.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 导入包
import UserAccountLoginView from '@/views/user/account/UserAccountLoginView'
import UserAccountRegisterView from '@/views/user/account/UserAccountRegisterView'
// 在const routes = [] 中加入
const routes = [
{
path: "/user/account/login/",
name: "user_account_login",
component: UserAccountLoginView,
},
{
path: "/user/account/register/",
name: "user_account_register",
component: UserAccountRegisterView,
},
]实现一个Module用来保存登录的用户信息.
store/user.js
。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
27export default {
state: {
id: "",
username: "",
photo: "",
token: "",
is_login: false,
},
getters: {
},
mutations: { // 用来修改数据
updateUser(state, user) {
state.id = user.id;
state.username = user.username;
state.photo = user.photo;
state.is_login = user.is_login;
},
updateToken(state, token) {
state.token = token;
}
},
actions: {
},
modules: {
}
}将实现的userModule加入全局Module中(
store/index.js
),需要导入ModuleUser包,然后加入到modules中1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16import { createStore } from 'vuex'
import ModulUser from '@/store/user'
export default createStore({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
user: ModulUser,
}
})
登录页面实现
需要在
user.js
中实现一个登录函数login()
,向后端提交一个请求并返回登录成功后用户的token
;还需要实现getinfo()
函数,用来将登录的用户信息保存在sotre
的state
中。这个函数需要写在actions
中。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
46actions: { // 实现函数,公有函数,可以被外面调用,然后调用私有函数对变量进行赋值
login(context, data) { // 向后端发送请求,进行登录
$.ajax({
url: "http://localhost:3000/user/account/token/",
type: "post",
data: {
username: data.username,
password: data.password,
},
success(resp) {
if(resp.error_msg === "success") {
context.commit("updateToken", resp.token); // 调用mutations中的函数对store中的变量进行赋值
data.success(resp);
} else {
data.error(resp);
}
},
error(resp) {
data.error(resp);
}
});
},
getinfo(context, data) { // 向后端发送请求,从用户的token来获取用户的详细信息。
$.ajax({
url: "http://localhost:3000/user/account/info/",
type: "get",
headers: {
Authorization: "Bearer " + context.state.token,
},
success(resp) {
if(resp.error_msg === "success") {
context.commit("updateUser", {
...resp, // 将resp的内容解析出来
is_login: true,
});
data.success(resp);
} else {
data.error(resp);
}
},
error(resp) {
data.error(resp);
}
});
},
}实现登录页面
UserAccountLoginView.vue
,进行调用login
函数,进行登录请求。如果登录成功后,再调用getinfo
函数向后端发送请求,来获取用户的详细信息。如果获取成功,则跳转至主页面home
页面。需要注意的是,这里调用
store
中actions
中的函数的调用方法是store.dispatch("用户名", {参数, 回调函数})
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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78<template>
<ContentField>
<div class="row justify-content-md-center">
<div class="col-3">
<form @submit.prevent="login">
<div class="mb-3">
<label for="username" class="form-label">用户名</label>
<input v-model="username" type="text" class="form-control" id="username" aria-describedby="请输入用户名">
</div>
<div class="mb-3">
<label for="password" class="form-label">密码</label>
<input v-model="password" type="password" class="form-control" id="password" aria-describedby="请输入密码">
</div>
<div class = "error_msg">{{ error_msg }}</div>
<button type="submit" class="btn btn-primary">登录</button>
</form>
</div>
</div>
</ContentField>
</template>
<script>
import ContentField from '@/components/ContentField.vue'
import { useStore } from 'vuex';
import { ref } from 'vue';
import router from '@/router/index';
export default {
components: {
ContentField
},
setup() {
const store = useStore();
let username = ref('');
let password = ref('');
let error_msg = ref('');
const login = () => {
error_msg.value = ""; // 清空error_msg
store.dispatch("login", { // 调用actions里的函数
username: username.value,
password: password.value,
success() {
// 登录成功,需要获取用户的详细信息
store.dispatch("getinfo", {
success() {
router.push({name: 'home'});
console.log(store.state.user);
}
});
},
error() {
error_msg.value = "用户名或密码错误";
}
});
}
return {
username,
password,
error_msg,
login,
}
}
}
</script>
<style scoped>
button {
width: 100%;
}
div.error_msg {
color: red;
}
</style>
导航栏优化
需要实现,当用户没有登录时,导航栏右侧显示的是【登录】【注册】两个按钮,当用户登录成功后,显示用户的用户名和一个下拉栏。
实现步骤
- 首先可以使用
v-if="$store.state.user.is_login"
和v-else
通过判断sotre.state.user
中的is_login
变量来判断是否登录。 - 如果已登录显示前部分的
ul
,并需要使用{{ $store.state.user.username }}
方法获取对应的用户名。 - 如果没登录,则实现两个按钮,并通过
:to
实现当点击时跳转至对应的页面。
1 | <ul class="navbar-nav" v-if="$store.state.user.is_login"> |
实现效果
未登录的页面,右上角显示【登录】【注册】按钮
点击登录按钮跳转至登录页面
登录成功后跳转回主页面并在右上角显示用户信息
实现退出功能
实现思想:将用户存在sotre.state.user
中的token及用户信息删除即可。
实现步骤
在
/store/user.js
的mutations
中添加函数logout
,用来将state
中保存的信息清空,并将is_login
设为false
1
2
3
4
5
6
7
8
9mutations: { // 用来给state赋值,相当于set(), 但是是私有的,
logout(state) { // 退出登录
state.id = "";
state.username = "";
state.photo = "";
state.token = "";
state.is_login = false;
},
},在
/store/user.js
的actions
中添加函数logout
,用来调用mutations
中的函数logout
。1
2
3
4
5actions: { //
logout(context) {
context.commit("logout");
},
}修改
NavBar.vue
,编写事件通过store.dispatch("logout")
调用actions
中的函数logout
。并实现点击【退出】按钮时调用logout
事件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<li><a class="dropdown-item" href="#" @click="logout">退出</a></li>
<script>
import { useRoute } from 'vue-router'
import { computed } from 'vue' // 进行实时计算
import { useStore } from 'vuex';
export default {
setup() { // 入口
const store = useStore();
const route = useRoute(); // 取得当前是哪个页面
let route_name = computed(() => route.name);
const logout = () => { // 退出登录事件
store.dispatch("logout");
}
return {
route_name,
logout,
}
}
}
</script>
实现效果
点击退出后,需要重新登录
实现前端页面的授权
这一部分可以使用后端编写过滤器进行实现,这里是从js实现的。
实现前端页面的授权,就是在当没有登录的时候,某些页面不能访问
实现步骤
1.在router/index.js
中每个路径后面加个是否需要授权的字段。requestAuth
为true时,表示该页面需要授权,为false时表示该页面不需要授权。
1 | const routes = [ |
2.然后在router/index.js
中实现router.beforeEach((to, from, next)
函数,这个函数会在每次router之前执行,,每次通过router进入页面之前会调用该函数。有三个参数:
to
: 跳转至哪个页面from
: 从哪个页面跳转过去next
: 页面执行下一步操作
具体实现思路就是当要跳转的页面需要授权并且此时没有登录时,则重定向到user_account_login
页面
1 | // router起作用之前执行,每次通过router进入页面之前会调用该函数, |
实现效果
不管点击除登录注册之外的其他页面都会重定向到登录页面。
注册页面实现
注册页面的实现和登录页面的实现大同小异。直接上代码:
需要注意的是:form
表单上的@submit.prevent=
需要改为下面定义的事件的名字register
。
1 | <template> |
实现效果
登录状态持久化
之前只是吧用户登录后的token
保存在了js
的store.state
中,所以每次刷新页面,token
都会消失,然后需要重新登录。这样显然是不符合业务的。因此需要将token
保存在浏览器的一小块硬盘空间中localStorage
。
实现步骤
1.当登录成功后将token保存在localStorage
中,localStorage.setItem("jwt_token", resp.token);
。在store/user.js
中修改
1 | login(context, data) { // 登录 |
2.当退出时,需要将localStorage
保存的token
删除.在store/user.js
中修改。
1 | logout(context) { |
3.在所有的加载登录页面前判断是否有jwt_token,如果有,则直接更新sotre.state.user.token
并从后端获取用户信息。在views/user/account/UserAccountLoginView.vue
中添加如下代码
1 | <script> |
页面优化
如果已经登录,还是会先跳转至登录页面,再跳转至主页面,虽然很快,但是会闪一下。同样的,右上角的用户名和登录注册之间也会闪一下,为了避免这种问题,需要对页面进行优化。
1.首先,先在store/user.js
中的state
添加一个变量pulling_info
,如果为true则表示正在从后端拉去信息。换句话说,当为false的时候则需要显示登录页面和登录注册按钮。
2.然后,再在store/user.js
的mutation
中添加更新pulling_info
的函数
1 | updatePullingInfo(state, pulling_info) { |
3.修改views/user/account/UserAccountLoginView.vue
,在组件上加上v-if="!$store.state.user.pulling_info"
,如果pulling_info是false就显示登录页面
1 | <ContentField v-if="!$store.state.user.pulling_info"> |
4.修改components/NavBar.vue
,在登录按钮组件中加上v-if="!$store.state.user.pulling_info"
1 | <ul class="navbar-nav" v-else-if="!$store.state.user.pulling_info"> |
实现效果
可以看到,在浏览器中的localStorage
中已经保存了jwt_token
当登录成功后,不管怎么刷新都不会自动退出登录了。