【问题标题】:Can't test authenticate-required method in controller Spring-boot无法在控制器 Spring-boot 中测试需要身份验证的方法
【发布时间】:2018-02-16 14:30:41
【问题描述】:

问题:当我尝试测试需要身份验证的方法(使用 MockUser)时,它返回 403 错误,我遗漏了一些东西,尝试了几种方法,但它们不起作用。我不明白为什么会这样,有人可以解释吗?

例如,我创建了一个简单的应用程序来演示这一点。

Spring-security 配置类

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity
public class SpringConfigure extends WebSecurityConfigurerAdapter {


@Override
protected void configure(HttpSecurity http) throws Exception {
    configureAuthorization(http);
    configureAuthentication(http);
     }



private void configureAuthorization(HttpSecurity http) throws Exception {
    http.authorizeRequests().antMatchers("/api/**").authenticated();
    }


private void configureAuthentication(HttpSecurity http) throws Exception {
    AuthenticationEntryPoint authenticationEntryPoint = (request, response, e) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
    }
}

控制器类

@RestController
@RequestMapping("api/hello")
public class HelloController {

@RequestMapping(method = RequestMethod.GET)
public String hello(){
    return "Hello";
   }
}

和我的测试班

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DemoApplication.class},webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class DemoApplicationTests {

RestTemplate restTemplate;

@Before
public void setUp(){
    restTemplate = new RestTemplate();
   }

@Test
@WithMockUser(username = "user")
public void helloTest() {
    System.out.println(SecurityContextHolder.getContext().getAuthentication());
    ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://localhost:8080/api/hello",String.class);
   }
}

@WithMockUser

创建的用户
Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER

【问题讨论】:

  • 你试过在测试中同时使用用户名和密码吗?
  • 是的,我试过了,结果是一样的

标签: java spring-boot spring-security integration-testing


【解决方案1】:

我运行了你的测试代码,看起来没问题。

@Test
@WithMockUser(username = "user")
public void helloTest() {
System.out.println(SecurityContextHolder.getContext().getAuthentication());
    ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://localhost:8080/api/hello",String.class);
   }
}
System.out.println(responseEntity);

结果:

org.springframework.security.authentication.UsernamePasswordAuthenticationToken@ca25360: Principal: org.springframework.security.core.userdetails.User@36ebcb: Username: user; Password: [PROTECTED]; Enabled: true; AccountNonExpired: true; credentialsNonExpired: true; AccountNonLocked: true; Granted Authorities: ROLE_USER; Credentials: [PROTECTED]; Authenticated: true; Details: null; Granted Authorities: ROLE_USER
<200,Hello

【讨论】:

  • 您使用 Spring-security 配置类运行?因为它仍然不起作用(仅当我将 authenticated() 更改为 permitAll() 时才起作用),起初我认为这可能是我的环境中的问题并尝试在我的家用计算机上运行它,但没有,结果是相同的。我不知道,也许是 gradle 中的东西或“异国情调”的东西。
【解决方案2】:

经过很长时间,我在这里找到了答案https://stackoverflow.com/a/15203488/8664578(非常感谢),并重复了这个答案(https://stackoverflow.com/a/23658269/8664578) - 测试需要身份验证的方法的最简单方法是:

@RunWith(SpringRunner.class)
@SpringBootTest(classes = {DemoApplication.class},webEnvironment = 
SpringBootTest.WebEnvironment.DEFINED_PORT)

@Autowired
private WebApplicationContext context;
@Before
public void setup() {
    mvc = MockMvcBuilders
          .webAppContextSetup(context)
          .apply(springSecurity())  
          .build();
   }

public class DemoApplicationTests {
@Test
@WithMockUser
public void helloTest() {
mockMvc.perform(MockMvcRequestBuilders.get("http://localhost:8080/api/hello")).andExpect(status().isOk());
    }
}

但一开始我的问题是如何使用 RestTemplate,我使用帖子中提出的解决方案,答案只是稍微改变了它(不是最好的,但解决方案)。根据上面的链接:

“HttpSessionSecurityContextRepository 检查给定的 HttpRequest 并尝试访问相应的 HttpSession。如果存在,它将尝试从 HttpSession 中读取 SecurityContext。如果失败,存储库会生成一个空的 SecurityContext”

我们可以给filterChain添加一个过滤器,它会添加我们想要的带有Spring上下文的会话属性,如下例所示:

public class CustomFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {

}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken("user","password", 
Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER")));
   SecurityContextHolder.getContext().setAuthentication(authenticationToken);

    HttpServletRequest httpServletRequest = (HttpServletRequest) request;

    HttpSession session = httpServletRequest.getSession();
    session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY,
            SecurityContextHolder.getContext());

    chain.doFilter(request,response);
}

@Override
public void destroy() {

   }
}

我们还需要向@Configuration 类添加过滤器,如下所示:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity
public class SpringConfigure extends WebSecurityConfigurerAdapter {


@Override
protected void configure(HttpSecurity http) throws Exception {
  configureAuthorization(http);
  configureAuthentication(http);
 }



private void configureAuthorization(HttpSecurity http) throws Exception {
  http.authorizeRequests().antMatchers("/api/**").authenticated().and().addFilterBefore(new CustomFilter(), SecurityContextPersistenceFilter.class);
}


private void configureAuthentication(HttpSecurity http) throws Exception {
AuthenticationEntryPoint authenticationEntryPoint = (request, response, e) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED, e.getMessage());
  }
}

最后测试类:

@Test
public void helloTest() {
ResponseEntity<String> responseEntity = restTemplate.getForEntity("http://localhost:8080/api/hello",String.class);
  }
}

希望对某人有所帮助。

【讨论】:

    猜你喜欢
    • 2022-01-24
    • 2017-12-19
    • 2015-06-24
    • 1970-01-01
    • 2017-11-12
    • 2013-03-31
    • 2022-11-10
    • 2014-07-08
    • 1970-01-01
    相关资源
    最近更新 更多