<aside> 👿 문제점

</aside>

<aside> 📌 코드 구성

</aside>

  1. 시큐리티 설정
@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();
		}
		
		// 다른 코드도 존재..

}
  1. 인터셉터 설정
@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 토큰 검사에서 제외합니다.
    }

		// 다른 코드도 존재...

}
  1. 로그인 컨트롤러
@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();
    }

}
  1. 로그인 html
<!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>
  1. 메인페이지 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>
  1. 메인페이지의 ajax요청 url 코드
@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>

  1. 먼저 오류내용을 확인했다.

    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
    
  2. 이상해서 코드를 다시 확인했다.