两层分开看这个提法是对的,而且有个几乎零成本的判据:直接看返回码是 401 还是 403。
链上 anyRequest().authenticated() 拦下来抛 AuthenticationException,经 ExceptionTranslationFilter 转成 401;@PreAuthorize 抛的是 AccessDeniedException,同一个过滤器转成 403。所以联调时链上明明 permitAll()、接口却回 403,基本可以直接跳到方法层去找,不用在链上反复排查。
方法层自己还有个坑值得一起记下:链放行后 SecurityContext 里装的是 AnonymousAuthenticationToken,此时 @PreAuthorize("isAuthenticated()") 照样 403 而不是 401,别拿"没登录就该 401"去倒推。另外注解靠 AOP 代理生效,同类内部 this.xxx() 自调用会整个绕过鉴权,这种漏检肉眼极难发现——要么把带注解的方法拆到独立 Bean,要么 exposeProxy 配 AopContext.currentProxy()。
PasswordEncoder 那段同意,单算法就用裸 BCrypt。补一句"为什么更难看出":裸 BCrypt 对 null 密文 matches 返回 false(只有 null rawPassword 才抛 IllegalArgumentException),所以密码列是空的时候表现就是纯 BadCredentialsException,像密码输错。断言我习惯放 loadUserByUsername 里,直接抛 IllegalStateException——这是数据问题不是用户问题,别混进 401 语义里。
真要用 DelegatingPasswordEncoder,记得把 defaultPasswordEncoderForMatches 显式指到 BCrypt,别留 null。

正式会员
认证极客









