Stack Overflow 社区您好,
我在 Spring Boot Web 应用程序中遇到问题,成功登录或注册后收到 403 Forbidden 错误。以下是我的设置的简要概述:
Spring Boot is used for the backend.
Thymeleaf is used for server-side templating.
JavaScript handles client-side interactions.
I'm implementing JWT token-based authentication.
问题:
上下文: 我已经成功实现了用户注册和登录功能。 成功登录或注册后,用户将被重定向到/main.html。
问题: 尽管身份验证成功,但我在尝试访问 /main.html 时遇到
403 Forbidden
错误。
安全配置: 我已经配置了 Spring Security 来处理身份验证和授权。 我正在使用 JWT 令牌进行身份验证。
代码片段:
安全配置:
package com.logistics.logisticsCompany.config;
import com.logistics.logisticsCompany.entities.users.User;
import jakarta.servlet.Filter;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfiguration {
private final JwtAuthenticationFilter jwtAuthFilter;
private final AuthenticationProvider authenticationProvider;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception{ //1:55:40
http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers( "/api/v1/auth/**", "/login", "/register") //todo: change whitelist pages
.permitAll()
.anyRequest()
.authenticated()
//.and() of course .and() is deprecated, because why not
)
.sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) //spring will create new session for each request
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
身份验证服务:
package com.logistics.logisticsCompany.auth;
import com.logistics.logisticsCompany.config.JwtService;
import com.logistics.logisticsCompany.entities.users.User;
import com.logistics.logisticsCompany.repository.UserRepository;
import com.logistics.logisticsCompany.repository.UserRoleRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.Set;
@Service
@RequiredArgsConstructor
public class AuthenticationService {
private final UserRepository repository;
private final PasswordEncoder passwordEncoder; //todo: add a bean -fixed - the bean wasn't public in ApplicationConfig
private final JwtService jwtService;
private final AuthenticationManager authenticationManager;
private final UserRoleRepository userRoleRepository;
public AuthenticationResponse register(RegisterRequest request) {
var user = User.builder()
.username(request.getUsername())
.password(passwordEncoder.encode(request.getPassword()))
.userRoleList(Set.of(userRoleRepository.findUserRoleById(1))) // Add the desired role to the set// todo: find a way to put
.build();
repository.save(user);
var jwtToken = jwtService.generateToken(user);
return AuthenticationResponse.builder()
.token(jwtToken) //1:52:21
.build();
}
public AuthenticationResponse authenticate(AuthenticationRequest request) { //login authentication
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
request.getUsername(),
request.getPassword()
)
);
var user = repository.findByUsername(request.getUsername());
var jwtToken = jwtService.generateToken(user);
return AuthenticationResponse.builder()
.token(jwtToken)
.build();
}
}
auth.js:
// auth.js
// Function to handle success response from authentication or registration
function handleSuccess(response) {
if (response.ok) {
return response.json().then(data => {
const token = data.token;
if (token) {
// Save the token to localStorage
localStorage.setItem('token', token);
// Redirect to a specified page or handle as needed
redirectToPage("/main.html");
} else {
console.error('Token not found in response:', data);
}
});
} else {
return response.json().then(data => {
console.error('Error:', data);
});
}
}
// Function to redirect to a specified page
function redirectToPage(page) {
window.location.href = page;
}
function authenticateUser() {
var username = document.getElementById("username").value;
var password = document.getElementById("password").value;
// Construct JSON payload
var payload = {
"username": username,
"password": password
};
// Log the token before making the request
console.log("Token in localStorage:", localStorage.getItem('token'));
// Send the JSON payload to the authentication endpoint with bearer token
fetch("/api/v1/auth/authenticate", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": "Bearer " + localStorage.getItem('token')
},
body: JSON.stringify(payload)
})
.then(handleSuccess)
.catch(error => console.error('Error:', error));
// Prevent form submission
return false;
}
function registerUser() {
var username = document.getElementById("username").value;
var password = document.getElementById("password").value;
// Construct JSON payload
var payload = {
"username": username,
"password": password
};
// Send the JSON payload to the registration endpoint
fetch("/api/v1/auth/register", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
})
.then(response => {
if (response.ok) {
// Registration successful, you might not get a token here
console.log("Registration successful");
// Redirect to login page or handle as needed
} else {
// Registration failed, handle errors
console.error('Error during registration:', response.status);
return response.json();
}
})
.then(data => {
console.log(data); // Handle additional registration response here
})
.catch(error => console.error('Error:', error));
// Prevent form submission
return false;
}
function sendAuthenticatedRequest() {
var token = localStorage.getItem('token'); // Retrieve the JWT token from localStorage
if (!token) {
console.error('Token not found. User is not authenticated.');
// Handle the case where the user is not authenticated, maybe redirect to login
return;
}
// Construct the headers with Authorization Bearer Token
var headers = {
'Authorization': 'Bearer ' + token,
'Content-Type': 'application/json'
};
// Make a GET request to /main.html with Authorization header
fetch('http://localhost:8082/main.html', {
method: 'GET',
headers: headers
})
.then(response => {
if (response.ok) {
// Handle the successful response, maybe display the content of main.html
return response.text();
} else {
// Handle the case where the server responds with an error (e.g., 403)
console.error('Error:', response.statusText);
}
})
.then(data => {
console.log('Response from /main.html:', data);
// You can update your UI with the content of main.html here
})
.catch(error => console.error('Error:', error));
}
// Call the function to send the authenticated request
sendAuthenticatedRequest();
身份验证控制器:
package com.logistics.logisticsCompany.auth;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/auth")
@RequiredArgsConstructor
public class AuthenticationController {
private final AuthenticationService service;
@PostMapping("/register")
public ResponseEntity<AuthenticationResponse> responseResponseEntity(@RequestBody RegisterRequest request
){
return ResponseEntity.ok(service.register(request));
}
@PostMapping("/authenticate")
public ResponseEntity<AuthenticationResponse> authenticationResponseResponseEntity(@RequestBody AuthenticationRequest request
){
return ResponseEntity.ok(service.authenticate(request));
}
}
登录.html:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
<!-- Bootstrap CSS -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm" crossorigin="anonymous">
<script src="src/main/resources/static/js/auth.js"></script>
</head>
<body class="container mt-5">
<div class="row justify-content-md-center">
<div class="col-md-6" style="max-width: 300px; text-align: center;">
<h1>Login</h1>
<br>
<form th:action="@{/api/v1/auth/authenticate}" method="post" onsubmit="return authenticateUser()">
<div class="form-group">
<label for="username">Username:</label>
<input type="text" class="form-control" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">Password:</label>
<input type="password" class="form-control" id="password" name="password" required>
</div>
<button type="submit" class="btn btn-primary btn-block">Login</button>
</form>
<p class="mt-3 text-center">Don't have an account? <a href="/register">Register here</a></p>
</div>
</div>
</body>
</html>
登录和注册都只使用用户名和密码,我完全不知道是否应该共享不记名令牌,如果是,如何共享。
我提前道歉,我第一次在这里发帖。 谢谢!
我尝试过的:
使用 Spring Security 和 JWT 实现用户身份验证和注册功能。配置 Spring Security 以允许无需身份验证即可访问特定端点,同时需要对其他端点进行身份验证。利用 JavaScript 中的 Fetch API 在身份验证时在 Authorization 标头中发送 Bearer Token。
预期结果:
用户成功登录或注册后,期望重定向到 main.html,并在授权标头中包含适当的 Bearer Token。这应该允许访问 main.html,因为用户已通过身份验证。
实际结果:
注册或登录成功后尝试访问main.html时遇到403 Forbidden错误。尽管拥有承载令牌,服务器仍不允许访问该资源。
后续步骤:
寻求帮助以确定 403 错误的根本原因,并确保经过身份验证的用户可以按预期成功访问 main.html。
您找到解决方案了吗?如果是,请告诉我。