目录
4.0.1:AuthorizeRequestsCustomizer(最重要的就是可以指定每个服务都可以自定义security规则,最终由总配置文件合并加载)
4.0.2:SecurityProperties(指定配置文件的值)
3. 自定义 BpmTaskCandidateStrategy 策略
2.1 并行 BpmParallelMultiInstanceBehavior
2.2 顺序 BpmSequentialMultiInstanceBehavior
1.1 BpmTaskAssignStartUserExpression
1.2 BpmTaskAssignLeaderExpression
1:项目简介,功能列表,技术选型
2:从零开始(基于Cloud微服务版本)
2.4:项目结构讲解
3:业务模块
3.1:依赖管理
因为所有的依赖管理都是基于yudao-dependencies的,而整个工程的父模块依赖了dependencies,所以其他模块都会通过父模块间接引入depencies的依赖。
3.2:框架封装管理(包含技术组件和业务组件两大类)
3.3:业务模块封装
3.4:开发一个新功能(基于代码生成器)
先启动前端。暂时是哦那个的node版本是18的,16的不支持。
4:技术模块
4.0:spring security
4.0.1:AuthorizeRequestsCustomizer(最重要的就是可以指定每个服务都可以自定义security规则,最终由总配置文件合并加载)
package cn.iocoder.yudao.framework.security.config;
import cn.iocoder.yudao.framework.web.config.WebProperties;
import jakarta.annotation.Resource;
import org.springframework.core.Ordered;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
/**
* 自定义的 URL 的安全配置
* 目的:每个 Maven Module 可以自定义规则!
*
* @author 芋道源码
*/
public abstract class AuthorizeRequestsCustomizer
implements Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry>, Ordered {
@Resource
private WebProperties webProperties;
protected String buildAdminApi(String url) {
return webProperties.getAdminApi().getPrefix() + url;
}
protected String buildAppApi(String url) {
return webProperties.getAppApi().getPrefix() + url;
}
@Override
public int getOrder() {
return 0;
}
}
这个类实现了Customizer接口,泛型参数是AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry,说明它用于定制Spring Security的授权请求配置。Customizer在Spring Security中用来提供自定义配置的方式,通常通过实现接口中的方法来实现具体的配置逻辑。
这个类还实现了Ordered接口,所以必须实现getOrder方法,这里返回0。这可能影响多个Customizer的执行顺序,但具体要看Spring Security的处理方式。
4.0.2:SecurityProperties(指定配置文件的值)
这个文件可以在yml中配置,也可以使用其中默认的值。其中yml中指定了值的话,优先级是要高于文件中的默认值。
package cn.iocoder.yudao.framework.security.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.validation.annotation.Validated;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import java.util.Collections;
import java.util.List;
@ConfigurationProperties(prefix = "yudao.security")
@Validated
@Data
public class SecurityProperties {
/**
* HTTP 请求时,访问令牌的请求 Header
*/
@NotEmpty(message = "Token Header 不能为空")
private String tokenHeader = "Authorization";
/**
* HTTP 请求时,访问令牌的请求参数
*
* 初始目的:解决 WebSocket 无法通过 header 传参,只能通过 token 参数拼接
*/
@NotEmpty(message = "Token Parameter 不能为空")
private String tokenParameter = "token";
/**
* mock 模式的开关
*/
@NotNull(message = "mock 模式的开关不能为空")
private Boolean mockEnable = false;
/**
* mock 模式的密钥
* 一定要配置密钥,保证安全性
*/
@NotEmpty(message = "mock 模式的密钥不能为空") // 这里设置了一个默认值,因为实际上只有 mockEnable 为 true 时才需要配置。
private String mockSecret = "test";
/**
* 免登录的 URL 列表
*/
private List<String> permitAllUrls = Collections.emptyList();
/**
* PasswordEncoder 加密复杂度,越高开销越大
*/
private Integer passwordEncoderLength = 4;
}
4.0.3:YudaoSecurityAutoConfiguration这个类有以下作用:
自动配置 Spring Security 组件:通过
@AutoConfiguration
注解,该类会在 Spring Boot 启动时自动加载,配置 Spring Security 所需的核心组件。优先级控制:通过
@AutoConfigureOrder(-1)
注解,确保该配置类在 Spring Security 默认配置之前加载,避免冲突。(必须在YudaoWebSecurityConfigurerAdapter
之前被加载,否则启动会报错)自定义安全配置:提供自定义的安全组件(如认证过滤器、加密器、异常处理器等),以满足项目的特定需求。
核心配置项:
以下是该类中定义的核心配置项及其作用:
(1)认证失败处理类
(2)权限不足处理类
(3)密码加密器
(4)Token 认证过滤器
(5)安全框架服务
(6)Security 上下文策略
package cn.iocoder.yudao.framework.security.config;
import cn.iocoder.yudao.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;
import cn.iocoder.yudao.framework.security.core.filter.TokenAuthenticationFilter;
import cn.iocoder.yudao.framework.security.core.handler.AccessDeniedHandlerImpl;
import cn.iocoder.yudao.framework.security.core.handler.AuthenticationEntryPointImpl;
import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService;
import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkServiceImpl;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.module.system.api.oauth2.OAuth2TokenApi;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.config.MethodInvokingFactoryBean;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
/**
* Spring Security 自动配置类,主要用于相关组件的配置
*
* 注意,不能和 {@link YudaoWebSecurityConfigurerAdapter} 用一个,原因是会导致初始化报错。
* 参见 https://stackoverflow.com/questions/53847050/spring-boot-delegatebuilder-cannot-be-null-on-autowiring-authenticationmanager 文档。
*
* @author 芋道源码
*/
@AutoConfiguration
@AutoConfigureOrder(-1) // 目的:先于 Spring Security 自动配置,避免一键改包后,org.* 基础包无法生效
@EnableConfigurationProperties(SecurityProperties.class)
public class YudaoSecurityAutoConfiguration {
@Resource
private SecurityProperties securityProperties;
/**
* 认证失败处理类 Bean
*/
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
return new AuthenticationEntryPointImpl();
}
/**
* 权限不够处理器 Bean
*/
@Bean
public AccessDeniedHandler accessDeniedHandler() {
return new AccessDeniedHandlerImpl();
}
/**
* Spring Security 加密器
* 考虑到安全性,这里采用 BCryptPasswordEncoder 加密器
*
* @see <a href="http://stackabuse.com/password-encoding-with-spring-security/">Password Encoding with Spring Security</a>
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(securityProperties.getPasswordEncoderLength());
}
/**
* Token 认证过滤器 Bean
*/
@Bean
public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler,
OAuth2TokenApi oauth2TokenApi) {
return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler, oauth2TokenApi);
}
@Bean("ss") // 使用 Spring Security 的缩写,方便使用
public SecurityFrameworkService securityFrameworkService(PermissionApi permissionApi) {
return new SecurityFrameworkServiceImpl(permissionApi);
}
/**
* 声明调用 {@link SecurityContextHolder#setStrategyName(String)} 方法,
* 设置使用 {@link TransmittableThreadLocalSecurityContextHolderStrategy} 作为 Security 的上下文策略
*/
@Bean
public MethodInvokingFactoryBean securityContextHolderMethodInvokingFactoryBean() {
MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();
methodInvokingFactoryBean.setTargetClass(SecurityContextHolder.class);
methodInvokingFactoryBean.setTargetMethod("setStrategyName");
methodInvokingFactoryBean.setArguments(TransmittableThreadLocalSecurityContextHolderStrategy.class.getName());
return methodInvokingFactoryBean;
}
}
其中TransmittableThreadLocalSecurityContextHolderStrategy是配置的security上下文内容:
Spring Security 默认使用
ThreadLocal
存储SecurityContext
(用户认证信息),但ThreadLocal
在异步线程中无法自动传递,导致异步任务无法获取当前用户身份。解决方案:
使用TransmittableThreadLocal
(TTL) 替代默认的ThreadLocal
,确保SecurityContext
能在父子线程间(尤其是线程池场景)正确传递。
import com.alibaba.ttl.TransmittableThreadLocal;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextImpl;
import org.springframework.util.Assert;
/**
* 基于 TransmittableThreadLocal 实现的 Security Context 持有者策略
* 目的是,避免 @Async 等异步执行时,原生 ThreadLocal 的丢失问题
*
*/
public class TransmittableThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
/**
* 使用 TransmittableThreadLocal 作为上下文
*/
private static final ThreadLocal<SecurityContext> CONTEXT_HOLDER = new TransmittableThreadLocal<>();
@Override
public void clearContext() {
CONTEXT_HOLDER.remove();
}
@Override
public SecurityContext getContext() {
SecurityContext ctx = CONTEXT_HOLDER.get();
if (ctx == null) {
ctx = createEmptyContext();
CONTEXT_HOLDER.set(ctx);
}
return ctx;
}
@Override
public void setContext(SecurityContext context) {
Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
CONTEXT_HOLDER.set(context);
}
@Override
public SecurityContext createEmptyContext() {
return new SecurityContextImpl();
}
}
4.0.4:YudaoSecurityRpcAutoConfiguration
这个类 YudaoSecurityRpcAutoConfiguration
是一个 Spring Boot 自动配置类,主要用于配置与 Feign 客户端 相关的安全组件。它的核心作用是为应用程序在远程调用(RPC)时提供安全支持,确保在 Feign 调用中能够正确传递用户身份信息(如登录用户信息)。
package com.oceania.framework.security.config;
import com.oceania.framework.security.core.rpc.LoginUserRequestInterceptor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
/**
* Security 使用到 Feign 的配置项(比如feign调用的时候透传token)
*
*/
@AutoConfiguration
public class SecurityRpcAutoConfiguration {
@Bean
public LoginUserRequestInterceptor loginUserRequestInterceptor() {
return new LoginUserRequestInterceptor();
}
}
package com.oceania.framework.security.core.rpc;
import com.oceania.framework.security.core.LoginUser;
import com.oceania.framework.security.core.util.SecurityFrameworkUtil;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
/**
* LoginUser 的 RequestInterceptor 实现类:
* Feign 请求时,将 Token信息 设置到 header 中,继续透传给被调用的服务
*
*/
@Slf4j
public class LoginUserRequestInterceptor implements RequestInterceptor {
@Override
@SneakyThrows
public void apply(RequestTemplate requestTemplate) {
String token = SecurityFrameworkUtil.getToken();
if (token == null) {
return;
}
try {
requestTemplate.header("Authorization", "Bearer " + token);
} catch (Exception ex) {
log.error("[apply] 无可用 Token,可能导致远程请求鉴权失败", ex);
throw ex;
}
}
}
4.1:功能权限
系统采用 RBAC 权限模型,全称是 Role-Based Access Control 基于角色的访问控制。“用户<->角色<->菜单” 的授权模型。 在这种模型中,用户与角色、角色与菜单之间构成了多对多的关系。
安全框架使用的是 Spring Security (opens new window)+ Token 方案,整体流程如下图所示:
Token 存储在数据库中,对应 system_oauth2_access_token
访问令牌表的 id
字段。考虑到访问的性能,缓存在 Redis 的 oauth2_access_token:%s (opens new window)键中。
默认配置下,Token 有效期为 30 天,可通过 system_oauth2_client
表中 client_id = default
的记录进行自定义:
- 修改
access_token_validity_seconds
字段,设置访问令牌的过期时间,默认 1800 秒 = 30 分钟 - 修改
refresh_token_validity_seconds
字段,设置刷新令牌的过期时间,默认 2592000 秒 = 30 天
权限注解
@PreAuthorize (opens new window)是 Spring Security 内置的前置权限注解,添加在接口方法上,声明需要的权限,实现访问权限的控制。
① 基于【权限标识】的权限控制
权限标识,对应 system_menu
表的 permission
字段,推荐格式为 ${系统}:${模块}:${操作}
,例如说 system:admin:add
标识 system 服务的添加管理员。
@PreAuthenticated (opens new window)是项目自定义的认证注解,添加在接口方法上,声明登录的用户才允许访问。
主要使用场景是,针对用户 App 的 /app-app/**
的 RESTful API 接口,默认是无需登录的,通过 @PreAuthenticated
声明它需要进行登录。使用示例如下:
具体的代码实现,可见 PreAuthenticatedAspect (opens new window)类。
4.2:数据权限
数据权限,实现指定用户可以操作指定范围的数据。例如说,针对员工信息的数据权限:
用户 | 数据范围 |
---|---|
普通员工 | 自己 |
部门领导 | 所属部门的所有员工 |
HR 小姐姐 | 整个公司的所有员工 |
上述的这个示例,使用硬编码是可以实现的,并且也非常简单。但是,在业务快速迭代的过程中,类似这种数据需求会越来越多,如果全部采用硬编码的方式,无疑会给我们带来非常大的开发与维护成本。
因此,项目提供 yudao-spring-boot-starter-biz-data-permission (opens new window)技术组件,只需要少量的编码,无需入侵到业务代码,即可实现数据权限。
4.2.1:实现原理
yudao-spring-boot-starter-biz-data-permission
技术组件的实现原理非常简单,每次对数据库操作时,他会自动拼接 WHERE data_column = ?
条件来进行数据的过滤。
例如说,查看员工信息的功能,对应 SQL 是 SELECT * FROM system_users
,那么拼接后的 SQL 结果会是:
用户 | 数据范围 | SQL |
---|---|---|
普通员工 | 自己 | SELECT * FROM system_users WHERE id = 自己 |
部门领导 | 所属部门的所有员工 | SELECT * FROM system_users WHERE dept_id = 自己的部门 |
HR 小姐姐 | 整个公司的所有员工 | SELECT * FROM system_users 无需拼接 |
明白了实现原理之后,想要进一步加入理解,后续可以找时间 Debug 调试下 MyBatis Plus 的 DataPermissionInterceptor 类的这三个方法:
#processSelect(...)
方法:处理 SELECT 语句的 WHERE 条件。#processUpdate(...)
方法:处理 UPDATE 语句的 WHERE 条件。#processDelete(...)
方法:处理 DELETE 语句的 WHERE 条件。
主要还是基于 MyBatis Plus 的 数据权限插件 (opens new window)。
具体的条件生成,可见项目的 DataPermissionRuleHandler 类。
4.2.2: 基于部门的数据权限
项目内置了基于部门的数据权限,支持 5 种数据范围:
- 全部数据权限:无数据权限的限制。
- 指定部门数据权限:根据实际需要,设置可操作的部门。
- 本部门数据权限:只能操作用户所在的部门。
- 本部门及以下数据权限:在【本部门数据权限】的基础上,额外可操作子部门。
- 仅本人数据权限:相对特殊,只能操作自己的数据。
1 后台配置
可通过管理后台的 [系统管理 -> 角色管理] 菜单,设置用户角色的数据权限。
2 字段配置
每个 Maven Module, 通过自定义 DeptDataPermissionRuleCustomizer (opens new window)Bean,配置哪些表的哪些字段,进行数据权限的过滤。
这段代码的核心作用是:
定义数据权限规则:
告诉系统哪些表需要根据 部门(Dept) 或 用户(User) 进行数据过滤。
配置规则自定义器:
通过
DeptDataPermissionRuleCustomizer
接口,动态添加数据权限规则。
以 yudao-module-system
模块来举例子,代码如下:
@Configuration(proxyBeanMethods = false)
public class DataPermissionConfiguration {
@Bean
public DeptDataPermissionRuleCustomizer sysDeptDataPermissionRuleCustomizer() {
return rule -> {
// dept 基于部门的数据权限
rule.addDeptColumn(AdminUserDO.class); // WHERE dept_id = ?
rule.addDeptColumn(DeptDO.class, "id"); // WHERE id = ?
// user 基于用户的数据权限
rule.addUserColumn(AdminUserDO.class, "id"); // WHERE id = ?
// rule.addUserColumn(OrderDO.class); // WHERE user_id = ?
};
}
}
注意,数据库的表字段必须添加:
- 基于【部门】过滤数据权限的表,需要添加部门编号字段,例如说
dept_id
字段。 - 基于【用户】过滤数据权限的表,需要添加部门用户字段,例如说
user_id
字段。
DeptDataPermissionRuleCustomizer
:一个函数式接口,用于自定义数据权限规则。
rule -> { ... }
:Lambda 表达式,实现 了
DeptDataPermissionRuleCustomizer
接口并添加自己的功能。
addDeptColumn
:添加基于 部门 的数据权限规则。
参数说明:
AdminUserDO.class
:表示对AdminUserDO
表启用部门数据权限。DeptDO.class, "id"
:表示对DeptDO
表的id
字段启用部门数据权限。
addUserColumn
:添加基于 用户 的数据权限规则。
参数说明:
AdminUserDO.class, "id"
:表示对AdminUserDO
表的id
字段启用用户数据权限。
动态拼接 SQL
在查询数据时,系统会根据配置的规则动态拼接 SQL。
比如:
如果配置了
addDeptColumn(AdminUserDO.class)
,查询AdminUserDO
表时会自动拼接WHERE dept_id = 当前用户部门ID
。如果配置了
addUserColumn(AdminUserDO.class, "id")
,查询时会自动拼接WHERE id = 当前用户ID
。
3:@DataPermission 注解
使用示例如下,可见 UserProfileController (opens new window)类:
// UserProfileController.java
@GetMapping("/get")
@Operation(summary = "获得登录用户信息")
@DataPermission(enable = false) // 关闭数据权限,避免只查看自己时,查询不到部门。
public CommonResult<UserProfileRespVO> profile() {
// .. 省略代码
if (user.getDeptId() != null) {
DeptDO dept = deptService.getDept(user.getDeptId());
resp.setDept(UserConvert.INSTANCE.convert02(dept));
}
// .. 省略代码
}
② includeRules
属性,配置生效的 DataPermissionRule (opens new window)数据权限规则。例如说,项目里有 10 种 DataPermissionRule 规则,某个方法只想其中的 1 种生效,则可以使用该属性。
③ excludeRules
属性,配置排除的 DataPermissionRule (opens new window)数据权限规则。例如说,项目里有 10 种 DataPermissionRule 规则,某个方法不想其中的 1 种生效,则可以使用该属性。
4. 自定义的数据权限规则
如果想要自定义数据权限规则,只需要实现 DataPermissionRule (opens new window)数据权限规则接口,并声明成 Spring Bean 即可。需要实现的只有两个方法:
public interface DataPermissionRule {
/**
* 返回需要生效的表名数组
* 为什么需要该方法?Data Permission 数组基于 SQL 重写,通过 Where 返回只有权限的数据
*
* 如果需要基于实体名获得表名,可调用 {@link TableInfoHelper#getTableInfo(Class)} 获得
*
* @return 表名数组
*/
Set<String> getTableNames();
/**
* 根据表名和别名,生成对应的 WHERE / OR 过滤条件
*
* @param tableName 表名
* @param tableAlias 别名,可能为空
* @return 过滤条件 Expression 表达式
*/
Expression getExpression(String tableName, Alias tableAlias);
}
#getTableNames()
方法:哪些数据库表,需要使用该数据权限规则。#getExpression(...)
方法:当操作这些数据库表,需要额外拼接怎么样的 WHERE 条件。
下面,艿艿带你写个自定义数据权限规则的示例,它的数据权限规则是:
- 针对
system_dict_type
表,它的创建人creator
要是当前用户。 - 针对
system_post
表,它的更新人updater
要是当前用户。
具体实现代码如下:
package cn.iocoder.yudao.module.system.framework.datapermission;
import cn.iocoder.yudao.framework.datapermission.core.rule.DataPermissionRule;
import cn.iocoder.yudao.framework.mybatis.core.util.MyBatisUtils;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import com.google.common.collect.Sets;
import net.sf.jsqlparser.expression.Alias;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import org.springframework.stereotype.Component;
import java.util.Set;
@Component // 声明为 Spring Bean,保证被 yudao-spring-boot-starter-biz-data-permission 组件扫描到
public class DemoDataPermissionRule implements DataPermissionRule {
@Override
public Set<String> getTableNames() {
return Sets.newHashSet("system_dict_type", "system_post");
}
@Override
public Expression getExpression(String tableName, Alias tableAlias) {
Long userId = SecurityFrameworkUtils.getLoginUserId();
assert userId != null;
switch (tableName) {
case "system_dict_type":
return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, "creator"), new LongValue(userId));
case "system_post":
return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, "updater"), new LongValue(userId));
default: return null;
}
}
}
① 启动前端 + 后端项目。
② 访问 [系统管理 -> 字典管理] 菜单,查看 IDEA 控制台,可以看到 system_dict_type
表的查询自动拼接了 AND creator = 1
的查询条件。
② 访问 [系统管理 -> 岗位管理] 菜单,查看 IDEA 控制台,可以看到 system_post
表的查询自动拼接了 AND updater = 1
的查询条件。
5. 如何忽略数据权限
可以使用 DataPermissionUtils 的 #executeIgnore(...)
方法,设置忽略数据权限。
具体的案例,可以通过 IDEA 查找下项目里,哪些地方调用了这个方法噢!
4.2.3:
权限控制的流程
用户登录:
用户输入账号密码,系统验证通过后生成 Token。
Token 中包含用户ID和角色ID。
加载权限:
根据角色ID,从数据库加载用户的权限列表和数据范围。
权限校验:
用户访问某个功能时,系统检查该功能是否在权限列表中。
如果没有权限,直接拒绝访问。
数据过滤:
用户查询数据时,系统根据数据范围动态拼接 SQL。
返回过滤后的数据。
4.3:用户体系
系统提供了 2 种类型的用户,分别满足对应的管理后台、用户 App 场景。
- AdminUser 管理员用户,前端访问 yudao-ui-admin-vue3 (opens new window)管理后台,后端访问
/admin-api/**
RESTful API 接口。 - MemberUser 会员用户,前端访问 yudao-mall-uniapp (opens new window)用户 App,后端访问
/app-api/**
RESTful API 接口。
虽然是不同类型的用户,他们访问 RESTful API 接口时,都通过 Token 认证机制,具体可见 《开发指南 —— 功能权限》
1. 表结构
2 种类型的时候,采用不同数据库的表进行存储,管理员用户对应 system_users (opens new window)表,会员用户对应 member_user (opens new window)表。如下图所示:
如果表需要关联多种类型的用户,例如说上述的 system_oauth2_access_token
访问令牌表,可以通过 user_type
字段进行区分。并且 user_type
对应 UserTypeEnum (opens new window)全局枚举,代码如下:
2. 如何获取当前登录的用户?
使用 SecurityFrameworkUtils (opens new window)提供的如下方法,可以获得当前登录用户的信息:
2.1 获取当前用户信息
public static LoginUser getLoginUser()
#2.2 获取当前用户编号(最常用)
public static Long getLoginUserId()
#2.3 获取当前用户昵称
public static LoginUser getLoginUserNickname()
注意,仅适合 AdminUser 管理员用户!
2.4 获取当前用户部门
public static Long getLoginUserDeptId()
注意,仅适合 AdminUser 管理员用户!
2.5 获取更多信息
① 在 OAuth2TokenServiceImpl 的 #buildUserInfo(...)
方法中,补充读取更多的用户信息,例如说 mobile
、sex
等等。如下图所示:
② 在 SecurityFrameworkUtils 新增对应的 getXXX()
静态方法,参考如下图所示:
3. 账号密码登录
3.1 管理后台的实现
使用 username
账号 + password
密码进行登录,由 AuthController (opens new window)提供 /admin-api/system/auth/login
接口。代码如下:
@PostMapping("/login")
@Operation(summary = "使用账号密码登录")
public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {
String token = authService.login(reqVO, getClientIP(), getUserAgent());
// 返回结果
return success(AuthLoginRespVO.builder().token(token).build());
}
3.2 用户 App 的实现
使用 mobile
手机 + password
密码进行登录,由 AppAuthController (opens new window)提供 /app-api/member/auth/login
接口。代码如下:
@PostMapping("/login")
@Operation(summary = "使用手机 + 密码登录")
public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {
String token = authService.login(reqVO, getClientIP(), getUserAgent());
// 返回结果
return success(AppAuthLoginRespVO.builder().token(token).build());
}
4. 手机验证码登录
#4.1 管理后台的实现
① 使用 mobile
手机号获得验证码,由 AuthController (opens new window)提供 /admin-api/system/auth/send-sms-code
接口。代码如下:
@PostMapping("/send-sms-code")
@Operation(summary = "发送手机验证码")
public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid AuthSendSmsReqVO reqVO) {
authService.sendSmsCode(getLoginUserId(), reqVO);
return success(true);
}
② 使用