Skip to content

Latest commit

 

History

History
370 lines (307 loc) · 15.3 KB

4.authentication.md

File metadata and controls

370 lines (307 loc) · 15.3 KB

4. 인증(Authentication) 프로세스 구현

4.1. 실전 프로젝트 구성

  • build.gradle 설정
plugins {
    id 'org.springframework.boot' version '2.3.3.RELEASE'
    id 'io.spring.dependency-management' version '1.0.10.RELEASE'
    id 'java'
}

group = 'kr.seok'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-security'

    implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    // https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity5
    implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.0.4.RELEASE'

    // https://mvnrepository.com/artifact/org.modelmapper/modelmapper
    implementation 'org.modelmapper:modelmapper:2.3.0'

    developmentOnly 'org.springframework.boot:spring-boot-devtools'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'

//    runtimeOnly 'org.postgresql:postgresql'
    /* h2 */
    implementation 'com.h2database:h2'

    annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'

    testImplementation('org.springframework.boot:spring-boot-starter-test') {
        exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
    }
    testImplementation 'org.springframework.security:spring-security-test'
}

test {
    useJUnitPlatform()
}
  • SecurityConfig 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /* Password Encode */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
    /* AuthenticationManagerBuilder InMemoryAuthentication 등록 */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        String password = passwordEncoder().encode("1234");
        auth.inMemoryAuthentication().withUser("user").password(password).roles("USER");
        auth.inMemoryAuthentication().withUser("manager").password(password).roles("MANAGER");
        auth.inMemoryAuthentication().withUser("admin").password(password).roles("ADMIN");
    }
    /* HttpSecurity Authentication AntMatcher */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/mypage").hasRole("USER")
                .antMatchers("/messages").hasRole("MANAGER")
                .antMatchers("/config").hasRole("ADMIN")
                .anyRequest().authenticated()
            .and()
                .formLogin()
        ;
    }
}

4.2. 메뉴 권한 및 WebIgnore 설정

  • SecurityConfig 설정
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /* Password Encode */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }
    /* static resource security 에서 제외 처리 */
    @Override
    public void configure(WebSecurity web) {
        web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations());
    }
    /* AuthenticationManagerBuilder InMemoryAuthentication 등록 */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        String password = passwordEncoder().encode("1234");
        auth.inMemoryAuthentication().withUser("user").password(password).roles("USER");
        auth.inMemoryAuthentication().withUser("manager").password(password).roles("MANAGER");
        auth.inMemoryAuthentication().withUser("admin").password(password).roles("ADMIN");
    }

    /* HttpSecurity Authentication AntMatcher */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/mypage").hasRole("USER")
                .antMatchers("/messages").hasRole("MANAGER")
                .antMatchers("/config").hasRole("ADMIN")
                .anyRequest().authenticated()
            .and()
                .formLogin()
        ;
    }
}

4.3. Form 인증

4.3.1. User 등록 / PasswordEncoder

  • PasswordEncoder

    • 비밀번호를 안전하게 암호화 하도록 제공
    • SpringSecurity 5.0 이전에는 기본 PasswordEncoder 가 평문을 지원하는 NoOpPasswordEncoder (현재는 Deprecated)
  • 생성

    • PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();
    • 여러 PasswordEncoder 유형을 선언한 뒤, 상황에 맞게 선택해서 사용할 수 있도록 지원하는 Encoder이다.
  • 암호화 포맷: {id}encodedPassword

    • 알고리즘 종류: bcrypt, noop, pbkdf2, scrypt, sha256 (기본 포맷은 Bcrypt : {bcrypt}
  • 인터페이스

    • encode(password)
      • 패스워드 암호화
    • matches(rawPassword: 원래 비밀번호, encodedPassword: 암호화된 비밀번호)
      • 패스워드 비교
  • 사용자 등록기능 구현 시나리오

    • PasswordEncoder를 사용하여 비밀번호 암호화 및 DB 저장
  • 사용자 로그인을 위한 domain, service, controller viewTemplate 생성

    • 사용자 등록을 위한 domain(Account) 생성

    • 사용자의 등록 요청을 위한 domain(AccountDto) 생성

    • 사용자 등록 requestMapping을 위한 controller method 생성

      • password 암호화를 위한 PasswordEncoder 생성 및 encode 추가
    • 사용자 Entity 를 저장하는 Service, Repository 생성

    • 사용자 회원가입처리를 위한 View 생성

    • 회원가입 view에 접근하기 위한 URL Path에 대해서 permitAll 설정

4.3.2. CustomUserDetailsService

  • UserDetailsService의 역할

    • 사용자 정보 및 권한 정보 DB 저장
    • 사용자 조회 인증 처리
  • UserDetailsService 시나리오 Flow

    • 사용자의 정보를 DB에 저장
    • DB에 등록된 사용자의 정보로 로그인 시 UserDetailsService 메서드 loadUserByUsername 의 진행 절차 확인
  • 사용자 DB 관리를 위한 기존의 인 메모리 사용자 설정 삭제

@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    String password = passwordEncoder().encode("1234");
    auth.inMemoryAuthentication().withUser("user").password(password).roles("USER");
    auth.inMemoryAuthentication().withUser("manager").password(password).roles("MANAGER");
    auth.inMemoryAuthentication().withUser("admin").password(password).roles("ADMIN");
}

4.3.3. CustomAuthenticationProvider

  • AuthenticationProvider
    • DB를 통한 사용자 정보의 유효성을 검증(password) 받은 UserDetails를 인증 토큰으로 생성하여 반환하는 역할을 하는 사용자 정의 클래스

4.3.4. Custom Login Form Page

  • 시큐리티에서 제공하는 로그인 페이지를 연결

4.3.5. 로그아웃 및 화면 보안 처리

  • 커스텀한 페이지에서 로그아웃 페이지에 대한 링크를 연결
  • 로그아웃 된 사용자의 세션 정보를 비우고 로그인 페이지로 redirect

4.3.6. WebAuthenticationDetails, AuthenticationDetailsSource

  • 인증 부가 기능
    • WebAuthenticationDetails
      • 사용자의 요청 정보 중 인증에 필요한 값 외에 추가적인 파라미터를 Authentication
    • AuthenticationDetailsSource
      • WebAuthenticationDetails 클래스를 생성하는 클래스

4.3.7. CustomAuthenticationSuccessHandler

  • 인증 성공 시 redirect 처리
    • default Url 설정
      • 사용자의 인증 성공 후 기본으로 설정되어 있는 url로 이동 (이전에 resource 요청이 없는 경우)
    • RequestCache Url 설정
      • 사용자의 이전 요청 정보를 세션에 저장하였다가 인증에 성공하는 경우 세션에 저장된 resource 요청 정보(url)를 기억하고 있다가 redirect

4.3.8. CustomAuthenticationFailureHandler

  • 인증 실패 시 처리 방법을 정의
    • 비밀번호아 맞지 않는 예외 발생 시
    • 비밀번호 외에 사용자의 추가 파라미터 요청정보가 잘못된 경우
      • 예외 발생시 나타낼 페이지 또는 resource를 정의하여 반환가능

4.3.9. Access Denied

  • 인가 거부 시 예외 발생과 인가 예외 페이지 이동 Handler 작성
    • 인가 거부에 대한 응답 메시지를 페이지로 전달할 수 있도록 Controller 작성

4.3.10. CSRF, CsrfFilter

4.3.11. 인증 사용자 정보 구하기

4.4. Ajax 인증

4.4.1. 흐름 및 개요

  • Ajax 인증과 Form 인증의 전반적인 처리과정은 비슷하다.

  • 동일하게 Filter 기반으로 인증처리를 수행한다.

  • 필터가 사용하는 각각의 클래스도 역할이 차이가 있을 뿐 전반적인 Flow는 동일하다.

  • Form 인증은 동기적인 방식으로 처리되고 Ajax 인증은 비동기 방식으로 처리된다.

  • 인증 Flow

    • AjaxAuthenticationFilter
      • 비동기 인증처리 필터
      • AjaxAuthenticationToken
        • 사용자의 인증처리 관련 파라미터로 인증 토큰을 생성하기 위한 클래스
      • AuthenticationManager
        • AjaxAuthenticationFilter가 인증 토큰을 전달하여 인증 처리를 위임
      • AjaxAuthenticationProvider
        • 실 인증 처리를 담당하는 클래스
      • UserDetailsService
        • DB 내에 저장된 사용자의 조회하는 역할
      • AjaxAuthenticationSuccessHandler
        • 인증 성공 시 처리
      • AjaxAuthenticationFailureHandler
        • 인증 실패 시 처리
  • 인가 Flow

    • FilterSecurityInterceptor

      • AuthenticationException
        • 인증 예외
      • AccessDeniedException
        • 인가 예외
    • ExceptionTranslationFilter

      • AjaxAuthenticationEntryPoint
        • 인증 실패 시 다시 로그인 페이지로 리다이렉트 처리
      • AjaxAccessDeniedHandler
        • 인가 실패 시 접근 불가 페이지 노출

4.4.2. AjaxAuthenticationFilter

  • Ajax 요청에 대한 인증 처리 가능한 Filter

    1. AjaxFilter를 생성하는 방법
      • AbstractAuthenticationProcessingFilter
      • Form 인증 처리하는 클래스도 해당 추상 클래스를 상속받음
    2. AjaxFilter의 작동 조건
      • AntPathRequestMatcher("/api/login")로 요청정보와 매칭하고 요청 방식이 Ajax이면 필터 적용
    3. AjaxFilter 처리 방식
      • AjaxAuthenticationToken을 생성하여 AuthenticationManager에게 전달하여 인증처리
    4. Ajax Filter를 FilterChain에 추가하는 방식
      • http.addFilterBefore(AjaxAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
  • Custom Filter 등록 코드 작성 요약

    • SecurityConfig
      • authenticationManagerBean 재정의를 통해 AuthenticationManager 등록
      • ajaxLoginProcessingFilter Bean 등록
        • Custom 한 AjaxLoginProcessingFilter에 AuthenticationManager 설정
      • configure 설정으로 addFilterBefore())
        • Custom한 ajaxLoginProcessingFilter를 UsernamePasswordAuthenticationFilter 전에 실행 될 수 있도록 설정

4.4.3. AjaxAuthenticationProvider

  • Ajax 인증 요청에 대해 실제 인증처리를 수행하는 클래스

    • Filter에서 특정 인증 요청 resource에 접근하여 요청한 request 정보를 인증 토큰으로 생성하여 AuthenticationManager에게 전달

    • AuthenticationManager는 인증 토큰 처리가 가능한 Provider를 찾아 Authentication 인증 객체를 전달

    • 인증 처리 메서드 authenticate()

      • 사용자 정의된 UserDetailsService 를 통해 DB에서 Username에 해당하는 Entity를 조회
      • UserDetailsService
        • 조회된 사용자의 정보를 User 클래스를 상속받은 사용자 정의 클래스 형태로 반환 (User 클래스는 UserDetails 인터페이스의 구현체)
      • username, password의 유효성 검증 처리
      • PasswordEncoder
        • Password를 암호화 할 때 현재 프로젝트에서는 Bcrypt 방식을 사용
      • 검증된 사용자의 요청값으로 AuthenticationToken을 생성
      • AjaxAuthenticationToken
        • UsernamePasswordAuthenticationToken 과 같은 방식으로 Token 생성하기 위해 그대로 생성
        • 두 개의 생성자가 존재
          1. 사용자의 request값으로 생성되어 provider 쪽으로 전달하기 위한 생성자
          2. Provider를 통해 username, password 값의 유효성이 검증되어 UserDetails credentials, authorities 등을 저장할 생성자
  • Provider가 실행되기 위한 사전 정보

    • Entity로 사용될 Account 클래스
    • Account DB 조회 JpaRepository 인터페이스 구현 UserRepository
    • UserDetailsService를 인터페이스를 구현한 CustomUserDetailsService

4.4.4. AjaxAuthenticationSuccessHandler

  • Ajax 방식의 인증 성공 후 처리
    • 인증 성공 status 값, ContentType, 인증 성공한 객체 값 반환

4.4.5. AjaxAuthenticationFailureHandler

  • Ajax 방식의 인증 실패 후 처리
    • 계정 여부 및 비밀번호 검증
    • 계정 사용 여부
    • 비밀번호 기한 만료 처리

4.4.6. AjaxLoginUrlAuthenticationEntryPoint & AjaxAccessDeniedHandler

  • FilterSecurityInterceptor

    • 인가 처리를 하는 필터 클래스
      • 익명 사용자가 접근하는 경우
      • 인가 예외 발생하는 경우
        • AuthenticationEntryPoint
      • 정상 인가 처리
        • AccessDeniedException
  • 시나리오

    • 인증되지 않은 사용자의 접근
    • 인증이 되었으나 인가되지 않은 사용자의 접근
    • 인증 & 인가가 정상처리된 사용자의 접근

4.4.7. DSL로 Config 설정하기

  • AbstractHttpConfigurer
    • 시큐리티 초기화 설정 클래스
    • 필터, 핸들러, 메서드, 속성 등을 한 곳에 정의하여 처리할 수 있는 편리함 제공
    • public void init(H http) throws Exception - 초기화
    • public void configure(H http) - 설정

4.4.8. 로그인 Ajax 구현 & CSRF

  • 시나리오

    1. 인증되지 않은 사용자의 페이지 접근

      • 인증되지 않은 사용자의 접근 시, 인증 예외 발생하여 로그인 페이지로 리다이렉트
    2. 인증은 되었으나 인가되지 않은 자원에 접근

      • 권한이 부족한 사용자는 인가 예외로 권한 부족에 대한 메시지 노출
  • AjaxFilter가 추가된 FilterChain 0 = {WebAsyncManagerIntegrationFilter@12087} 1 = {SecurityContextPersistenceFilter@12088} 2 = {HeaderWriterFilter@12089} 3 = {CsrfFilter@12090} 4 = {LogoutFilter@12091} 5 = {AjaxLoginProcessingFilter@12092} 6 = {RequestCacheAwareFilter@12093} 7 = {SecurityContextHolderAwareRequestFilter@12094} 8 = {AnonymousAuthenticationFilter@12095} 9 = {SessionManagementFilter@12096} 10 = {ExceptionTranslationFilter@12097} 11 = {FilterSecurityInterceptor@12098}