我正在从头开始编写新的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;
}
RoleDTO:
@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 ...
答案 0 :(得分:0)
好的,我找到了解决方案。缺少的是我已添加到SecurityConfig.java类的@EnableGlobalMethodSecurity批注:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//...
}
现在已添加注释,一切正常。 jsr250Enabled
参数引用@RolesAllowed
注释,而如果我改用securedEnabled
,则@Secured
是必需的。