swagger 推广后台 优化对战百佳历史接口
This commit is contained in:
@@ -29,12 +29,6 @@
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
<optional>true</optional> <!-- 表示依赖不会传递 -->
|
||||
</dependency>
|
||||
<!-- spring-doc -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Mysql驱动包 -->
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
|
||||
@@ -4,8 +4,6 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@@ -33,7 +31,6 @@ import com.ruoyi.system.service.ISysDictTypeService;
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Api(tags = "管理端 字段值管理")
|
||||
@RestController
|
||||
@RequestMapping("/system/dict/data")
|
||||
public class SysDictDataController extends BaseController
|
||||
@@ -44,7 +41,6 @@ public class SysDictDataController extends BaseController
|
||||
@Autowired
|
||||
private ISysDictTypeService dictTypeService;
|
||||
|
||||
@ApiOperation("获取字典值")
|
||||
@PreAuthorize("@ss.hasPermi('system:dict:list')")
|
||||
@GetMapping("/list")
|
||||
public TableDataInfo list(SysDictData dictData)
|
||||
@@ -91,7 +87,6 @@ public class SysDictDataController extends BaseController
|
||||
/**
|
||||
* 新增字典类型
|
||||
*/
|
||||
@ApiOperation("新增字典值")
|
||||
@PreAuthorize("@ss.hasPermi('system:dict:add')")
|
||||
@Log(title = "字典数据", businessType = BusinessType.INSERT)
|
||||
@PostMapping
|
||||
@@ -104,7 +99,6 @@ public class SysDictDataController extends BaseController
|
||||
/**
|
||||
* 修改保存字典类型
|
||||
*/
|
||||
@ApiOperation("修改字典值")
|
||||
@PreAuthorize("@ss.hasPermi('system:dict:edit')")
|
||||
@Log(title = "字典数据", businessType = BusinessType.UPDATE)
|
||||
@PutMapping
|
||||
|
||||
@@ -3,7 +3,6 @@ package com.ruoyi.web.controller.system;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import io.swagger.annotations.Api;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
@@ -24,7 +23,6 @@ import com.ruoyi.system.service.ISysMenuService;
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
@Api(tags = "管理端认证api")
|
||||
@RestController
|
||||
public class SysLoginController
|
||||
{
|
||||
|
||||
@@ -11,11 +11,10 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* swagger 用户测试方法
|
||||
* 用户测试方法(若依自带示例,未对外使用)
|
||||
*
|
||||
* @author ruoyi
|
||||
*/
|
||||
//@Api(tags = "用户信息管理")
|
||||
@RestController
|
||||
@RequestMapping("/test/user")
|
||||
public class TestController extends BaseController
|
||||
@@ -26,7 +25,6 @@ public class TestController extends BaseController
|
||||
users.put(2, new UserEntity(2, "ry", "admin123", "15666666666"));
|
||||
}
|
||||
|
||||
// @ApiOperation("获取用户列表")
|
||||
@GetMapping("/list")
|
||||
public R<List<UserEntity>> userList()
|
||||
{
|
||||
@@ -34,8 +32,6 @@ public class TestController extends BaseController
|
||||
return R.ok(userList);
|
||||
}
|
||||
|
||||
// @ApiOperation("获取用户详细")
|
||||
// @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
|
||||
@GetMapping("/{userId}")
|
||||
public R<UserEntity> getUser(@PathVariable Integer userId)
|
||||
{
|
||||
@@ -49,13 +45,6 @@ public class TestController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
// @ApiOperation("新增用户")
|
||||
// @ApiImplicitParams({
|
||||
// @ApiImplicitParam(name = "userId", value = "用户id", dataType = "Integer", dataTypeClass = Integer.class),
|
||||
// @ApiImplicitParam(name = "username", value = "用户名称", dataType = "String", dataTypeClass = String.class),
|
||||
// @ApiImplicitParam(name = "password", value = "用户密码", dataType = "String", dataTypeClass = String.class),
|
||||
// @ApiImplicitParam(name = "mobile", value = "用户手机", dataType = "String", dataTypeClass = String.class)
|
||||
// })
|
||||
@PostMapping("/save")
|
||||
public R<String> save(UserEntity user)
|
||||
{
|
||||
@@ -67,7 +56,6 @@ public class TestController extends BaseController
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
// @ApiOperation("更新用户")
|
||||
@PutMapping("/update")
|
||||
public R<String> update(@RequestBody UserEntity user)
|
||||
{
|
||||
@@ -84,8 +72,6 @@ public class TestController extends BaseController
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
// @ApiOperation("删除用户信息")
|
||||
// @ApiImplicitParam(name = "userId", value = "用户ID", required = true, dataType = "int", paramType = "path", dataTypeClass = Integer.class)
|
||||
@DeleteMapping("/{userId}")
|
||||
public R<String> delete(@PathVariable Integer userId)
|
||||
{
|
||||
@@ -101,19 +87,14 @@ public class TestController extends BaseController
|
||||
}
|
||||
}
|
||||
|
||||
//@ApiModel(value = "UserEntity", description = "用户实体")
|
||||
class UserEntity
|
||||
{
|
||||
// @ApiModelProperty("用户ID")
|
||||
private Integer userId;
|
||||
|
||||
// @ApiModelProperty("用户名称")
|
||||
private String username;
|
||||
|
||||
// @ApiModelProperty("用户密码")
|
||||
private String password;
|
||||
|
||||
// @ApiModelProperty("用户手机")
|
||||
private String mobile;
|
||||
|
||||
public UserEntity()
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
package com.ruoyi.web.core.config;
|
||||
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import io.swagger.models.auth.In;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import springfox.documentation.builders.PathSelectors;
|
||||
import springfox.documentation.builders.RequestHandlerSelectors;
|
||||
import springfox.documentation.service.ApiKey;
|
||||
import springfox.documentation.service.AuthorizationScope;
|
||||
import springfox.documentation.service.SecurityReference;
|
||||
import springfox.documentation.service.SecurityScheme;
|
||||
import springfox.documentation.spi.DocumentationType;
|
||||
import springfox.documentation.spi.service.contexts.SecurityContext;
|
||||
import springfox.documentation.spring.web.plugins.Docket;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
public class SwaggerConfig {
|
||||
|
||||
/**
|
||||
* 后台接口分组
|
||||
*/
|
||||
// @Bean
|
||||
public Docket adminDocket() {
|
||||
return new Docket(DocumentationType.OAS_30)
|
||||
.select()
|
||||
// 扫描注解
|
||||
.apis(RequestHandlerSelectors.basePackage("com.ruoyi.admin.controller"))
|
||||
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
|
||||
// 扫描所有.apis(RequestHandlerSelectors.any())
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.groupName("admin")
|
||||
// 设置安全模式,访问token
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户业务接口分组
|
||||
*/
|
||||
@Bean
|
||||
public Docket userDocket() {
|
||||
return new Docket(DocumentationType.OAS_30)
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.ruoyi.user.controller"))
|
||||
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.groupName("user")
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
/**
|
||||
* 第三方业务接口分组
|
||||
*/
|
||||
@Bean
|
||||
public Docket thirdPartyDocket() {
|
||||
return new Docket(DocumentationType.OAS_30)
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.ruoyi.thirdparty"))
|
||||
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.groupName("thirdParty")
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
/**
|
||||
* 玩法业务接口分组
|
||||
*/
|
||||
@Bean
|
||||
public Docket playingMethodDocket() {
|
||||
return new Docket(DocumentationType.OAS_30)
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.ruoyi.playingmethod.controller"))
|
||||
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.groupName("playingMethod")
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
/**
|
||||
* 糖果玩法业务接口分组
|
||||
*/
|
||||
@Bean
|
||||
public Docket gameSugarMethodDocket() {
|
||||
return new Docket(DocumentationType.OAS_30)
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.ruoyi.game.sugar.controller"))
|
||||
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.groupName("gameSugarMethod")
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
/**
|
||||
* 任务业务接口分组
|
||||
*/
|
||||
@Bean
|
||||
public Docket taskDocket() {
|
||||
return new Docket(DocumentationType.OAS_30)
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.ruoyi.task.controller"))
|
||||
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.groupName("task")
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
/**
|
||||
* 推广后台接口分组
|
||||
*/
|
||||
@Bean
|
||||
public Docket promoDocket() {
|
||||
return new Docket(DocumentationType.OAS_30)
|
||||
.select()
|
||||
.apis(RequestHandlerSelectors.basePackage("com.ruoyi.promo.controller"))
|
||||
.apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
|
||||
.paths(PathSelectors.any())
|
||||
.build()
|
||||
.groupName("promo")
|
||||
.securitySchemes(securitySchemes())
|
||||
.securityContexts(securityContexts());
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全模式,这里指定token通过Authorization请求头传递
|
||||
*/
|
||||
private List<SecurityScheme> securitySchemes() {
|
||||
List<SecurityScheme> apiKeyList = new ArrayList<SecurityScheme>();
|
||||
apiKeyList.add(new ApiKey("Authorization", "Authorization", In.HEADER.toValue()));
|
||||
return apiKeyList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全上下文
|
||||
*/
|
||||
private List<SecurityContext> securityContexts() {
|
||||
List<SecurityContext> securityContexts = new ArrayList<>();
|
||||
securityContexts.add(SecurityContext.builder()
|
||||
.securityReferences(defaultAuth())
|
||||
.operationSelector(o -> o.requestMappingPattern().matches("/.*"))
|
||||
.build());
|
||||
return securityContexts;
|
||||
}
|
||||
|
||||
/**
|
||||
* 默认的安全引用
|
||||
*/
|
||||
private List<SecurityReference> defaultAuth() {
|
||||
AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
|
||||
AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
|
||||
authorizationScopes[0] = authorizationScope;
|
||||
List<SecurityReference> securityReferences = new ArrayList<>();
|
||||
securityReferences.add(new SecurityReference("Authorization", authorizationScopes));
|
||||
return securityReferences;
|
||||
}
|
||||
}
|
||||
@@ -70,12 +70,12 @@ spring:
|
||||
enabled: true
|
||||
statViewServlet:
|
||||
enabled: true
|
||||
# 设置白名单,不填则允许所有访问
|
||||
allow:
|
||||
# 白名单:只允许本机访问(原来留空 = 任意 IP 都能打开控制台)
|
||||
allow: 127.0.0.1
|
||||
url-pattern: /druid/*
|
||||
# 控制台管理用户名和密码
|
||||
login-username: ruoyi
|
||||
login-password: 123456
|
||||
# 控制台管理用户名和密码(河南单独一套,勿用常州/默认值)
|
||||
login-username: druidhn
|
||||
login-password: Hn@Druid#7t4Wq9
|
||||
filter:
|
||||
stat:
|
||||
enabled: true
|
||||
|
||||
@@ -69,13 +69,15 @@ spring:
|
||||
webStatFilter:
|
||||
enabled: true
|
||||
statViewServlet:
|
||||
enabled: true
|
||||
# 设置白名单,不填则允许所有访问
|
||||
allow:
|
||||
# 生产关闭 Druid 控制台:/druid/** 是匿名放行的,开着等于把全部 SQL 语句、URI 统计、连接信息挂到公网
|
||||
# 需要临时排障时改成 true,并保持 allow 白名单 + 非默认口令,用完立刻关回
|
||||
enabled: false
|
||||
# 白名单:只允许本机访问(原来留空 = 任意 IP)
|
||||
allow: 127.0.0.1
|
||||
url-pattern: /druid/*
|
||||
# 控制台管理用户名和密码
|
||||
login-username: ruoyi
|
||||
login-password: 123456
|
||||
# 控制台管理用户名和密码(河南单独一套,临时开启时使用,勿改回默认值)
|
||||
login-username: druidhn
|
||||
login-password: Hn@Druid#7t4Wq9
|
||||
filter:
|
||||
stat:
|
||||
enabled: true
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.ruoyi.framework.config;
|
||||
|
||||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||
import com.ruoyi.common.core.domain.entity.UserData;
|
||||
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
/**
|
||||
* 三端 token 隔离规则验证(不启动 Spring,直接测 SecurityConfig 里的三个判定方法):
|
||||
* <ul>
|
||||
* <li>后台面 /admin|/system|/monitor|/tool|/test → 只认后台 sys_user 账号签发的 token</li>
|
||||
* <li>推广面 /promo/** → 只认推广后台签发的 token(loginScope = promo)</li>
|
||||
* <li>玩家面 /api/** → 只认玩家端签发的 token(loginScope = player)</li>
|
||||
* </ul>
|
||||
* 关键点:主播(01)/招商(04)/卡商(05) 是同一张 tt_user 表、两端都能登录,LoginUser 长得一模一样,
|
||||
* 只有登录端标记能区分,所以「主播的玩家端 token」不能进推广接口,「主播的推广 token」也不能进玩家接口。
|
||||
*/
|
||||
public class SecurityRulesTest {
|
||||
|
||||
private final SecurityConfig config = new SecurityConfig();
|
||||
|
||||
/** 后台账号登录(sys_user) */
|
||||
private static LoginUser adminLogin() {
|
||||
LoginUser loginUser = new LoginUser();
|
||||
loginUser.setUser(new SysUser());
|
||||
loginUser.setLoginScope(LoginUser.SCOPE_ADMIN);
|
||||
return loginUser;
|
||||
}
|
||||
|
||||
/** 推广后台登录(超管 99 / 主播 01 / 招商 04 / 卡商 05) */
|
||||
private static LoginUser promoLogin(String userType) {
|
||||
LoginUser loginUser = new LoginUser();
|
||||
loginUser.setUserData(UserData.builder().userType(userType).build());
|
||||
loginUser.setLoginScope(LoginUser.SCOPE_PROMO);
|
||||
return loginUser;
|
||||
}
|
||||
|
||||
/** 玩家端登录(通用用户 00 / 主播 01 / 招商 04 / 卡商 05) */
|
||||
private static LoginUser playerLogin(String userType) {
|
||||
LoginUser loginUser = new LoginUser();
|
||||
loginUser.setUserData(UserData.builder().userType(userType).build());
|
||||
loginUser.setLoginScope(LoginUser.SCOPE_PLAYER);
|
||||
return loginUser;
|
||||
}
|
||||
|
||||
/** 本次改造前签发的老 token:没有登录端标记 */
|
||||
private static LoginUser legacyLogin(String userType) {
|
||||
LoginUser loginUser = new LoginUser();
|
||||
loginUser.setUserData(UserData.builder().userType(userType).build());
|
||||
return loginUser;
|
||||
}
|
||||
|
||||
/** 已认证的令牌:与 JwtAuthenticationTokenFilter 一致,用三参构造(两参构造是"未认证"状态) */
|
||||
private static Supplier<Authentication> auth(LoginUser principal) {
|
||||
return principal == null ? () -> null
|
||||
: () -> new UsernamePasswordAuthenticationToken(principal, null, Collections.emptyList());
|
||||
}
|
||||
|
||||
/** 未认证的令牌:两参构造,isAuthenticated() 为 false */
|
||||
private static Supplier<Authentication> unauthenticated(LoginUser principal) {
|
||||
return () -> new UsernamePasswordAuthenticationToken(principal, null);
|
||||
}
|
||||
|
||||
private boolean adminAllowed(LoginUser principal) {
|
||||
return config.checkAdminUser(auth(principal), null).isGranted();
|
||||
}
|
||||
|
||||
private boolean promoAllowed(LoginUser principal) {
|
||||
return config.checkPromoUser(auth(principal), null).isGranted();
|
||||
}
|
||||
|
||||
private boolean playerAllowed(LoginUser principal) {
|
||||
return config.checkPlayerUser(auth(principal), null).isGranted();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void adminEndOnlyAcceptsBackendAccount() {
|
||||
assertTrue(adminAllowed(adminLogin()), "后台账号应能访问后台接口");
|
||||
assertFalse(adminAllowed(promoLogin("99")), "推广超管 token 不能访问后台接口");
|
||||
assertFalse(adminAllowed(promoLogin("01")), "推广 token 不能访问后台接口");
|
||||
assertFalse(adminAllowed(playerLogin("00")), "玩家 token 不能访问后台接口");
|
||||
assertFalse(adminAllowed(playerLogin("01")), "主播的玩家端 token 不能访问后台接口");
|
||||
assertFalse(adminAllowed(null), "未登录不能访问后台接口");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void promoEndOnlyAcceptsPromoToken() {
|
||||
for (String userType : new String[]{"99", "04", "01", "05"}) {
|
||||
assertTrue(promoAllowed(promoLogin(userType)), "推广后台 token(" + userType + ")应能访问推广接口");
|
||||
}
|
||||
assertFalse(promoAllowed(playerLogin("01")), "主播的玩家端 token 不能访问推广接口");
|
||||
assertFalse(promoAllowed(playerLogin("00")), "玩家 token 不能访问推广接口");
|
||||
assertFalse(promoAllowed(adminLogin()), "后台 token 不能访问推广接口");
|
||||
assertFalse(promoAllowed(legacyLogin("01")), "本次改造前的老 token 没有端标记,必须重新登录");
|
||||
assertFalse(promoAllowed(null), "未登录不能访问推广接口");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void playerEndOnlyAcceptsPlayerToken() {
|
||||
for (String userType : new String[]{"00", "01", "04", "05"}) {
|
||||
assertTrue(playerAllowed(playerLogin(userType)), "玩家端 token(" + userType + ")应能访问 /api/**");
|
||||
}
|
||||
assertFalse(playerAllowed(promoLogin("01")), "主播的推广 token 不能访问 /api/**");
|
||||
assertFalse(playerAllowed(promoLogin("99")), "推广超管 token 不能访问 /api/**");
|
||||
assertFalse(playerAllowed(adminLogin()), "后台 token 不能访问 /api/**");
|
||||
assertFalse(playerAllowed(legacyLogin("00")), "本次改造前的老 token 没有端标记,必须重新登录");
|
||||
assertFalse(playerAllowed(null), "未登录不能访问 /api/**");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unauthenticatedTokenIsDeniedEverywhere() {
|
||||
Supplier<Authentication> notAuthenticated = unauthenticated(adminLogin());
|
||||
assertFalse(config.checkAdminUser(notAuthenticated, null).isGranted());
|
||||
assertFalse(config.checkPromoUser(notAuthenticated, null).isGranted());
|
||||
assertFalse(config.checkPlayerUser(notAuthenticated, null).isGranted());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user