Hello

사용자가 로그인을 하지않아 Exception 발생 경우 처리 방법

by 볼빵빵오춘기
더보기

페이지를 만들다보면 사용자가 로그인한 경우에만 들어갈 수 있는 페이지가 있다. 

예를 들면, 게시판에 글을 쓰는데 로그인한 사용자만 글을 쓸 수 있는 경우 

이런 경우 처리를 해줘야할 것 같은데 단순하게 생각해서 아래와 같이 코드를 작성하였다. 

    @GetMapping("/communityW")
    public String writeForm(Model model,@AuthenticationPrincipal PrincipalDetails principalDetails){
        if(principalDetails.getUsername()==null || principalDetails==null){
            model.addAttribute("message", "로그인한 회원만 이용가능합니다.");
            model.addAttribute("searchUrl", "/loginForm");
        }
        return "community/write";
    }

 

이렇게 적다보니 이런 경우의 페이지가 한 두 개면 저것도 나쁘지않은 것 같긴한데 라고 생각했는데 

생각보다 많다. 나쁘지않다가 아니라 나쁘다.

토이 프로젝트를 해보면서 페이지도 몇 개없고 저런 페이지가 얼마나 되겠어 했는데 

게시판 하나에 글 쓰기 페이지, 글 수정 페이지, 글 삭제  페이지에 들어간다 생각하면

내가 하는 토이 프로젝트에 게시판이 3개니깐 그러면 저런 코드를 9번 반복하게 된다. 

 

어후 이 부분이 한 번에 완벽하게 작동하고 프로젝트에서 클라이언트가 컨펌해주고 끝나면 좋겠지만

그럴 경우가 얼마나 된다고.. 

 

자 그러면 사용자가 로그인해야만 사용할 수 있는 페이지를 어떻게 처리하면 좋을까?

사용자가 로그인을 하지않아 Exception 발생 경우 처리 방법

사전에 검사하기

  • 컨트롤러 메서드 실행 전에 principalDetails.getUsername()이 null인지 확인하고, null일 경우에 대응하는 로직을 추가한다.
  • 컨트롤러에서 사용자 정보를 검사하고 필요한 경우 예외를 방지할 수 있다.
  • 이 로직은 컨트롤러마다 중복될 수 있으므로, 중복을 줄이고 유지보수성을 높이기 위해 AOP(Aspect-Oriented Programming)와 같은 기술을 활용하여 중복 코드를 추상화하는 방법도 고려할 수 있다.
@GetMapping("/updateForm")
public String updateForm(int id, Model model, @AuthenticationPrincipal PrincipalDetails principalDetails) {
    if (principalDetails == null || principalDetails.getUsername() == null) {
        // 사용자 정보가 없는 경우 처리할 로직 (예: 로그인 페이지로 리다이렉트)
        return "redirect:/loginForm";
    }

    // 사용자 정보가 있는 경우 정상적인 처리
    // ...
}

 

Service 계층에서 처리하기

Controller는 단순히 요청을 받고 반환하는 역할을 하므로, principalDetails.getUsername()이 필요한 비즈니스 로직이나 데이터 접근은 Service 계층으로 이동하여 처리한다.

// Controller
@GetMapping("/updateForm")
public String updateForm(int id, Model model, @AuthenticationPrincipal PrincipalDetails principalDetails) {
    userService.updateForm(id, model, principalDetails);
    return "updateForm";
}
// Service
public void updateForm(int id, Model model, PrincipalDetails principalDetails) {
    if (principalDetails == null || principalDetails.getUsername() == null) {
        // 사용자 정보가 없는 경우 처리할 로직
        throw new IllegalArgumentException("사용자 정보 없음!!.");
    }

    // 사용자 정보가 있는 경우 정상적인 처리
    // ...
}

 

Spring Security의 Access Control 설정

  • Spring Security를 이용하여 접근 제어를 설정하고, 접근 권한에 따라 자동으로 처리되도록 설정할 수도 있다.
  • 이 경우, 컨트롤러에서 사용자 정보가 없는 경우가 발생하지 않도록 할 수 있다.

 

Spring Security의 Access Control 설정하기

로그인하지 않은 상태로 보호된 페이지에 접근하거나, 권한이 없는 상태로 특정 페이지에 접근했을 때 처리하는 방법을 설정할 수 있다.

이를 위해 HttpSecurity의 exceptionHandling 메서드를 사용하여 커스텀 접근 거부 페이지를 설정한다.

 

SecurityConfig 설정 코드 추가

1. exceptionHandling

  • authenticationEntryPoint는 인증되지 않은 사용자가 보호된 페이지에 접근할 때 처리하는 메서드이다.
    /loginForm?error=true로 리다이렉트하도록 설정했다.
  • accessDeniedHandler는 권한이 없는 사용자가 특정 페이지에 접근할 때 처리하는 메서드이다.
    /accessDenied로 리다이렉트하도록 설정했다.

 

2. authenticationEntryPoint 메서드

  • AuthenticationEntryPoint를 구현하여 인증되지 않은 사용자가 접근할 때의 동작을 정의한다.
  • 로그인 페이지로 리다이렉트하며, URL에 error=true를 추가하여 오류 메시지를 표시할 수 있다.

 

3. accessDeniedHandler 메서드

  • AccessDeniedHandler를 구현하여 권한이 없는 사용자가 접근할 때의 동작을 정의한다.
  • 접근 거부 페이지로 리다이렉트한다.

✅ 이렇게 설정하면 보호된 페이지에 접근했을 때, 로그인 페이지로 리다이렉트하거나 권한이 없을 때 접근 거부 페이지로 리다이렉트하도록 할 수 있습니다. 이를 통해 사용자의 접근 권한을 보다 세밀하게 관리할 수 있다.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	// .. 

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .formLogin(formLogin -> formLogin
                .loginPage("/loginForm")
                .loginProcessingUrl("/login")
                .successHandler(loginSuccessHandler())
                .failureHandler(loginFailureHandler()))
            .authorizeRequests(authorize -> authorize
                .antMatchers("/admin/**").hasRole("ADMIN")
                .anyRequest().permitAll())
            .oauth2Login(oauth2 -> oauth2
                .loginPage("/loginForm")
                .userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint
                    .userService(principalOauth2UserService))
                .successHandler(loginSuccessHandler())
                .failureHandler(loginFailureHandler()))
            .exceptionHandling(exceptionHandling -> exceptionHandling
                .authenticationEntryPoint(new CustomAuthenticationEntryPoint())
                .accessDeniedHandler(new CustomAccessDeniedHandler()))
            .csrf().disable()
            .cors().and();
    }
    
    @Bean
    public CustomAuthenticationEntryPoint customAuthenticationEntryPoint() {
        return new CustomAuthenticationEntryPoint();
    }

    @Bean
    public CustomAccessDeniedHandler customAccessDeniedHandler() {
        return new CustomAccessDeniedHandler();
    }
	// .. 
}

 

CustomAuthenticationEntryPoint 구현 (401 처리)

public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        response.sendRedirect("/accessDenied");
    }
}

 

CustomAccessDeniedHandler 구현 (403 처리)

public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.sendRedirect("/accessDenied2");
    }
}

 

accessDenied.html & accessDenied2.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Access Denied</title>
    <link rel="stylesheet" th:href="@{/css/styles.css}">
</head>
<body>
    <div class="container">
        <h1>접근 거부 (401)</h1>
        <p>죄송합니다. 해당 페이지에 접근할 권한이 없습니다.</p>
        <p>다시 로그인하려면 <a th:href="@{/loginForm}">여기</a>를 클릭하세요.</p>
        <p>홈페이지로 돌아가려면 <a th:href="@{/}">여기</a>를 클릭하세요.</p>
    </div>
</body>
</html>
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Access Denied</title>
    <link rel="stylesheet" th:href="@{/css/styles.css}">
</head>
<body>
    <div class="container">
        <h1>접근 거부 (403)</h1>
        <p>죄송합니다. 해당 페이지에 접근할 권한이 없습니다.</p>
        <p>다시 로그인하려면 <a th:href="@{/loginForm}">여기</a>를 클릭하세요.</p>
        <p>홈페이지로 돌아가려면 <a th:href="@{/}">여기</a>를 클릭하세요.</p>
    </div>
</body>
</html>

 


 

참고링크 

https://velog.io/@stoph/Spring-Security%EC%97%90%EC%84%9C-%EC%98%88%EC%99%B8-%ED%95%B8%EB%93%A4%EB%A7%81-%EC%BB%A4%EC%8A%A4%ED%85%80%ED%95%98%EA%B8%B0

https://yoo-dev.tistory.com/28

https://mangkyu.tistory.com/146

 

 

블로그의 정보

Hello 춘기's world

볼빵빵오춘기

활동하기