提交所有代码

This commit is contained in:
郭庆泰
2026-08-31 19:01:00 +08:00
parent f4d8bc1463
commit a14d95288c
53 changed files with 1356 additions and 286 deletions
@@ -16,6 +16,7 @@ import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.filter.CorsFilter;
import com.ruoyi.framework.config.properties.PermitAllUrlProperties;
import com.ruoyi.framework.security.filter.JwtAuthenticationTokenFilter;
import com.ruoyi.framework.security.filter.LeaderReadOnlyFilter;
import com.ruoyi.framework.security.handle.AuthenticationEntryPointImpl;
import com.ruoyi.framework.security.handle.LogoutSuccessHandlerImpl;
@@ -80,6 +81,8 @@ public class SecurityConfig
})
.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
// leader 只读拦截: 在 JWT 鉴权之后 (SecurityContext 已就绪), 任何业务写操作前拦下
.addFilterAfter(new LeaderReadOnlyFilter(), JwtAuthenticationTokenFilter.class)
.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
.addFilterBefore(corsFilter, LogoutFilter.class)
.build();
@@ -0,0 +1,84 @@
package com.ruoyi.framework.security.filter;
import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.HttpMethod;
import org.springframework.web.filter.OncePerRequestFilter;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.common.constant.HttpStatus;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ServletUtils;
import com.ruoyi.common.utils.StringUtils;
/**
* 「项目负责人」(leader) 角色只读拦截.
*
* <p>leader 是纯查看角色: 除了查看自己的资料/改密码 (账号信息页), 不允许任何业务写操作.
* 后端兜底 (前端隐藏按钮之外, 防 devtools 直接调接口):
* <ul>
* <li>GET 请求一律放行.</li>
* <li>非 GET (POST/PUT/DELETE) 仅放行本人资料维护: {@code /system/user/profile} 与
* {@code /system/user/profile/updatePwd} (SysProfileController 的 GET/PUT 与改密码).</li>
* <li>其余非 GET 一律 403.</li>
* </ul>
* 与 JwtAuthenticationTokenFilter 之后注册 (SecurityConfig), 依赖其已写入 SecurityContext.
*/
public class LeaderReadOnlyFilter extends OncePerRequestFilter
{
/** 放行的本人资料维护路径 (账号信息页) */
private static final String[] SELF_SERVICE_ALLOWED = {
"/system/user/profile",
"/system/user/profile/updatePwd",
};
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException
{
if (HttpMethod.GET.matches(request.getMethod())
|| !isLeader()
|| isSelfService(request.getRequestURI()))
{
chain.doFilter(request, response);
return;
}
// leader 非 GET 且非本人资料维护 → 只读拒绝
response.setStatus(HttpStatus.FORBIDDEN);
ServletUtils.renderString(response, JSON.toJSONString(
AjaxResult.error(HttpStatus.FORBIDDEN, "项目负责人无操作权限")));
}
/** 当前登录人是否为 leader (role_type 单一可信源), 未登录/异常视为非 leader (放行交给后续鉴权) */
private boolean isLeader()
{
try
{
return "leader".equals(SecurityUtils.getLoginUser().getUser().getRoleType());
}
catch (Exception e)
{
return false;
}
}
/** 是否命中本人资料维护白名单 (账号信息页: 查看/修改资料 + 改密码) */
private boolean isSelfService(String uri)
{
if (StringUtils.isEmpty(uri))
{
return false;
}
for (String path : SELF_SERVICE_ALLOWED)
{
if (uri.equals(path) || uri.startsWith(path + "/"))
{
return true;
}
}
return false;
}
}