【问题标题】:Spring Boot Unit Tests with JWT Token Security使用 JWT 令牌安全性的 Spring Boot 单元测试
【发布时间】:2017-12-27 17:39:54
【问题描述】:

我正在使用 Spring Boot 创建一个后端,并且刚刚添加了 JWT 安全性。

我已经使用 REST 客户端进行了一些测试,并且 JWT 安全性工作正常,但是我的所有单元测试现在都返回 403 错误代码。

我已经为它们添加了@WithMockUser 注释,但它们仍然不起作用:

@Test
@WithMockUser
public void shouldRedirectToInstaAuthPage() throws Exception {
    mvc.perform(MockMvcRequestBuilders.get("/instaAuth")).andExpect(status().is3xxRedirection());
}

这里有没有我遗漏的其他配置?

这里是安全配置:

@Configuration
@EnableWebSecurity
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
      protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable().authorizeRequests()
            .antMatchers("/").permitAll()
            .antMatchers(HttpMethod.POST, "/login").permitAll()
            .anyRequest().authenticated()
            .and()
            // We filter the api/login requests
            .addFilterBefore(new JWTLoginFilter("/login", authenticationManager()),
                    UsernamePasswordAuthenticationFilter.class)
            // And filter other requests to check the presence of JWT in header
            .addFilterBefore(new JWTAuthenticationFilter(),
                    UsernamePasswordAuthenticationFilter.class);
      }

      @Override
      protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // Create a default account
        auth.inMemoryAuthentication()
            .withUser("john")
            .password("123")
            .roles("ADMIN");
      }
}

和方法安全性:

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true, proxyTargetClass = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {

    @Override
    protected MethodSecurityExpressionHandler createExpressionHandler() {
        return new OAuth2MethodSecurityExpressionHandler();
    }

}

【问题讨论】:

  • 看看这个以前的answer 到一个类似的问题。
  • 嗨@punkrocker27ka,感谢您的回复。在那篇文章中,问题出在 Oauth2 身份验证配置上,但我使用的是 JWT,所以我没有资源服务器。
  • 它们不是相互排斥的。如果可能的话,你能在 GitHub 上分享一个最小的示例项目吗?这样可以更轻松地进行故障排除。
  • 好的,让我尝试按照他们在该主题上的建议进行操作。我也会尝试在 GitHub 上上传一些东西。
  • 嗨,我自己生成了 JWT 令牌并添加到测试中,现在一切正常。我希望我在这里没有做坏事。我将发布一个包含详细信息的答案。

标签: java spring unit-testing spring-boot jwt


【解决方案1】:

使用此 createToken() 方法进行测试时需要注意的一件事是,您的测试无法测试不存在的用户。
这是因为 createToken() 仅根据您输入的字符串生成 JWT 令牌。
如果您想确保不存在的用户无法获得访问权限,我建议您将 createToken() 方法设为私有,而是使用请求来获取令牌,如下所示:

@Test
public void existentUserCanGetTokenAndAuthentication() throws Exception {
    String username = "existentuser";
    String password = "password";

    String body = "{\"username\":\"" + username + "\", \"password\":\" 
                  + password + "\"}";

    MvcResult result = mvc.perform(MockMvcRequestBuilders.post("/v2/token")
            .content(body))
            .andExpect(status().isOk()).andReturn();

    String response = result.getResponse().getContentAsString();
    response = response.replace("{\"access_token\": \"", "");
    String token = response.replace("\"}", "");

    mvc.perform(MockMvcRequestBuilders.get("/test")
        .header("Authorization", "Bearer " + token))
        .andExpect(status().isOk());
}

以类似的方式,您可以表明不存在的用户将无法获得此结果:

@Test
public void nonexistentUserCannotGetToken() throws Exception {
    String username = "nonexistentuser";
    String password = "password";

    String body = "{\"username\":\"" + username + "\", \"password\":\" 
                  + password + "\"}";

    mvc.perform(MockMvcRequestBuilders.post("/v2/token")
            .content(body))
            .andExpect(status().isForbidden()).andReturn();
}

【讨论】:

    【解决方案2】:

    我相信我已经解决了这个问题(我希望我没有做坏事或在我的后端制造安全漏洞)。

    我按照@punkrocker27ka 的建议查看了this answer。他们说他们正在为测试手动生成 Oauth 令牌,所以我决定为我的 JWT 令牌做同样的事情。

    所以我更新了生成 JWT 令牌并验证它们的类,如下所示:

    public class TokenAuthenticationService {
    
        static final long EXPIRATIONTIME = 864_000_000; // 10 days
        static final String SECRET = "ThisIsASecret";
        static final String TOKEN_PREFIX = "Bearer";
        static final String HEADER_STRING = "Authorization";
    
        public static void addAuthentication(HttpServletResponse res, String username) {
    
            String jwt = createToken(username);
    
            res.addHeader(HEADER_STRING, TOKEN_PREFIX + " " + jwt);
        }
    
        public static Authentication getAuthentication(HttpServletRequest request) {
            String token = request.getHeader(HEADER_STRING);
            if (token != null) {
                // parse the token.
                String user = Jwts.parser()
                        .setSigningKey(SECRET)
                        .parseClaimsJws(token.replace(TOKEN_PREFIX, ""))
                        .getBody()
                        .getSubject();
    
                return user != null ?
                        new UsernamePasswordAuthenticationToken(user, null, Collections.emptyList()) :
                            null;
            }
            return null;
        }
    
        public static String createToken(String username) {
            String jwt = Jwts.builder()
                    .setSubject(username)
                    .setExpiration(new Date(System.currentTimeMillis() + EXPIRATIONTIME))
                    .signWith(SignatureAlgorithm.HS512, SECRET)
                    .compact();
    
            return jwt;
        }
    }
    

    然后我为它创建了一个新的测试:

    @RunWith(SpringRunner.class)
    @SpringBootTest
    @AutoConfigureMockMvc
    public class TokenAuthenticationServiceTest {
    
        @Autowired
        private MockMvc mvc;
    
        @Test
        public void shouldNotAllowAccessToUnauthenticatedUsers() throws Exception {
            mvc.perform(MockMvcRequestBuilders.get("/test")).andExpect(status().isForbidden());
        }
    
        @Test
        public void shouldGenerateAuthToken() throws Exception {
            String token = TokenAuthenticationService.createToken("john");
    
            assertNotNull(token);
            mvc.perform(MockMvcRequestBuilders.get("/test").header("Authorization", token)).andExpect(status().isOk());
        }
    
    }
    

    然后我运行了测试,它们通过了,所以令牌被接受,不需要 @WithMockUser 注释。我会将它添加到我的其他测试类中。

    PS:测试端点如下。

    /**
     * This controller is used only for testing purposes.
     * Especially to check if the JWT authentication is ok.
     */
    @RestController
    public class TestController {
    
        @RequestMapping(path = "/test", method = RequestMethod.GET)
        public String testEndpoint() {
            return "Hello World!";
        }
    }
    

    【讨论】:

    • 在您的回答中,您提供了一个线索,即“John”命名用户存在于您的实际数据库中。也许在调用“mvc.perform”函数之前,您应该模拟您的用户存储库。例如,您可以将此称为“ given(userRepository.findUserById("john")).willReturn(new User(("john"));”。在上面的示例中,我假设您有一个具有 findUserById 的用户存储库函数。这样做,您可能无法向正在阅读您的代码的人提供任何线索。
    • 很好的答案@Felipe。但是,对于缺少的 JWT,API 应该返回 401 Unauthorized,而不是 403 Forbidden。因此mvc.perform(MockMvcRequestBuilders.get("/test")).andExpect(status().isUnauthorized()); 会更合适。
    猜你喜欢
    • 2020-07-18
    • 2015-09-19
    • 2019-03-30
    • 2019-02-05
    • 2020-08-20
    • 2021-06-17
    • 2021-07-14
    • 2021-08-27
    • 1970-01-01
    相关资源
    最近更新 更多