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。只有在控制器中调用时,才会返回一个空的Accountnull 属性)

@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 包装器

使用 EnableWebSecurity 时 AuthenticationPrincipal 为空

AuthenticationPrincipal 返回空的 UserDetails 对象