【发布时间】:2019-03-07 08:45:52
【问题描述】:
我正在从头开始编写一个新的 Spring Boot 应用程序,以便更好地了解其配置的工作原理。现在,我正在尝试创建无法正常工作的访问控制。
我的 UserDetailsService 系统使用自定义 UserDTO 对象进行验证:
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return new UserDTO(
userRepository.findByEmailAddress(email)
.orElseThrow(() -> new UsernameNotFoundException("User '" + email + "' not found."))
);
}
用户实体:
@Entity
@Table(name = "USERS")
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Builder
public class User extends BaseEntity {
private String firstName;
private String lastName;
private String emailAddress;
private String nickname;
private String password;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
private boolean enabled;
@ManyToMany
@JoinTable(
name = "USER_ROLE",
joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
private Set<Role> roles;
public Set<Role> getRoles() {
return roles;
}
}
这是 UserDTO 类的样子:
@Getter
@Setter
@NoArgsConstructor
public class UserDTO implements UserDetails {
private Long id;
private String firstName;
private String lastName;
private String username;
private String nickname;
private String password;
private Set<RoleDTO> authorities;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
private boolean enabled;
public UserDTO(User user){
this.id = user.getId();
this.firstName = user.getFirstName();
this.lastName = user.getLastName();
this.username = user.getEmailAddress();
this.nickname = user.getNickname();
this.password = user.getPassword();
this.authorities = user.getRoles().stream().map(RoleDTO::new).collect(Collectors.toSet());
this.accountNonExpired = user.isAccountNonExpired();
this.accountNonLocked = user.isAccountNonLocked();
this.credentialsNonExpired = user.isCredentialsNonExpired();
this.enabled = user.isEnabled();
}
}
H2中的角色表:
INSERT INTO PUBLIC.ROLES(ID, VERSION, AUTHORITY) VALUES
(1, 0, 'ROLE_USER'),
(2, 0, 'ROLE_MODERATOR'),
(3, 0, 'ROLE_ADMIN');
还有控制器:
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public UserSimpleDTO createUser(@RequestBody UserDTO user){
return userService.createUser(user);
}
@GetMapping("/{id}")
@ResponseStatus(HttpStatus.OK)
public UserSimpleDTO getUserGeneralData(@PathVariable long id){
return userService.getUserGeneralData(id);
}
@GetMapping("/{id}/details")
@ResponseStatus(HttpStatus.OK)
@RolesAllowed("ROLE_MODERATOR")
public UserDTO getUserDetailedInfo(@PathVariable long id) {
return userService.getUserDetailedInfo(id);
}
我的角色实体和 DTO 类如下所示:
@Entity
@Table(name = "ROLES")
@Getter
@NoArgsConstructor
public class Role extends BaseEntity implements GrantedAuthority {
private String authority;
}
角色DTO:
@Getter
@NoArgsConstructor
public class RoleDTO implements GrantedAuthority {
private String authority;
public RoleDTO(Role role){
this.authority = role.getAuthority();
}
}
当我以普通用户身份运行测试时,getUserDetailedInfo() 返回 200 而不是 401,但模拟的 User 对象显然只有 ROLE_USER:
缺少什么导致@RolesAllowed 注释无法正常工作,或者此代码中的某处有错误?
编辑:
这是我用来测试给定方法的测试套件,但我忘了添加:
@Test
@WithAnonymousUser
public void getUserDetailedInfoDoesNotAllowAnonymous2() {
getUserDetailedInfoREST(1, 401);
}
@Test
@WithMockUser
public void getUserDetailedInfoDoesNotAllowUser2() {
getUserDetailedInfoREST(1, 403);
}
@Test
@WithMockModerator
public void getUserDetailedInfoAllowsModerator2() {
getUserDetailedInfoREST(1, 200);
}
@Test
@WithMockAdmin
public void getUserDetailedInfoDAllowsAdmin2() {
getUserDetailedInfoREST(1, 200);
}
这是getUserDetailedInfoREST()测试方法的实现:
private String getUserDetailedInfoREST(long userId, int expectedStatus) {
try {
String response = this.mockMvc.perform(get("/users/" + userId + "/details"))
.andDo(print())
.andExpect(status().is(expectedStatus))
.andDo(this::mapMvcResultToUserDTO)
.andReturn().getResponse().getContentAsString();
return response;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
我的自定义注释:
@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(value="admin",roles= {"USER", "MODERATOR", "ADMIN"})
public @interface WithMockAdmin {
}
@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(value="moderator",roles= {"USER", "MODERATOR"})
public @interface WithMockModerator {
}
我还使用我自己的MockHttpServletRequestBuilder 方法对其进行了测试,这些方法通过数据库中的真实现有用户而不是模拟用户,我也想知道它的好坏做法,因为我很难决定。它使测试的可读性降低了一点,但是使用真实用户而不是模拟用户感觉非常诱人:
private static final String ADMIN_USERNAME = "admin@mail.com";
private static final String ADMIN_PASSWORD = "admin123";
public static MockHttpServletRequestBuilder getAsAdmin(String urlTemplate, Object... uriVars) {
return MockMvcRequestBuilders.get(urlTemplate, uriVars).with(httpBasic(ADMIN_USERNAME, ADMIN_PASSWORD));
}
//... and the other HTTP methods+users implementations ...
【问题讨论】:
-
哪个模拟用户?您添加了很多代码,但忘记了测试。
-
感谢指出,我已经更新了问题
标签: java rest unit-testing spring-boot spring-security