我正在尝试使用 Spring Security 以及用户名和密码凭据在我的 REST API 应用程序中编写身份验证。
我有自定义控制器,可将用户名和密码令牌保存到 SecurityContext。该 SecurityContext 保存到 SecurityContextRepository。
当我使用 Postman 登录应用程序然后使用其他 api 端点时,它会成功完成。但是当我尝试在浏览器中执行相同操作时,成功登录后使用 api 端点收到 401 错误。
我注意到:登录端点响应设置标头
Set-Cookie: JSESSIONID=...
,Postman 在使用其他 api 端点时使用它来发送。我在浏览器中看到该标头,但浏览器不存储 cookie(我已在开发工具中检查过)
access-control-allow-credentials: true
access-control-allow-origin: http://localhost:3000
cache-control: no-cache, no-store, max-age=0, must-revalidate
connection: keep-alive
content-length: 0
date: Mon, 30 Dec 2024 18:28:02 GMT
expires: 0
keep-alive: timeout=60
pragma: no-cache
set-cookie: JSESSIONID=E2AE75224A50A04D4E64790FA8C2AE46; Path=/; HttpOnly
vary: Origin
vary: Access-Control-Request-Method
vary: Access-Control-Request-Headers
x-content-type-options: nosniff
x-frame-options: DENY
x-xss-protection: 0
首先,我设置了 CORS 配置以允许使用任何标头、方法和凭据。但这并没有帮助。
我尝试根据响应手动创建 cookie,但每个下一个请求都返回 401 错误。
我尝试过配置 cookie secure 和 http-only 属性,但也没有帮助。
我尝试使用不同的浏览器,但结果相同。
我还向 React 应用程序的请求添加了
withCredentials: true
标头,但没有帮助
应用程序使用相同的域(localhost)但不同的端口(spring:8080,react:3000)
import { List } from "antd";
import axios from "axios";
import { useLoaderData } from "react-router";
export const loader = async () => {
const url = "http://localhost:8080/api/campaigns/get";
try {
const response = await axios.get(url, {
headers: {
withCredentials: true,
},
});
console.log(response);
return response.data;
} catch (err) {
alert(err.message);
}
};
const CampaignList = () => {
const campaigns = useLoaderData();
return (
<List
header={<div>Кампании</div>}
dataSource={campaigns}
renderItem={(item) => (
<List.Item>
<a href={item}>{item.name}</a>
</List.Item>
)}
/>
);
};
export default CampaignList;
@EnableWebSecurity(debug = true)
@RequiredArgsConstructor
@Configuration(proxyBeanMethods = false)
public class SecurityConfig {
@Autowired
private CorsConfigurationSource corsConfigurationSource;
@Autowired
private SecurityContextRepository securityContextRepository;
@Bean
public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.cors(
cors -> cors
.configurationSource(corsConfigurationSource)
)
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(requests -> requests
.requestMatchers("/api/users/create", "/auth/login", "/error").permitAll()
.anyRequest().authenticated()
)
.securityContext(securityContext -> securityContext
.securityContextRepository(securityContextRepository)
.requireExplicitSave(true)
)
.httpBasic(Customizer.withDefaults());
return http.build();
}
@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowUrlEncodedDoubleSlash(true);
return firewall;
}
}
@Configuration
public class CorsConfig {
@Bean
public UrlBasedCorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("http://localhost:3000/"));
configuration.setAllowedMethods(List.of("*"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
@RestController
@RequestMapping("/auth")
public class AuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private SecurityContextRepository securityContextRepository;
@PostMapping("/login")
public ResponseEntity<?> login(
@RequestBody SignInRequest signInRequest,
HttpServletRequest request,
HttpServletResponse response) {
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(signInRequest.getUsername(), signInRequest.getPassword())
);
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
securityContextRepository.saveContext(context, request, response);
return new ResponseEntity<>(HttpStatus.OK);
}
}
@Configuration
public class SecurityContextRepositoryConfig {
@Bean
public SecurityContextRepository securityContextRepository() {
return new HttpSessionSecurityContextRepository();
}
}
错误是我使用 withCredentials 作为标头。但它是财产。我在
index.js
这个属性默认设置为true
axios.defaults.withCredentials = true;