@PostAuthorize失效问题:如何阻止用户访问其他用户的Item资源
我来帮你捋一捋这个@PostAuthorize不生效的问题哈,大概率是这几个点没注意到:
这是最容易被忽略的前提!@PreAuthorize、@PostAuthorize这类方法级安全注解默认是不启用的,你需要在Spring Security的配置类上加上@EnableGlobalMethodSecurity(prePostEnabled = true)注解才能让它们生效:
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { // 你的其他安全配置代码 }
你当前用#model['username']来做对比,其实可以更直接地引用model里的item对象,既省去额外往model塞username的步骤,也能避免可能的引用问题:
@PostAuthorize("principal.username == #model['item'].user.username") @RequestMapping(value = "/show/{id}", method = RequestMethod.GET) public String single(@PathVariable Long id, Model model) { Item item = itemService.findById(id); model.addAttribute("item", item); return "item"; }
@PostAuthorize是方法执行完之后才做验证,也就是说不管用户有没有权限,item都会被查询出来,多少有点性能浪费和数据泄露风险。你可以换成@PreAuthorize,在方法执行前就完成权限校验:
方式一:直接在表达式中调用service
@PreAuthorize("principal.username == @itemService.findById(#id).user.username") @RequestMapping(value = "/show/{id}", method = RequestMethod.GET) public String single(@PathVariable Long id, Model model) { Item item = itemService.findById(id); model.addAttribute("item", item); return "item"; }
这里的@itemService是SpEL引用Spring Bean的语法,会直接调用service查询item并对比用户名。
方式二:封装权限校验方法避免重复查询
上面的方式会调用两次findById,你可以在service层加一个专门的权限校验方法:
// ItemService接口 boolean isItemOwnedByUser(Long itemId, String username); // ItemServiceImpl实现 @Override public boolean isItemOwnedByUser(Long itemId, String username) { Item item = findById(itemId); return item != null && username.equals(item.getUser().getUsername()); }
然后在控制器里用这个方法做校验:
@PreAuthorize("@itemService.isItemOwnedByUser(#id, principal.username)") @RequestMapping(value = "/show/{id}", method = RequestMethod.GET) public String single(@PathVariable Long id, Model model) { Item item = itemService.findById(id); model.addAttribute("item", item); return "item"; }
要确保当前用户的Authentication对象里的principal是包含username属性的对象(比如UserDetails的实现类),如果是自定义的用户对象,要保证它有public的getUsername()方法,这样SpEL才能正确读取principal.username。
按照上面的步骤排查,应该就能解决你的问题啦!
内容的提问来源于stack exchange,提问作者learnAndImprove

