diff --git a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java index 146b9cdd5..d3f70b07e 100644 --- a/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java +++ b/yudao-framework/yudao-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/yudao/framework/tenant/core/security/TenantSecurityWebFilter.java @@ -12,13 +12,13 @@ import cn.iocoder.yudao.framework.tenant.core.service.TenantFrameworkService; import cn.iocoder.yudao.framework.web.config.WebProperties; import cn.iocoder.yudao.framework.web.core.filter.ApiRequestFilter; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; +import lombok.extern.slf4j.Slf4j; +import org.springframework.util.AntPathMatcher; + import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.extern.slf4j.Slf4j; -import org.springframework.util.AntPathMatcher; - import java.io.IOException; import java.util.Objects; @@ -62,7 +62,7 @@ public class TenantSecurityWebFilter extends ApiRequestFilter { if (tenantId == null) { tenantId = user.getTenantId(); TenantContextHolder.setTenantId(tenantId); - // 如果传递了租户编号,则进行比对租户编号,避免越权问题 + // 如果传递了租户编号,则进行比对租户编号,避免越权问题 } else if (!Objects.equals(user.getTenantId(), TenantContextHolder.getTenantId())) { log.error("[doFilterInternal][租户({}) User({}/{}) 越权访问租户({}) URL({}/{})]", user.getTenantId(), user.getId(), user.getUserType(), @@ -101,13 +101,14 @@ public class TenantSecurityWebFilter extends ApiRequestFilter { } private boolean isIgnoreUrl(HttpServletRequest request) { + String apiUri = request.getRequestURI().substring(request.getContextPath().length()); // 快速匹配,保证性能 - if (CollUtil.contains(tenantProperties.getIgnoreUrls(), request.getRequestURI())) { + if (CollUtil.contains(tenantProperties.getIgnoreUrls(), apiUri)) { return true; } // 逐个 Ant 路径匹配 for (String url : tenantProperties.getIgnoreUrls()) { - if (pathMatcher.match(url, request.getRequestURI())) { + if (pathMatcher.match(url, apiUri)) { return true; } } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/event/BpmProcessInstanceStatusEvent.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/event/BpmProcessInstanceStatusEvent.java index 17947d24b..ebea5ada0 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/event/BpmProcessInstanceStatusEvent.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/api/event/BpmProcessInstanceStatusEvent.java @@ -28,6 +28,11 @@ public class BpmProcessInstanceStatusEvent extends ApplicationEvent { */ @NotNull(message = "流程实例的状态不能为空") private Integer status; + /** + * 流程实例结束的原因 + */ + private String reason; + /** * 流程实例对应的业务标识 * 例如说,请假 diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java index c82414b53..45d5973f4 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/convert/task/BpmProcessInstanceConvert.java @@ -119,8 +119,9 @@ public interface BpmProcessInstanceConvert { @Mapping(source = "from.id", target = "to.id", ignore = true) void copyTo(BpmProcessDefinitionInfoDO from, @MappingTarget BpmProcessDefinitionRespVO to); - default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, Integer status) { - return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status) + default BpmProcessInstanceStatusEvent buildProcessInstanceStatusEvent(Object source, ProcessInstance instance, + Integer status, String reason) { + return new BpmProcessInstanceStatusEvent(source).setId(instance.getId()).setStatus(status).setReason(reason) .setProcessDefinitionKey(instance.getProcessDefinitionKey()).setBusinessKey(instance.getBusinessKey()); } @@ -133,10 +134,10 @@ public interface BpmProcessInstanceConvert { default BpmMessageSendWhenProcessInstanceRejectReqDTO buildProcessInstanceRejectMessage(ProcessInstance instance, String reason) { return new BpmMessageSendWhenProcessInstanceRejectReqDTO() - .setProcessInstanceName(instance.getName()) - .setProcessInstanceId(instance.getId()) - .setReason(reason) - .setStartUserId(NumberUtils.parseLong(instance.getStartUserId())); + .setProcessInstanceName(instance.getName()) + .setProcessInstanceId(instance.getId()) + .setReason(reason) + .setStartUserId(NumberUtils.parseLong(instance.getStartUserId())); } default BpmProcessInstanceBpmnModelViewRespVO buildProcessInstanceBpmnModelView(HistoricProcessInstance processInstance, @@ -153,7 +154,7 @@ public interface BpmProcessInstanceConvert { // 基本信息 respVO.setProcessInstance(BeanUtils.toBean(processInstance, BpmProcessInstanceRespVO.class, o -> o .setStatus(FlowableUtils.getProcessInstanceStatus(processInstance))) - .setStartUser(buildUser(processInstance.getStartUserId(), userMap, deptMap))); + .setStartUser(buildUser(processInstance.getStartUserId(), userMap, deptMap))); respVO.setTasks(convertList(taskInstances, task -> BeanUtils.toBean(task, BpmTaskRespVO.class) .setStatus(FlowableUtils.getTaskStatus(task)).setReason(FlowableUtils.getTaskReason(task)) .setAssigneeUser(buildUser(task.getAssignee(), userMap, deptMap)) @@ -179,8 +180,8 @@ public interface BpmProcessInstanceConvert { } default UserSimpleBaseVO buildUser(Long userId, - Map userMap, - Map deptMap) { + Map userMap, + Map deptMap) { if (userId == null) { return null; } diff --git a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java index 7ff8a132c..1ea9eccbd 100644 --- a/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-server/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java @@ -960,7 +960,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService // 3. 发送流程实例的状态事件 processInstanceEventPublisher.sendProcessInstanceResultEvent( - BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status)); + BpmProcessInstanceConvert.INSTANCE.buildProcessInstanceStatusEvent(this, instance, status, reason)); // 4. 流程后置通知 if (Objects.equals(status, BpmProcessInstanceStatusEnum.APPROVE.getStatus())) { diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.http b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.http index f770ed91d..cdcebbfb1 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.http +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.http @@ -35,6 +35,14 @@ tenant-id: {{adminTenantId}} grant_type=password&username=admin&password=admin123&scope=user.read +### 请求 /system/oauth2/token + client_credentials 接口 => 成功 +POST {{baseUrl}}/system/oauth2/token +Content-Type: application/x-www-form-urlencoded +Authorization: Basic ZGVmYXVsdDphZG1pbjEyMw== +tenant-id: {{adminTenantId}} + +grant_type=client_credentials&scope=user.read + ### 请求 /system/oauth2/token + refresh_token 接口 => 成功 POST {{baseUrl}}/system/oauth2/token Content-Type: application/x-www-form-urlencoded diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java index d06523e9c..51687b792 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/controller/admin/oauth2/OAuth2OpenController.java @@ -21,17 +21,17 @@ import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService; import cn.iocoder.yudao.module.system.service.oauth2.OAuth2GrantService; import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; import cn.iocoder.yudao.module.system.util.oauth2.OAuth2Utils; -import io.swagger.v3.oas.annotations.tags.Tag; +import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameters; -import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.annotation.security.PermitAll; +import jakarta.servlet.http.HttpServletRequest; import lombok.extern.slf4j.Slf4j; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import jakarta.annotation.Resource; -import jakarta.annotation.security.PermitAll; -import jakarta.servlet.http.HttpServletRequest; import java.util.Collections; import java.util.List; import java.util.Map; @@ -94,6 +94,7 @@ public class OAuth2OpenController { @Parameter(name = "scope", example = "user_info"), @Parameter(name = "refresh_token", example = "123424233"), }) + @SuppressWarnings("EnhancedSwitchMigration") public CommonResult postAccessToken(HttpServletRequest request, @RequestParam("grant_type") String grantType, @RequestParam(value = "code", required = false) String code, // 授权码模式 @@ -119,15 +120,23 @@ public class OAuth2OpenController { grantType, scopes, redirectUri); // 2. 根据授权模式,获取访问令牌 - OAuth2AccessTokenDO accessTokenDO = switch (grantTypeEnum) { - // TODO @xingyu:这里改了,可能会影响 jdk8 版本哈; - case AUTHORIZATION_CODE -> - oauth2GrantService.grantAuthorizationCodeForAccessToken(client.getClientId(), code, redirectUri, state); - case PASSWORD -> oauth2GrantService.grantPassword(username, password, client.getClientId(), scopes); - case CLIENT_CREDENTIALS -> oauth2GrantService.grantClientCredentials(client.getClientId(), scopes); - case REFRESH_TOKEN -> oauth2GrantService.grantRefreshToken(refreshToken, client.getClientId()); - default -> throw new IllegalArgumentException("未知授权类型:" + grantType); - }; + OAuth2AccessTokenDO accessTokenDO; + switch (grantTypeEnum) { + case AUTHORIZATION_CODE: + accessTokenDO = oauth2GrantService.grantAuthorizationCodeForAccessToken(client.getClientId(), code, redirectUri, state); + break; + case PASSWORD: + accessTokenDO = oauth2GrantService.grantPassword(username, password, client.getClientId(), scopes); + break; + case CLIENT_CREDENTIALS: + accessTokenDO = oauth2GrantService.grantClientCredentials(client.getClientId(), scopes); + break; + case REFRESH_TOKEN: + accessTokenDO = oauth2GrantService.grantRefreshToken(refreshToken, client.getClientId()); + break; + default: + throw new IllegalArgumentException("未知授权类型:" + grantType); + } Assert.notNull(accessTokenDO, "访问令牌不能为空"); // 防御性检查 return success(OAuth2OpenConvert.INSTANCE.convert(accessTokenDO)); } diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImpl.java index 7cd5271a0..688a54223 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImpl.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2ApproveServiceImpl.java @@ -7,13 +7,16 @@ import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ApproveDO; import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO; import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2ApproveMapper; import com.google.common.annotations.VisibleForTesting; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; import java.time.LocalDateTime; -import java.util.*; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet; diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2CodeServiceImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2CodeServiceImpl.java index 11150fae5..7a6a5132f 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2CodeServiceImpl.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2CodeServiceImpl.java @@ -4,10 +4,10 @@ import cn.hutool.core.util.IdUtil; import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2CodeDO; import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2CodeMapper; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; -import jakarta.annotation.Resource; import java.time.LocalDateTime; import java.util.List; diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2GrantServiceImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2GrantServiceImpl.java index e95fecccc..afd74b980 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2GrantServiceImpl.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2GrantServiceImpl.java @@ -9,9 +9,9 @@ import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2CodeDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.enums.ErrorCodeConstants; import cn.iocoder.yudao.module.system.service.auth.AdminAuthService; +import jakarta.annotation.Resource; import org.springframework.stereotype.Service; -import jakarta.annotation.Resource; import java.util.List; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; @@ -86,8 +86,8 @@ public class OAuth2GrantServiceImpl implements OAuth2GrantService { @Override public OAuth2AccessTokenDO grantClientCredentials(String clientId, List scopes) { - // TODO 芋艿:项目中使用 OAuth2 解决的是三方应用的授权,内部的 SSO 等问题,所以暂时不考虑 client_credentials 这个场景 - throw new UnsupportedOperationException("暂时不支持 client_credentials 授权模式"); + // 特殊:https://yuanbao.tencent.com/bot/app/share/chat/wFj642xSZHHx + return oauth2TokenService.createAccessToken(0L, UserTypeEnum.ADMIN.getValue(), clientId, scopes); } @Override diff --git a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java index fb0e756a2..5c628b8e1 100644 --- a/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java +++ b/yudao-module-system/yudao-module-system-server/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java @@ -197,6 +197,9 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { * @return 用户信息 */ private Map buildUserInfo(Long userId, Integer userType) { + if (userId == null || userId <= 0) { + return Collections.emptyMap(); + } if (userType.equals(UserTypeEnum.ADMIN.getValue())) { AdminUserDO user = adminUserService.getUser(userId); return MapUtil.builder(LoginUser.INFO_KEY_NICKNAME, user.getNickname()) @@ -205,7 +208,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { // 注意:目前 Member 暂时不读取,可以按需实现 return Collections.emptyMap(); } - return null; + throw new IllegalArgumentException("未知用户类型:" + userType); } private static String generateAccessToken() { diff --git a/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2GrantServiceImplTest.java b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2GrantServiceImplTest.java index 52c722831..ed0672f24 100644 --- a/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2GrantServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2GrantServiceImplTest.java @@ -135,13 +135,6 @@ public class OAuth2GrantServiceImplTest extends BaseMockitoUnitTest { refreshToken, clientId)); } - @Test - public void testGrantClientCredentials() { - assertThrows(UnsupportedOperationException.class, - () -> oauth2GrantService.grantClientCredentials(randomString(), emptyList()), - "暂时不支持 client_credentials 授权模式"); - } - @Test public void testRevokeToken_clientIdError() { // 准备参数 diff --git a/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java index 0aad4446a..36b69c75d 100644 --- a/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java +++ b/yudao-module-system/yudao-module-system-server/src/test/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImplTest.java @@ -236,6 +236,7 @@ public class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest { public void testCheckAccessToken_refreshToken() { // mock 数据(访问令牌) OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class) + .setUserId(0L) .setExpiresTime(LocalDateTime.now().plusDays(1)); oauth2RefreshTokenMapper.insert(refreshTokenDO); // 准备参数