使用EnableWebSecurity时,AuthenticationPrincipal为空

时间:2015-04-15 17:37:14

标签: spring spring-mvc spring-security

截至Spring Security doc: 34.1 @EnableWebMvcSecurity个州,@EnableWebMvcSecurity已被@EnableWebSecurity取代。

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

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

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

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

以下是相关的源代码:

控制器:

@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 ...
}

4 个答案:

答案 0 :(得分:2)

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

@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter {

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

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

答案 1 :(得分:2)

正如对此post的评论中所述,您返回的帐户类需要从authentication.getPrincipal()分配,这意味着您的帐户类很可能需要实现UserDetails接口(作为最低限度),因为org.springframework.security.core.userdetails.User类。请查看Java的UserDetailspage的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方法中,我从数据库中检索了我的用户信息,在User对象中创建了一个新的org.springframework.security.core.userdetails.User对象,填充的用户名,密码和权限并将其返回。 我的方法结束时有以下内容。

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

因为我返回了实现User界面的UserDetails@AuthenticationPrincipal User user对我有效。

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

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

如果您真的热衷于通过@AuthenticationPrincipal使用自己的课程,或者您需要的不仅仅是用户名和密码 我可能会围绕您的Account类创建一个包装类(从Account中取出逻辑)并使用包装类实现UserDetails我已成功完成此操作。

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

答案 2 :(得分:1)

  

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 :(得分:0)

我遇到了同样的问题。我必须自己制作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 anyamore。 希望这(将)帮助