Spring Boot REST Controller与角色的测试

时间:2018-10-02 08:30:38

标签: java rest unit-testing spring-boot spring-security

我正在从头开始编写新的Spring Boot应用程序,以更好地了解其配置如何工作。现在,我正在尝试创建无法正常使用的访问控制。

我的UserDetailsS​​ervice系统使用自定义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_USERenter image description here

缺少什么,因此@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 ...

1 个答案:

答案 0 :(得分:0)

好的,我找到了解决方案。缺少的是我已添加到SecurityConfig.java类的@EnableGlobalMethodSecurity批注:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(jsr250Enabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
//...
}

现在已添加注释,一切正常。 jsr250Enabled参数引用@RolesAllowed注释,而如果我改用securedEnabled,则@Secured是必需的。