简体   繁体   English

Spring Boot REST Controller与角色的测试

[英]Spring Boot REST Controller testing with roles

I'm writing a new Spring Boot app from the scratch to have a better understanding of how its' configuration works. 我从头开始编写一个新的Spring Boot应用程序,以更好地了解其配置如何工作。 Right now, I'm trying to create access control which doesn't work as it should. 现在,我正在尝试创建无法正常使用的访问控制。

My UserDetailsService system uses custom UserDTO object for verifiction: 我的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."))
                );
    }

User entity: 用户实体:

@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;
    }
}

This is how the UserDTO class looks like: 这是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();
    }

}

The roles table in H2: H2中的角色表:

INSERT INTO PUBLIC.ROLES(ID, VERSION, AUTHORITY) VALUES
(1, 0, 'ROLE_USER'),
(2, 0, 'ROLE_MODERATOR'),
(3, 0, 'ROLE_ADMIN');

And the controller: 和控制器:

@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);
}

My role entity and DTO classes look like this: 我的角色实体和DTO类如下所示:

@Entity
@Table(name = "ROLES")
@Getter
@NoArgsConstructor
public class Role extends BaseEntity implements GrantedAuthority {

    private String authority;

}

RoleDTO: RoleDTO:

 @Getter
    @NoArgsConstructor
    public class RoleDTO implements GrantedAuthority {

        private String authority;

        public RoleDTO(Role role){
           this.authority = role.getAuthority();
        }
    }

When I run tests as normal user, the getUserDetailedInfo() returns 200 instead of 401, but the mocked User object clearly has the ROLE_USER only: 当我以普通用户身份运行测试时,getUserDetailedInfo()返回200而不是401,但是ROLE_USER User对象显然只有ROLE_USER 在此处输入图片说明

What is missing so the @RolesAllowed annotation does not work properly, or is there a mistake somewhere in this code? 缺少什么,因此@RolesAllowed批注无法正常工作,或者此代码中是否有错误?

EDIT: 编辑:

There's the test suite I use to test given method which I forgot to add: 我使用了测试套件来测试我忘记添加的给定方法:

@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);
}

This is the implementation of getUserDetailedInfoREST() test method: 这是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;
    }

My custom annotations: 我的自定义注释:

@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 {
}

I've also tested it by using my own MockHttpServletRequestBuilder methods that pass the real existing users in database instead of mocked ones, also would like to know if its good or bad practice since it's hard for me to decide. 我还通过使用自己的MockHttpServletRequestBuilder方法(通过数据库中的现有现有用户而不是MockHttpServletRequestBuilder用户)来测试了它,还想知道它的好坏,因为这对我来说很难决定。 It makes the tests a little less readable, but using the real users instead of mocked ones feels very temptating: 它使测试的可读性降低了一些,但是使用真实用户而不是模拟用户会非常诱人:

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 ...

Ok, I've found the solution. 好的,我找到了解决方案。 What was missing was the @EnableGlobalMethodSecurity annotation which I've added to the SecurityConfig.java class: 缺少的是我已添加到SecurityConfig.java类的@EnableGlobalMethodSecurity批注:

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

Now that the annotation has been added, everything works fine. 现在已经添加了注释,一切正常。 The jsr250Enabled parameter refers to @RolesAllowed annotation, while securedEnabled would be necessary if I used @Secured instead. jsr250Enabled参数引用@RolesAllowed批注,而如果我改用@Secured ,则securedEnabled是必需的。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM