以用户身份登录,然后以另一个不在SpringBoot API集成测试中工作的用户身份登录在React前端工作正常吗



My SpringBoot应用程序使用身份验证和授权过滤器来接受对"/api/v1/login"URL的请求,检查请求的凭据,如果成功,则将承载JWT令牌添加到响应标头中。

当我同时运行我的API和单独的CORS React项目时,我可以作为一个用户注册、登录和访问网站功能,然后再单击注册和登录(无需注销(,这一切都很好。

然而,在我的集成测试中,我调用一个方法来注册多个用户并作为一个用户登录,这是成功的,但当我试图在joinLeague((测试中发送另一个登录调用时,服务器会返回401错误:

错误消息:未经授权。

授权过滤器:

public class AuthorizationFilter extends BasicAuthenticationFilter {
public AuthorizationFilter(AuthenticationManager authenticationManager) { super(authenticationManager);}
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws IOException, ServletException {
String header = request.getHeader("Authorization");

if(header == null || !header.startsWith("Bearer")) {
filterChain.doFilter(request,response);
return;
}
UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request,response);
}

private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
String token = request.getHeader("Authorization");
if(token != null) {
String user = Jwts.parser().setSigningKey("SecretKeyToGenJWTs".getBytes())
.parseClaimsJws(token.replace("Bearer",""))
.getBody()
.getSubject();
if(user != null) {
return new UsernamePasswordAuthenticationToken(user, null, new ArrayList<>());
}
return null;
}
return null;
}
}

身份验证过滤器:

public class AuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private String apiVersion = "v1";
private AuthenticationManager authenticationManager;
public AuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
setFilterProcessesUrl("/api/v1/login");
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
com.example.gambit2.domain.User creds = new ObjectMapper().readValue(request.getInputStream(), com.example.gambit2.domain.User.class);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(creds.getUsername(), creds.getPassword(),new ArrayList<>()));
}
catch(IOException e) {
throw new RuntimeException("Could not read request" + e);
}
}
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain, Authentication authentication)
{
String token = Jwts.builder()
.setSubject(((User) authentication.getPrincipal()).getUsername())
.setExpiration(new Date(System.currentTimeMillis() + 864_000_000))
.signWith(SignatureAlgorithm.HS512, "SecretKeyToGenJWTs".getBytes())
.compact();
response.addHeader("Authorization","Bearer " + token);
}
}

测试:

@Transactional
@Test
public void joinLeague() throws Exception {
String token = signupAndLogin();
System.out.println(token);
createLeague(token);
//Correctly grabs the right user.
User user = userRepository.findByUsername(username2);

System.out.println();
System.out.println(user.toString());
assertThat(user.getId() == 2);
MvcResult mvcResult2 = mvc.perform(post("/api/v1/login")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(user)))
.andReturn();
String token2 = mvcResult2.getResponse().getHeader("Authorization");
System.out.println("Bearer Token: USER 2 TEST" + token2);
assertThat(token2);
System.out.println();
System.out.println(token2);
System.out.println();
String content = "{"username":"pp", "leagueId":"1"}";
MvcResult mvcResult = mvc.perform(post("http://localhost:8080/api/v1/leagues/join-league")
.contentType(MediaType.APPLICATION_JSON)
.content(content).header("Authorization", token2))
.andExpect(status().isOk())
.andReturn();
String jsonResult = mvcResult.getResponse().getContentAsString();
System.out.println(jsonResult);
}
signupAndLogin() {
User user = new User(1L, username1, "ExamplePass72-", "London");
mvc.perform(post("http://localhost:8080/api/v1/users/signup")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(user)))
.andExpect(status().isOk());
Optional<User> savedUser = userService.getUser(1L);
System.out.println(savedUser.get().getUserStats().get(0).toString());

//2nd User
User user2 = new User(2L, username2, "ExamplePass72-", "London");

mvc.perform(post("http://localhost:8080/api/v1/users/signup")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(user2)))
.andExpect(status().isOk());
Optional<User> savedUser2 = userService.getUser(2L);
System.out.println(savedUser2.toString());
//3rd User
User user3 = new User(3L, username3, "ExamplePass72-", "London");

mvc.perform(post("http://localhost:8080/api/v1/users/signup")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(user3)))
.andExpect(status().isOk());
Optional<User> savedUser3 = userService.getUser(3L);
System.out.println(savedUser3.toString());
//LOGIN
MvcResult mvcResult = mvc.perform(post("/api/v1/login")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(user)))
.andExpect(status().isOk())
.andReturn();
String token = mvcResult.getResponse().getHeader("Authorization");
//PRINTS THE CORRECT BEARER TOKEN, EVERYTHING WORKS FINE.
System.out.println("Bearer Token: " + token);
return token;
}

此外,如果在上述方法中,我将第二次登录直接放在第一次登录之后,它会很好地登录并返回不同的承载令牌。

如有任何帮助,我们将不胜感激,谢谢。

编辑:WebSecConfig:

@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

private UserDetailsService userDetailsService;
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
private static final String[] AUTH_WHITELIST = {
"/v2/api-docs",
"/swagger-resources",
"/swagger-resources/**",
"/configuration/ui",
"/configuration/security",
"/swagger-ui.html",
"/webjars/**",
"/api/v1/**"
};
public WebSecurityConfiguration(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.cors().and().csrf().disable().authorizeRequests()
.antMatchers(AUTH_WHITELIST).permitAll()
.antMatchers(HttpMethod.GET, "/users/signup").permitAll()
.antMatchers(HttpMethod.POST, "/users/signup").permitAll()
.antMatchers(HttpMethod.GET, "/login").permitAll()
.antMatchers(HttpMethod.POST, "api/v1/login").permitAll()
.anyRequest().authenticated()
.and().addFilter(new AuthenticationFilter(authenticationManager()))
.addFilter(new AuthorizationFilter(authenticationManager()))
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
httpSecurity.csrf().disable();
}
public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
final CorsConfiguration configuration = new CorsConfiguration();
List<String> allowedOrigins = new ArrayList<>();
allowedOrigins.add("http://localhost:8080");
allowedOrigins.add("http://localhost:3000");
configuration.setAllowedOrigins(allowedOrigins);
configuration.setAllowedMethods(List.of("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH"));
// setAllowCredentials(true) is important, otherwise:
// The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
configuration.setAllowCredentials(true);
// setAllowedHeaders is important! Without it, OPTIONS preflight request
// will fail with 403 Invalid CORS request
configuration.setAllowedHeaders(List.of("Cache-Control", "Content-Type", "Authorization"));
// allow header "Location" to be read by clients to enable them to read the location of an uploaded group logo
configuration.addExposedHeader("Authorization");
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}

@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().regexMatchers("^/users/signup$");
}
}

当我把它放在signUpAndLogin((中进行测试时

MvcResult mvcResult = mvc.perform(post("/api/v1/login")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(user)))
.andExpect(status().isOk())
.andReturn();
String token = mvcResult.getResponse().getHeader("Authorization");
System.out.println("Bearer Token: " + token);
MvcResult mvcResult2 = mvc.perform(post("/api/v1/login")
.contentType(MediaType.APPLICATION_JSON)
.content(asJsonString(user2)))
.andExpect(status().isOk())
.andReturn();
String token2 = mvcResult2.getResponse().getHeader("Authorization");
System.out.println("Bearer Token: " + token2);


assertThat(!token2.equals(token));

和以前一样,但现在以用户2的身份立即登录,它运行良好,令牌为2!=令牌1。

感谢

错误:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: Multiple representations of the same entity [com.example.gambit2.domain.User#1] are being merged. Managed: [com.example.gambit2.domain.User@1ec8afc4]; Detached: [com.example.gambit2.domain.User@4addfb95]; nested exception is java.lang.IllegalStateException: Multiple representations of the same entity [com.example.gambit2.domain.User#1] are being merged. Managed: [com.example.gambit2.domain.User@1ec8afc4]; Detached: [com.example.gambit2.domain.User@4addfb95]

我认为这就是问题所在。在测试中,您正在这样做:

MvcResult mvcResult = mvc.perform(post("http://localhost:8080/api/v1/leagues/join-league")
.contentType(MediaType.APPLICATION_JSON)
.content(content).header("Authorization", token2))
.andExpect(status().isOk())
.andReturn();

在过滤器中,你正在这样做:

String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer")) {
filterChain.doFilter(request, response);
return;
}

您请求中的授权一开始不包含Bearer。正因为如此,你才进入这种状态。因此,您不会在您的请求中设置授权使用:

UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(request);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);

在下链子之前。

试试这个:

MvcResult mvcResult = mvc.perform(post("http://localhost:8080/api/v1/leagues/join-league")
.contentType(MediaType.APPLICATION_JSON)
.content(content).header("Authorization", "Bearer " + token2))
.andExpect(status().isOk())
.andReturn();

此外,请注意您的过滤器中的这一点,因为Bearer后面有空间

.parseClaimsJws(token.replace("Bearer",""))

最新更新