使用 EnableWebSecurity 时 AuthenticationPrincipal 为空

Posted

技术标签:

【中文标题】使用 EnableWebSecurity 时 AuthenticationPrincipal 为空【英文标题】:AuthenticationPrincipal is empty when using EnableWebSecurity 【发布时间】:2015-06-21 19:35:07 【问题描述】:

截至Spring Security doc: 34.1 @EnableWebMvcSecurity 状态,@EnableWebMvcSecurity 已替换为@EnableWebSecurity

但是当我尝试通过@AuthenticationPrincipal 获取控制器中的UserDetails 时,我得到了一个空对象:用户名是""。我也试过@EnableWebMvcSecurity,但不幸的是UserDetailsnull

但我可以通过传统方式获得UserDetails,如下所示:

SecurityContextHolder.getContext().getAuthentication().getPrincipal();

我的问题是,当我使用 @EnableWebSecurity 时,获取自定义 UserDetails (Account) 的正确方法是什么?

以下是相关源码:

控制器:

@RequestMapping(method = RequestMethod.POST)
@Secured("ROLE_USER")
public String postRoom(@Valid @ModelAttribute Room room, BindingResult result, Model model, @AuthenticationPrincipal Account principal) 
    if (result.hasErrors()) 
        return "room_form";
    

    Account account = accountRepository.findByUsername(principal.getUsername());
    room.setAccountId(account.getId());
    room.setLastModified(new Date());
    roomRepository.save(room);
    return "room_list";

安全配置:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 
    @Autowired
    private DataSource dataSource;

    @Autowired
    private SecurityProperties security;

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http.authorizeRequests().anyRequest().permitAll()
            .and().formLogin().loginPage("/login").failureUrl("/login?error").permitAll()
            .and().logout().permitAll()
            .and().rememberMe()
            .and().csrf().disable();
    

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 
            auth.jdbcAuthentication().dataSource(this.dataSource).passwordEncoder(new BCryptPasswordEncoder(8));
    

还有Account.java

@Entity
@Table(name = "users")
public class Account implements Serializable 
    @Id
    @GeneratedValue
    private Long id;

    private String username;
    private String password;
    private boolean enabled;

    @Lob
    private byte[] avatar;

// getter / setter ...

【问题讨论】:

或许也可以添加您的安全配置。 Account 是用来通过UserDetailsService 进行身份验证的对象吗?还可以尝试使用Principal 对象而不是Account,看看是否有任何结果。 嗨,吉姆,我也有类似的问题。您是否找到了通过上下文获得Principal 而使用@AuthenticationPrincipal 注释时却没有的原因? 没有。请尝试传统方式。 【参考方案1】:

正如post 的其中一个 cmets 中所述,您返回的 Account 类需要从 authentication.getPrincipal() 分配,这意味着您的 Account 类很可能需要实现 UserDetails 接口(至少) 因为 org.springframework.security.core.userdetails.User 类确实如此。查看UserDetails 或page 的Java API 文档了解一下。

如果你把@AuthenticationPrincipal 后面的类型改成User,那么null 问题可能就消失了。

根据您设置数据库的方式,让 Account 类实现 UserDetails 可能会在 Account 中引入太多逻辑,因为它应该是一个模型。

由于您使用的是@EnableWebSecurity,因此您无需像某些帖子所建议的那样配置自定义HandlerMethodArgumentResolver

我的个人建议

注意我使用的是 Spring Boot

由于使用 jdbcAuthentication 需要您以预定义的方式设置数据库(而且您心目中的架构很可能与他们创建的架构完全不同),因此最好的办法是创建自定义架构并配置您自己的架构用户认证服务(不难)。就个人而言,我在configureGlobal... 方法中配置了自定义用户认证服务。一个班轮。

auth.userDetailsService(userService).passwordEncoder...

我的自定义 userService 实现了UserDetailsService 接口,它只为您提供了一种方法来实现public UserDetails loadUserByUsername(String username) Spring Security 将调用该方法来验证用户。在 loadUserByUsername 方法中,我从数据库中检索了我的用户信息,创建了一个新的 org.springframework.security.core.userdetails.User 对象,将用户名、密码和权限填充到 User 对象中并返回它。 我的方法结束时有以下内容。

return new User(user.getUsername(), user.getPassword(), permissions);

因为我返回了实现UserDetails 接口的User,所以@AuthenticationPrincipal User user 对我有用。

请注意permissions 变量必须是实现GrantedAuthority 接口的类或该类型的集合。该接口也只有一种方法供您实现public String getAuthority(),它基本上可以返回您喜欢的任何字符串(可能是您数据库中权限/角色的名称)。

我假设您知道 Spring Security 如何处理授权。令人难过的是,Spring Security 仅使用基于角色的授权(如果我错了,我很乐意纠正),而不是组 + 权限来进行更精细的控制。但是,您可以通过在数据库中创建 groups 表和 rights 表并让相应的类实现 GrantedAuthority 接口来解决此问题。您将有效地将“角色”分为这两个类别。

如果您真的热衷于通过@AuthenticationPrincipal 使用自己的课程,或者您想要的不仅仅是用户名和密码 我可能会在您的 Account 类周围创建一个包装器类(以从 Account 中取出逻辑)并让包装器类实现 UserDetails 我已经成功地做到了。

最后,我强烈建议您在存储库层之上添加一个服务层,并让您的控制器与服务层对话。此设置增加了一层安全性(因为黑客在到达您的存储库层之前也必须破解您的服务层)并且还将逻辑从存储库层中取出,因为它应该只用于 CRUD,仅此而已。

【讨论】:

【参考方案2】:

AuthenticationPrincipal 为空。 但我可以通过传统方式获取 UserDetails,如下所示:

SecurityContextHolder.getContext().getAuthentication().getPrincipal();

argument-resolvers 的配置对我有帮助

<mvc:annotation-driven>
    <mvc:argument-resolvers>
        <bean class="org.springframework.security.web.method.annotation.AuthenticationPrincipalArgumentResolver"/>
    </mvc:argument-resolvers>
</mvc:annotation-driven>

你也可以看到34.2 @AuthenticationPrincipal部分

一旦正确配置了 AuthenticationPrincipalArgumentResolver,您就可以在 Spring MVC 层中与 Spring Security 完全解耦。

我如何理解这意味着您应该手动添加参数解析器。或者

通过使用第 34.1 节,“@EnableWebMvcSecurity”,您将自动将其添加到您的 Spring MVC 配置中

还有related question

【讨论】:

【参考方案3】:

我还没有尝试过,但这应该适合你:

@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter 

    @Bean
    public AuthenticationPrincipalArgumentResolver authenticationPrincipalArgumentResolver()
        return new AuthenticationPrincipalArgumentResolver();
    

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) 
        argumentResolvers.add(authenticationPrincipalArgumentResolver());
    

【讨论】:

“requestEntityMethodArgumentResolver”这个变量是从哪里来的? @VijayShegokar 我只想删除那一点。不知道 Jannik 从哪里得到它,但在 Spring 5.1.13 中,这是不必要的。【参考方案4】:

我遇到了同样的问题。我必须自己制作HandlerMethodArgumentResolver 和注释。 以下代码已经过测试并且可以工作

首先创建注解

@Target( ElementType.PARAMETER, ElementType.ANNOTATION_TYPE )
@Retention(RetentionPolicy.RUNTIME)
public @interface CurrentUser

还有一个简单的配置

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "x.x.x")
public class ApplicationConfiguration extends WebMvcConfigurerAdapter

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) 
        argumentResolvers.add(new HandlerMethodArgumentResolver() 

            public boolean supportsParameter(MethodParameter parameter) 
                return findMethodAnnotation(CurrentUser.class, parameter) != null;
            

            public Object resolveArgument(
                    MethodParameter parameter,
                    ModelAndViewContainer mavContainer,
                    NativeWebRequest webRequest,
                    WebDataBinderFactory binderFactory) throws Exception
            
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                Object principal = authentication.getPrincipal();

                if(principal != null && !parameter.getParameterType().isAssignableFrom(principal.getClass()))
                    throw new ClassCastException(principal + " is not assignable to " + parameter.getParameterType());

                return principal;
            

            <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) 
                T annotation = parameter.getParameterAnnotation(annotationClass);
                if(annotation != null) 
                    return annotation;
                
                Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
                for(Annotation toSearch : annotationsToSearch) 
                    annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass);
                    if(annotation != null) 
                        return annotation;
                    
                
                return null;
            
        );
    

然后在控制器中使用它

@RequestMapping("/userget")
public User message(@CurrentUser User user)
    return user;

请注意,User 不再需要扩展 UserDetails。 希望这(将)有所帮助

【讨论】:

以上是关于使用 EnableWebSecurity 时 AuthenticationPrincipal 为空的主要内容,如果未能解决你的问题,请参考以下文章

Spring web security:@EnableWebSecurity 过时了吗?

java Spring @EnableResourceServer 和 @EnableWebSecurity

Spring security 的 @EnableWebSecurity 与 oauth 的 @EnableResourceServer

@EnableWebSecurity 和 @EnableWebFluxSecurity 有啥区别?

@EnableWebSecurity 和 @EnableWebMvcSecurity 有啥区别?

如何在@WebMvcTest 测试中忽略@EnableWebSecurity 注释类