본문 바로가기

프로그래밍/Spring & MyBatis

[Spring] 에러처리 + 에러페이지 커스터마이징 하기

오늘의 포스팅 내용은 바로 Spring Framework에서 에러가 발생했을 시 처리하는 방법에 대해서 알아보려고 해요.


크게 네가지 파트로 나눌껀데요~

이유는 Spring Security에 의해서 돌아가는 에러는 처리방법이 약간 까다롭기때문이에요.



(1) AJAX를 이용하는 서비스가 로그인이 필요할 경우

(2) 에러페이지 커스터마이징

(3) 일반 에러 처리

(4) NoHandlerFoundException

(5) AccessDeniedException



위 네가지에 대해서 포스팅을 할껀데 1,2,3번은 모두 Spring Security가 관리해요.

그래서 다루기가 약간 까다롭지요~


그럼 포스팅을 시작해볼까요!


(1) AJAX를 이용하는 서비스가 로그인이 필요할 경우

사실 오늘 하는 포스팅중 가장 필요한 포스팅이 아닐까해요.


AJAX는 페이지가 바뀌지않고 서비스를 이용할수 있도록 해야한다했었죠?


근데 로그인이 안돼있거나 세션아웃(자동로그아웃)떄문에 서비스를 이용하기위해 로그인이 필요하다면?


버튼을 눌렀을때 알림창이 나타나는 서비스일경우 

Javascript에서 예외처리가 전혀 안되있으면 다음과 같은 현상이 발생합니다.

로그인 페이지 코드가 정직하게 알람창으로 띄워졌죠?


F12로 개발자 모드 창을 띄워서 살펴보면 ajaxTest?_=1470377241012는 이와같은 현상을 테스트를 위해 임시로 만들어놓은거에요.

인증이 필요한 서비스를 로그인 안한 상태에서 AJAX 요청을 했더니 곧바로 로그인 페이지가 응답하였어요.  


AJAX요청에 로그인이 필요하면 누가봐도 예외상황이지만 AJAX에서는 success 처리됩니다. 

그렇게 되면 Spring Security의 내부 처리가 꼬여서 원하지 않는 방향으로 웹서비스가 운영될 수 있어요.



그러면 어떻게 다음과같이 잘 감지시킬 수 있을까?


생각보다 매우 간단합니다. SecurityContextXml.java의 내용을 조금만 바꿔주면 되거든요.

success 처리가 안되고 error 처리로 우회하게끔 처리를 해야겠죠?


* 테스트를 위한 각 페이지의 권한설정도 바뀌었으니 유의해주세요!

- SpringSecurityXml.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/ajaxTest").authenticated()
                .antMatchers("/login**").anonymous()
                .antMatchers("/register**").anonymous()
                .antMatchers("/register/**").anonymous()
                .antMatchers("/visitors_book**").authenticated()
                .antMatchers("/visitors_book/**").authenticated()
                .and()
            .formLogin()
                .loginPage("/login").permitAll(false)
                .usernameParameter("login_email").passwordParameter("login_password")
                .loginProcessingUrl("/login-request")
                .failureHandler(simpleUrlAuthenticationFailureHandler("login_email""/login"))
                .and()
            .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/")
                .invalidateHttpSession(true)
                .deleteCookies(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY)
                .and()
            .rememberMe()
                .rememberMeParameter("login_rememberme")
                .tokenRepository(getPersistentTokenRepository())
                .tokenValiditySeconds(1209600)
                .and()
            .exceptionHandling() // Here
                .authenticationEntryPoint(loginUrlAuthenticationEntryPoint()); // Here
    }
 
    @Bean
    public LoginUrlAuthenticationEntryPoint loginUrlAuthenticationEntryPoint(){
        
        LoginUrlAuthenticationEntryPoint entry = new LoginUrlAuthenticationEntryPoint("/login"){
            @Override
            public void commence(final HttpServletRequest request, final HttpServletResponse response,
                    final AuthenticationException authException) throws IOException, ServletException{
                
                String ajaxHeader = request.getHeader("X-Requested-With");
                if(ajaxHeader != null && "XMLHttpRequest".equals(ajaxHeader)){
                    //InsufficientAuthenticationException
                    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
                }
                else{
                    super.commence(request, response, authException);
                }
            }
        };
        return entry;
    }
cs

* configure(HttpSecurity http) 메소드는 이제 너무 많이 보셨죠?

보시는 바와 같이 30번, 31번 줄이 추가됐어요. 기존에 쓰던 핸들러가 아닌 사용자가 정한걸 쓰려는 속셈이죠.


그리고 새로 추가된 loginUrlAuthenticationEntryPoint()가 있어요.

LoginUrlAuthenticationEntryPoint는 로그인이 필요할때 동작하는 클래스에요.


해당클래스를 약간 바꿀껀데요 바로 LoginUrlAuthenticationEntryPoint 클래스 내부의
commence(HttpServletRequest, HttpServletResponse, AuthenticationException) 메소드를 살짝 바꿔주면 되는데요!


오버라이딩으로 새로 선언한걸 보시면 HttpServletRequest 변수에서 특정 헤더를 가지고오죠?

헤더라는건 말 그대로 클라이언트에서 어떤것을 요청할때 따라붙는 머릿말같은거라고 할수있어요.


(거의 모든 분들이 AJAX 사용 시 jQuery라이브러리를 이용하기때문) jQuery를 이용해 AJAX요청을 하면

X-Requested-With라는 헤더가 붙고 그 헤더에는 XMLHttpRequest라는 값이 따라와요.


* 해당하는 헤더가 있다면

 response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); 메소드를 실행시키는거에요.

이 명령어는 Unauthorized(401, 권한없음) 에러코드를 보내주는거에요!


이 작업만 하시면 간단하게 완료됩니다!


* 이제 테스트를 해봐야겠죠?


'com.exam.springproject' 패키지 내에 ErrorController 클래스를 생성해주세요.


- ErrorController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.exam.springproject;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.WebAttributes;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
 
@Controller
public class ErrorController {
 
    @ResponseBody
    @RequestMapping(value = "/ajaxTest", method = RequestMethod.GET, produces=MediaType.TEXT_PLAIN_VALUE)
    public String page(HttpServletRequest request, HttpServletResponse response){
        return "ajaxTest";
    }
}
 
cs

다 작성하셨다면 home.jsp 파일에 아래의 내용을 추가해주세요!


그리고 body태그가 끝나는 바로 위에 아래의 내용을 추가해주세요!

- home.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<button onclick="ajTest();">AJAX Error Test</button>
<script src="${pageContext.request.contextPath}/resources/javascript/home.js"></script>
<script>
    var contextPath = "${pageContext.request.contextPath}";
    
    var csrfHeaderName = "${_csrf.headerName}";
    var csrfToken = "${_csrf.token}";
    
    function ajTest(){
        $.get({
            url: contextPath + "/ajaxTest",
            cache: false,
            success: function(data, status, xhr){
                alert(data);
                console.log(status, xhr);
            },
            error: function(xhr, status, error){
                
                if(xhr.status == 401){
                    if(confirm("로그인이 필요합니다. 지금 바로 로그인하시겠습니까?")){
                        window.open(contextPath + "/login""_self");
                    }
                }
                else{
                    alert(xhr.status + ": " + error);
                }
                console.log(xhr, status, error);
            }
        });
    }
</script>
cs

대충 감이 오시죠? 어떻게 동작할지..


* 이제 서버를 키고 테스트해봅니다!

- 위에서 각페이지에 대한 권한설정을 바꿨기때문에 이제 메인페이지도 로그인 없이 접근이 가능하실꺼에요.


- 성공적으로 서버에 접속하면 로그인을 합니다!

 

가장 아래에 AJAX Error Test라는 버튼이 있는데 그것을 클릭합니다~
 



- 로그인 한 상태 버튼을 누르면 다음과 같이 ajaxTest라는 글이 경고창에 뜹니다!
 


이제 로그아웃을 해보세요!
 



로그아웃 후 AJAX Error Test버튼을 다시 눌러보면?

로그인 하시겠냐는 메시지가 뜨고 확인을 누르게 되면 로그인 페이지로 즉각 이동하게됩니다!

※ 꼭 401번 상태코드로 안해도 되고, X-Requested-With라는 헤더를 이용 하실 필요가 없습니다!

상태코드는 맘에드는 다른거로 하셔도 되고, 헤더는 개발자님이 직접 다른거로 만드셔도 됩니다!


대신 그에따른 처리는 다 해줘야겠죠?




(2) 에러페이지 커스터마징

원래 웹 서버에서 에러가 뜨면 다음과 같이 나타납니다!

이 페이지는 Apache Tomcat에서 띄우는 에러페이지입니다!

근데 남이 볼 페이진데 저렇게 없어보여야 되겠어요?


보통 잘나가는 웹사이트들은 에러페이지조차 예쁘게들 만드시죠.


혹은 일반사용자에게 보여주고싶지 않은 문제를 가릴때도 사용들 하시지요.

예를 들면...


이런 SQL에 관련된 에러들은 그대로 노출시키기엔 부담이 좀 있겠죠?


전 에러페이지를 다음과 같이 만들께요.

Deployed Resources > webapp > WEB-INF > views 폴더에

error 폴더를 만들고 그 안에 error.jsp파일을 생성할게요.


- error.jsp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Insert title here</title>
</head>
<body>
<h2>${EXCEPTION_HANDLER.errorCode} - ${EXCEPTION_HANDLER.errorCodeString}</h2>
<h3>${EXCEPTION_HANDLER.errorClass}</h3>
<p>${EXCEPTION_HANDLER.errorMessage}</p>
</body>
</html>
cs

leonard_special-17

완전 없어보이게 만들었긴 한데... 넓은 아량으로 봐주세요...

테스트를 위한거니까요... 


내용이 (3) 일반 에러 처리로 이어집니다!


(3) 일반 에러 처리

여기서 일반에러라는 것은 Spring Security와 연관되지 않는 모든 오류를 말해요.


아까 보셨던 SQL 관련에러는 물론 파라메터 에러등 웹서비스에서 일어날 수 있는 모든 오류를 말하는데,

개발자가 미처 고려하지 못한 에러가 있을 수 있죠?


혹은 개발자가 일반적으로 컨트롤 할 수 없는 에러.

예를 들어 위와 같은 @RequestParam의 타입이 int일 경우

숫자이외의 다른 문자가 들어가면 다음과 같은 에러가 뜹니다.


* MethodArgumentTypeMismatchException

위의 예외 사항이 발생하면 @RequestMapping으로 메소드 내부에 들어오기도 전에 오류가 나서 예외를 처리 할 수 없게됩니다.


이처럼 개발자가 예상하지 못했던 에러를 Spring Framework에서는 기능을 제공합니다! 

다음과 같이 하면 됩니다! 아주 쉬워요.


* 우선 'com.exam.settings' 패키지에 CustomExceptionHandler라는 클래스를 생성했어요.



- CustomExceptionHandler.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.exam.settings;
 
import java.sql.SQLException;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.NoHandlerFoundException;
@ControllerAdvice
public class CustomExceptionHandler {
    
    private final Logger logger = LoggerFactory.getLogger(CustomExceptionHandler.class);
    
    private final static String EXCEPTION_HANDLER = "EXCEPTION_HANDLER";
    
    @ResponseBody
    
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    public Map<String, Object> methodArgumentTypeMismatchException(
            HttpServletRequest request, HttpServletResponse response, 
            MethodArgumentTypeMismatchException e){
        
        return makeExceptionAttribute(request, HttpStatus.BAD_REQUEST, e, null);
        
    }
    
    @ExceptionHandler(SQLException.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public ModelAndView sqlException(
            HttpServletRequest request, HttpServletResponse response, SQLException e){
        
        makeExceptionAttribute(request, HttpStatus.INTERNAL_SERVER_ERROR, e, null);
 
        return new ModelAndView("/error/error");
    }
    
    private Map<String, Object> makeExceptionAttribute(
            HttpServletRequest request, HttpStatus status, Exception e, String message){
        
        Map<String, Object> map = new HashMap<String, Object>();
        StringBuilder errorMessage = new StringBuilder();
        errorMessage.append(e.getMessage());
        if(message != null){
            errorMessage.append("<br>").append(message);
        }
        
        map.put("errorCode", status.value());
        map.put("errorCodeString", status.getReasonPhrase());
        map.put("errorClass", e.getClass().getName());
        map.put("errorMessage", e.getMessage());
        
        if(request != null){
            request.setAttribute(EXCEPTION_HANDLER, map);
        }
        return map;
    }
    
    
}
 
cs

* 클래스 선언해주는 곳 위에 어노테이션이 하나 있어요. @ControllerAdvice 어노테이션

각각의 메소드에 @ExceptionHandler, @InitBinder, @ModelAttribute 어노테이션 중 하나가 붙어있으면

@RequestMapping 어노테이션이 적용된 웹 서비스에 대해서 위의 어노테이션이 작동 해요.


* 여기서 우리는 @ExceptionHandler 어노테이션을 사용할껀데 딱 봐도 예외상황 처리를 위한 어노테이션이죠?

@ExceptionHandler 어노테이션에 원하는 클래스의 예외상황 처리 명시해주면 웹 서비스중 일어나는 모든 에러를 처리해줍니다~! 


* 또한 @ResponseStatus 어노테이션에 응답코드를 명시해주면 View Page로 가는 응답코드를 강제변환 시킬 수 있습니다!

유연하게 사용하시면 정말 편리한 기능이지요.


반환 타입도 원하는 View Page나 

@ResponseBody 어노테이션을 이용한 원하는 데이터 방식(JSON 혹은 문자열 등)으로도 반환이 가능합니다.


* 그래서 지금은 두 가지의 예외상황(MethodArgumentTypeMismatchException, SQLException)을 정의하였습니다!


테스트를 해볼까요? 먼저 SQL Exception은 MySQL에서 일어나는 웬만한 에러를 잡아줍니다!

테스트 방법은... 회원가입 할 때 이미 있는 아이디 혹은 이미 있는 닉네임으로 가입해서

DuplicateKeyException(키 중복 예외)를 발생 시켜보겠습니다~


- 제가 평소에 로그인하는 계정이에요! 해당 열(Unique)이 Unique 속성을 가지고 있으니까 다시 가입하려하면

에러가 발생하겠죠?



- 가입 버튼을 누르면 다음과 같이 에러가 제대로 잡혀서 바뀐 에러페이지도 보이고 에러문구도 제대로 뜨죠?

하지만 여전히 SQL문에 대한 에러가 모두 다 뜨니 가리셔야겠다면 처리 로직을 조금 바꾸시는게 좋겠죠? 


* makeExceptionAttribute(HttpServletRequest, HttpStatus, Exception, String메소드를

원하시는 방향으로 살짝 바꿔주시면 됩니다! 

* 다음 테스트는 파라메터에서 명시된 타입이 아닐 시 발생하는

MethodArgumentTypeMismatchException을 처리하도록 하겠습니다!


이 에러는 전에 잘 사용하던 페이지를 이용해보도록 할꺼구요~

@ResponseBody에 Map형태로 반환 되죠? 즉, JSON형태로 에러코드가 반환될거같네요!


* 웹브라우저 주소창에 다음과 같이 입력합니다!

(http://localhost:8080/springproject/visitors_book/reply/get?vb_id=a)

※ 로그인이 필요한 서비스라 로그인페이지가 먼저 뜰 수 있어요!


- Internet Explorer 11의 결과


- Chrome의 결과


※ Internet Explorer 11은 확인 한 결과 HTTP 응답코드가 400이면 무조건 저 페이지를 띄우는것 같아요.

그래도 AJAX 요청에서는 제대로 된 결과가 돌아오니 걱정마세요!

단, AJAX가 아닌 일반 에러 페이지를 사용할때는 참고를 하셔야겠죠?


- 아래의 사진은 Internet Explorer에서 제대로 된 AJAX 문자열이 왔다는 증거입니다.

 

leonard_special-14

 에러 종류가 한두개도 아니고 어떻게 다 일일히 처리하나 !!!


@ExceptionHandler(Exception.class) 한줄이면 명시해주지 않은 예외는 모두다 처리 된답니다!!


(4) NoHandlerFoundException

NoHandlerFoundException은 에러클래스 이름으로 유추할 수 있듯

@RequestMapping 어노테이션으로 처리되지 않은 곳을 접근하려 할때 나타나는거에요!


* 근데 해당 오류는 일반적으로는 발생시킬수 없고 Tomcat이 알아서 처리해요.

그런데 간단한 작업 하나면 한방에 작업 완료!


* 오랜만에 SecurityWebXml.java에 들어가봅니다!

다음과 같이 수정해주세요~


-SecurityWebXml.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    @Override
    protected void beforeSpringSecurityFilterChain(ServletContext servletContext){
        
        super.insertFilters(servletContext, getCharacterEncodingFilter(), getMultipartFilter());
        
        AnnotationConfigWebApplicationContext ctx = 
                new AnnotationConfigWebApplicationContext();
        ctx.register(ServletContextXml.class);
        ContextLoaderListener listener = new ContextLoaderListener(ctx);
        servletContext.addListener(listener);
        ctx.setServletContext(servletContext);
        
        DispatcherServlet dispatcher = new DispatcherServlet(ctx);
        dispatcher.setThrowExceptionIfNoHandlerFound(true);
        
        ServletRegistration.Dynamic servlet = 
                servletContext.addServlet("dispatcher", dispatcher);
        
        servlet.addMapping("/");
        servlet.setLoadOnStartup(1);
    }
cs


14번째 줄에 dispatcher.setThrowExceptionIfNoHandlerFound(true); 가 보이시나요?

저 위의 메소드만 사용하면 NoHandlerFoundException을 발생시켜서 직접 조절할 수 있게됩니다!


근데 직접 제어하려면 뭘 해야할까요?

네. @ExceptionHandler 어노테이션에 해당 예외를 등록시켜줘야겠죠?


- CustomExceptionHandler.java

1
2
3
4
5
6
7
8
9
    @ExceptionHandler(NoHandlerFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ModelAndView noHandlerFoundException(
            HttpServletRequest request, HttpServletResponse response, NoHandlerFoundException e){
        
        makeExceptionAttribute(request, HttpStatus.NOT_FOUND, e, e.getRequestURL());
        
        return new ModelAndView("/error/error");
    }
cs

* CustomExceptionHandler.java에 위와 같이 메소드를 작성해주시면 작업 끝!!


* 바로 테스트 들어가볼까요?


@RequestMapping으로 매핑되지 않은 페이지에 접근합니다 !!

즉, URL에 아무거나 막 쳐서 들어가라는거죠!



* 위와 같이 No Handler Found Exception이 우리가 만든 에러페이지에 정상적으로 뜨는걸 보실 수 있어요!


(5) AccessDeniedException 

AccessDeniedException은 접근이 허용되지 않은 사용자가 특정 페이지에 접근 하려할때 발생하는 오류에요!


예를 들면 관리자 전용페이지를 일반 사용자가 접근하려하거나, 비회원 전용페이지를 인증된 회원이 접근하려했을때요!


해당 오류는 처리하는 방법이 약간 까다롭습니다!

* 아까 ErrorController 클래스 만들었던거 기억하시죠?

거기에다가 다음의 페이지 처리 메소드를 추가해줍니다!


- ErrorController.java

1
2
3
4
5
6
7
8
9
10
    @RequestMapping(value = "/error/accessDenied", method = {RequestMethod.GET, RequestMethod.POST})
    public void accessDenied(HttpServletRequest request, HttpServletResponse response) throws IOException{
        
        Object obj = request.getAttribute(WebAttributes.ACCESS_DENIED_403);
        if(obj != null && obj instanceof AccessDeniedException){
            throw (AccessDeniedException) obj;
        }
        
        response.sendError(HttpServletResponse.SC_BAD_REQUEST);
    }
cs


AccessDeniedException 예외는 발생하게 되면 HttpServletRequest 객체안의 Attribute에 Exception 객체 통채로 저장됩니다.


HttpServletRequest 클래스의 getAttribute(String) 메소드로

WebAttributes.ACCESS_DENIED_403이라는 static 변수로 발생한 AccessDeniedException 객체를 얻어 올 수 있습니다!


저장된 AccessDeniedException 객체를 확인하여 맞으면 해당 Exception을 다시 강제로 발생시켜 

@ExceptionHandler로 핸들링이 가능하게끔 합니다.


※ 이게 귀찮으시거나 전혀 다른 예외처리를 적용시키고 싶으시다면 굳이

 throw (AccessDeniedException) obj 하지 마시고 if문 내부에서 처리해주셔도 됩니다!


* 그리고 CustomExceptionHandler.java에 @ExceptionHandler 어노테이션으로 해당 예외처리를 등록시켜주세요!

1
2
3
4
5
6
7
8
9
    @ExceptionHandler(AccessDeniedException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public ModelAndView accessDeniedException(
            HttpServletRequest request, HttpServletResponse response, AccessDeniedException e){
        
        makeExceptionAttribute(request, HttpStatus.FORBIDDEN, e, null);
 
        return new ModelAndView("/error/error");
    }
cs


* 마지막으로 SecurityContextXml.java의 내용을 바꿔주시면 끝납니다!

- SecurityContextXml.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/ajaxTest").authenticated()
                .antMatchers("/login**").anonymous()
                .antMatchers("/register**").anonymous()
                .antMatchers("/register/**").anonymous()
                .antMatchers("/visitors_book**").authenticated()
                .antMatchers("/visitors_book/**").authenticated()
                .and()
            .formLogin()
                .loginPage("/login").permitAll(false)
                .usernameParameter("login_email").passwordParameter("login_password")
                .loginProcessingUrl("/login-request")
                .failureHandler(simpleUrlAuthenticationFailureHandler("login_email""/login"))
                .and()
            .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/")
                .invalidateHttpSession(true)
                .deleteCookies(AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY)
                .and()
            .rememberMe()
                .rememberMeParameter("login_rememberme")
                .tokenRepository(getPersistentTokenRepository())
                .tokenValiditySeconds(1209600)
                .and()
            .exceptionHandling()
                .authenticationEntryPoint(loginUrlAuthenticationEntryPoint())
                .accessDeniedPage("/error/accessDenied");
    }
cs

메소드의 가장 마지막부분에 .accessDeniedPage("/error/accessDenied"); 메소드가 있죠?

반드시 @RequestMapping으로 매핑이 적용된 페이지주소를 기입해주세요!


여기까지 하셨으면 완료입니다!


* 자 이제 테스트를 해야겠네요!


- 메인페이지에서 로그인을 반드시 해줍니다!

 

- 로그인이 완료되면 로그인하는 페이지로 URL을 직접 입력하여 강제로 진입합니다!


짜잔!

로그인을 했는데 로그인 페이지로 다시 접근하려고 해서 접근 오류를 발생시켰어요!


"/login**" 페이지는 로그인이 안된 사용자만 접근하도록 했었죠!


* 마지막으로 AccessDeniedException까지 페이지를 제 입맛대로 바꿔보았습니다!


moon_and_james-2

꼭 커스터마이징을 하지않더라도 AccessDeniedException 오류가 나타나면 나름의 처리 메소드를 구현하셔도 매우 좋아요!