본문 바로가기

개인적으로 공부한 것을 정리해 놓은 블로그입니다 틀린 것이 있으면 댓글 부탁 드립니다!


Spring Security

스프링시큐리티 공부 5 - form 로그인 1

반응형

1. 스프링 시큐리티 form 로그인 동작방식

 

 

스프링 시큐리티에서 폼 로그인 처리를 담당하는 필터는

 

UsernamePasswordAuthenticationFilter 이다. 

 

- POST/loging에 대한 요청을 처리한다 . processingUrl을 변경하면 주소를 바꿀 수 있다. 

- form 인증을 처리해주는 필터로 스프링 시큐리티에서 가장 일반적으로 쓰인다 

 

UsernamePasswordAuthenticationFilter에 대한 설정은 스프링 시큐리티 설정파일의 

configure(HttpSecurity http) 메서드 안에서 설정해 줄 수 있다 . 

 

파라미터로 들어오는 HttpSecurity 타입인 http에  .을 찍으면 여러 필터들에 대한 설정할 수 있게 해주는 메서드들이 있는데 

그 중에 http.formLogin()이 폼 로그인에 대해 설정하는 메서드이다 . formLogin안에는 폼 로그인 페이지, 로그인 성공시 , 실패시 Url설정등 여러가지 설정을 가능하게 해주는 메서드들이 들어 있다.

 

그 중 몇가지를 살펴보면 

 

 

로그인 데이터에 대한 메서드

 

filterProcessingUrl() : 로그인을 처리해 줄 URL (POST)을 설정함. 

usernameParameter : Post에 username에 대한 값을 넘겨줄 파라미터의 이름 설정

passwordParameter : Post에 password에 대한 값을 넘겨줄 파라미터의 이름 설정

 

 

로그인 성공시 

 

defaultSuccessUrl: 로그인 성공시 Url 지정 (보통 많이 사용하며 , 원래 가려던 페이지가 있는 경우  alwaysUse 옵션을  false로 설정 해주는 것이 중요함)

 

 

successHandler :  Handler를 통해 성공시 Url을 관리

 

-설정이 둘다 켜있을때 동작이 안될 수 있으니 둘 중 하나를 사용해야한다.

 

 

로그인 실패시 

 - failureUrl : 실패시의 Url을 설정하는 메서드

 - failureHandler

 

 

authenticationDetailSource 

 

Details에는 주로 IP, 세션정보 ,  기타 인증요청에 대한 정보들이 담겨있기 떄문에 해당 정보들을 컨트롤러 혹은 서비스단에서 사용할 수 있게 Authentication 객체의 details에 들어갈 정보를 직접 만들어 준다 인터페이스 형태로 되어있으며 여러 필터에서 구현된다. 

 

 

 

UsernamePasswordAuthenticationFilter의 주요 로직

 

    @Override
	public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
			throws AuthenticationException {
		if (this.postOnly && !request.getMethod().equals("POST")) {
			throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
		}
		String username = obtainUsername(request);
		username = (username != null) ? username : "";
		username = username.trim();
		String password = obtainPassword(request);
		password = (password != null) ? password : "";
		UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
		// Allow subclasses to set the "details" property
		setDetails(request, authRequest);
		return this.getAuthenticationManager().authenticate(authRequest);
	}

 

POST /login 요청이 날라왔을때 이름과 비밀번호를 확인하고 UsernamePasswordAuthenticationToken 을 만든다. 

만들어진 토큰은 인증이 되지않은 상태(authenticated false) 이기 떄문에  해당 토큰을 AuthenticationManager에게 넘겨 인증을 위임한다.   authenticationManager는 자신이 관리하는 authenticationProvider들에게 해당 인증이 가능한지 찾아서 인증이 가능한 authenticationProvider에게 인증을 요청하고 인증을 거친 후에  토큰의 인증상태를 true로 바꾼 후에 토큰을 리턴해 준다.

 

 

 

DefaultLoginPageGeneratingFilter

 

- GET/ login에 대한 요청을 처리한다. (기본 로그인 폼 응답)  

- 별도의 로그인 페이지 설정을 하지 않으면 제공되는 페이지 .

- 기본 로그인 폼을 제공한다. 

- OAuth2/OpenID/Saml2 로그인과도 같이 사용할 수 있다 . 

 

DefaultLoginPageGeneratingFilter 소스를 열어보면 다른 설정을 하지 않았을 때 기본적으로 보았던 로그인 창의 html를 

생성하는 메서드가 있다. 

 

기본 로그인창

 

 

기본 로그인 페이지 생성 메서드 

 

DefaultLogOutPageGeneratingFilter

 

GET /logout의 요청에 대한 페이지를 생성해준다 

 

기본 로그아웃 페이지

 

LogOut 버튼을 누르면

 

POST /logout 을 요청하게되고 logoutFilter가 동작하게 된다.

 

logoutFilter의 주요 로직

 

	private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws IOException, ServletException {
		if (requiresLogout(request, response)) {
			Authentication auth = SecurityContextHolder.getContext().getAuthentication();
			if (this.logger.isDebugEnabled()) {
				this.logger.debug(LogMessage.format("Logging out [%s]", auth));
			}
			this.handler.logout(request, response, auth);
			this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
			return;
		}
		chain.doFilter(request, response);
	}

로그아웃 url에 대한 검증을 하고 , 로그아웃 핸들러들에게 logout을 요청한다

 

로그아웃 핸들러의 종류 

 

securityContextLogoutHandler 세션 SecurityContext를 clear 한다 
CookieClearingLogoutHanlder clear 대상이 된 쿠키들을 삭제 
CsrfLogoutHandler csrfTokenRepository 에서 csrf 토큰을 claer한다 .
HeaderWriterLogoutHandler  
RememberMeServices  remember-me 쿠키를 삭제한다.
LogoutSuccessEventPublishingLogoutHanlder  로그아웃이 성공하면 이벤트를 발행한다.

 

 

폼 로그인 구현시 신경써야할 것 

 

 

리소스  폴더는 스프링 시큐리티를 타지 않게 구현  

 

스프링 시큐리티를 타는 경로의 페이지가 로딩될때 그 페이지를 구성하는 js css 파일 들에 대해서도 요청이 일어난다 .

따로 설정하지 않는다면 스프링 시큐리티는 기본적으로 모든 요청에 대해 검증을 요구하기 때문에 리소스 파일들에 대해서는 스프링 시큐

리티를 타지 않도록 구현해야 해당 리소스들이 로딩 된다. 

스프링 시큐리티 config 파일에  configure(WebSecurity web)을 오버라이딩하여 웹에 관한 설정을 할 수 있다 .

 

    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().requestMatchers(
                PathRequest.toStaticResources().atCommonLocations()
        );
    }

 

PathRequest.toStaticResources().atCommonLocations()는  웹 리소스에 대한 경로를 지정하기 위해서 사용한다 .

포함시키고 싶지 않은 경로가 있다면 .exclude() 로 원하는 경로를 제외 시킬 수도 있다.  

 

 

로그인 페이지 지정시  로그인 페이지를 permitAll() 해줘야한다.

 

 폼 로그인시 로그인페이지에 대하여 다른 설정을 하지 않는다면 스프링에서 제공하는 기본 로그인 페이지가 제공된다 그 페이지를 그대로 사용할 일은 거의 없을 것이다  

request.anyRequest().authenticated(); 로 설정해 놨을때   직접만든 로그인 페이지를  permitAll() 로 해주지 않으면 로그인 페이지로 이동시에도 인증을 요구하기 때문에 무한루프에 빠질 수 있다. 

 

csrf토큰필터가 켜져있는지 꺼져있는지 확인

 

다른 설정이 없다면 스프링에서 기본적으로 실행시키는 필터중 csrf 필터가 있기 떄문에 폼로그인시 csrf 토큰을 함께 보내주지 않으면 csrf 필터를 통과하지 못하기 때문에 로그인이 거부 된다 사용하는 뷰 라이브러리에 맞게 폼로그인시 csrf토큰을 함께 보내줘야한다   thymeleaf의 경우 th:action="@{/login}" 으로 경로만 설정해줘도 csrf 토큰은 자동으로 생성해준다.  

 

 

디폴트 로그인이 없으면 디폴트 로그아웃도 없다 . 

 

로그인 페이지를 직접 만들어서 지정했을시에는 로그아웃 페이지가 생성되지 않기 때문에 원한다면 직접 페이지를 만들어야하고 페이지가 없다면 로그아웃 post 요청시에 바로 로그아웃된다

 

 

로그인 설정시 defaultSuccessUrl의 두번째 파라미터인 alwaysUse를 false로 해놓자 

 

login.defaultSuccessUrl(성공시 url , Boolean alwaysUse)  만약 사용자가  로그인 페이지로 직접 접근하는 것이 아니라 접근 권한이 없는 경로로 이동중에 사이트가 로그인을 요구할때 로그인에 성공한다면 사용자가 원래 가려던 페이지로 보내줘야하는데  defaultSuccessUrl의 alwayUse를 true로 하면 항상 defaultSuccessUrl로 지정한 Url로 보내버리기 떄문에 alwaysUse는 false로 지정해 놓자.

반응형