父模块依赖

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
79
80
81
82
83
84
85
<modules>
<module>security-jwt</module>
<module>web-jwt</module>
<module>shiro-jwt</module>
</modules>

<packaging>pom</packaging>

<properties>
<java.version>8</java.version>
<skipTests>true</skipTests>
<spring-boot.version>2.6.13</spring-boot.version>
<mybatis-plus.version>3.5.5</mybatis-plus.version>
<mysql.version>8.0.33</mysql.version>
<lombok.version>1.18.30</lombok.version>
</properties>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>

</dependencyManagement>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

<!-- Hutool 全部模块 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.11</version> <!-- 请根据需要选择版本 -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</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-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

子模块依赖

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
</dependencies>

配置文件yml

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
server:
port: 9001
spring:
application:
name: web-jwt
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://59.110.47.222:3306/work?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC
username: root
password: 196691
jackson:
date-format: yyyy-MM-dd HH:mm:ss
time-zone: GMT+8
defaultPropertyInclusion: non_null
mvc:
throw-exception-if-no-handler-found: true
web:
resources:
add-mappings: false
mybatis-plus:
mapper-locations: classpath:mapper/*.xml
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
call-setters-on-nulls: true

jwt:
secret: zw
expire: 3600000

结果封装

commonResult.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.zou.webjwt.result;

import lombok.Data;

@Data
public class CommonResult<T> {
private int code;
private String msg;

private T data;

CommonResult(int code ,String msg){
this.code = code;
this.msg = msg;
}
}

commonResultEnum.java

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
package cn.zou.webjwt.result;

public enum CommonResultEnum {
SUCCESS(200, "请求成功"),
USER_EXIT_ERROR(513, "用户已存在"),
REGISTER_SUCCESS(200, "注册成功"),
LOGIN_SUCCESS(200, "登录成功"),
//用户相关

PASSWORD_ERROR(511, "用户密码错误"),
USER_OR_PASSWORD_ERROR(511, "用户或者密码失败"),
USER_NOT_EXIST_ERROR(512, "用户不存在"),

LOGIN_ERROR(511, "用户密码错误"),
//参数错误
COMMON_ERROR_MISSING_REQUIRED_PARAMETER(501, "缺少必传参数"),
//全局异常,仅过滤器可用,其他地方不允许使用
COMMON_EXCEPTION(500, "服务器错误"),
//权限不够
ACCESS_DENIED(403, "权限不够"),
//认证失败
AUTHENTICATION_FAILED(401, "用户名或者密码错误"),
//token无效
//TOKEN_INVALID(401, "token无效")
TOKEN_INVALID(401, "token无效");
private final CommonResult<?> result;

CommonResultEnum(int code, String msg) {
result = new CommonResult<>(code, msg);
}

public <T> CommonResult<T> getResult() {
return (CommonResult<T>) result;
}

public <T> CommonResult<T> setData(T data) {
CommonResult<T> commonResult = new CommonResult<>(result.getCode(), result.getMsg());
commonResult.setData(data);
return commonResult;
}

public <T> CommonResult<T> setMsg(String msg) {
return new CommonResult<>(result.getCode(), msg);
}

}

异常封装

自定义异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.zou.webjwt.exception;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;


@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class CustomerException extends RuntimeException{
private String msg;
}

全局异常

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
package cn.zou.webjwt.exception;


import cn.zou.webjwt.result.CommonResult;
import cn.zou.webjwt.result.CommonResultEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;


@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

/**
* 参数校验异常捕获
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public CommonResult<Object> methodArgumentNotValidException(MethodArgumentNotValidException e) {
FieldError fieldError = e.getBindingResult().getFieldError();
if (fieldError == null) return CommonResultEnum.COMMON_ERROR_MISSING_REQUIRED_PARAMETER.getResult();
log.error("[{}]{}", fieldError.getField(), fieldError.getDefaultMessage());
return CommonResultEnum.COMMON_ERROR_MISSING_REQUIRED_PARAMETER.setMsg("[" + fieldError.getField() + "]" + fieldError.getDefaultMessage());
}
/**
* 自定义异常捕获
*/
@ExceptionHandler(CustomerException.class)
public CommonResult<Object> CustomerException(CustomerException e ) {
return CommonResultEnum.COMMON_EXCEPTION.setMsg(e.getMsg());

}
/**
* 所有错误请求方式异常捕获
*/
@ExceptionHandler(Exception.class)
public CommonResult<Object> exception(Exception e) {
log.error("未知异常", e);
return CommonResultEnum.COMMON_EXCEPTION.getResult();
}
}

webConfig

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
package cn.zou.webjwt.config;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.web.method.HandlerTypePredicate;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.PathMatchConfigurer;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Slf4j
@Configuration
@RequiredArgsConstructor
public class WebConfig implements WebMvcConfigurer {

private final JwtInterceptor jwtInterceptor;

//接口统一拼接/api
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer.addPathPrefix("/api", HandlerTypePredicate.forBasePackage("cn.zou.webjwt.controller"));
}

//加上自定义拦截器,哪些请求需要携带token,哪些请求不需要
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor).addPathPatterns("/api/**")
.excludePathPatterns("/api/user/register")
.excludePathPatterns("/api/user/login")
.excludePathPatterns("/api/file/upload")
.excludePathPatterns("/api/file/**")
.order(Ordered.HIGHEST_PRECEDENCE);;
}
//跨域处理
@Override
public void addCorsMappings(CorsRegistry registry) {
log.warn("经过cors");
//添加映射路径
registry.addMapping("/**")
//是否发送Cookie
.allowCredentials(true)
//设置放行哪些原始域 SpringBoot2.4.4下低版本使用.allowedOrigins("*")
.allowedOriginPatterns("*")
//放行哪些请求方式
.allowedMethods(new String[]{"GET", "POST", "PUT", "DELETE"})
//.allowedMethods("*") //或者放行全部
//放行哪些原始请求头部信息
.allowedHeaders("*")
//暴露哪些原始请求头部信息
.exposedHeaders("*");
}

}

JwtInterceptor拦截器

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
package cn.zou.webjwt.config;

import cn.zou.webjwt.exception.CustomerException;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtInterceptor implements HandlerInterceptor {

private final JwtUtil jwtUtil;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
//1.从请求头获取token
String bearerToken = request.getHeader("Authorization");

if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
String token = bearerToken.substring(7);
//3.token过期,校验失败
if(!jwtUtil.validateJwt(token)){
throw new CustomerException("token验证失败");
}
} else {
//2.没有token或者token不对
return false;
}
//用户信息保存threadLocal
String token = bearerToken.substring(7);
Claims claims = jwtUtil.parserJwt(token);
Integer userId = (Integer) claims.get("userId");
UserContext.setUserId(userId);
return true;
}
}

JwtUtil工具类

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
package cn.zou.webjwt.config;

import cn.zou.webjwt.entity.UserEntity;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.HashMap;

@Component
public class JwtUtil {
@Value("${jwt.secret}")
private String secret;

@Value("${jwt.expire}")
private long expire;


// 生成token
public String createJwt(UserEntity user) {
HashMap<String, Object> claims = new HashMap<>();
claims.put("userId", user.getId());
claims.put("username", user.getUsername());
return Jwts.builder()
.setClaims(claims) //载荷
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expire)) //失效时间
.signWith(SignatureAlgorithm.HS256, secret) //加密
.compact();
}

// 解析token
public Claims parserJwt(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
// 校验token
public boolean validateJwt(String token){
try{
Jwts.parser().setSigningKey(secret).parseClaimsJws(token);
return true;
}catch (Exception e){
return false;
}
}
}

UserContext上下文

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
package cn.zou.webjwt.config;

public class UserContext {
private static final ThreadLocal<Integer> tl = new ThreadLocal<>();
/**
* 保存当前登录用户信息到ThreadLocal
* @param userId 用户id
*/
public static void setUserId(Integer userId) {
tl.set(userId);
}

/**
* 获取当前登录用户信息
* @return 用户id
*/
public static Integer getUserId() {
return tl.get();
}

/**
* 移除当前登录用户信息
*/
public static void removeUserId(){
tl.remove();
}
}

controller

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
package cn.zou.webjwt.controller;

import cn.hutool.crypto.digest.BCrypt;
import cn.zou.webjwt.config.JwtUtil;
import cn.zou.webjwt.entity.UserEntity;
import cn.zou.webjwt.mapper.UserMapper;
import cn.zou.webjwt.result.CommonResult;
import cn.zou.webjwt.result.CommonResultEnum;
import cn.zou.webjwt.valid.AddGroup;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping(value="user",name="用户")
public class UserController {
private final UserMapper userMapper;
private final JwtUtil jwtUtil;

@PostMapping(value = "register" ,name = "注册")
public CommonResult<CommonResultEnum> register(@Validated(AddGroup.class) @RequestBody UserEntity userEntity){
//1.查询用户名是否重复
LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<UserEntity>()
.eq(UserEntity::getUsername,userEntity.getUsername());
UserEntity user = userMapper.selectOne(wrapper);
if(user != null){
return CommonResultEnum.USER_EXIT_ERROR.getResult();
}
//2.密码加密
userEntity.setPassword(BCrypt.hashpw(userEntity.getPassword()));
//3.注册用户
userMapper.insert(userEntity);

return CommonResultEnum.REGISTER_SUCCESS.getResult();

}

@PostMapping(value = "login" ,name = "登录")
public CommonResult<String> login(@Validated(AddGroup.class) @RequestBody UserEntity userEntity){
//1.查询用户是否存在
LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<UserEntity>()
.eq(UserEntity::getUsername, userEntity.getUsername());
UserEntity user = userMapper.selectOne(wrapper);
if(user == null){
return CommonResultEnum.USER_NOT_EXIST_ERROR.getResult();
}

//2.查询密码是否正确
if(!BCrypt.checkpw(userEntity.getPassword(),user.getPassword())){
return CommonResultEnum.PASSWORD_ERROR.getResult();
}
//3.生成token
String jwt = jwtUtil.createJwt(user);
String bearerToken = "Bearer "+jwt;
return CommonResultEnum.LOGIN_SUCCESS.setData(bearerToken);
}

@PostMapping(value = "get" ,name = "用户列表")
public CommonResult<List<UserEntity>> get(){
LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<UserEntity>()
.select(UserEntity::getId, UserEntity::getUsername);
List<UserEntity> users = userMapper.selectList(wrapper);
return CommonResultEnum.SUCCESS.setData(users);
}
}