swagger 推广后台 优化对战百佳历史接口
This commit is contained in:
@@ -5,14 +5,12 @@ import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.CacheControl;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
@@ -54,11 +52,6 @@ public class ResourcesConfig implements WebMvcConfigurer
|
||||
|
||||
registry.addResourceHandler("/upload/**")
|
||||
.addResourceLocations("file:" + RuoYiConfig.getProfile() + "/upload/");
|
||||
|
||||
/** swagger配置 */
|
||||
registry.addResourceHandler("/swagger-ui/**")
|
||||
.addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
|
||||
.setCacheControl(CacheControl.maxAge(5, TimeUnit.HOURS).cachePublic());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -36,6 +36,7 @@ import java.util.function.Supplier;
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
|
||||
public class SecurityConfig {
|
||||
|
||||
@Qualifier("apiUserDetailsServiceImpl")
|
||||
@Autowired
|
||||
private UserDetailsService apiUserDetailsService;
|
||||
@@ -101,13 +102,21 @@ public class SecurityConfig {
|
||||
// 静态资源
|
||||
.requestMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html",
|
||||
"/**/*.css", "/**/*.js", "/profile/**", "/uploads/**", "/upload/**").permitAll()
|
||||
// Swagger & Druid
|
||||
.requestMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
|
||||
// Druid 监控台:生产已在 application-pro.yml 关闭 statViewServlet(enabled=false)
|
||||
// 并限制 allow=127.0.0.1 + 非默认口令;这里保留放行是为了本地开发能打开控制台
|
||||
.requestMatchers("/druid/**").permitAll()
|
||||
// WebSocket
|
||||
.requestMatchers("/api/websocket/skins").permitAll()
|
||||
.requestMatchers("/ws/**").permitAll()
|
||||
// 后台接口:只认「后台 sys_user 账号」的 token(详见 checkAdminUser)
|
||||
.requestMatchers("/admin/**").access(this::checkAdminUser)
|
||||
// 后台接口(游戏管理后台 + 框架自带系统管理/监控/代码生成/测试接口):
|
||||
// 只认「后台 sys_user 账号」的 token,推广 / 玩家 token 一律 403(详见 checkAdminUser)
|
||||
// /common/** 是文件上传下载(只有后台 ruoyi-ui 在用),不放进三端前缀里会对所有端开放
|
||||
.requestMatchers("/admin/**", "/system/**", "/monitor/**", "/tool/**", "/test/**", "/common/**")
|
||||
.access(this::checkAdminUser)
|
||||
// 推广接口:只认推广身份的 token,玩家(00)与后台账号一律 403(详见 checkPromoUser)
|
||||
.requestMatchers("/promo/**").access(this::checkPromoUser)
|
||||
// 玩家接口:只认玩家端身份的 token,推广超管与后台账号一律 403(详见 checkPlayerUser)
|
||||
.requestMatchers("/api/**").access(this::checkPlayerUser)
|
||||
// 其他所有请求都需要认证
|
||||
.anyRequest().authenticated();
|
||||
})
|
||||
@@ -138,8 +147,8 @@ public class SecurityConfig {
|
||||
* → LoginUser.userData 是 UserData,LoginUser.user 为 null → 拒绝(403)。
|
||||
* 这样即使某个后台 Controller 忘了标 @PreAuthorize,也不会被玩家 token 调用。
|
||||
*/
|
||||
private AuthorizationDecision checkAdminUser(Supplier<? extends Authentication> authentication,
|
||||
RequestAuthorizationContext context) {
|
||||
AuthorizationDecision checkAdminUser(Supplier<? extends Authentication> authentication,
|
||||
RequestAuthorizationContext context) {
|
||||
Authentication auth = authentication.get();
|
||||
if (auth == null || !auth.isAuthenticated()) {
|
||||
return new AuthorizationDecision(false);
|
||||
@@ -150,6 +159,48 @@ public class SecurityConfig {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* /promo/** 的访问判定:只允许「推广后台签发」的 token(loginScope = promo)。
|
||||
*
|
||||
* 同一个 tt_user(主播 01 / 招商 04 / 卡商 05)既能登录玩家端也能登录推广后台,
|
||||
* 两端拿到的 LoginUser(userId + userData)完全一样,光看 userType 区分不了,
|
||||
* 所以这里必须按登录端标记卡:玩家端签发的 token 即使账号是主播,也进不来推广接口。
|
||||
*/
|
||||
AuthorizationDecision checkPromoUser(Supplier<? extends Authentication> authentication,
|
||||
RequestAuthorizationContext context) {
|
||||
LoginUser loginUser = loginUserOf(authentication);
|
||||
if (loginUser == null || loginUser.getUserData() == null) {
|
||||
return new AuthorizationDecision(false);
|
||||
}
|
||||
return new AuthorizationDecision(LoginUser.SCOPE_PROMO.equals(loginUser.getLoginScope()));
|
||||
}
|
||||
|
||||
/**
|
||||
* /api/** 的访问判定:只允许「玩家端签发」的 token(loginScope = player)。
|
||||
*
|
||||
* 推广后台 token(哪怕底层账号就是 tt_user)一律拒绝;后台账号(sys_user)没有 userData,也进不来。
|
||||
*/
|
||||
AuthorizationDecision checkPlayerUser(Supplier<? extends Authentication> authentication,
|
||||
RequestAuthorizationContext context) {
|
||||
LoginUser loginUser = loginUserOf(authentication);
|
||||
if (loginUser == null || loginUser.getUserData() == null) {
|
||||
return new AuthorizationDecision(false);
|
||||
}
|
||||
return new AuthorizationDecision(LoginUser.SCOPE_PLAYER.equals(loginUser.getLoginScope()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 取出已认证的 LoginUser,其余情况(匿名 / 未认证 / 非本框架主体)返回 null
|
||||
*/
|
||||
private LoginUser loginUserOf(Supplier<? extends Authentication> authentication) {
|
||||
Authentication auth = authentication.get();
|
||||
if (auth == null || !auth.isAuthenticated()) {
|
||||
return null;
|
||||
}
|
||||
return auth.getPrincipal() instanceof LoginUser loginUser ? loginUser : null;
|
||||
}
|
||||
|
||||
|
||||
@Bean
|
||||
public BCryptPasswordEncoder bCryptPasswordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
|
||||
+3
-1
@@ -61,6 +61,8 @@ public class UserDetailsServiceImpl implements UserDetailsService
|
||||
|
||||
public UserDetails createLoginUser(SysUser user)
|
||||
{
|
||||
return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
|
||||
LoginUser loginUser = new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
|
||||
loginUser.setLoginScope(LoginUser.SCOPE_ADMIN);
|
||||
return loginUser;
|
||||
}
|
||||
}
|
||||
|
||||
+5
@@ -45,6 +45,11 @@ public class WsTokenService {
|
||||
if (loginUser == null) {
|
||||
return null;
|
||||
}
|
||||
// 只认玩家端 token:推广/后台 token 即使 userId 撞上某个 tt_user 的 id 也不能连玩家 WS
|
||||
// (与 SecurityConfig.checkPlayerUser 口径一致;本次改造前签发的老 token 无端标记,同样要重新登录)
|
||||
if (!LoginUser.SCOPE_PLAYER.equals(loginUser.getLoginScope())) {
|
||||
return null;
|
||||
}
|
||||
return loginUser.getUserId().equals(expectedUserId) ? uuid : null;
|
||||
} catch (Exception e) {
|
||||
log.warn("WS Token 校验异常, userId={}", expectedUserId, e);
|
||||
|
||||
Reference in New Issue
Block a user