AuthenticationPrincipal 返回空的 UserDetails 对象
Posted
技术标签:
【中文标题】AuthenticationPrincipal 返回空的 UserDetails 对象【英文标题】:AuthenticationPrincipal returns empty UserDetails object 【发布时间】:2020-03-10 04:51:46 【问题描述】:我正在尝试通过 spring security + jwt 令牌来保护我的 api 端点。到目前为止,令牌生成器和验证器运行良好。当我在方法参数中使用AuthenthicationPrincipal
来获取当前UserDetails
时,就会出现问题。我有我的班级Account
实现UserDetails
,我的TokenAuthenticationProvider
根据标头承载令牌提供必要的Account
。
配置和控制器部分代码:
@Configuration
@EnableWebSecurity(debug = true)
@EnableGlobalMethodSecurity(prePostEnabled = true)
@RequiredArgsConstructor
class SecurityConfig extends WebSecurityConfigurerAdapter
@Value("$spring.data.rest.basePath")
private String apiBasePath;
private RequestMatcher protectedUrls;
private RequestMatcher publicUrls;
@NotNull
private final TokenAuthenticationProvider provider;
@PostConstruct
private void postConstruct()
protectedUrls = new OrRequestMatcher(
// Api
new AntPathRequestMatcher(apiBasePath + "/**")
);
publicUrls = new NegatedRequestMatcher(protectedUrls);
@Override
protected void configure(HttpSecurity http) throws Exception
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
// when request a protected page without having authenticated
.defaultAuthenticationEntryPointFor(forbiddenEntryPoint(),
protectedUrls)
.and()
// Authenticating with rest requests
.authenticationProvider(provider)
.addFilterBefore(restAuthenticationFilter(), // injecting TokenAuthenticationProvider here
AnonymousAuthenticationFilter.class)
.authorizeRequests()
.requestMatchers(protectedUrls)
.authenticated()
.and()
// Disable server rendering for logging
.formLogin().disable()
.httpBasic().disable()
.logout().disable();
在调试模式下跟踪显示TokenAuthenticationProvider
已正确检索到Account
。只有在控制器中调用时,才会返回一个空的Account
(null
属性)
@RepositoryRestController
@RequiredArgsConstructor
@BasePathAwareController
@RequestMapping(path = "account")
class AccountController
@GetMapping("current")
@ResponseBody
Account getCurrent(@AuthenticationPrincipal Account account)
return account;
过滤器链是正确的:
servletPath:/api/account/current
pathInfo:null
headers:
authorization: Bearer eyJhbGciOiJIUzI1NiIsInppcCI6IkdaSVAi...
user-agent: PostmanRuntime/7.19.0
accept: */*
cache-control: no-cache
postman-token: 73da9eb7-2ee1-43e8-9cd0-2658e4f32d1f
host: localhost:8090
accept-encoding: gzip, deflate
connection: keep-alive
Security filter chain: [
WebAsyncManagerIntegrationFilter
SecurityContextPersistenceFilter
HeaderWriterFilter
CsrfFilter
RequestCacheAwareFilter
SecurityContextHolderAwareRequestFilter
TokenAuthenticationFilter
AnonymousAuthenticationFilter
SessionManagementFilter
ExceptionTranslationFilter
FilterSecurityInterceptor
]
我查看了一些教程和问题,但无法推断出任何合适的答案。
https://octoperf.com/blog/2018/03/08/securing-rest-api-spring-security/(这激发了我当前的实现)
https://svlada.com/jwt-token-authentication-with-spring-boot/#jwt-authentication
@AuthenticationPrincipal return empty User(这个使用DelegatingFlashMessagesConfiguration
,在我的情况下不存在)
和配置或过滤器的顺序有关系吗?
【问题讨论】:
我也有同样的问题,有解决办法吗? 在调试模式下,我清楚地看到身份验证包含在 SecurityContextHolder 中。但是,由于某种原因,AuthenticationPrincipal 注释没有选择它。因此,我的解决方法是直接通过SecurityContextHolder.getContext().getAuthentication().getPrincipal()
调用
我发现这与BasePathAwareController注解有关。在我的情况下,使用它注释的控制器不会收到填充的主体对象,而使用 RestController 注释的完全相同的控制器得到的信息就好了。
@JensWegar 我从来没有注意到它。你找到解决办法了吗?
@phet 不,还没有。目前我通过创建一个 bean 来解决它,该 bean 又调用 SecurityContextHolder.getContext().getAuthentication().getPrincipal()。不想到处直接调用静态方法,因为它使单元测试更难。这样我可以在需要时轻松地模拟和注入主体
【参考方案1】:
为了使@AuthenticationPrincipal 正常工作,我需要覆盖 addArgumentResolvers
@Configuration
public class WevMvcConfiguration extends WebMvcConfigurationSupport
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers)
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
new AuthenticationPrincipalArgumentResolver()
使用导入org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver
自定义类实现UserDetails
public class UserPrincipal implements UserDetails
private static final long serialVersionUID = 1L;
private ObjectId id;
private String name;
private String username;
private String email;
@JsonIgnore
private String password;
//constructor, getter, etc
现在工作得很好。
@GetMapping(value="/me")
public User getMe(@AuthenticationPrincipal UserPrincipal currentUser)
logger.debug("name: "+currentUser.getUsername());
return this.userService.findByUsername(currentUser.getUsername());
【讨论】:
+1 春季安全前的文档 4 状态您必须提供自己的AuthenticationPrincipalArgumentResolver
。我认为在上下文中单独声明 tat bean 就足够了。它不是。你的答案应该在文档中以上是关于AuthenticationPrincipal 返回空的 UserDetails 对象的主要内容,如果未能解决你的问题,请参考以下文章
@AuthenticationPrincipal 是如何工作的?
@AuthenticationPrincipal 与 Spring Boot 不工作
@AuthenticationPrincipal 返回空用户
使用 @AuthenticationPrincipal 注入自定义 OidcUser 包装器