gpt4 book ai didi

java - 如何测试使用 @PreAuthorized(hasAnyAuthority(...)) 注释的 Spring Boot Controller 方法

转载 作者:行者123 更新时间:2023-11-30 01:41:50 25 4
gpt4 key购买 nike

我的 Controller 类如下:

 @PostMapping(path="/users/{id}")
@PreAuthorize("hasAnyAuthority('CAN_READ')")
public @ResponseBody ResponseEntity<User> getUser(@PathVariable int id) {
...
}

我有以下资源服务器配置

@Configuration
public class ResourceServerCofig implements ResourceServerConfigurer {

private static final String RESOURCE_ID = "test";

@Override
public void configure(HttpSecurity http) throws Exception {
http.cors()
.and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/public/**").permitAll()
.anyRequest().authenticated();
}

@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.resourceId(RESOURCE_ID);

}
}

最后我的测试如下所示:

 @RunWith(SpringRunner.class)
@WebMvcTest(ClientController.class)
public class ClientControllerTest {

@Autowired
private MockMvc mockMvc;

@WithMockUser(authorities={"CAN_READ"})
@Test
public void should_get_user_by_id() throws Exception {
...

mockMvc.perform(MockMvcRequestBuilders.get("/user/1")).
andExpect(MockMvcResultMatchers.status().isOk()).
andExpect(MockMvcResultMatchers.header().string(HttpHeaders.CONTENT_TYPE, "application/json")).
andExpect(MockMvcResultMatchers.jsonPath("$.name").value("johnson"));
}
}

问题是我总是收到 401 HTTP 状态,并显示消息 unauthorized","error_description":"访问此资源需要完全身份验证

我应该如何为 @PreAuthorized 带注释的 Controller 方法编写测试?

最佳答案

我花了一天的时间来弄清楚如何解决这个问题。我最终得到了一个我认为还不错的解决方案,并且可以帮助很多人。

根据您在测试中尝试执行的操作,您可以执行 mockMvc 来测试您的 Controller 。请注意,未调用 AuthorizationServer。您仅留在资源服务器中进行测试。

  • 创建一个将在 OAuth2AuthenticationProcessingFilter 中使用的 bean InMemoryTokenStore 来对您的用户进行身份验证。很棒的是,您可以在执行测试之前将 token 添加到 InMemoryTokenStore 中。 OAuth2AuthenticationProcessingFilter 将根据用户正在使用的 token 对用户进行身份验证,并且不会调用任何远程服务器。
@Configuration
public class AuthenticationManagerProvider {

@Bean
public TokenStore tokenStore() {
return new InMemoryTokenStore();
}
}
  • 注释@WithMockUser不适用于OAuth2。事实上,OAuth2AuthenticationProcessingFilter 始终检查您的 token ,而不考虑 SecurityContext。我建议使用与 @WithMockUser 相同的方法,但使用您在代码库中创建的注释。 (进行一些易于维护和清理的测试):
    @WithMockOAuth2Scope 包含自定义身份验证所需的几乎所有参数。您可以删除那些您永远不会使用的内容,但我做了很多工作以确保您看到其中的可能性。 (将这 2 个类放入您的测试文件夹中)
@Retention(RetentionPolicy.RUNTIME)
@WithSecurityContext(factory = WithMockOAuth2ScopeSecurityContextFactory.class)
public @interface WithMockOAuth2Scope {

String token() default "";

String clientId() default "client-id";

boolean approved() default true;

String redirectUrl() default "";

String[] responseTypes() default {};

String[] scopes() default {};

String[] resourceIds() default {};

String[] authorities() default {};

String username() default "username";

String password() default "";

String email() default "";
}

然后,我们需要一个类来解释此注释,并用测试所需的数据填充我们的“InMemoryTokenStore”。

@Component
public class WithMockOAuth2ScopeSecurityContextFactory implements WithSecurityContextFactory<WithMockOAuth2Scope> {

@Autowired
private TokenStore tokenStore;

@Override
public SecurityContext createSecurityContext(WithMockOAuth2Scope mockOAuth2Scope) {

OAuth2AccessToken oAuth2AccessToken = createAccessToken(mockOAuth2Scope.token());
OAuth2Authentication oAuth2Authentication = createAuthentication(mockOAuth2Scope);
tokenStore.storeAccessToken(oAuth2AccessToken, oAuth2Authentication);

return SecurityContextHolder.createEmptyContext();
}


private OAuth2AccessToken createAccessToken(String token) {
return new DefaultOAuth2AccessToken(token);
}

private OAuth2Authentication createAuthentication(WithMockOAuth2Scope mockOAuth2Scope) {

OAuth2Request oauth2Request = getOauth2Request(mockOAuth2Scope);
return new OAuth2Authentication(oauth2Request,
getAuthentication(mockOAuth2Scope));
}

private OAuth2Request getOauth2Request(WithMockOAuth2Scope mockOAuth2Scope) {
String clientId = mockOAuth2Scope.clientId();
boolean approved = mockOAuth2Scope.approved();
String redirectUrl = mockOAuth2Scope.redirectUrl();
Set<String> responseTypes = new HashSet<>(asList(mockOAuth2Scope.responseTypes()));
Set<String> scopes = new HashSet<>(asList(mockOAuth2Scope.scopes()));
Set<String> resourceIds = new HashSet<>(asList(mockOAuth2Scope.resourceIds()));

Map<String, String> requestParameters = Collections.emptyMap();
Map<String, Serializable> extensionProperties = Collections.emptyMap();
List<GrantedAuthority> authorities = AuthorityUtils.createAuthorityList(mockOAuth2Scope.authorities());

return new OAuth2Request(requestParameters, clientId, authorities,
approved, scopes, resourceIds, redirectUrl, responseTypes, extensionProperties);
}

private Authentication getAuthentication(WithMockOAuth2Scope mockOAuth2Scope) {
List<GrantedAuthority> grantedAuthorities = AuthorityUtils.createAuthorityList(mockOAuth2Scope.authorities());

String username = mockOAuth2Scope.username();
User userPrincipal = new User(username,
mockOAuth2Scope.password(),
true, true, true, true, grantedAuthorities);

HashMap<String, String> details = new HashMap<>();
details.put("user_name", username);
details.put("email", mockOAuth2Scope.email());

TestingAuthenticationToken token = new TestingAuthenticationToken(userPrincipal, null, grantedAuthorities);
token.setAuthenticated(true);
token.setDetails(details);

return token;
}

}
  • 一切设置完毕后,在 src/test/java/your/package/ 下创建一个简单的测试类。此类将执行 mockMvc 操作,并使用 @WithMockOAuth2Scope 创建测试所需的 token 。
@WebMvcTest(SimpleController.class)
@Import(AuthenticationManagerProvider.class)
class SimpleControllerTest {

@Autowired
private MockMvc mockMvc;

@Test
@WithMockOAuth2Scope(token = "123456789",
authorities = "CAN_READ")
public void test() throws Exception {
mockMvc.perform(get("/whoami")
.header("Authorization", "Bearer 123456789"))
.andExpect(status().isOk())
.andExpect(content().string("username"));
}
}

我想出了这个解决方案,感谢:

对于好奇的人:
测试时,Spring 加载一个 InMemoryTokenStore,并让您可以采用您提供的一个(作为 @Bean)。在生产中运行时,就我而言,Spring 使用 RemoteTokenStore,它调用远程授权服务器来检查 token (http://authorization_server/oauth/check_token)。
当您决定使用 OAuth2 时,Spring 会触发 OAuth2AuthenticationProcessingFilter。这是我所有调试 session 期间的入口点。

我学到了很多东西,谢谢你。
您可以找到 source code here

希望对你有帮助!

关于java - 如何测试使用 @PreAuthorized(hasAnyAuthority(...)) 注释的 Spring Boot Controller 方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59666903/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com