Spring Security doc: 34.1 @EnableWebMvcSecurity状态开始,@EnableWebMvcSecurity替换为@EnableWebSecurity

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

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

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

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

以下是相关的源代码:

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

最佳答案

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

如果将@AuthenticationPrincipal之后的类型更改为User,则空问题可能会消失。

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

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

我个人的建议

注意我正在使用Spring Boot

由于使用jdbcAuthentication要求您以预定义的方式设置数据库(而且很可能您想到的架构与其创建的架构完全不同),所以最好的选择是创建一个自定义架构并配置您自己的用户身份验证服务(不难)。我个人使用configureGlobal...方法配置了自定义用户身份验证服务。一支类轮。

auth.userDetailsService(userService).passwordEncoder...

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

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

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

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

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

最后,我强烈建议您在存储库层的顶部添加一个服务层,并让您的 Controller 与该服务层进行对话。此设置增加了一层安全性(因为黑客在进入您的存储库层之前也必须要对您的服务层进行黑客攻击),并且也将逻辑删除了存储库层,因为它仅应用于CRUD,仅此而已。

关于spring - 使用EnableWebSecurity时AuthenticationPrincipal为空,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/29657039/

10-15 19:01