我正在尝试在 Spring Boot 中实现登录身份验证,我使用了 Spring Security 并在前端使用了 React 表单来进行身份验证,我使用 axios 来调用我的
/login
,效果很好,表单打开并完美进行身份验证,但是问题在于,登录成功后,我无法访问我的 spring 控制器中声明的其他端点
这是我的 Spring 安全配置
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(auth->auth
.requestMatchers("/","/login/**","/register")
.permitAll()
.anyRequest()
.authenticated())
.logout(logout->logout
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true))
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(session->session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))
.build();
}
我的登录控制器
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody RegisterUser registerUser) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(registerUser.getEmail(), registerUser.getPassword())
);
if (authentication.isAuthenticated()) {
return ResponseEntity.ok().body("Login successful");
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Login failed");
}
} catch (AuthenticationException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Login failed");
}
}
这是我的反应形式
axios.defaults.baseURL="http://localhost:8080"
axios.defaults.withCredentials=true;
const Login=()=>{
const [email, setEmail]=React.useState('');
const [password, setpassword]=React.useState('');
const [error, setError] = React.useState('');
const navigate =useNavigate();
const handlesubmit= async (e)=>{
e.preventDefault();
try {
const response = await axios.post("/login" ,{
email,
password,
});
if(response.status===200){
console.error(email,password)
navigate('/');
}
else {
navigate('/logs')
}
}
catch (e){
console.error(e)
setError("login failed")
}
}
尝试在安全配置中使用
login->login.form("/login")
和 loginprocesssurl("/login")
但不起作用,我还将代理设置为 spring 运行的端口,但没有结果
我现在想要的是登录成功后(是的)我想访问只有经过身份验证的用户才能访问的其他端点
对于自定义端点,缺少一个细节:在登录请求/响应期间未填充
SecurityContext
。如果不存储它,尽管身份验证成功,进一步的请求仍将是匿名的。
首选方法是在
SecurityFilterChain
: 中添加登录逻辑
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(...)
.formLogin(it -> it
.loginPage("/") // mapped to index.html, letting react handle it
.loginProcessingUrl("/login")
.successHandler((request, response, authentication) -> {
response.setStatus(HttpStatus.OK.value());
response.getWriter().print("Login successful");
})
.failureHandler((request, response, exception) -> {
response.sendError(HttpStatus.UNAUTHORIZED.value(), "Login failed");
})
)
.logout(...)
...
.build();
}
这是为什么呢?它添加了额外的
UsernamePasswordAuthenticationFilter
,其中包含 @PostMapping("/login")
端点的身份验证逻辑 + 它将 SecurityContext
存储在
ThreadLocal
中。随后,它被
SecurityContextRepository
保存在链下的另一个过滤器中。 (注意,从6版本开始需要显式保存) 如果您确实想控制端点中的登录流程(可能会产生一些副作用),请将上下文保存在那里:
...
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody RegisterUser registerUser) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(registerUser.getEmail(), registerUser.getPassword())
);
if (authentication.isAuthenticated()) {
// storing context in ThreadLocal, later will be saved in SecurityContextPersistenceFilter
SecurityContext context = SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
return ResponseEntity.ok().body("Login successful");
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Login failed");
}
} catch (AuthenticationException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Login failed");
}
}
...