이 영역을 누르면 첫 페이지로 이동
천천히 꾸준히 조용히 블로그의 첫 페이지로 이동

천천히 꾸준히 조용히

페이지 맨 위로 올라가기

천천히 꾸준히 조용히

천천히 꾸준히 조용히.. i3months 블로그

[Spring Security6] 인증 서버 구축

  • 2023.08.15 01:44
  • Spring/Spring Security
반응형

 

 

 

페이스북, 구글, 깃허브 등 대기업들은 자신만의 OAuth2 인증 서버를 가지고 있다.

 

따라서 사용자의 구글 정보를 활용하는 서드 파티 애플리케이션을 개발할 때는 구글이 제공하는 Auth Server를 사용해 사용자의 정보에 안전하게 접근하거나 사용자를 인증할 수 있다.

 

만들어진 Auth Server를 사용하는건 쉽지만, 직접 Auth Server를 구축하는건 쉽지 않다.

애플리케이션을 개발할 때 MSA 기반으로 설계한다면 인증 서버를 다른 요소들과 독립적으로 구축해야 한다.

 

Keycloak, Spring Security OAuth, AWS Cognito, Okta 등 여러 가지 기술을 활용해 인증 서버를 구축할 수 있다.

각 기술들은 OAuth2, OpenID Connect 등의 프로토콜을 지원하고, 인증 관련 기능을 통합해서 제공한다.

 

스프링 시큐리티가 제공하는 OAuth2와 OpenID Connect를 사용하면 애플리케이션 내에서 인증 기능을 편하게 구현할 수 있다. 

 

구글, 페이스북 등 소셜 계정을 사용해서 애플리케이션에 로그인하는 소셜 로그인 기능도 구현할 수 있고, 사용자의 구글 정보를 활용해서 애플리케이션 내부에서 활용할 수 있고, OpenID Connect를 사용해 인증된 사용자에 대한 기본 정보를 토큰 형식으로 받아올 수도 있다.

 

이렇듯 애플리케이션 내부에서 사용되는 인증을 구현하는건 스프링 시큐리티를 사용하면 편하지만, 인증과 권한 부여를 중앙 집중적으로 관리하는 인증 서버를 구축할 때는 Keycloak 등 다른 기술을 사용하면 편하다.

 

물론 인증 서버를 구축할 때도 스프링 시큐리티를 써도 되긴 하지만...

Keycloak 같은 솔루션을 사용하면 미리 만들어진 템플릿과 구축된 설정을 통해 빠르게 인증 서버를 구축할 수 있고, 관리하기 편하도록 백오피스를 제공하는 등 여러 편의 기능을 제공해 편하게 인증 서버를 구축할 수 있다.

 

 

 

Keycloak 등 인증 서버를 구축하는 기술으로 Auth Server를 구축했으면 spring-boot-starter-oauth2-resource-server 라이브러리를 사용해 스프링으로 Resource Server를 구축할 수 있다. 

 

인증 관련 로직은 따로 구축된 인증 서버에게 전담시키고 스프링 애플리케이션에서는 인증 서버에서 받아온 사용자의 권한과 역할을 바탕으로 리소스를 전달하는 로직에만 집중할 수 있다.

 

 

public class KeycloakRoleConverter  implements Converter<Jwt, Collection<GrantedAuthority>> {

    @Override
    public Collection<GrantedAuthority> convert(Jwt jwt) {
        Map<String, Object> realmAccess = (Map<String, Object>) jwt.getClaims().get("realm_access");

        if (realmAccess == null || realmAccess.isEmpty()) {
            return new ArrayList<>();
        }

        Collection<GrantedAuthority> returnValue = ((List<String>) realmAccess.get("roles"))
                .stream().map(roleName -> "ROLE_" + roleName)
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        
        return returnValue;
    }
}

@Configuration
public class ProjectSecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
        requestHandler.setCsrfRequestAttributeName("_csrf");
        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new KeycloakRoleConverter());

        http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .cors(corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() {
                    @Override
                    public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
                        CorsConfiguration config = new CorsConfiguration();
                        config.setAllowedOrigins(Collections.singletonList("http://localhost:4200"));
                        config.setAllowedMethods(Collections.singletonList("*"));
                        config.setAllowCredentials(true);
                        config.setAllowedHeaders(Collections.singletonList("*"));
                        config.setExposedHeaders(Arrays.asList("Authorization"));
                        config.setMaxAge(3600L);
                        return config;
                    }
                })).csrf((csrf) -> csrf.csrfTokenRequestHandler(requestHandler).ignoringRequestMatchers("/contact", "/register")
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
                .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)
                .authorizeHttpRequests((requests) -> requests
                        .requestMatchers("/myAccount").hasRole("USER")
                        .requestMatchers("/myBalance").hasAnyRole("USER", "ADMIN")
                        .requestMatchers("/myLoans").authenticated()
                        .requestMatchers("/myCards").hasRole("USER")
                        .requestMatchers("/user").authenticated()
                        .requestMatchers("/notices", "/contact", "/register").permitAll())
                .oauth2ResourceServer(oauth2ResourceServerCustomizer ->
                        oauth2ResourceServerCustomizer.jwt(jwtCustomizer -> jwtCustomizer.jwtAuthenticationConverter(jwtAuthenticationConverter)));
        return http.build();
    }
}

 

 

시큐리티 설정은 인증 서버를 사용하지 않을 때와 크게 다르지 않다.

설정 클래스에서 Keycloak 인증서버를 사용함을 명시해주고, http form 기반으로 인증하지 않음을 명시해주자.

 

KeycloakRoleConverter 클래스에서 JWT를 통해 사용자의 Roles를 추출하고 시큐리티의 GrantedAuthority 형식으로 변환한다.

 

oauth2ResourceServer 메서드로 해당 애플리케이션을 OAuth2 Resource Server로 설정한다.

Keycloak에서 발행된 JWT를 인증 방식으로 사용한다.

 

 

 

React로 프론트엔드 애플리케이션을 만들었다. 이 애플리케이션을 A 라고 하자.

Keycloak으로 Auth Server를 구축했다. 이 서버를 B 라고 하자.

SpringBoot로 Resource Server를 구축했다. 이 서버를 C 라고 하자. 

 

사용자는 A를 통해 서비스에 액세스한다.

 

 

 

 

위와 같이 Authorization Code Grant Type 흐름으로 인증을 진행하는데, React는 자바스크립트라서 A에서 Client Secret을 안전하게 보관할 수 없다.

 

따라서 OAuth2에서는 PKCE를 사용해 프론트엔드 애플리케이션에서 클라이언트 시크릿을 안전하게 보관한다.

 

Proof Key for Code Exchange (PKCE) 는 OAuth의 Authorization Code 흐름을 보완하기 위해 도입됐고, Code Verifier와 Code Challenge를 통해 클라이언트 시크릿을 안전하게 보관한다.

 

Code Verifier : 클라이언트가 생성하는 무작위 문자열으로, 토큰 요청 시 사용된다.

Code Challenge : Code Verifier를 통해 생성한다. SHA-256 해시 알고리즘을 사용해 Code Verifier를 해싱한 후 인코딩한다.

 

1. 인증 서버에 Authorization Code를 요청할 때 Code Challenge를 함께 보낸다. 

2. 인증이 완료됐으면 인증 서버는 Code Challenge를 저장하고 클라이언트에게 Authorization Code를 발급해준다.

3. 클라이언트는 Access Token을 요청할 때 Authorization Code와 Code Verifier를 함께 보낸다.

4. 인증 서버는 Code Verifier를 바탕으로 Code Challenge를 다시 계산하고 값이 일치하는 경우 Access Token을 발급한다.

 

이런 과정 덕분에 해커가 인증 코드를 가져가더라도 Access Token을 발급받을 수 없다.

 

Keycloak 같은 인증 서버 구축 기술은 PKCE를 기본적으로 제공하니 프론트엔드와 백엔드를 따로 개발하는 경우 사용하자.

 

 

 

반응형
저작자표시 (새창열림)

'Spring > Spring Security' 카테고리의 다른 글

[Spring Security] 인증 메커니즘  (0) 2024.05.30
[Spring Security] 초기화 과정  (0) 2024.05.27
[Spring Security6] OAuth2와 OpenID Connect  (0) 2023.08.13
[Spring Security 6] 메서드 단위 권한 요청  (0) 2023.08.12
[Spring Security6] Json Web Token  (0) 2023.08.11

댓글

이 글 공유하기

  • 구독하기

    구독하기

  • 카카오톡

    카카오톡

  • 라인

    라인

  • 트위터

    트위터

  • Facebook

    Facebook

  • 카카오스토리

    카카오스토리

  • 밴드

    밴드

  • 네이버 블로그

    네이버 블로그

  • Pocket

    Pocket

  • Evernote

    Evernote

다른 글

  • [Spring Security] 인증 메커니즘

    [Spring Security] 인증 메커니즘

    2024.05.30
  • [Spring Security] 초기화 과정

    [Spring Security] 초기화 과정

    2024.05.27
  • [Spring Security6] OAuth2와 OpenID Connect

    [Spring Security6] OAuth2와 OpenID Connect

    2023.08.13
  • [Spring Security 6] 메서드 단위 권한 요청

    [Spring Security 6] 메서드 단위 권한 요청

    2023.08.12
다른 글 더 둘러보기

정보

천천히 꾸준히 조용히 블로그의 첫 페이지로 이동

천천히 꾸준히 조용히

  • 천천히 꾸준히 조용히의 첫 페이지로 이동

검색

방문자

  • 전체 방문자
  • 오늘
  • 어제

카테고리

  • 분류 전체보기 (677)
    • Algorithm (205)
      • Data Structure (5)
      • Theory && Tip (33)
      • Baekjoon (166)
      • ALGOSPOT (1)
    • Spring (123)
      • Spring (28)
      • Spring Web MVC (20)
      • Spring Database (14)
      • Spring Boot (6)
      • Spring 3.1 (11)
      • Spring Batch (6)
      • Spring Security (16)
      • JPA (12)
      • Spring Data JPA (5)
      • QueryDSL (4)
      • eGovFramework (1)
    • Programming Language (74)
      • C (25)
      • C++ (12)
      • Java (19)
      • JavaScript (15)
      • Python (1)
      • PHP (2)
    • Computer Science (142)
      • Machine Learning (38)
      • Operating System (18)
      • Computer Network (28)
      • System Programming (22)
      • Universial Programming Lang.. (8)
      • Computer Architecture (4)
      • Compiler Design (11)
      • Computer Security (13)
    • Database (21)
      • Database (7)
      • MySQL (3)
      • Oracle (3)
      • Redis (5)
      • Elasticsearch (3)
    • DevOps (20)
      • Docker && Kubernetes (8)
      • Jenkins (4)
      • Amazon Web Service (8)
    • Mobile (28)
      • Android (21)
      • Flutter (7)
    • 💡 솔루션 (17)
    • 👥 모각코 (9)
    • 💬 기록 (7)
    • 📚 공부 (6)
    • -------------- (25)

최근 글

나의 외부 링크

메뉴

  • 홈
반응형

정보

i3months의 천천히 꾸준히 조용히

천천히 꾸준히 조용히

i3months

블로그 구독하기

  • 구독하기
  • RSS 피드

티스토리

  • 티스토리 홈
  • 이 블로그 관리하기
  • 글쓰기
Powered by Tistory / Kakao. Copyright © i3months.

티스토리툴바