spring MVC에서 Vue history mode 설정하기 (spring security 사용 중)

Denia·2024년 1월 14일
0

TroubleShooting

목록 보기
13/25

나는 url이 깔끔하게 보여지는게 좋아서, vue에서 history mode를 사용하고 있다.

vue를 build 후 build된 파일을 spring에 올려서 테스트를 해보다가 특이한 이슈를 발견했다.

/ 주소는 접속이 되는데 해당 주소에서 다른 주소로 이동을 하면 whitelabel error page가 뜨면서 제대로 화면이 나오지 않았다.

해당 내용으로 좀 더 찾아보니 vue에서 history mode를 사용해서 발생한 문제였다.

지금부터 해당 문제를 해결한 내용에 관해서 기술을 해보겠다.

발생 원인

  1. history mode를 사용하는 경우, 다른 주소로 이동하려고 할 때 클라이언트내에서 다 처리 되는게 아니라 서버에다가 이동하려는 해당 url로 요청을 보내고 응답을 받아온다.
  2. 서버에서는 비지니스 로직 관련 api만 정의되어 있지, 다른 view page 요청 관련해서는 api가 정의되어 있지 않기 때문에 해당하는 controller를 찾을 수 없다.
  3. 해당하는 controller를 찾을 수 없으므로 서버는 error 페이지를 return한다. (※ 그게 바로 whitelabel error page다.)

해결 방법

vue 와 spring 모두에서 처리해야 하는 일들이 있습니다.

vue

  • 잘못된 주소로 요청을 하는 경우, not found page를 보여주자.

NotFound Page 생성

<script setup>
</script>

<template>
  <div>
    <h1>해당 페이지를 찾을 수 없습니다.</h1>
    <p>요청 주소를 다시 한번 확인해주세요.</p>
  </div>
</template>

<style scoped>
</style>

router.js에 NotFound Page 추가 및 catch-all 라우트를 구현

routes에 아래 내용을 추가하자

  • {path: '/notFound', component: NotFound}
  • {path: "/:pathMatch(.*)*", redirect: "/notFound"}
import {createRouter, createWebHistory} from "vue-router";
import NotFound from "@/pages/NotFound.vue";

const routes = [
    {path: '/', component: Home},
    {path: '/login', component: Login},
    {path: '/notFound', component: NotFound},
    {path: "/:pathMatch(.*)*", redirect: "/notFound"}
]

export const router = createRouter({
    history: createWebHistory(),
    routes
})

spring

  • 비지니스 로직 관련 api를 제외하고는 모두 view page에 대한 요청으로 인식하도록 filter를 만들어서 filter chain에 등록한다.
    • index.html 하나로 동작되는 SPA 이므로, 비지니스 로직 관련 api를 제외하고는 모두 / 로 forward 시켜주자. (/로 forwaring 되면 index.html로 연결이 되므로)

history mode filter 추가

//Forwards all routes to the main route
//This is for SPA (Single Page Application) [Ex. React, Angular, Vue]
public class HistoryModeFilter extends OncePerRequestFilter {
    // '/'로 시작하고 그 뒤에 점이 .이 없는 문자열과 일치
    // '/api'로 시작하는 URL은 제외
    // /home 또는 /user/profile과 일치, 하지만 /api/data, /api?query, /image.jpg, /script.js 와는 일치하지 않습니다.
    private Pattern patt = Pattern.compile("^(?!/api)/([^.]*)");
    // Main route
    private String endpoint = "/";

    @Override
    protected void doFilterInternal(@NonNull HttpServletRequest request,
                                    @NonNull HttpServletResponse response,
                                    @NonNull FilterChain filterChain)
            throws ServletException, IOException {
        if (this.patt.matcher(request.getRequestURI()).matches()) {
            RequestDispatcher rd = request.getRequestDispatcher(endpoint);
            rd.forward(request, response);
        } else {
            filterChain.doFilter(request, response);
        }
    }
}

security configuration에 filter 등록 및 white list에 root 주소(/) 등록

  • spring boot 3.2 사용했습니다.
  • FilterSecurityInterceptor.classdeprecated되었고, AuthorizationFilter.class를 사용하라고 해서 사용했습니다.
@RequiredArgsConstructor
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfiguration {
    private static final String[] WHITE_LIST_URL = {
    		"/api/auth/**", //로그인 및 회원가입
            "/assets/**", //js 및 css 파일
            "/favicon.ico", //favicon
            "/", //root 경로
            "/index.html", //index.html 파일
    };

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(req ->
                        req
                                .requestMatchers(WHITE_LIST_URL).permitAll()
                                .anyRequest().authenticated()
                )
                .addFilterBefore(new HistoryModeFilter(), AuthorizationFilter.class)

        return http.build();
    }
}

참고

HTML5 히스토리 모드
Spring MVC - Enable HTML5 history mode

profile
HW -> FW -> Web

0개의 댓글