From the frontend, a request is sent to the server. If the data is correct, the user is redirected to their dashboard (in this case, the admin page). The HTML file of this page sends a GET request to the controller along with the JWT token retrieved from a cookie. Here is the code of the page:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Office Page</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<h1>Welcome to Admin Office</h1>
<script>
function getJwtTokenFromCookie() {
const cookies = document.cookie.split(';');
let jwtToken = null;
cookies.forEach(cookie => {
const parts = cookie.split('=');
const name = parts[0].trim();
const value = parts[1] ? parts[1].trim() : '';
if (name === 'JWT_TOKEN') {
jwtToken = value;
}
});
return jwtToken;
}
const jwtToken = getJwtTokenFromCookie();
console.log('JWT Token:', jwtToken);
if (jwtToken) {
const headers = {
Authorization: `Bearer ${jwtToken}`
};
console.log('Request headers:', headers);
axios.get('http://localhost:8080/admin_office', { headers })
.then(response => {
console.log('Server response:', response);
})
.catch(error => {
console.error('Error:', error);
});
} else {
console.log('JWT Token not found in cookies.');
}
</script>
</body>
</html>
When I allow access to /admin_office in WebSecurityConfig (.antMatchers(“/admin_office/**”).permitAll()), as shown in the code below,
@Configuration
@RequiredArgsConstructor
@EnableWebSecurity
@Slf4j
public class WebSecurityConfig {
private final JWTTokenConfig jwtTokenConfig;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.cors().disable()
.authorizeRequests(authorize -> authorize
.antMatchers("/host_page", "/register", "/login", "/music_files").permitAll()
.antMatchers("/swagger-ui/**", "/swagger-resources/*", "/v3/api-docs/**", "/swagger-ui.html").permitAll()
.antMatchers("/personal_office/**").authenticated()
.antMatchers(HttpMethod.GET, "/main").permitAll()
.antMatchers("/free_subscription").hasAnyRole("FREE", "OPTIMAL", "MAXIMUM", "ADMIN")
.antMatchers("/optimal_subscription").hasAnyRole("MAXIMUM", "OPTIMAL", "ADMIN")
.antMatchers("/maximum_subscription").hasAnyRole("MAXIMUM", "ADMIN")
.antMatchers("/admin_office/**").permitAll()
//.antMatchers("/admin_office/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore((Filter) jwtTokenConfig, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
I see that there is an authentication header with a JWT token:
GET /admin_office HTTP/1.1
Accept: application/json, text/plain, */*
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: uk,uk-UA;q=0.9,en-US;q=0.8,en;q=0.7,ru;q=0.6
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwicm9sZXMiOlsiUk9MRV9BRE1JTiJdLCJleHAiOjE3MTkyNTMxNDQsImlhdCI6MT cxOTI1MDE0NH0.uE0Udi4vmYqmEw9ONpW0lpIUHWRFK4yfxwZvGUgzXl4
Connection: keep-alive
Cookie: Idea-4b25f4ef=61bb4bd3-acaa-48f6-a981-7d36d9342587; XSRF-TOKEN=8f2e4045-e028-4522- 8b27-91fe42f6c2ae; JSESSIONID=BCEC9B62DF292BBD69ACB107F692B9FA; JWT_TOKEN=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwicm9sZXMiOlsiUk9MRV9BRE1JTiJdLCJleHAiOjE3MTkyNTMxNDQs ImlhdCI6MTcxOTI1MDE0NH0.uE0Udi4vmYqmEw9ONpW0lpIUHWRFK4yfxwZvGUgzXl4
Host: localhost:8080
Referer: http://localhost:8080/admin_office
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
X-XSRF-TOKEN: 8f2e4045-e028-4522-8b27-91fe42f6c2ae
sec-ch-ua: "Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
But when I restrict access to /admin_office (.antMatchers(“/admin_office/**”).hasRole(“ADMIN”)):
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.cors().disable()
.authorizeRequests(authorize -> authorize
.antMatchers("/host_page", "/register", "/login", "/music_files").permitAll()
.antMatchers("/swagger-ui/**", "/swagger-resources/*", "/v3/api-docs/**", "/swagger-ui.html").permitAll()
.antMatchers("/personal_office/**").authenticated()
.antMatchers(HttpMethod.GET, "/main").permitAll()
.antMatchers("/free_subscription").hasAnyRole("FREE", "OPTIMAL", "MAXIMUM", "ADMIN")
.antMatchers("/optimal_subscription").hasAnyRole("MAXIMUM", "OPTIMAL", "ADMIN")
.antMatchers("/maximum_subscription").hasAnyRole("MAXIMUM", "ADMIN")
//.antMatchers("/admin_office/**").permitAll()
.antMatchers("/admin_office/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore((Filter) jwtTokenConfig, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
I receive a 403 error on the frontend. The request looks like this:
GET /admin_office HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: uk,uk-UA;q=0.9,en-US;q=0.8,en;q=0.7,ru;q=0.6
Connection: keep-alive
Cookie: Idea-4b25f4ef=61bb4bd3-acaa-48f6-a981-7d36d9342587; XSRF-TOKEN=8f2e4045-e028-4522- 8b27-91fe42f6c2ae; JSESSIONID=BCEC9B62DF292BBD69ACB107F692B9FA; JWT_TOKEN=eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxIiwicm9sZXMiOlsiUk9MRV9BRE1JTiJdLCJleHAiOjE3MTkyNTQ5ODYs ImlhdCI6MTcxOTI1MTk4Nn0.gcxzXYncPCI4rCj1m1Uaaex66380KgAGsKjYeSxzaj8
Host: localhost:8080
Referer: http://localhost:8080/host_page
Sec-Fetch-Dest: empty
Sec-Fetch-Mode: cors
Sec-Fetch-Site: same-origin
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36
sec-ch-ua: "Not/A)Brand";v="8", "Chromium";v="126", "Google Chrome";v="126"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
On the backend, everything is working. Below is the HTML file of the page that redirects to the user’s dashboard:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login</title>
</head>
<body>
<h2>Login</h2>
<form id="loginForm" method="POST" action="/login">
<label for="phoneNumber">Phone Number:</label>
<input type="text" id="phoneNumber" name="phoneNumber" required><br><br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required><br><br>
<button type="submit">Login</button>
</form>
<script>
document.getElementById('loginForm').addEventListener('submit', function(event) {
event.preventDefault();
const phoneNumber = document.getElementById('phoneNumber').value;
const password = document.getElementById('password').value;
fetch('/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ phoneNumber: phoneNumber, password: password })
})
.then(response => {
if (response.redirected) {
window.location.href = response.url;
} else if (response.ok) {
return response.json();
} else {
return response.json().then(error => {
throw new Error(error.message);
});
}
})
.catch(error => {
alert('Login failed: ' + error.message);
});
});
</script>
</body>
</html>
and my JWTTokenConfig:
@Component
@RequiredArgsConstructor
@Slf4j
public class JWTTokenConfig extends OncePerRequestFilter {
private final JWTService jwtService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authHeader != null && authHeader.startsWith("Bearer ")) {
jwt = authHeader.substring(7);
try {
username = jwtService.extractUserName(jwt);
} catch (ExpiredJwtException e) {
log.debug("Token lifetime has expired");
} catch (SignatureException e) {
log.debug("The signature is incorrect");
} catch (Exception e) {
log.debug("Error parsing token: " + e.getMessage());
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
List<GrantedAuthority> authorities = jwtService.getRoles(jwt).stream()
.map(role -> new SimpleGrantedAuthority(role))
.collect(Collectors.toList());
log.info("User {} with roles {}", username, authorities);
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
username,
null,
authorities
);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
filterChain.doFilter(request, response);
}
}
The frontend is open in Google Chrome.
Where could the reason be?