Commit 2f655a816beba1d5dd02d6c49e992f667b0392ad
1 parent
19829c6c
fix: 修复钱包充值退款权限与 IoT 固件上传校验
- 钱包充值退款复用用户余额修改权限,阻止无权限用户发起退款(#1328) - 允许 .bin 固件上传、预签名及直传登记,保留未知后缀和内容类型校验 - 补充方法权限拦截与固件上传回归用例 星球反馈:https://t.zsxq.com/bAppp
Showing
4 changed files
with
309 additions
and
5 deletions
yudao-module-infra/src/main/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImpl.java
| ... | ... | @@ -22,9 +22,11 @@ import cn.iocoder.yudao.module.infra.framework.file.core.utils.FileTypeUtils; |
| 22 | 22 | import com.google.common.annotations.VisibleForTesting; |
| 23 | 23 | import jakarta.annotation.Resource; |
| 24 | 24 | import lombok.SneakyThrows; |
| 25 | +import org.apache.tika.mime.MimeTypes; | |
| 25 | 26 | import org.springframework.stereotype.Service; |
| 26 | 27 | |
| 27 | 28 | import java.util.List; |
| 29 | +import java.util.Locale; | |
| 28 | 30 | import java.util.Set; |
| 29 | 31 | |
| 30 | 32 | import static cn.hutool.core.date.DatePattern.PURE_DATE_PATTERN; |
| ... | ... | @@ -63,6 +65,14 @@ public class FileServiceImpl implements FileService { |
| 63 | 65 | ); |
| 64 | 66 | |
| 65 | 67 | /** |
| 68 | + * 允许上传的二进制文件后缀,例如说 IoT OTA 固件的 .bin | |
| 69 | + * | |
| 70 | + * 为什么不直接放行 application/octet-stream?它也是所有未知后缀、未知内容的识别结果,放行后等于不限制类型。 | |
| 71 | + * 所以只放行这里的后缀,并且上传内容也必须识别为 application/octet-stream(不能是可执行文件、脚本、HTML 等) | |
| 72 | + */ | |
| 73 | + private static final Set<String> ALLOWED_BINARY_EXTENSIONS = Set.of("bin"); | |
| 74 | + | |
| 75 | + /** | |
| 66 | 76 | * 上传文件的前缀,是否包含日期(yyyyMMdd) |
| 67 | 77 | * |
| 68 | 78 | * 目的:按照日期,进行分目录 |
| ... | ... | @@ -106,8 +116,10 @@ public class FileServiceImpl implements FileService { |
| 106 | 116 | } |
| 107 | 117 | type = FileTypeUtils.getMineType(content, name); |
| 108 | 118 | String nameType = StrUtil.isNotEmpty(FileUtil.extName(name)) ? validateFileType(name) : null; |
| 109 | - if (!ALLOWED_FILE_TYPES.contains(type) | |
| 110 | - || (FileTypeUtils.isImage(nameType) && ObjectUtil.notEqual(type, nameType))) { | |
| 119 | + if (!isAllowedFileType(type, name) | |
| 120 | + // 图片、二进制文件(例如说 .bin 固件):内容识别的类型,必须与后缀一致 | |
| 121 | + || ((FileTypeUtils.isImage(nameType) || isAllowedBinaryFile(nameType, name)) | |
| 122 | + && ObjectUtil.notEqual(type, nameType))) { | |
| 111 | 123 | throw exception(FILE_TYPE_NOT_ALLOWED); |
| 112 | 124 | } |
| 113 | 125 | // 1.2.2 处理 name 为空的情况 |
| ... | ... | @@ -221,12 +233,24 @@ public class FileServiceImpl implements FileService { |
| 221 | 233 | |
| 222 | 234 | private String validateFileType(String name) { |
| 223 | 235 | String type = FileTypeUtils.getMineType(name); |
| 224 | - if (!ALLOWED_FILE_TYPES.contains(type)) { | |
| 236 | + if (!isAllowedFileType(type, name)) { | |
| 225 | 237 | throw exception(FILE_TYPE_NOT_ALLOWED); |
| 226 | 238 | } |
| 227 | 239 | return type; |
| 228 | 240 | } |
| 229 | 241 | |
| 242 | + private static boolean isAllowedFileType(String type, String name) { | |
| 243 | + return ALLOWED_FILE_TYPES.contains(type) || isAllowedBinaryFile(type, name); | |
| 244 | + } | |
| 245 | + | |
| 246 | + private static boolean isAllowedBinaryFile(String type, String name) { | |
| 247 | + if (ObjectUtil.notEqual(type, MimeTypes.OCTET_STREAM)) { | |
| 248 | + return false; | |
| 249 | + } | |
| 250 | + String extension = FileUtil.extName(name); | |
| 251 | + return StrUtil.isNotEmpty(extension) && ALLOWED_BINARY_EXTENSIONS.contains(extension.toLowerCase(Locale.ROOT)); | |
| 252 | + } | |
| 253 | + | |
| 230 | 254 | @Override |
| 231 | 255 | public FileDO getFile(Long id) { |
| 232 | 256 | return validateFileExists(id); | ... | ... |
yudao-module-infra/src/test/java/cn/iocoder/yudao/module/infra/service/file/FileServiceImplTest.java
| ... | ... | @@ -322,6 +322,79 @@ public class FileServiceImplTest extends BaseDbUnitTest { |
| 322 | 322 | } |
| 323 | 323 | |
| 324 | 324 | @Test |
| 325 | + public void testCreateFile_success_bin() throws Exception { | |
| 326 | + for (String name : new String[]{"固件.bin", "FIRMWARE.BIN", "firmware.Bin"}) { | |
| 327 | + // 准备参数:IoT OTA 固件,Tika 无法识别的二进制内容 | |
| 328 | + byte[] content = buildFirmwareContent(); | |
| 329 | + // mock 方法 | |
| 330 | + FileClient client = mock(FileClient.class); | |
| 331 | + when(fileConfigService.getMasterFileClient()).thenReturn(client); | |
| 332 | + String url = randomString(); | |
| 333 | + when(client.upload(same(content), anyString(), eq("application/octet-stream"))).thenReturn(url); | |
| 334 | + | |
| 335 | + // 调用:不信任客户端传入的 type | |
| 336 | + String result = fileService.createFile(content, name, "iot/ota", "image/png"); | |
| 337 | + // 断言 | |
| 338 | + assertEquals(url, result); | |
| 339 | + FileDO file = fileMapper.selectOne(FileDO::getUrl, url); | |
| 340 | + assertEquals(name, file.getName()); | |
| 341 | + assertEquals("application/octet-stream", file.getType()); | |
| 342 | + assertEquals((long) content.length, file.getSize()); | |
| 343 | + } | |
| 344 | + } | |
| 345 | + | |
| 346 | + @Test | |
| 347 | + public void testCreateFile_typeNotAllowed_binContent() { | |
| 348 | + byte[] elf = buildFirmwareContent(); | |
| 349 | + System.arraycopy(new byte[]{0x7F, 'E', 'L', 'F', 2, 1, 1}, 0, elf, 0, 7); | |
| 350 | + elf[16] = 2; // e_type = ET_EXEC | |
| 351 | + byte[] exe = buildFirmwareContent(); | |
| 352 | + exe[0] = 'M'; | |
| 353 | + exe[1] = 'Z'; | |
| 354 | + for (byte[] content : new byte[][]{elf, exe, | |
| 355 | + "#!/bin/sh\nrm -rf /tmp/test\n".getBytes(StandardCharsets.UTF_8), | |
| 356 | + "<html><script>alert(1)</script></html>".getBytes(StandardCharsets.UTF_8), | |
| 357 | + "<svg xmlns=\"http://www.w3.org/2000/svg\"><script>alert(1)</script></svg>".getBytes(StandardCharsets.UTF_8), | |
| 358 | + ResourceUtil.readBytes("file/erweima.jpg")}) { | |
| 359 | + // 调用,并断言异常:.bin 的内容,必须是无法识别为其他类型的二进制 | |
| 360 | + assertServiceException(() -> fileService.createFile(content, "固件.bin", null, "application/octet-stream"), | |
| 361 | + FILE_TYPE_NOT_ALLOWED); | |
| 362 | + // 断言未上传文件、未保存记录 | |
| 363 | + verifyNoInteractions(fileConfigService); | |
| 364 | + assertEquals(0L, fileMapper.selectCount()); | |
| 365 | + } | |
| 366 | + } | |
| 367 | + | |
| 368 | + @Test | |
| 369 | + public void testCreateFile_typeNotAllowed_binaryOtherExtension() { | |
| 370 | + for (String name : new String[]{"固件.so", "固件.dump", "固件.unknown", "固件"}) { | |
| 371 | + // 准备参数:同样识别为 application/octet-stream 的其它后缀,不放行 | |
| 372 | + byte[] content = buildFirmwareContent(); | |
| 373 | + | |
| 374 | + // 调用,并断言异常 | |
| 375 | + assertServiceException(() -> fileService.createFile(content, name, null, "application/octet-stream"), | |
| 376 | + FILE_TYPE_NOT_ALLOWED); | |
| 377 | + // 断言未上传文件、未保存记录 | |
| 378 | + verifyNoInteractions(fileConfigService); | |
| 379 | + assertEquals(0L, fileMapper.selectCount()); | |
| 380 | + } | |
| 381 | + } | |
| 382 | + | |
| 383 | + /** | |
| 384 | + * 构造固件内容:类似 ARM Cortex-M 的向量表开头,包含 0x00 等不可打印字节,Tika 无法识别 | |
| 385 | + */ | |
| 386 | + private static byte[] buildFirmwareContent() { | |
| 387 | + byte[] content = new byte[256]; | |
| 388 | + byte[] header = new byte[]{0x00, 0x50, 0x00, 0x20, (byte) 0xC1, 0x01, 0x00, 0x08, | |
| 389 | + (byte) 0xC9, 0x01, 0x00, 0x08, (byte) 0xCB, 0x01, 0x00, 0x08}; | |
| 390 | + System.arraycopy(header, 0, content, 0, header.length); | |
| 391 | + for (int i = header.length; i < content.length; i++) { | |
| 392 | + content[i] = (byte) (i * 7); | |
| 393 | + } | |
| 394 | + return content; | |
| 395 | + } | |
| 396 | + | |
| 397 | + @Test | |
| 325 | 398 | public void testCreateFile_fileIsEmpty() { |
| 326 | 399 | // 准备参数 |
| 327 | 400 | byte[] content = new byte[0]; |
| ... | ... | @@ -337,7 +410,7 @@ public class FileServiceImplTest extends BaseDbUnitTest { |
| 337 | 410 | for (String extension : new String[]{"jpg", "JPEG", "jfif", "png", "gif", "webp", "bmp", "ico", "tiff", |
| 338 | 411 | "avif", "heic", "heif", "pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "csv", "md", |
| 339 | 412 | "zip", "rar", "mp3", "mp4", "svg", "html", "htm", "xml", "markdown", "eml", "msg", "epub", |
| 340 | - "json", "opus", "ogg", "m4a", "webm", "amr", "wma", "wmv", "flv", "mkv"}) { | |
| 413 | + "json", "opus", "ogg", "m4a", "webm", "amr", "wma", "wmv", "flv", "mkv", "bin", "BIN"}) { | |
| 341 | 414 | // 准备参数 |
| 342 | 415 | String name = "测试文件." + extension; |
| 343 | 416 | // mock 方法 |
| ... | ... | @@ -360,7 +433,8 @@ public class FileServiceImplTest extends BaseDbUnitTest { |
| 360 | 433 | @Test |
| 361 | 434 | public void testPresignPutUrl_typeNotAllowed() { |
| 362 | 435 | // 准备参数 |
| 363 | - for (String name : new String[]{"test.php", "test.jsp", "test.js", "test.exe", "test.unknown", "test"}) { | |
| 436 | + for (String name : new String[]{"test.php", "test.jsp", "test.js", "test.exe", "test.unknown", "test", | |
| 437 | + "test.so", "test.dump", "test.sh", "test.bin.exe"}) { | |
| 364 | 438 | // 调用,并断言异常 |
| 365 | 439 | assertServiceException(() -> fileService.presignPutUrl(name, null), FILE_TYPE_NOT_ALLOWED); |
| 366 | 440 | // 校验调用 |
| ... | ... | @@ -491,6 +565,47 @@ public class FileServiceImplTest extends BaseDbUnitTest { |
| 491 | 565 | } |
| 492 | 566 | |
| 493 | 567 | @Test |
| 568 | + public void testCreateFileByPresignedPath_success_bin() { | |
| 569 | + // 准备参数:IoT OTA 固件,后缀大小写不敏感 | |
| 570 | + FileCreateReqVO reqVO = randomPojo(FileCreateReqVO.class, o -> { | |
| 571 | + o.setPath("iot/ota/firmware.bin"); | |
| 572 | + o.setName("FIRMWARE.BIN"); | |
| 573 | + o.setUrl("https://www.iocoder.cn/iot/ota/firmware.bin"); | |
| 574 | + o.setType("text/html"); // 不信任客户端传入的 type | |
| 575 | + }); | |
| 576 | + | |
| 577 | + // 调用 | |
| 578 | + Long fileId = fileService.createFile(reqVO); | |
| 579 | + | |
| 580 | + // 断言 | |
| 581 | + FileDO file = fileMapper.selectById(fileId); | |
| 582 | + assertEquals("iot/ota/firmware.bin", file.getPath()); | |
| 583 | + assertEquals("FIRMWARE.BIN", file.getName()); | |
| 584 | + assertEquals("application/octet-stream", file.getType()); | |
| 585 | + } | |
| 586 | + | |
| 587 | + @Test | |
| 588 | + public void testCreateFileByPresignedPath_typeNotAllowed_bin() { | |
| 589 | + for (String[] file : new String[][]{{"firmware.bin", "iot/firmware.exe"}, {"firmware.bin", "iot/firmware.so"}, | |
| 590 | + {"firmware.so", "iot/firmware.so"}, {"firmware.exe", "iot/firmware.bin"}, | |
| 591 | + {"firmware.bin", "iot/firmware.html"}, {"firmware", "iot/firmware.bin"}}) { | |
| 592 | + // 准备参数 | |
| 593 | + String name = file[0]; | |
| 594 | + String path = file[1]; | |
| 595 | + FileCreateReqVO reqVO = randomPojo(FileCreateReqVO.class, o -> { | |
| 596 | + o.setName(name); | |
| 597 | + o.setPath(path); | |
| 598 | + o.setType("application/octet-stream"); | |
| 599 | + }); | |
| 600 | + | |
| 601 | + // 调用,并断言异常 | |
| 602 | + assertServiceException(() -> fileService.createFile(reqVO), FILE_TYPE_NOT_ALLOWED); | |
| 603 | + // 断言未保存记录 | |
| 604 | + assertEquals(0L, fileMapper.selectCount()); | |
| 605 | + } | |
| 606 | + } | |
| 607 | + | |
| 608 | + @Test | |
| 494 | 609 | public void testCreateFileByPresignedPath_nameInvalid() { |
| 495 | 610 | // 准备参数 |
| 496 | 611 | FileCreateReqVO reqVO = randomPojo(FileCreateReqVO.class, o -> { | ... | ... |
yudao-module-pay/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeController.java
| ... | ... | @@ -8,6 +8,7 @@ import io.swagger.v3.oas.annotations.Operation; |
| 8 | 8 | import io.swagger.v3.oas.annotations.Parameter; |
| 9 | 9 | import io.swagger.v3.oas.annotations.tags.Tag; |
| 10 | 10 | import lombok.extern.slf4j.Slf4j; |
| 11 | +import org.springframework.security.access.prepost.PreAuthorize; | |
| 11 | 12 | import org.springframework.validation.annotation.Validated; |
| 12 | 13 | import org.springframework.web.bind.annotation.*; |
| 13 | 14 | |
| ... | ... | @@ -40,6 +41,7 @@ public class PayWalletRechargeController { |
| 40 | 41 | @PostMapping("/refund") |
| 41 | 42 | @Operation(summary = "发起钱包充值退款") |
| 42 | 43 | @Parameter(name = "id", description = "编号", required = true, example = "1024") |
| 44 | + @PreAuthorize("@ss.hasPermission('pay:wallet:update-balance')") // 退款会扣减用户钱包余额,复用「用户余额修改」权限 | |
| 43 | 45 | public CommonResult<Boolean> refundWalletRecharge(@RequestParam("id") Long id) { |
| 44 | 46 | walletRechargeService.refundWalletRecharge(id, getClientIP()); |
| 45 | 47 | return success(true); | ... | ... |
yudao-module-pay/src/test/java/cn/iocoder/yudao/module/pay/controller/admin/wallet/PayWalletRechargeControllerSecurityTest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.module.pay.controller.admin.wallet; | |
| 2 | + | |
| 3 | +import cn.iocoder.yudao.framework.common.biz.system.permission.PermissionCommonApi; | |
| 4 | +import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; | |
| 5 | +import cn.iocoder.yudao.framework.security.core.LoginUser; | |
| 6 | +import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService; | |
| 7 | +import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkServiceImpl; | |
| 8 | +import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO; | |
| 9 | +import cn.iocoder.yudao.module.pay.service.wallet.PayWalletRechargeService; | |
| 10 | +import jakarta.annotation.Resource; | |
| 11 | +import org.junit.jupiter.api.AfterEach; | |
| 12 | +import org.junit.jupiter.api.BeforeEach; | |
| 13 | +import org.junit.jupiter.api.Test; | |
| 14 | +import org.springframework.aop.support.AopUtils; | |
| 15 | +import org.springframework.context.annotation.Bean; | |
| 16 | +import org.springframework.context.annotation.Configuration; | |
| 17 | +import org.springframework.security.access.AccessDeniedException; | |
| 18 | +import org.springframework.security.authentication.AnonymousAuthenticationToken; | |
| 19 | +import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; | |
| 20 | +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | |
| 21 | +import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; | |
| 22 | +import org.springframework.security.core.authority.AuthorityUtils; | |
| 23 | +import org.springframework.security.core.context.SecurityContextHolder; | |
| 24 | +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; | |
| 25 | + | |
| 26 | +import java.util.Collections; | |
| 27 | + | |
| 28 | +import static org.junit.jupiter.api.Assertions.*; | |
| 29 | +import static org.mockito.ArgumentMatchers.*; | |
| 30 | +import static org.mockito.Mockito.*; | |
| 31 | + | |
| 32 | +/** | |
| 33 | + * {@link PayWalletRechargeController} 的方法级权限测试 | |
| 34 | + * | |
| 35 | + * 通过真实的 Spring Security 方法拦截({@link EnableMethodSecurity})+ 真实的 {@code @ss} 权限判断, | |
| 36 | + * 校验发起钱包充值退款需要 pay:wallet:update-balance 权限,拒绝时不会调用 Service | |
| 37 | + * | |
| 38 | + * @author 芋道源码 | |
| 39 | + */ | |
| 40 | +@SpringJUnitConfig(PayWalletRechargeControllerSecurityTest.Config.class) | |
| 41 | +public class PayWalletRechargeControllerSecurityTest { | |
| 42 | + | |
| 43 | + private static final String PERMISSION = "pay:wallet:update-balance"; | |
| 44 | + private static final Long USER_ID = 1L; | |
| 45 | + private static final Long RECHARGE_ID = 1024L; | |
| 46 | + | |
| 47 | + @Resource | |
| 48 | + private PayWalletRechargeController controller; | |
| 49 | + @Resource | |
| 50 | + private PayWalletRechargeService walletRechargeService; | |
| 51 | + @Resource | |
| 52 | + private PermissionCommonApi permissionApi; | |
| 53 | + | |
| 54 | + @BeforeEach | |
| 55 | + public void setUp() { | |
| 56 | + reset(walletRechargeService, permissionApi); | |
| 57 | + SecurityContextHolder.clearContext(); | |
| 58 | + } | |
| 59 | + | |
| 60 | + @AfterEach | |
| 61 | + public void tearDown() { | |
| 62 | + SecurityContextHolder.clearContext(); | |
| 63 | + } | |
| 64 | + | |
| 65 | + @Test | |
| 66 | + public void testControllerIsProxied() { | |
| 67 | + // 确认测试走的是方法安全代理,而不是裸对象 | |
| 68 | + assertTrue(AopUtils.isAopProxy(controller)); | |
| 69 | + } | |
| 70 | + | |
| 71 | + @Test | |
| 72 | + public void testRefundWalletRecharge_hasPermission() { | |
| 73 | + // mock 方法 | |
| 74 | + loginAdmin(); | |
| 75 | + when(permissionApi.hasAnyPermissions(eq(USER_ID), eq(PERMISSION))).thenReturn(true); | |
| 76 | + | |
| 77 | + // 调用 | |
| 78 | + assertTrue(controller.refundWalletRecharge(RECHARGE_ID).getData()); | |
| 79 | + // 断言 | |
| 80 | + verify(permissionApi).hasAnyPermissions(eq(USER_ID), eq(PERMISSION)); | |
| 81 | + verify(walletRechargeService).refundWalletRecharge(eq(RECHARGE_ID), isNull()); | |
| 82 | + } | |
| 83 | + | |
| 84 | + @Test | |
| 85 | + public void testRefundWalletRecharge_noPermission() { | |
| 86 | + // mock 方法 | |
| 87 | + loginAdmin(); | |
| 88 | + when(permissionApi.hasAnyPermissions(anyLong(), any(String[].class))).thenReturn(false); | |
| 89 | + | |
| 90 | + // 调用,并断言 | |
| 91 | + assertThrows(AccessDeniedException.class, () -> controller.refundWalletRecharge(RECHARGE_ID)); | |
| 92 | + verify(permissionApi).hasAnyPermissions(eq(USER_ID), eq(PERMISSION)); | |
| 93 | + verifyNoInteractions(walletRechargeService); | |
| 94 | + } | |
| 95 | + | |
| 96 | + @Test | |
| 97 | + public void testRefundWalletRecharge_anonymous() { | |
| 98 | + // 未登录:Web 过滤链会放入匿名用户 | |
| 99 | + SecurityContextHolder.getContext().setAuthentication(new AnonymousAuthenticationToken("key", "anonymousUser", | |
| 100 | + AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"))); | |
| 101 | + | |
| 102 | + // 调用,并断言 | |
| 103 | + assertThrows(AccessDeniedException.class, () -> controller.refundWalletRecharge(RECHARGE_ID)); | |
| 104 | + verifyNoInteractions(permissionApi, walletRechargeService); | |
| 105 | + } | |
| 106 | + | |
| 107 | + @Test | |
| 108 | + public void testRefundWalletRecharge_noAuthentication() { | |
| 109 | + // 没有任何认证信息,同样拒绝:取决于 Spring Security 是否读取认证信息,抛出拒绝访问或认证缺失 | |
| 110 | + Exception ex = assertThrows(Exception.class, () -> controller.refundWalletRecharge(RECHARGE_ID)); | |
| 111 | + assertTrue(ex instanceof AccessDeniedException || ex instanceof AuthenticationCredentialsNotFoundException, | |
| 112 | + "非预期的异常:" + ex); | |
| 113 | + verifyNoInteractions(permissionApi, walletRechargeService); | |
| 114 | + } | |
| 115 | + | |
| 116 | + @Test | |
| 117 | + public void testUpdateWalletRechargerPaid_callbackNotRestricted() { | |
| 118 | + // 支付回调:仍由 @PermitAll + 内部校验,不受后台权限约束 | |
| 119 | + PayOrderNotifyReqDTO reqDTO = PayOrderNotifyReqDTO.builder().merchantOrderId("1").payOrderId(2L).build(); | |
| 120 | + | |
| 121 | + // 调用 | |
| 122 | + assertTrue(controller.updateWalletRechargerPaid(reqDTO).getData()); | |
| 123 | + // 断言 | |
| 124 | + verify(walletRechargeService).updateWalletRechargerPaid(eq(1L), eq(2L)); | |
| 125 | + verifyNoInteractions(permissionApi); | |
| 126 | + } | |
| 127 | + | |
| 128 | + private static void loginAdmin() { | |
| 129 | + LoginUser loginUser = new LoginUser(); | |
| 130 | + loginUser.setId(USER_ID); | |
| 131 | + loginUser.setUserType(UserTypeEnum.ADMIN.getValue()); | |
| 132 | + loginUser.setTenantId(1L); | |
| 133 | + SecurityContextHolder.getContext().setAuthentication( | |
| 134 | + new UsernamePasswordAuthenticationToken(loginUser, null, Collections.emptyList())); | |
| 135 | + } | |
| 136 | + | |
| 137 | + @Configuration | |
| 138 | + @EnableMethodSecurity(securedEnabled = true) | |
| 139 | + public static class Config { | |
| 140 | + | |
| 141 | + @Bean | |
| 142 | + public PayWalletRechargeController payWalletRechargeController() { | |
| 143 | + return new PayWalletRechargeController(); | |
| 144 | + } | |
| 145 | + | |
| 146 | + @Bean | |
| 147 | + public PayWalletRechargeService payWalletRechargeService() { | |
| 148 | + return mock(PayWalletRechargeService.class); | |
| 149 | + } | |
| 150 | + | |
| 151 | + @Bean | |
| 152 | + public PermissionCommonApi permissionCommonApi() { | |
| 153 | + return mock(PermissionCommonApi.class); | |
| 154 | + } | |
| 155 | + | |
| 156 | + @Bean("ss") | |
| 157 | + public SecurityFrameworkService securityFrameworkService(PermissionCommonApi permissionApi) { | |
| 158 | + return new SecurityFrameworkServiceImpl(permissionApi); | |
| 159 | + } | |
| 160 | + | |
| 161 | + } | |
| 162 | + | |
| 163 | +} | ... | ... |