<aside> 👿 문제점
</aside>
<aside> 📌 코드 구성
</aside>
@Slf4j
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(
HttpSecurity http,
CustomAuthenticationFilter customAuthenticationFilter,
JwtAuthorizationFilter jwtAuthorizationFilter
) throws Exception {
log.debug("[+] WebSecurityConfig Start !!! ");
return http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/resources/**").permitAll()
.requestMatchers("/main/rootPage").permitAll()
.anyRequest().authenticated()
)
// 1. 먼저, JwtAuthorizationFilter가 실행되어 요청 헤더에서 JWT 토큰을 추출하고 이 토큰을 검증한다.
.addFilterBefore(jwtAuthorizationFilter, BasicAuthenticationFilter.class)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.formLogin(login -> login
.loginPage("/login")
.permitAll()
)
// .formLogin(AbstractHttpConfigurer::disable)
// 2. CustomAuthenticationFilter가 실행되어 사용자 이름과 비밀번호를 사용하여 인증을 수행한다. 이 필터는 주로 로그인 요청을 처리하는 데 사용된다.
.addFilterBefore(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
// 다른 코드도 존재..
}
@Slf4j
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtTokenInterceptor())
.addPathPatterns("/**") // 모든 URL에 대해 JWT 토큰 검사를 적용합니다.
.excludePathPatterns("/user/login", "/login", "/main/rootPage"); // 로그인 페이지는 JWT 토큰 검사에서 제외합니다.
}
// 다른 코드도 존재...
}
@Slf4j
@RequiredArgsConstructor
@Controller
public class LoginController {
private final AuthenticationManager authenticationManager;
/**
* [View] 로그인 페이지를 연다.
*/
@GetMapping("/login")
public String login() {
return "login";
}
/**
* [Action] 로그인 프로세스를 동작시킨다.
*/
@PostMapping("/user/login")
public ResponseEntity<?> authenticateUser(@RequestBody UserDto userDto) {
log.warn("이걸 탔다.");
// Authenticate user
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
userDto.loginId(),
userDto.password()
)
);
SecurityContextHolder.getContext().setAuthentication(authentication);
// Return success status
return ResponseEntity.ok().build();
}
}
<!DOCTYPE html>
<html xmlns:th="<http://www.thymeleaf.org>">
<head>
<title>Login</title>
<script src="<https://code.jquery.com/jquery-3.7.0.js>" integrity="sha256-JlqSTELeR4TLqP0OG9dxM7yDPqX1ox/HfgiSLBj8+kM=" crossorigin="anonymous"></script>
<script src="<https://code.jquery.com/ui/1.13.2/jquery-ui.js>" integrity="sha256-xLD7nhI62fcsEZK2/v8LsBcb4lG7dgULkuXoXB/j91c=" crossorigin="anonymous"></script>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f0f0;
}
.login-form {
background-color: #fff;
padding: 20px;
border-radius: 5px;
box-shadow: 0px 0px 10px rgba(0,0,0,0.1);
}
.login-form input, .login-form button {
width: 100%;
padding: 10px;
margin-bottom: 10px;
border-radius: 5px;
border: 1px solid #ddd;
}
.login-form button {
background-color: #007BFF;
color: #fff;
cursor: pointer;
}
.login-form button:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div class="login-form">
<div>
<label>Username: </label>
<input type="text" id="username"/>
</div>
<div>
<label>Password: </label>
<input type="password" id="password"/>
</div>
<div>
<button id="login">Login</button>
</div>
</div>
<script>
$(document).ready(function() {
// 로그인 버튼 클릭 이벤트
$('#login').click(function() {
let username = $('#username').val();
let password = $('#password').val();
// 로그인 요청 처리
$.ajax({
url: '/user/login',
type: 'post',
contentType: 'application/json',
data: JSON.stringify({
loginId: username,
password: password
}),
success: function(data, textStatus, jqXHR) {
// Store the JWT token only when login is successful
let jwt = jqXHR.getResponseHeader('Authorization');
// Remove 'Bearer ' from the start of the token
jwt = jwt.replace('Bearer ', '');
debugger;
localStorage.setItem('jwt', jwt);
console.log("JWT:", jwt);
// Redirect to /main/rootPage
window.location.href = "/main/rootPage";
},
error: function(err) {
console.log(err);
}
});
});
});
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>Main Page</title>
<script src="<https://code.jquery.com/jquery-3.7.0.js>" integrity="sha256-JlqSTELeR4TLqP0OG9dxM7yDPqX1ox/HfgiSLBj8+kM=" crossorigin="anonymous"></script>
<script src="<https://code.jquery.com/ui/1.13.2/jquery-ui.js>" integrity="sha256-xLD7nhI62fcsEZK2/v8LsBcb4lG7dgULkuXoXB/j91c=" crossorigin="anonymous"></script>
<style>
body {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f0f0f0;
}
.logout-button {
padding: 10px 20px;
border-radius: 5px;
border: none;
background-color: #007BFF;
color: #fff;
cursor: pointer;
}
.logout-button:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<div>
<button id="logout" class="logout-button">Logout</button>
</div>
<script>
$(document).ready(function() {
// Send a token verification request to the server
$.ajax({
url: '/api/user/verifyToken',
type: 'POST',
headers: {
'Authorization': 'Bearer ' + localStorage.getItem('jwt')
},
success: function(response) {
// Handle success
console.log(response);
},
error: function(error) {
// Handle error
console.log(error);
// If the token is invalid, redirect to the login page
if (error.status === 401) {
alert("Session expired. Please login again.");
localStorage.removeItem('jwt');
window.location.href = '/login';
}
}
});
$('#logout').click(function() {
// Remove the JWT token
localStorage.removeItem('jwt');
// Redirect to the login page
window.location.href = '/login';
});
});
</script>
</body>
</html>
@RequiredArgsConstructor
@RestController
@RequestMapping("/api/user")
public class UserController {
private final UserService userService;
@PreAuthorize("hasAuthority('ADMIN')")
@GetMapping("/profile")
public UserDto getProfile() {
SecurityUserDetailsDto userDetails = userService.getAuthenticatedUser();
String loginId = userDetails.getUserDto().loginId();
// Use the username to fetch the user's profile from the database
return Optional.ofNullable(userService.findByLoginId(loginId))
.orElseThrow(() -> new ProfileApplicationException(ErrorCode.USER_NOT_FOUND));
}
@PostMapping("/verifyToken")
public ResponseEntity<?> verifyToken(@RequestHeader("Authorization") String token) {
try {
// Verify the token
TokenUtils.isValidToken(token);
// If the token is valid, return a success message
return ResponseEntity.ok("Token is valid");
} catch (JwtException e) {
// If the token is not valid, return an error message
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Token is not valid");
}
}
}
<aside> 📌 문제 해결을 위한 노력
</aside>
먼저 오류내용을 확인했다.
2023-08-06T14:30:13.445+09:00 DEBUG 10564 --- [nio-8080-exec-2] c.j.p.c.s.filter.JwtAuthorizationFilter : [+] header Check: null
2023-08-06T14:30:13.445+09:00 ERROR 10564 --- [nio-8080-exec-2] c.j.p.c.s.filter.JwtAuthorizationFilter : OTHER TOKEN ERROR
com.jinan.profile.exception.ProfileApplicationException: null
2023-08-06T14:30:16.668+09:00 DEBUG 10564 --- [nio-8080-exec-3] o.s.security.web.FilterChainProxy : Securing GET /main/rootPage
2023-08-06T14:30:16.669+09:00 DEBUG 10564 --- [nio-8080-exec-3] c.j.p.c.s.filter.JwtAuthorizationFilter : [+] header Check: null
2023-08-06T14:30:16.669+09:00 ERROR 10564 --- [nio-8080-exec-3] c.j.p.c.s.filter.JwtAuthorizationFilter : OTHER TOKEN ERROR
간략히 요약하자면 header에 토큰이 null이 나온다.
나는 localStorage에 jwt가 저장된것을 확인까지 했다.
이상해서 코드를 다시 확인했다.