父依赖

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
<modules>
<module>security-jwt</module>
<module>web-jwt</module>
<module>shiro-jwt</module>
<module>satoken-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>

子依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<dependencies>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.39.0</version>
</dependency>
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<version>1.39.0</version>
</dependency>
<!-- Redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

配置文件

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
server:
port: 9004
spring:
application:
name: satoken
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


############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: Authorization
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: 3600
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: false
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# 是否输出操作日志
is-log: true
# jwt密钥
jwt-secret-key: 4ThZZfDmT93QDLeoUeanjAfZR12345678
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# token前缀
# token-prefix: Bearer
# 是否尝试从 cookie 里读取 Token,此值为 false 后,StpUtil.login(id) 登录时也不会再往前端注入Cookie
# 注意:当使用 redis 作为缓存数据时要将 此关闭,否则不会将redis作为缓存,而是继续使用cookie
# is-read-cookie: false

satoken配置

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
package cn.zou.satokenjwt.utils;


import cn.dev33.satoken.interceptor.SaInterceptor;
import cn.dev33.satoken.jwt.StpLogicJwtForSimple;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class SaTokenConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。
registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
.addPathPatterns("/**")
.excludePathPatterns("/user/login")
.excludePathPatterns("/user/register");
}

@Bean
public StpLogic getStpLogicJwt() {
// Sa-Token 整合 jwt (Simple 简单模式)
return new StpLogicJwtForSimple();
}
/**
* 权限接口实现(使用bean注入方便用户替换)
*/
@Bean
public StpInterface stpInterface(StpInterfaceImpl stpInterfaceImpl) {
return stpInterfaceImpl;
}
}

satoken拦截器权限

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
package cn.zou.satokenjwt.utils;

import cn.dev33.satoken.stp.StpInterface;
import cn.zou.satokenjwt.entity.RoleEntity;
import cn.zou.satokenjwt.entity.UserEntity;
import cn.zou.satokenjwt.mapper.UserMapper;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;


@Component
@RequiredArgsConstructor
public class StpInterfaceImpl implements StpInterface {
private final UserMapper userMapper;
@Override
public List<String> getPermissionList(Object loginId, String loginType) {

UserEntity userEntity = userMapper.selectOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getId, loginId));

// 权限
List<String> permissions = userMapper.getPermByUsername(userEntity.getUsername());
return permissions;
}

@Override
public List<String> getRoleList(Object loginId, String loginType) {
//角色
UserEntity userEntity = userMapper.selectOne(new LambdaQueryWrapper<UserEntity>().eq(UserEntity::getId, loginId));
List<RoleEntity> roles = userMapper.getRoleByUsername(userEntity.getUsername());
ArrayList<String> list = new ArrayList<>();
roles.forEach(roleEntity -> list.add(roleEntity.getRoleName()));
return list;
}
}

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package cn.zou.satokenjwt.controller;

import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.zou.satokenjwt.entity.UserEntity;
import cn.zou.satokenjwt.mapper.UserMapper;
import cn.zou.satokenjwt.result.CommonResult;
import cn.zou.satokenjwt.result.CommonResultEnum;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.web.bind.annotation.*;

import java.util.List;


@Slf4j
@RestController
@RequiredArgsConstructor
@RequestMapping(value = "user", name = "用户")
public class UserController {

private final UserMapper userMapper;

@PostMapping(value = "login", name = "登录")
public CommonResult<Object> login(@RequestBody UserEntity user) {

//判断用户是否存在
LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserEntity::getUsername, user.getUsername());

UserEntity userEntity = userMapper.selectOne(wrapper);
if (userEntity == null) {
return CommonResultEnum.USER_NOT_EXIST_ERROR.getResult();
}
//比对密码
if(!userEntity.getPassword().equals(user.getPassword())) {
return CommonResultEnum.PASSWORD_ERROR.getResult();
}
//生成token
StpUtil.login(userEntity.getId());
String tokenValue = StpUtil.getTokenValue();
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
System.out.println(tokenValue);
System.out.println(tokenInfo);
return CommonResultEnum.SUCCESS.setData(tokenInfo);
}
@PostMapping(value = "register", name = "注册")
public CommonResult<String> register(@RequestBody UserEntity user) {
//判断用户名是否重复
LambdaQueryWrapper<UserEntity> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(UserEntity::getUsername, user.getUsername());
UserEntity userEntity = userMapper.selectOne(wrapper);
if (userEntity != null) {
return CommonResultEnum.USER_EXIT_ERROR.getResult();
}
userMapper.insert(user);
return CommonResultEnum.REGISTER_SUCCESS.getResult();
}

@PostMapping(value = "getAll", name = "查询所有用户")
public CommonResult<List<UserEntity>> getAll() {
//查询所有用户
List<UserEntity> users = userMapper.selectList(null);
return CommonResultEnum.SUCCESS.setData(users);
}
@PostMapping("/admin")
@SaCheckRole("管理员")
public String admin() {
return "管理员权限!";
}

@PostMapping("/common")
@SaCheckRole("普通用户")
public String common() {
return "普通用户权限";
}

@PostMapping("/query")
@SaCheckPermission("query") // 只有拥有'user:read'权限的用户才能访问
public String query() {
return "query success!";
}

@PostMapping("/add")
@SaCheckPermission("add")
public String add() {
return "add success!";
}

@PostMapping("/delete")
@SaCheckPermission("delete")
public String delete() {
return "delete success!";
}

@PostMapping("/update")
@SaCheckPermission("update")
public String update() {
return "update success!";
}
}

返回结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package cn.zou.satokenjwt.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;
}

}

枚举

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

public enum CommonResultEnum {

USER_NOT_EXIST_ERROR(512, "用户不存在"),
PASSWORD_ERROR(511, "用户密码错误"),

SUCCESS(200, "请求成功"),
REGISTER_SUCCESS(200, "注册成功"),
ONT_TOKEN(403, "未读取有效token"),

USER_OR_PASSWORD_ERROR(511, "用户或者密码失败"),
USER_EXIT_ERROR(513, "用户已存在"),
LOGIN_ERROR(511, "用户密码错误"),
//参数错误
COMMON_ERROR_MISSING_REQUIRED_PARAMETER(501, "缺少必传参数"),
//全局异常,仅过滤器可用,其他地方不允许使用
COMMON_EXCEPTION(500, "服务器错误"),
//权限不够
ACCESS_DENIED(403, "菜单权限不够"),
ROLE_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(), result.getMsg());
}

}

全局异常

自定义异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package cn.zou.satokenjwt.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
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
package cn.zou.satokenjwt.exception;


import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.zou.satokenjwt.result.CommonResult;
import cn.zou.satokenjwt.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;

import java.nio.file.AccessDeniedException;


@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());
}
/**
* 未读取有效token
*/
@ExceptionHandler(NotLoginException.class)
public CommonResult<CommonResultEnum> NotLoginException(NotLoginException e) {
log.error("未读取有效token");
return CommonResultEnum.ONT_TOKEN.getResult();
}
/**
* 无菜单权限异常
*/
@ExceptionHandler(NotPermissionException.class)
public CommonResult<CommonResultEnum> NotPermissionException(NotPermissionException e) {
log.error("无菜单权限");
return CommonResultEnum.ACCESS_DENIED.getResult();
}
/**
* 无角色权限异常
*/
@ExceptionHandler(NotRoleException.class)
public CommonResult<CommonResultEnum> NotRoleException(NotRoleException e) {
log.error("无角色权限");
return CommonResultEnum.ROLE_DENIED.getResult();
}
/**
* 所有错误请求方式异常捕获
*/
@ExceptionHandler(Exception.class)
public CommonResult<Object> exception(Exception e) {
log.error("未知异常", e);
return CommonResultEnum.COMMON_EXCEPTION.getResult();
}
/**
* 自定义异常捕获
*/
@ExceptionHandler(CustomerException.class)
public CommonResult<Object> CustomerException(CustomerException e ) {
return CommonResultEnum.COMMON_EXCEPTION.setMsg(e.getMsg());

}
}

mapper

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
package cn.zou.satokenjwt.mapper;


import cn.zou.satokenjwt.entity.RoleEntity;
import cn.zou.satokenjwt.entity.UserEntity;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface UserMapper extends BaseMapper<UserEntity> {


@Select("select r.role_name from my_user as u " +
"LEFT JOIN user_role as ur on u.id =ur.user_id " +
"left join my_role as r on r.id = ur.role_id " +
"where u.username = #{username}")
List<RoleEntity> getRoleByUsername(String username);

@Select("select p.perm_name from my_user as u " +
"LEFT JOIN user_role as ur on u.id = ur.user_id " +
"left join role_perm as rp on ur.role_id = rp.role_id " +
"left join my_perm as p on p.id = rp.perm_id " +
"where u.username = #{username}")
List<String> getPermByUsername(String username);
}