使用EnableWebSecurity时AuthenticationPrincipal为空 [英] AuthenticationPrincipal is empty when using EnableWebSecurity

查看:245
本文介绍了使用EnableWebSecurity时AuthenticationPrincipal为空的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

截至春季安全性文档:34.1 @EnableWebMvcSecurity 指出,@EnableWebMvcSecurity被替换为@EnableWebSecurity.

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

But when I try to get the UserDetails in controller by the @AuthenticationPrincipal, I got a empty object: the username is "". I also tried the @EnableWebMvcSecurity, but unfortunately the UserDetails is null.

但是我可以通过传统方式获得UserDetails,就像这样:

But I can get the UserDetails by the traditional way, like this:

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

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

My question is, what the correct way to get my custom UserDetails (Account) when I use @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 ...
}

推荐答案

如对此页面.

As mentioned in one of the comments on this post, the Account class you're returning needs to be assignable from authentication.getPrincipal() which means your Account class most likely needs to implement the UserDetails interface (as a minimum) since the org.springframework.security.core.userdetails.User class does. Look at Java's API doc for UserDetails or this page for a feel.

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

If you change the type after @AuthenticationPrincipal to User, the null problem will probably go away.

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

Depending on how you setup your database, having the Account class implement UserDetails might introduce too much logic into Account since it's supposed to be a model.

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

Since you're using @EnableWebSecurity, you don't need to configure a custom HandlerMethodArgumentResolver as some posts suggest.

请注意我正在使用Spring Boot

Note I'm using Spring Boot

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

Since using jdbcAuthentication requires you to setup your database in a pre-defined way (and most likely the schema you had in mind is nothing like the one they created), your best bet is to create a custom schema and configure your own user authentication service (it's not hard). Personally, I configured a custom user authentication service in the configureGlobal... method. One liner.

auth.userDetailsService(userService).passwordEncoder...

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

where my custom userService implements the UserDetailsService interface which only provides one method for you to implement public UserDetails loadUserByUsername(String username) Spring Security will call that method to authenticate users. Within that loadUserByUsername method, I retrieved my user info from the database, created a new org.springframework.security.core.userdetails.User object, stuffed username, password, and permissions into the User object and returned it. I have the following at the end of my method.

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

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

Because I returned User which implements UserDetails interface, @AuthenticationPrincipal User user will work for me.

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

DO NOTE that permissions variable needs to be a class which implements the GrantedAuthority interface, or a collection of that type. That interface also has only one method for you to implement public String getAuthority(), which basically can return any string you like (presumably the names of permission / roles in your database).

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

I'll assume you know how Spring Security deals with authorization. It's saddening that Spring Security uses solely Role-based authorization (if I'm wrong I'd be happy to be corrected), instead of group + permission for a more granular control. You can, however, work around that by creating a groups table and a rights table in your database and having a corresponding classes implement GrantedAuthority interface. You'll effectively be separating "roles" into those two categories.

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

If you are really keen on using your own class via @AuthenticationPrincipal, or if you want more than username and password I would probably create a wrapper class around your Account class (to take logic out of Account) and have the wrapper class implement UserDetails I've done this successfully.

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

Lastly, I would highly recommend adding a service layer on top of your repository layer, and get your controllers to talk to the service layer. This setup adds a layer of security (since hackers will have to hack your service layer as well before reaching your repository layer) and also takes the logic out the repository layer since it should only be used for CRUD, nothing more.

这篇关于使用EnableWebSecurity时AuthenticationPrincipal为空的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆