如何将 JWT 不记名令牌传递给另一个请求?

问题描述 投票:0回答:1

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。

spring-boot jwt http-status-code-403 bearer-token
1个回答
0
投票

您找到解决方案了吗?如果是,请告诉我。

© www.soinside.com 2019 - 2024. All rights reserved.