Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
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
Tags
more
Archives
Today
Total
관리 메뉴

DOing

[Spring Security] 로그인 성공, 실패 - 2 본문

Spring

[Spring Security] 로그인 성공, 실패 - 2

mangdo 2021. 4. 4. 18:54

저번 포스팅에서는 로그인과 로그아웃 기능을 구현해보았다.

이번 포스팅에서는 로그인의 성공, 실패 기능을 구현하겠다. 단순히 특정 페이지로 이동할 수도 있고 Handler를 통해서 특정한 로직을 수행하도록 할 수도 있다. 더불어서 로그인은 성공했으나, 사용자가 권한이 없을때 나오는 접근 제한 페이지와 로직도 구현해보자.

💡 <security:form-login>이용하기

기본으로 제공되는 로그인 페이지

기본으로 제공되는 로그인 페이지는 위의 그림과 같다. 만약 직접 커스텀을 하고 싶다면 security-context.xml에 아래의 코드를 추가해준다.

<security:http auto-config="true" use-expressions="true">
	<security:form-login login-page="/customLogin"/>
</security:http>

이 외에도 <security:form-login>태그에서는 로그인에 관련된 다른 일들을 할 수 있다.

<security:http auto-config="true" use-expressions="true">
	<security:form-login username-parameter="email" password-parameter="userpw"
    		login-processing-url="/mypage/login" login-page="/mypage/customlogin" 
            default-target-url="/" authentication-failure-url="/mypage/login"/>
</security:http>

- username-parameter : 입력한 ID에 대한 파라미터명

- password-parameter : 입력한 PW에 대한 파라미터명

- login-processing-url : View 페이지의 <form action=" "> 에서 지정한 URL

- login-page : 로그인 페이지 URL

- default-target-url : 로그인에 성공 페이지 URL

- authentication-failure-url : 로그인에 실패 페이지 URL

 

1. 로그인 성공 로직 구현하기

 지금까지는 단순히 로그인에 성공하면 단순하게 특정 페이지로 이동시켰다. 하지만 로그인 성공 이후에 특정한 동작을 수행하도록 할 수도 있다.

 

 로그인에 성공하면 사용자의 권한을 체크하여 member의 권한이라면 sample/member페이지로 이동시키고  admin의 권한이라면 sample/admin페이지로 이동시키는 로직을 구현해보자.

이런 경우에는 AuthenticationSuccessHandler라는 인터페이스를 구현해서 사용할  있다. AuthenticationSuccessHandler말고 SavedRequestAwareAuthenticationSuccessHandler 이용할 수도 있다. 이 클래스는 사용자가 원래 보려고 했던 페이지의 정보를 유지해서 로그인 후에 다시 원했던 페이지로 이동하는 방식이다.

 

@Service
public class CustomLoginSuccessHandler implements AuthenticationSuccessHandler {
	
	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {
		
        log.warn("login success");
		
		List<String> roleNames = new ArrayList<>();
		
		// 사용자가 가진 모든 권한을 문자열로 체크한다!
		// 사용자는 여러권한을 가졌을 수 있다.
		authentication.getAuthorities().forEach(authority->{
			roleNames.add(authority.getAuthority());
		});
		
		log.warn("ROLE NAMES: "+roleNames);
		
		if(roleNames.contains("ROLE_ADMIN")) {
			response.sendRedirect("/sample/admin");
			return;
		}
		if(roleNames.contains("ROLE_MEMBER")) {
			response.sendRedirect("/sample/member");
			return;
		}
		response.sendRedirect("/");
	}
}

 

AuthenticationSuccessHandler를 구현시킨 클래스를 security-context.xml에 빈으로 등록시킨다.

등록시킨 빈을 <security:form-login>태그에 다음과 같이 설정하자.


<bean id = "customLoginSuccess" class="com.phonemall.security.CustomLoginSuccessHandler"></bean>

<security:http auto-config="true" use-expressions="true">
	<security:form-login username-parameter="email" password-parameter="userpw"
    		login-processing-url="/mypage/login" login-page="/mypage/customlogin" 
            authentication-success-handler-ref="customLoginSuccess" authentication-failure-url="/mypage/login"/>
</security:http>

2. 로그인 실패 로직 구현하기

 같은 원리로 로그인 실패 로직을 구현해보자. 이번에는 AuthenticationFailureHandler를 라는 인터페이스를 구현하면 된다. 로그인을 실패하는 원인에는 아이디가 틀렸을 수도 있고 비밀번호가 틀렸을 수도 있고 비활성화된 계정일 수 있다.

이런 경우들을 로그인일 실패했을때 사용자에게 알려주는 로직을 만들어 보자.

 

@Service
public class CustomLoginFailHandler implements AuthenticationFailureHandler {

	@Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException accessException) throws IOException, ServletException {
		
		log.error("Access Denied Handler");
		log.error("Redirect....");
		
		if (accessException instanceof AuthenticationServiceException) {
			request.setAttribute("error", "존재하지 않는 사용자입니다.");
		
		} else if(accessException instanceof BadCredentialsException) {
			request.setAttribute("error", "비밀번호가 틀립니다.");
			
		} else if(accessException instanceof LockedException) {
			request.setAttribute("error", "잠긴 계정입니다..");
			
		} else if(accessException instanceof DisabledException) {
			request.setAttribute("error", "비활성화된 계정입니다..");
			
		} else if(accessException instanceof AccountExpiredException) {
			request.setAttribute("error", "만료된 계정입니다..");
			
		} else if(accessException instanceof CredentialsExpiredException) {
			request.setAttribute("error", "비밀번호가 만료되었습니다.");
		}
		
		// 로그인 페이지로 다시 포워딩
		RequestDispatcher dispatcher = request.getRequestDispatcher("/mypage/customlogin");
		dispatcher.forward(request, response);
		
	}

}

동일하게 security-context.xml에 빈으로 등록시킨다.

등록시킨 빈을 <security:form-login>태그에 다음과 같이 설정하자.

<bean id = "customLoginSuccess" class="com.phonemall.security.CustomLoginSuccessHandler"></bean>
<bean id = "customLoginFail" class="com.phonemall.security.CustomLoginFailHandler"/>

<security:http auto-config="true" use-expressions="true">
	<security:form-login username-parameter="email" password-parameter="userpw"
    		login-processing-url="/mypage/login" login-page="/mypage/customlogin" 
            authentication-success-handler-ref="customLoginSuccess" authentication-failure-handler-ref="customLoginFail"/>
</security:http>

 

💡 접근 제한 페이지

403 error

 로그인을 한 일반 계정이 관리자 페이지에 접근하려 할 수 있다. 이때는 다음과 같은 403에러 페이지가 뜨게 되는데 사용자 입장에서 조금 더 깔끔한 페이지를 제공하면 좋을 것이다. security-context.xml에 태그를 추가해보자.

<bean id = "customLoginSuccess" class="com.phonemall.security.CustomLoginSuccessHandler"></bean>
<bean id = "customLoginFail" class="com.phonemall.security.CustomLoginFailHandler"/>

<security:http auto-config="true" use-expressions="true">
	<security:form-login username-parameter="email" password-parameter="userpw"
    		login-processing-url="/mypage/login" login-page="/mypage/customlogin" 
            authentication-success-handler-ref="customLoginSuccess" authentication-failure-handler-ref="customLoginFail"/>
	<security:access-denied-handler error-page="/mypage/accessError" />
</security:http>

1. 접근 제한 로직 구현하기

 마찬가지로 단순하게 페이지를 제공하는 것이 아닌 특정한 동작을 수행하게 만들고싶을 수 있다. 이번에는 AccessDeniedHandler 인터페이스를 구현하면된다.

@Service
public class CustomAccessDeniedHandler implements AccessDeniedHandler{

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

}

 마지막으로 security-context.xml에 빈으로 등록시키고 태그를 추가하자.

<bean id = "customLoginSuccess" class="com.phonemall.security.CustomLoginSuccessHandler"></bean>
<bean id = "customLoginFail" class="com.phonemall.security.CustomLoginFailHandler"/>
<bean id = "customAccessDenied" class="com.phonemall.security.CustomAccessDeniedHandler"></bean>

<security:http auto-config="true" use-expressions="true">
	<security:form-login username-parameter="email" password-parameter="userpw"
    		login-processing-url="/mypage/login" login-page="/mypage/customlogin" 
            authentication-success-handler-ref="customLoginSuccess" authentication-failure-handler-ref="customLoginFail"/>
    <security:access-denied-handler ref="customAccessDenied" />
</security:http>

'Spring' 카테고리의 다른 글

[Spring Boot] 세션저장소  (0) 2021.04.11
[Spring Security] 소셜 로그인 - 구글  (0) 2021.04.10
[Spring Security] 로그인 로그아웃 - 1  (0) 2021.04.04
[Spring] Spring Security  (0) 2021.04.03
[Spring] @RestController  (0) 2021.04.02