조급하면 모래성이 될뿐

로그인 성공시 추가작업 본문

구현 기록/SpringSecurity

로그인 성공시 추가작업

Pawer0223 2020. 2. 19. 16:44

참조

[ 사랑이 고픈 프로그래머 ] [ to-dy블로그 ] 

 

목표

 

- 로그인 성공시 무조건 MainPage가아니고, 이전에 요청했던 URL로 이동할 수 있다. ( redirectURL설정 )

ex) 로그인 되지않은 상태에서 게시판조회를 할 수 없다고 가정해보자. 화면에서 게시판 조회를 누르면 로그인 창이 뜨게 될것이다. 이 때 로그인 성공했을 때 게시판 조회화면이 출력되도록 한다.

 

- 로그인 성공시 로그인 실패 횟수를 초기화시켜준다.

 

- 로그인 성공시 실패 에러 세션을 삭제한다.

 

 

성공 처리


로그인 성공 후 부가 작업을 하고 싶을 때, SpringSecurity에서 제공하는 인터페이스인 AuthenticationSuccessHandler를 구현한 클래스를 만들어서 onAutenticationSuccess 메서드를 재정의 해주면 된다!

 

로그인 성공 후 이동할 URL을 지정하면 해당 URL로 이동하는 기능을 구현해보자.

 

먼저 로그인 성공 시 처리될 URL을 받아오는 우선순위가 존재하는데, 아래와 같다.

 

1. Request Parameter(loginRedirect)에 로그인 작업을 마친 뒤 redirect 할 URL을 지정.

( 로그인 폼의 input 태그를 추가하여 loginRedirect 할 경로를 함께 보내는 방법이다. )

2. 세션에 저장된 URL 

3. REFERER 헤더 값을 읽어서 로그인 페이지 방문 전 페이지의 URL을 읽는다. 

( HttpServletRequest객체의 getHeader 메서드를 이용해서 Refferer 헤더 값을 읽어온다. )

4. 1~3 없다면 default속성을 따른다. 

 

여기서 [2. 세션에 저장된 URL ]에 대해 추가 설명을 하자면,SpringSecurity가 알아서 생성한 Session을 활용하는 것인데, 그 구조는 아래와 같다.

 

세션의 생성시점.

- SpringSecurity는 인증을 하지 않은 상태에서 권한이 필요한 화면을 접근하려고 할 경우 로그인 화면을 띄운다. 

- 이때 로그인 화면을 띄우기 전에 필요한 정보를 세션에 저장한다. 

 

저장되는 URL 정보.

- 이 세션에는 요청한URL이 무엇인지에 대한 정보도 담고있다.

- 우리는 이 URL을 꺼낸다.

 

URL이 저장되기 위해 사용되는 인터페이스.

- SpringSecurity가 제공하는 RequestCache 인터페이스를 이용해 URL을 세션에 저장한다.

- 이 인터페이스는 로그인 화면을 보여주기 전에 사용자 요청을 저장하고 이를 꺼내오는 메커니즘을 가지고 있다.

 

- 사용자 요청을 세션에 저장한다는 말은, RequestCache인터페이스의 구현 클래스인 HttpSessionRequestCache클래스를 이용해서 사용자 요청 정보들이 들어있는 DefaultSavedRequest 클래스 객체를 세션에 저장한다는 뜻이다.

 

- 여기서 DefaultSavedRequest는 사용자의 요청이 저장되는 단위이다.

 

- 사용자의 요청한 request파라미터 값, 그 당시 헤더 값들 등이 SavedRequest인터페이스를 구현한 클래스에 담기게 되는데, SpringSecurity는 해당 인터페이스를 구현한 DefaultSavedRequest클래스를 제공하고 있다.

 

세션을 통해 URL을 가져오는 방법

- HttpSessionRequestCache 클래스 객체를 생성 후  DefaultSavedRequest 클래스 객체를 가져와서 getRedirectURL메서드를 호출하면 된다..!


정리하면 로그인 성공 시 저장할 redirectURL을 우선순위에 따라서 가져오게 된다.

 

1순위 없으면 2순위, 없으면 3 , 3도 없으면 Default로 이동 URL이 지정되는 것이다.

 

위 내용은 [ 사랑이 고픈 프로그래머 ] 블로그의 내용을 정리한 것이다.

 

직접 구현할 방식은 1순위인 request parameter는 별도의 로그인 폼의 수정이 필요하기에, 넘길 것이고 2순위의 세션 URL을 확인하여 존재하지 않는다면 Default속성으로 보낼 것이다.

 

3순위 REFERER를 사용하지 않는 이유는 REFERER는 사용할 때 직접 로그인에 성공한 경우 헤더에 로그인 화면에 대한 url값이 저장되게 된다.

 

그래서 직접 로그인했을 때 redirect로 REFERER헤더의 URL을 참조하면 계속 로그인 화면으로 가게 되기 때문에 사용하지 않을 것이다.

 

이제, 직접 로그인 성공 후 처리될 프로세스를 정의해보자.

 

참고로.. 지금 구현한 방식과 코드는 온전히.. 해당 블로그를 참조하였다.. 세부적인 내용만 Search 해서 위에 정리를 했을 뿐이다.. [ to-dy블로그 ]   

 

역시나 먼저 SecurityConfig설정을 해준다.

 

* SecurityConfig

	@Override
	protected void configure(HttpSecurity http) throws Exception{
		http
			.authorizeRequests() // 해당 메소드 아래는 
				.antMatchers("/" , "/login" , "/service" , "/resources/**" , "/create").permitAll()
				.antMatchers("/admin").hasRole("ADMIN") 
				.antMatchers("/user").hasRole("USER")
				.antMatchers("/member").hasRole("MEMBER")
				.anyRequest().authenticated()
				.and()
			.formLogin()
						.loginPage("/login")
						.loginProcessingUrl("/loginProcess")
						.defaultSuccessUrl("/loginSuccess")
						.successHandler(successHandler)
						.failureHandler(failureHandler)
				.permitAll()
				.and()
			 .logout()
			 	.permitAll()
			 	.logoutUrl("/logout")
			 	.logoutSuccessUrl("/")
			 	.and()
			 .exceptionHandling()
			 	.accessDeniedPage("/accessDenied_page");
	}

추가한 부분은. successHandler(successHandler)이다.

 

여기서 인자로 사용된 참조 변수의 자료형 AuthenticationSuccessHandler를 구현한 클래스는 우리가 새로 정의해줄 것이다.

 

해당 구현 클래스가 사용되기 위한 @Bean 객체 선언도 같이 해주자.

	  // 로그인 성공 처리를 위한 Handler
	  @Bean
	  public AuthenticationSuccessHandler successHandler() {
		  log.info("[ BEAN ] : AuthenticationSuccessHandler");
		  // loginIdname, defaultUrl
	      return new CustomAuthenticationSuccessHandler("username", "/loginSuccess");
	  }

우리가 정의할 구현체 클래스의 명칭은 CustomAuthenticationSuccessHandler가 될 것이다.

 

생성자 인자 값의 의미는

1. id입력 input 태그의 name값

2. 로그인 성공 시 defaultURL

이 되겠다.

 

* AuthenticationSuccessHandler 구현 클래스 신규정의 

 

- 해당 인터페이스를 구현했을 때 필수 override 된 메서드가 onAuthenticationSuccess이다. 이 메서드가 핵심이다!

- failUreHandler 인터페이스와 다른 점은 3번째 인자가 Authentication의 참조 변수이다.

- 해당 클래스에는 인증에 성공한 사용자의 정보를 가지고 있다.

 

먼저 전역 변수 선언 및 getter, setter, 생성자까지 정의해 주었다.

public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
	
	private String username;
	private String defaultUrl;
    
	// Getter, Setter
	public String getDefaultUrl() {
		return defaultUrl;
	}

	public String getUsername() {
		return username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public void setDefaultUrl(String defaultUrl) {
		this.defaultUrl = defaultUrl;
	}
	
	// Constructor
	public CustomAuthenticationSuccessHandler(String username , String defaultUrl){
		this.username = username;
		this.defaultUrl = defaultUrl;
	} 

 

다음으로는 redirectUrl을 우선순위에 따라 정의해주자.

 

위에서 설명했지만, RequestCache인터페이스를 통해 세션에 저장된 정보를 확인 후, 없다면 defaultURL이 될 것이다.

 

그리고 화면을 이동하기 위해서 org.springframework.security.web.RedirectStrategy 인터페이스를 구현한 객체를 받아서 처리할 것이다.

 

RedirectStrategy는 Spring Security가 화면 이동에 대한 규칙을 정의하는 부분을 만든 인터페이스로 이 인터페이스를 구현한 객체로 화면 redirect를 하면 된다.

 

먼저 URL 설정, 화면 이동을 위한 인터페이스의 구현 클래스들을 사용하기 위한 전역 변수를 선언해 준다.

	private RequestCache requestCache = new HttpSessionRequestCache();
	private RedirectStrategy redirectStragtegy = new DefaultRedirectStrategy();

다음은 세션에 있는 SavedRequest의 객체 정보가 있는지 확인하여 있다면 세션에 담긴 이전 페이지에 대한 정보를 getRedirectUrl() 메서드를 통해서 받아올 것이다.

* SavedRequest는 사용자의 요청이 저장되는 단위이다, SpringSecurity에 의해 생성된다. 위의 표 참조..

 

객체 정보가 없다면 default로 지정된 URL을 지정할 것이다.

	// redirectUrl 지정 메서드
	protected void resultRedirectStrategy(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
		
		SavedRequest savedRequest = requestCache.getRequest(request, response);
		
		if ( savedRequest != null ) {
			String targetUrl = savedRequest.getRedirectUrl();
			log.info( " GO !!! savedRequest.getRedirectUrl : " + targetUrl );
			redirectStragtegy.sendRedirect(request, response, targetUrl);
		}else {
			log.info( " GO !!! savedRequest.getRedirectUrl : " + defaultUrl );
			redirectStragtegy.sendRedirect(request, response, defaultUrl);
		}
	}

위 코드는 직관적이니, 세션에서 redirectURL을 가져오는 방법 정도에 주목하면 되겠다..

 

다음은 로그인 실패 에러 세션을 지워주어야 한다.

 

로그인을 한 번에 딱 성공하면 좋겠지만, 오타나 캡스락 등등.. 여러 이유로 몇 번의 로그인 실패를 한 후에 로그인을 하게 되는 경우들이 자주 있을 것이다..

 

이렇게 "로그인 실패"가 한 번이라도 있었다면 에러가 세션에 저장되어 남아있게 된다.

 

고로, 로그인에 성공했을 때는 기존에 생성된 실패 관련한 세션을 제거해주어야 한다.

	// 남아있는 에러세션이 있다면 지워준다.
	protected void clearAuthenticationAttributes(HttpServletRequest request) {
		HttpSession session = request.getSession(false);
		if( session == null ) return ;
		session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
	}

 

마지막으로 로그인 성공 시 추가로 처리해야 할 프로세스는 로그인 실패 횟수를 초기화해주는 것이다.

 

로그인을 성공하였다면, 이전까지의 실패 횟수는 0으로 reset 해주어야 한다.

 

관련 처리를 위한 간단한 update수행을 위한 프로세스를 추가해 주면 된다.

 

* AccountMapper.java

	/*
	 * 실패횟수 초기화
	 */
	void resetFailCnt(String id);
    

 

* AccountMapper.xml

 <update id="resetFailCnt" parameterType="String" >
		UPDATE USER_INFO 
		SET failCnt = 0
		WHERE ID = #{id}
</update>

* AccountRepository.java

	public void resetFailCnt(String username) {
		accountMapper.resetFailCnt(username);
	}

 

이제 위에서 설명한 성공 후 처리될 프로세스들을 onAuthenticationSuccess에서 순차적으로 처리되도록 사용하면 끝이다.

 

음.. Redirect URL을 설정하는 메서드가 가장 나중에 수행되어야 하는 것은 분명하고, 나머지 로직들은 본인의 주관에 맞게끔 지정해주면 된다.

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws IOException, ServletException {
		
		String requestUserName = request.getParameter(username);
		
		// 실패회수 초기화
		accoutDao.resetFailCnt(requestUserName);
		// 에러세션 지우기
		clearAuthenticationAttributes(request);
		// Redirect URL 작업.
		resultRedirectStrategy(request, response, authentication);
		
	}

이로써 SpringSecurity를 활용해서 로그인 성공 시 반드시 수행되어야 할 프로세스들도 처리되도록 구현해주었다.

 

*  update 및 redirect 되는지 로그 확인

반응형