从Spring Security doc: 34.1 @EnableWebMvcSecurity状态开始,@EnableWebMvcSecurity
替换为@EnableWebSecurity
。
但是,当我尝试通过UserDetails
在 Controller 中获取@AuthenticationPrincipal
时,我得到了一个空对象:用户名是""
。我也尝试了@EnableWebMvcSecurity
,但不幸的是UserDetails
是null
。
但是我可以通过传统方式获得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/