본문 바로가기
Backend 개발자/Springboot

로그인 인증 인가 처리 보안 스프링 시큐리티를 사용하는 이유? Spring Security 1편

by by 앵과장 2022. 4. 4.
반응형

안녕하세요

앵과장 입니다.

 

Spring Security 를 알아야 하는 가장 근복적인 이유 와 동기부여

회원 서비스 직접 개발할일이 많지 않을수 있습니다.

매년 3월달은 많은 개발자분들에게 설레임과 분노를 느낄수 있는 시작이 아닌가 생각됩니다.

 

바로!!!!!!!

많은 분들이 떨리는 마음으로 받고 처우 협상(통보...)을 진행하게 되는데요!!

 

처우 협상 이후 손가락 만큼 가지고 있던 애사심 조차

사라지게 됩니다.

 

이번년도 경제 상황이 좋지 않아 물가 상승률 4프로정도 올랐다는 체감을 할수가 있습니다. 

 

4프로 보다 못하다 그렇다면 이글 꼭 정독하시기 바랍니다.

많은 회사들 과제에 회원 기능 개발을 원합니다

 

저도 참 많은곳에 과제중 회원으로 구현한 내용이 있어 공유드립니다.

 

 

GitHub - lswteen/user: springboot jpa security restdoc restful API

springboot jpa security restdoc restful API. Contribute to lswteen/user development by creating an account on GitHub.

github.com

 

 

GitHub - lswteen/idus-security: 백패커/아이디어스 과제

백패커/아이디어스 과제. Contribute to lswteen/idus-security development by creating an account on GitHub.

github.com

 

Spring Secruit 기본설명과 아키텍처

Spring Security 는 Spring 기반으로 어플리케이션 보안 및 End Point들에 대한 기능을 제공하는 Framework 입니다.

인증 과 권한에 대한 Filter 흐름을 처리하는 End Point 역활을 제공해줍니다.

Filter는 Dispatcher Servlet 으로 가기전에 적용되므로 가장먼저 URL 요청을 받지만 Interceptor는 Controller 사이에 위치한다는 점에서 적용 시기의 차이가 있습니다.

Spring Security 는 보안과 관련해서 체계적으로 많은 옵션을 제공하기 때문에 개발자 입장에서 일일이 보안관련 로직을 작성하지 않아도 된다는 장점이 있습니다.

 

이러한 Spring Security 아키텍처는 많은곳에서 소개되어 있는 내용을 공유 하도록 하겠습니다.

 

1. 사용자가 Form으로 로그인 정보를 입력하고 Http프로토콜로 인증요청을 Server로 보냄

2. AuthenticationFilter (UsernamePasswordAuthenticationFilter)가 HttpServletRequest에서 사용자가 보낸 아이디와 패스워드를 인터셉트 함

아이디와 패스워드 유효성 검사 이후 HttpServletRequest에서 사용자아이디, 패스워드 인증을 담당할 AuthenticationManager 인터페이스(Providermanager)에게 인증용 객체(UsernamePasswordAuthenticationToken)를 위함

3. AuthenticationFilter 에 인증용 객체(UsernamePasswordAuthenticationToken) 전달

4. 인증할 AuthenticationProvider에게 Authentication 객체 (UsernamePasswordAuthenticationToken) 다시전달 받음

5. DB에서 사용자 인증정보를 가져올 UserDetailService에 유니크한 식별자(email, hp, name, id)를 전달 후

사용자 정보(사용자아이디, 암호화된 패스워드, 권한)를 UserDetails(인증용 객체와 도메인 객체를 분리하지 않기 위해서 실제 사용되는 도메인 객체 UserDetails 상속하기도 함) 객체로 전달 받음

6. AuthenticationProvider는 UserDetails 객체를 전달 받은 이후 실제 사용자의 입력정보와 UserDetails 객체를 가지고 인증 시도

7,8,9 요청한 내용들을 기반으로 Response전달함.

10. 인증이 완료되면 사용자 정보를 가진 Authentication 객체를 SecurityContextHolder에 담은 이후 AuthenticationSuccessHandle를 실행

(실패시 AuthenticationFailureHandler 실행)  

스프링 시큐리티 기본 구조

 

SecurityContextPersistenceFilter SecurityContextRepository에서
SecurityContext를 로드하고 저장
LogoutFilter 로그아웃 URL로 지정된 가상 URL에
대한 요청을 감시하고 매칭되는 요청이 있으면 사용자를 로그아웃
UsernamepasswordAuthenticationFilter 사용자명, 비밀번호 form 기반 인증에
사용하는 URL요청을 감시하고 요청이 감지되면 인증 진행

- AuthenticationManager를 통한 인증실행
- 인증 성공시, 얻은 Authentication 객체를 SecurityContext에 저장후 AuthenticationSuccessHandler실행
- 인증 실패시, AuthenticationFailureHandler 실행
DefaultLoginPageGeneratingFilter

form 또는 openId 기반 인증에 사용하는 가상 URL에 대한 요청을 감시하고 로그인 form 기능을 수행하는데 필요한 HTML 생성 
BasicAuthenticationFilter HTTP 기본 인증 헤더 감시 후  처리
RequestCacheAwareFilter 로그인 성공 이후 인증 요청에 의해 가로채어진 사용자의 원래 요청을 재구성 하는데 사용

SecurityContextHolderAwareRequestFilter
HttpServletRequest를 HttpServletRequestWrapper를 상속하는 하위 클래스(SecurityContextHolderAwareRequestWrapper)로 감싸서 필터 체인상 하단에 위치한 요청 프로세서에 추가 컨텍스트를 제공
AnonymousAuthenticationFilter 해당 필터가 호출되는 시점까지 사용자가 인증을 받지 못했다면 요청관련 인증 토큰에서 사용자가 익명사용자로 노출됨
SessionManagementFilter 인증된 주체를 바탕으로 세션 트래킹을 처리해 단일 주체와 관련한 모든 세션들이 트래킹 되도록 서포트함
ExceptionTranslationFilter 보호된 요청을 처리하는 동안 발생할수 있는 기대한 예외의 기본 라우팅과 위임을 처리
FilterSecurityInterceptor 권한부여와 관련한 결정을 AccessDecisionManager에게 위임하여 권한 부여 결정 및 접근 제어 결정을 쉽게 만들어줌

스프링 시큐리티에는 로그인, 인증, 권한여부, 핸들링, Exceptoin 등 여러부분에 필요한 Filter, interceptor에 기능들을 제공해주기때문에 필요한 부분에 대해서 기능을 인지하고 필요한 요소를 잘활용하시는 방법을 선택하시면 도움이 될것 같습니다.

 

Authentication

패키지경로 : org.springframework.security.core

해당 객체는 SecurityContext (내부메모리)에 사용

public interface Authentication extends Principal, Serializable {

	/**
	 * Set by an <code>AuthenticationManager</code> to indicate the authorities that the
	 * principal has been granted. Note that classes should not rely on this value as
	 * being valid unless it has been set by a trusted <code>AuthenticationManager</code>.
	 * <p>
	 * Implementations should ensure that modifications to the returned collection array
	 * do not affect the state of the Authentication object, or use an unmodifiable
	 * instance.
	 * </p>
	 * @return the authorities granted to the principal, or an empty collection if the
	 * token has not been authenticated. Never null.
	 */
	Collection<? extends GrantedAuthority> getAuthorities();

	/**
	 * The credentials that prove the principal is correct. This is usually a password,
	 * but could be anything relevant to the <code>AuthenticationManager</code>. Callers
	 * are expected to populate the credentials.
	 * @return the credentials that prove the identity of the <code>Principal</code>
	 */
	Object getCredentials();

	/**
	 * Stores additional details about the authentication request. These might be an IP
	 * address, certificate serial number etc.
	 * @return additional details about the authentication request, or <code>null</code>
	 * if not used
	 */
	Object getDetails();

	/**
	 * The identity of the principal being authenticated. In the case of an authentication
	 * request with username and password, this would be the username. Callers are
	 * expected to populate the principal for an authentication request.
	 * <p>
	 * The <tt>AuthenticationManager</tt> implementation will often return an
	 * <tt>Authentication</tt> containing richer information as the principal for use by
	 * the application. Many of the authentication providers will create a
	 * {@code UserDetails} object as the principal.
	 * @return the <code>Principal</code> being authenticated or the authenticated
	 * principal after authentication.
	 */
	Object getPrincipal();

	/**
	 * Used to indicate to {@code AbstractSecurityInterceptor} whether it should present
	 * the authentication token to the <code>AuthenticationManager</code>. Typically an
	 * <code>AuthenticationManager</code> (or, more often, one of its
	 * <code>AuthenticationProvider</code>s) will return an immutable authentication token
	 * after successful authentication, in which case that token can safely return
	 * <code>true</code> to this method. Returning <code>true</code> will improve
	 * performance, as calling the <code>AuthenticationManager</code> for every request
	 * will no longer be necessary.
	 * <p>
	 * For security reasons, implementations of this interface should be very careful
	 * about returning <code>true</code> from this method unless they are either
	 * immutable, or have some way of ensuring the properties have not been changed since
	 * original creation.
	 * @return true if the token has been authenticated and the
	 * <code>AbstractSecurityInterceptor</code> does not need to present the token to the
	 * <code>AuthenticationManager</code> again for re-authentication.
	 */
	boolean isAuthenticated();

	/**
	 * See {@link #isAuthenticated()} for a full description.
	 * <p>
	 * Implementations should <b>always</b> allow this method to be called with a
	 * <code>false</code> parameter, as this is used by various classes to specify the
	 * authentication token should not be trusted. If an implementation wishes to reject
	 * an invocation with a <code>true</code> parameter (which would indicate the
	 * authentication token is trusted - a potential security risk) the implementation
	 * should throw an {@link IllegalArgumentException}.
	 * @param isAuthenticated <code>true</code> if the token should be trusted (which may
	 * result in an exception) or <code>false</code> if the token should not be trusted
	 * @throws IllegalArgumentException if an attempt to make the authentication token
	 * trusted (by passing <code>true</code> as the argument) is rejected due to the
	 * implementation being immutable or implementing its own alternative approach to
	 * {@link #isAuthenticated()}
	 */
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;

}

유저의 요청을 AuthenticationFilter 에서 Authentication객체로 변환 후 AuthenticationManager(ProviderManager)에게 넘겨주고 AuthenticationProvider(DaoAuthenticationProvider)가 실제 인증을 한 이후 인증 완료되면 Authentication객체를 반환해줍니다.

 

AbstractAuthenticationProcessingFilter

웹 기반 인증 요청에서 사용되는 컴포넌트로 Post form 데이터를 포함하는 요청을 처리합니다.

사용자 비밀번호를 다른 필터로 전달하기 위해서 Authentication 객체를 생성하고 일부 프로퍼티를 설장합니다.

 

AuthenticationManager

인증 요청을 받고 Authentication 객체에 Setup 합니다.

 

AuthenticationProvider

실제 인증이 일어나고 인증성공시 Authentication 객체의 authenticated 값을 true로 설정합니다.

 

Spring Security 는 ProviderManager 라는 AuthenticationManager 인터페이스 유일한 구현체를 제공합니다.

Providermanager는 하나 또는 여러개의 AuthenticationProvider 구현체를 사용 할수 있습니다.

AuthenticationProvider는 많이 사용되고 Providermanager(AuthenticationManager 구현체)와 통합되기 때문에 기본적인 동작에 대한 이해를 높이는것이 필요합니다.

 

Password Authentication

DaoAuthenticationProvider 는 UserDetailsService 타입 오브젝트로 위임합니다.

UserDetailsService 는 UserDetails 구현체를 리턴하는 역활입니다.

UserDetails Secruit에서 처리할 유저정보에대한 구현체를 구성할수 있는 인터페이스 입니다.

public interface UserDetails extends Serializable {

	/**
	 * Returns the authorities granted to the user. Cannot return <code>null</code>.
	 * @return the authorities, sorted by natural key (never <code>null</code>)
	 */
	Collection<? extends GrantedAuthority> getAuthorities();

	/**
	 * Returns the password used to authenticate the user.
	 * @return the password
	 */
	String getPassword();

	/**
	 * Returns the username used to authenticate the user. Cannot return
	 * <code>null</code>.
	 * @return the username (never <code>null</code>)
	 */
	String getUsername();

	/**
	 * Indicates whether the user's account has expired. An expired account cannot be
	 * authenticated.
	 * @return <code>true</code> if the user's account is valid (ie non-expired),
	 * <code>false</code> if no longer valid (ie expired)
	 */
	boolean isAccountNonExpired();

	/**
	 * Indicates whether the user is locked or unlocked. A locked user cannot be
	 * authenticated.
	 * @return <code>true</code> if the user is not locked, <code>false</code> otherwise
	 */
	boolean isAccountNonLocked();

	/**
	 * Indicates whether the user's credentials (password) has expired. Expired
	 * credentials prevent authentication.
	 * @return <code>true</code> if the user's credentials are valid (ie non-expired),
	 * <code>false</code> if no longer valid (ie expired)
	 */
	boolean isCredentialsNonExpired();

	/**
	 * Indicates whether the user is enabled or disabled. A disabled user cannot be
	 * authenticated.
	 * @return <code>true</code> if the user is enabled, <code>false</code> otherwise
	 */
	boolean isEnabled();

}

 

Authentication : 사용자 ID, 패스워드 인증 요청 컨텍스트에 대한 정보가 있으며 인증 이후에 사용자 상세정보와 같은 UserDetails 타입 Object를 포함할수 있습니다.

UserDetails : 이름, 이메일, 전화번호와 같은 사용자 프로파일 정보를 저장하기 위한 용도로 사용합니다.

 

Authentication Exceptoin

인증과 관련된 모든 예외는 AuthenticationException 을 상속 합니다.

AuthenticationException 은 개발자에게 상세한 디버깅 정보를 제공하기 위해 두개의 맴버 필드가 존재합니다.

 

authentication : 인증 요청관련 Authentication 객체를저장합니다.

extrainformation : 인증 예외 관련 부가 정보를 저장합니다. 예를 들어 UsernameNotFoundException 예외는 인증에 실패한 유저의 ID정보를 저장하고 있습니다.

 

코드 구현

코드에 대한 gradle 설정 및 샘플들은 아래 2개의 github에서 확인하실수 있습니다.

부족한부분이 많지만 개발에 많은 도움이 되었으면 합니다.

 

GitHub - lswteen/user: springboot jpa security restdoc restful API

springboot jpa security restdoc restful API. Contribute to lswteen/user development by creating an account on GitHub.

github.com

 

 

GitHub - lswteen/idus-security: 백패커/아이디어스 과제

백패커/아이디어스 과제. Contribute to lswteen/idus-security development by creating an account on GitHub.

github.com