Link Search Menu Expand Document

스프링 시큐리티 인증 구현 - 로그인


기본 클래스

  • UserDetails: 사용자 정보 인터페이스
  • UserDetailsService: 사용자 정보 전달 인터페이스
  • AuthenticationProvider:

커스텀

위 기본 클래스들을 구현하여 커스텀 할 수 있으며 다음 항목들을 커스텀한다.

  • 로그인 액션(JWT 생성 + Cookie에 토큰 설정(세션 비활성화) + 리디렉션)
  • 사용자(롤, 권한)

로그인 액션 커스텀

@Configuration
//@EnableWebSecurity // Boot에서는 WebSecurityEnablerConfiguration에서 자동 Import
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http
            .authorizeHttpRequests((authorize) -> authorize
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                    .authorizationEndpoint(authorization -> authorization
                        .baseUri("/oauth2/authorization")
                        .authorizationRequestRepository(this.authorizationRequestRepository())
                    )
//                .redirectionEndpoint(redirection -> redirection
//                    .baseUri("/*/oauth2/code/*")
//                )
//                .userInfoEndpoint(userInfo -> userInfo
//                    .userService(this.oauth2UserService())
//                )
                    .successHandler(new OAuth2LoginSuccessHandler())
                    .failureHandler(new OAuth2LoginFailureHandler())
            );

        return http.build();

    }
}
  • CSRF: REST API는 무상태성으로 CSRF 방어가 필요없다고 하나 토큰을 쿠키에 저장한다면 HttpOnly 설정으로 방어 필요(Auth 헤더는 CSRF로 부터 안전하나 클라이언트가 토큰 보관하기 위해 세션/쿠키 활용 필요)

사용자 커스텀

public class CustomUserDetails implements UserDetails{
	
	private String ID;
	private String PASSWORD;
	private String NAME;
	private String AUTHORITY;
	private boolean ENABLED;
	
	@Override
	public Collection<? extends GrantedAuthority> getAuthorities() {
		ArrayList<GrantedAuthority> auth = new ArrayList<GrantedAuthority>();
		auth.add(new SimpleGrantedAuthority(AUTHORITY));
		return auth;
	}
    
    // ...

}

스프링 시큐리티 인증 구현 - OAuth2


Registration Provider 설정

인가서버가 클라이언트에게 권한 부여하기 위해 리소스 소유자 인증&인가 정책 설정이 필요하다. 시큐리티 의존성 추가 후 구성 파일에 oauth2 정보를 설정하면 로그인 화면이 연동된다.

# application.yml
spring:
  security:
    oauth2:
      client:
        registration:
          # 클라이언트 정보
        provider:
          # 인증서버 정보

(참고) ClientRegistration Config를 통해서도 인증 서버를 연동할 수 있다. Issuer 경로에서 메타정보를 통해 연동 설정을 자동으로 등록한다.

@Configuration
public class SecurityConfig {

    // ...

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        return new InMemoryClientRegistrationRepository(this.nxasClientRegistration());
    }

    private ClientRegistration nxasClientRegistration() {

        // OAuth2 Registration 등록없이 Provider 메타정보로 연동
        return ClientRegistrations.fromIssuerLocation("인증서버경로")
            .registrationId("nxas")
            .clientId("클라이언트ID")
            .redirectUri("리다이렉트URL")
            .build();
    }
}

SecurityFilterChain 커스텀 - OAuth2Login

SecurityConfig 설정에서 로그인 핸들러를 커스텀한다.(리디렉션, jwt 생성, 쿠키 설정 등)

@Configuration
public class SecurityConfig {


    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {

        http
            .authorizeHttpRequests((authorize) -> authorize
                .anyRequest().authenticated()
            )
            .oauth2Login(oauth2 -> oauth2
                    .authorizationEndpoint(authorization -> authorization
                        .baseUri("/oauth2/authorization")
                        .authorizationRequestRepository(this.authorizationRequestRepository())
                    )
//                .redirectionEndpoint(redirection -> redirection
//                    .baseUri("/*/oauth2/code/*")
//                )
//                .userInfoEndpoint(userInfo -> userInfo
//                    .userService(this.oauth2UserService())
//                )
                    .successHandler(new OAuth2LoginSuccessHandler())
                    .failureHandler(new OAuth2LoginFailureHandler())
            );

        return http.build();

    }

    @Bean
    public AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository() {
      return new HttpSessionOAuth2AuthorizationRequestRepository();
    }
}

SecurityFilterChain 커스텀 - OAuth2Client

최종 사용자 인증 부분을 커스텀하기 위해서는 OAuth2Client 메서드를 사용하여 인가까지만 자동 진행한다.
OAuth2Login 메서드는 클아이언트 인증까지 자동으로 진행된다.

  • OAuth2AuthorizedClient: 인가받은 클라이언트

SecurityFilterChain 커스텀 - AuthorizationServer

엔드포인트와 인증을 위한 시큐리티 설정을 구성한다.

OAuth2User 유저 객체

인증 완료 후 저장된 인증 정보 객체를 얻는 방법은 다음과 같다.

public class AuthController {

    @GetMapping(value = "/user")
    public OAuth2User user(Authentication authentication) {

        OAuth2AuthenticationToken authenticationToken = (OAuth2AuthenticationToken) authentication;
//        OAuth2AuthenticationToken authenticationToken2 = (OAuth2AuthenticationToken) SecurityContextHolder.getContext().getAuthentication();

        OAuth2User oAuth2User = (OAuth2User) authenticationToken.getPrincipal();

        return oAuth2User;
    }

    @GetMapping(value = "/oAuth2User")
    public OAuth2User oAuth2User(@AuthenticationPrincipal OAuth2User oAuth2User) {
        return oAuth2User;
    }
}

로그인 성공 핸들러

핸들러를 통해 결과 처리 커스텀

// 로그인 핸들러 - jwt 생성, 쿠키에 세팅, 리디렉션

리소스 서버 인가 로직 구현


실무에서는 인증 서버와 리소스 서버를 통일하여 시큐리티가 복잡하였는데
본 포스팅에서는 분리하여 설명 할 수 있도록 작성한다.

클라이언트 구현


쿠키 설정

백엔드에서 인증 완료 후 응답 헤더의 쿠키에 토큰을 설정해서 리디렉션 했다면,
클라이언트에서는 쿠키를 유지하여 이후 요청 마다 토큰을 전송하게 된다.

export const serviceAxios = axios.create({
  baseURL: process.env.API_URL,
  timeout: 5000,
  headers: {
    'Content-Type': 'application/json'
  },
  withCredentials: true // 쿠키 유지
});

서버 기타 설정


SSL 설정

  • 권한 인증을 쿠키로 진행 할 경우 보안 설정 필수
  • 서브 도메인이 일치하지 않는 경우 다음의 설정 필수
    • SameSite: None
    • scure(SSL): true
  • 로컬에서 검증하기 위해 임의 CA 발급 ```shell

    choco 설치

mkcert 설치

choco install mkcert

openssl 설치

choco install openssl

인증서 생성

SSL 설정 필요 프로젝트의 classpath로 이동

mkcert -install # 최초 한번 mkcert -key-file key.pem -cert-file cert.pem “*.서브도메인.com” localhost 127.0.0.1 ::1 openssl pkcs12 -export -in cert.pem -inkey key.pem -out keystore.p12 -name career-api -CAfile C:...\mkcert\rootCA.pem -caname C:...\rootCA-key.pem -chain -passout pass:비밀번호 keytool -importkeystore -srckeystore keystore.p12 -srcstoretype PKCS12 -destkeystore keystore.jks -deststoretype JKS -srcstorepass 비밀번호 -deststorepass 비밀번호 keytool.exe -import -alias gateway-cert -keystore “C:\Program Files...\jdk-17.0.2\lib\security\cacerts” -storepass changeit -file “C:...\resources\cert.pem” keytool -v -list -keystore keystore.p12 ```