如何为JAX-RS服务添加动态授权?实现用户专属资源访问控制
嘿,针对你遇到的这个REST接口权限控制问题——允许所有客户调用/rest/{custno}/machines/{machno}但只能查看自己的机器,而且之前的Interceptor在Tomcat 7/8里失效的情况,我给你整理几个实用的解决方案:
方案1:修复/适配Spring MVC HandlerInterceptor(适配Tomcat)
你之前在WebSphere上能用Interceptor但Tomcat不行,大概率是拦截器的注册方式或路径匹配逻辑在两个容器里有差异。试试用标准的Spring MVC HandlerInterceptor来实现:
- 自定义拦截器类,在
preHandle方法里完成权限校验:
public class MachineOwnershipInterceptor implements HandlerInterceptor { @Autowired private MachineService machineService; // 假设你有这个服务用来校验机器归属 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 从会话获取当前登录客户的custno String currentCustNo = (String) request.getSession().getAttribute("currentCustNo"); if (currentCustNo == null) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "未登录"); return false; } // 从URL路径解析参数:这里用Spring的PathVariable解析更可靠,或者自己拆分路径 String requestURI = request.getRequestURI(); // 比如路径格式是/rest/123/machines/456,拆分后获取custno和machno String[] parts = requestURI.split("/"); String pathCustNo = parts[2]; String machNo = parts[4]; // 两个校验:1. URL里的custno必须和当前用户一致;2. 该机器确实属于这个客户 if (!currentCustNo.equals(pathCustNo) || !machineService.isMachineOwnedByCust(pathCustNo, machNo)) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "无权限访问该机器"); return false; } return true; } }
- 注册拦截器到Spring MVC配置(JavaConfig示例):
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Autowired private MachineOwnershipInterceptor machineOwnershipInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(machineOwnershipInterceptor) .addPathPatterns("/rest/*/machines/*"); // 精准匹配目标接口路径 } }
这个方式在Tomcat和WebSphere上都能稳定运行,因为是Spring MVC的标准拦截器机制,不受容器差异影响。
方案2:用Spring Security的@PreAuthorize(更优雅的方法级控制)
既然@RolesAllowed满足不了资源归属校验的需求,试试Spring Security的@PreAuthorize,它支持SpEL表达式,可以直接在方法层面做细粒度校验:
- 首先开启方法级安全支持:
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) // 开启@PreAuthorize注解支持 public class SecurityConfig extends WebSecurityConfigurerAdapter { // 你的安全配置逻辑...比如用户认证、会话管理等 }
- 在你的REST接口方法上添加
@PreAuthorize:
@RestController @RequestMapping("/rest/{custno}/machines") public class MachineController { @Autowired private MachineService machineService; @GetMapping("/{machno}") @PreAuthorize("#custno == authentication.principal.custno && @machineService.isMachineOwnedByCust(#custno, #machno)") public Machine getMachine(@PathVariable String custno, @PathVariable String machno) { return machineService.getMachine(machno); } }
这里的SpEL表达式做了两件事:
- 校验URL里的
custno和当前登录用户的custno一致 - 调用
machineService校验该机器确实属于这个客户
如果校验不通过,Spring Security会自动返回403 Forbidden。
方案3:JAX-RS容器请求过滤器(如果是JAX-RS服务)
如果你用的是JAX-RS(比如Jersey、RESTeasy)而不是Spring MVC,那应该用标准的ContainerRequestFilter来实现,而不是自定义Interceptor:
@Provider @Priority(Priorities.AUTHORIZATION) // 确保在认证之后执行 public class MachineOwnershipFilter implements ContainerRequestFilter { @Context private UriInfo uriInfo; @Context private SecurityContext securityContext; @Autowired private MachineService machineService; @Override public void filter(ContainerRequestContext requestContext) throws IOException { // 获取路径参数 Map<String, String> pathParams = uriInfo.getPathParameters(); String custno = pathParams.getFirst("custno"); String machno = pathParams.getFirst("machno"); // 获取当前登录用户的custno(假设SecurityContext里存了用户信息) String currentCustNo = securityContext.getUserPrincipal().getName(); // 这里根据你的认证逻辑调整 if (!custno.equals(currentCustNo) || !machineService.isMachineOwnedByCust(custno, machno)) { requestContext.abortWith(Response.status(Response.Status.FORBIDDEN) .entity("无权限访问该机器") .build()); } } }
然后在JAX-RS配置里注册这个过滤器,就能在Tomcat等容器里正常生效了。
为什么之前的Interceptor在Tomcat失效?
大概率是WebSphere对自定义拦截器的生命周期、路径匹配规则和Tomcat不一致,比如WebSphere可能允许拦截器直接访问路径参数,而Tomcat需要通过Spring的请求上下文来获取;或者拦截器的注册顺序在Tomcat里没排对,导致还没拿到会话信息就执行了校验。用上面的标准框架拦截器/过滤器机制,就能避免容器差异带来的问题。
内容的提问来源于stack exchange,提问作者paul

