截至Spring Security doc: 34.1 @EnableWebMvcSecurity州,@ EnableWebMvcSecurity被@EnableWebSecurity取代.
但是当我尝试通过@AuthenticationPrincipal在控制器中获取UserDetails时,我得到了一个空对象:用户名为“”.我也尝试了@EnableWebMvcSecurity,但不幸的是UserDetails是null.
但我可以通过传统方式获取UserDetails,如下所示:
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
我的问题是,当我使用@EnableWebSecurity时,获取自定义UserDetails(帐户)的正确方法是什么?
以下是相关的源代码:
控制器:
@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 ...
}
如果在@AuthenticationPrincipal之后将类型更改为User,则null问题可能会消失.
根据您设置数据库的方式,让Account类实现UserDetails可能会在Account中引入太多逻辑,因为它应该是一个模型.
由于您使用的是@EnableWebSecurity,因此您无需像某些帖子所建议的那样配置自定义HandlerMethodArgumentResolver.
我个人的建议
注意我正在使用Spring Boot
由于使用jdbcAuthentication要求您以预定义的方式设置数据库(并且很可能您所考虑的模式与他们创建的模式完全不同),最好的办法是创建自定义模式并配置自己的用户身份验证服务(这并不难).就个人而言,我在configureGlobal …方法中配置了自定义用户身份验证服务.一个班轮.
auth.userDetailsService(userService).passwordEncoder...
我的自定义userService实现UserDetailsService接口,它只提供一种方法来实现公共UserDetails loadUserByUsername(String username)Spring Security将调用该方法来验证用户.在该loadUserByUsername方法中,我从数据库中检索了我的用户信息,在User对象中创建了一个新的org.springframework.security.core.userdetails.User对象,填充用户名,密码和权限并将其返回.
我的方法结束时有以下内容.
return new User(user.getUsername(),user.getPassword(),permissions);
因为我返回了实现UserDetails接口的User,所以@AuthenticationPrincipal User用户将为我工作.
请注意,权限变量必须是实现GrantedAuthority接口的类,或者是该类型的集合.该接口也只有一种方法可以实现公共String getAuthority(),它基本上可以返回您喜欢的任何字符串(可能是数据库中的权限/角色的名称).
我假设你知道Spring Security如何处理授权.令人遗憾的是,Spring Security仅使用基于角色的授权(如果我错了,我会很乐意予以纠正),而不是组许可来进行更精细的控制.但是,您可以通过在数据库中创建组表和权限表并使用相应的类实现GrantedAuthority接口来解决此问题.您将有效地将“角色”分为这两类.
如果您真的热衷于通过@AuthenticationPrincipal使用自己的类,或者您想要的不仅仅是用户名和密码
我可能会围绕你的Account类创建一个包装类(从Account中取出逻辑)并让包装类实现UserDetails我已经成功完成了这个.
最后,我强烈建议在存储库层的顶部添加服务层,并让控制器与服务层进行通信.这个设置增加了一层安全性(因为黑客在到达你的存储库层之前也必须破解你的服务层)并且还将逻辑从存储库层中取出,因为它只应该用于CRUD,仅此而已.