본문으로 바로가기

로그인 화면 커스터마이징을 했지만 부족한 부분들이 있다. 그래서 조금 더 로그인스럽게 보이기 위해 몇 가지 더 작업을 할 것이다. 지금 할 작업들은 에러 메시지 작업과 Access Denied 페이지 작업이다. 


현재 로그인 페이지 상태에서 잘못된 로그인 정보를 입력해보자. 로그인에 실패했지만 아무런 에러 메시지를 보지 못하고 로그인 페이지만 보일 것이다. 로그인이 실패했으면 왜 실패했는 지 에러 메시지를 띄워주어야 한다. 스프링에서 제공하는 기본 로그인 화면을 사용했을 때에는 잘못된 로그인 정보를 입력하면 에러 메시지가 보였다. 그렇기에 우리도 잘못된 로그인 정보를 받으면 에러 메시지를 띄워주도록 하자.


Spring Security에서 예외가 발생(로그인 실패)하게 되면 해당 예외에 대한 객체를 만들어서 세션에 저장한다. 해당 세션을 저장할 때 사용되는 Key의 이름은 SPRING_SECURITY_LAST_EXCEPTION 이다. 세션에서 해당 키를 이용하여 message 변수 값을 읽어와 화면에 출력하면 된다.


사실 세션을 통해서 에러 메시지를 출력하는 것은 좋지 못하다. 예를 들어 해킹을 통해 인증 실패가 계속 발생한다고 해보자. 그럼 세션에 예외 객체를 계속 저장할 것이다. 이렇게 여러번 계속 예외를 던지게 되면 WAS 메모리가 가득차서 Full CG가 발생하게 되어 웹 페이지 응답이 느려질 것이다. 그래서 예외가 발생할 경우에는 세션에 저장하지 않도록 해주는 것이 좋다. 지금은 어떠한 컨트롤러도 핸들러도 사용하지 않기 때문에 나중에 만들어가면서 에러 메시지를 세션에 이용하지 않게 할 것이다.


LoginPage.jsp

1
2
3
4
5
6
7
<c:if test="${not empty SPRING_SECURITY_LAST_EXCEPTION}">
    <font color="red">
        <p>Your login attempt was not successful due to <br/>
            ${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message}</p>
        <c:remove var="SPRING_SECURITY_LAST_EXCEPTION" scope="session"/>
    </font>
</c:if>
cs


인증에 실패했을 경우 기본 속성값(/login?error)으로 인하여, 파라미터 error를 셋팅하여 로그인 페이지에 보여준다. 이때 세션에 저장된 SPRING_SECURIRY_LAST_EXCEPTION 키 값도 같이 오게 된다. 따라서 <c:if> 태그를 사용하여 해당 키가 비어있지 않으면(인증에 실패했으면) 세션을 통해 저장되어 있는 에러 메시지를 보여준다.



위의 코드를 삽입하고 실행해보자. 위의 사진에 빨간 글씨가 인증에 실패했을 때 나오는 에러 문구인데, Bad credentials 라고 적힌 부분이 ${sessionScope["SPRING_SECURITY_LAST_EXCEPTION"].message 로 세션에 저장되어 온 에러 메시지이다. 이 메세지들 또한 원하는 메시지로 출력할 수 있다. 여기서 끝내도 되지만 나는 한글로 에러 메시지를 띄우고 원하는 문구를 삽입할 것이다.


해당 메시지들은 어디에 저장되어 있을까? 메이븐의 라이브러리(Libraries)에 들어가서 spring-security-core-버전.jar 에 들어가면 각 나라 언어별로 메시지 프로퍼티 파일들이 있다. message.properties 파일을 보면 key 값에 따라 메세지 내용들이 담겨 있는 것을 확인할 수 있다. 위의 에러 메시지는 key가 AbstractUserDetailsAuthenticationProvider.bacCredentials 인 것을 읽어 온 것이다. message 프로퍼티 파일들의 key 값을 동일하게 해준다면, 우리가 원하는 에러 메시지를 사용하여 새로운 message 프로퍼티 파일들을 만들 수 있다.


security_message.properties

1
2
3
4
5
AbstractUserDetailsAuthenticationProvider.badCredentials=아이디나 비밀번호가 맞지 않습니다. 다시 확인해주세요.
AbstractUserDetailsAuthenticationProvider.credentialsExpired=비밀번호 유효기간이 만료 되었습니다. 관리자에게 문의하세요.
AbstractUserDetailsAuthenticationProvider.disabled=계정이 비활성화되었습니다. 관리자에게 문의하세요.
AbstractUserDetailsAuthenticationProvider.expired=계정이 만료되었습니다. 관리자에게 문의하세요.
AbstractUserDetailsAuthenticationProvider.locked=계정이 잠겨있습니다. 관리자에게 문의하세요.
cs


WEB-INF 폴더에 message 폴더를 만들고 security_message.properties 파일을 생성하고 다음의 내용을 넣었다. message.properties를 그대로 복사해서 수정해도 되지만 난 일단 간단하게 사용할 것만 수정하였다. 이렇게 원하는 문구로 수정을 했으면 메세지 파일을 사용할 수 있게끔 설정을 해야 한다. 혹시 유니코드 문자로 나오는 게 싫으면 이클립스에 propedit 플러그인을 설치하면 된다.


context-root.xml

1
2
3
4
5
6
7
8
9
    <bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basenames">
            <list>
                <value>/WEB-INF/message/security_message</value>
            </list>
        </property>
        <property name="defaultEncoding" value="UTF-8"/>
        <property name="cacheSeconds" value="5"/>
    </bean>
cs


root-context 설정 파일에 넣었다. servlet-context 설정 파일에 넣으면 spring security가 messageSource에 접근할 수 없다. 따라서 root-context 설정 파일에 해당 설정을 해야 한다. 여기까지 설정을 했다면 실행을 해보자. 그러면 인증을 실패했을 때 한글로 에러메시지가 커스터마이징 된 것을 볼 수 있다.




이제는 접근 불가 페이지를 커스터마이징 해보자. 인증 후에 권한에 따라 접근 경로가 달라지는데 접근 권한이 없는 경로에 진입하게 되면 Access Denied 403 에러 페이지가 뜬다. 어느 사이트에 가도 이런 식의 에러 페이지는 절대 뜨지 않는다. 그렇기 때문에 커스터 마이징을 할 것이다. 거창한 것없이 그냥 접근 불가 페이지를 보여준다. 참고로 이것도 에러 메시지 세션으로 존재하여 원하는 접근 불가 메시지를 출력 할 수 있다.


context-security.xml

1
2
3
<access-denied-handler
    error-page="/access_denied_page"
/>
cs


403 페이지가 뜨지 않고 지정한 페이지로 이동하게 설정한다. ref 속성을 사용한다면 핸들러를 이용할 수 있다.


LoginController.java

1
2
3
4
    @RequestMapping(value="/access_denied_page")
    public String accessDeniedPage() throws Exception {
        return "/access_denied_page";
    }
cs


access_denied_page.jsp

1
<h6>권한이 없어 접근이 불가합니다.<br>관리자에게 문의하세요.</h6>
cs


그냥 페이지를 보여주는 것이기때문에 정말 별거 없다. 접근 불가 페이지 설정하는 context-security 부분만 잘 알아두면 된다. 이렇게 접근 불가 페이지를 보여주는 것이 아니라 알람창과 함께 메인 페이지로 가는 방법도 있다. 그건 GitHub에 있으니 그 방법을 쓰고 싶다면 참고해도 된다.



이번 글은 정말 개인 취향이기때문에 속성 설정부분만 유의하면 된다. GitHub