<개요>

- 대부분의 서비스에서 공통적으로 필요한 기능이 사용자 관리/로그인 기능이다. 단순한 것 같지만 생각보다 많은 리소스가 필요한 시스템이다. (특히 권한연계까지 들어갈 경우)

- 기존에는 자체적으로 사용자 관리 시스템을 운영하고 있었다. (Password Grant방식)

 그 이유는 특별한 사용자의 정보를 담지 않고 있으며 인증, Resource Server, Client가 모두 같은 System Boundary에 포함되어 있었기 때문에 가장 간편한 방식으로 개발하였다.

- 이제부터는 관리기능을 확장시키고 추가 정보를 담아야 하는 요건이 생겼으며, 이를 위해서 사용자 인증에 관련된 부분을 별도 시스템으로 분리하여 구성하는 것이 좋다고 판단하였다.

- AWS Cognito의 경우 외부연동, MFA인증등 편리한 기능을 기본적으로 가지고 있으며 JWT Token, IAM 연동도 가능한 것으로 보여서 가능성을 검토해본다.

 a. 기존 시스템을 유지하면서 개선할 것인지 (Spring Boot / Security / JWT Token)

 b. 새로 개발할 것인지 (e.g AWS Cognito)

 c. 혹은 a,b를 혼합하여 가져갈 수 있을지 (커스터마이징의 범위)

 

<내용>

1. 사용자 풀

사용자 풀

- 사용자를 생성하고 그룹을 할당할 수 있다.

- 이메일 확인 및 비밀번호 초기화 등의 기능을 기본으로 제공한다.

- 그룹은 개별적으로 추가 가능하다.

- AWS Cognito user pool은 기본적으로 대소문자를 구분하지 않도록 되어있다. (권장사항)

  만약에 대소문자를 구별하고 싶다면 Make user name case sensitive 옵션을 활성화 해야한다.

https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-case-sensitivity.html

 

User pool case sensitivity - Amazon Cognito

User pool case sensitivity Amazon Cognito user pools that you create in the AWS Management Console are case insensitive by default. When a user pool is case insensitive, user@example.com and User@example.com refer to the same user. When user names in a use

docs.aws.amazon.com

 

2. 연동자격증명 공급자

- 우리가 SNS연동을 하기 위해서는 일반적으로 App을 생성하고 할당받은 Client ID, Secret을 이용하여 API호출 및 연계개발을 하게 되는데 이부분이 쉽게 설정으로 가능하도록 되어있다. (많이 사용하는 Google / Apple / Facebook)

- 그 외에도 표준 OIDC를 준수한다면 쉽게 추가하고 속성을 매핑할 수 있는 구조로 되어있다.

자격 증명 공급자 : Google

 

3. SMS / SNS

-  스타트업에서는 내부에 문자 / 이메일 발송기능이 없는 경우도 많은데 AWS SES / SNS 등과 쉽게 연동하여 활용이 가능하다.

- 단, SMS는 현재 도쿄 리전에서만 사용이 가능하며 샌드박스 내에서는 등록된 번호로만 발신이 가능하다.

 

4. App 구성

- AWS Cognito도 결국 우리가 만든 서비스와 연결되기 위해서 여러가지 요소가 필요한데 이에 대한 부분도 제공하고 있다.

- 도메인 : Cognito 도메인 or 사용자 지정도메인 가능

- 리소스 서버 구성 (OAuth 2.0)

- 호스팅 UI, 앱 클라이언트

 

5. OAuth 2.0 

- 앱 유형을 선택하기 전에 OAuth 2.0에 대한 기본적인 이해도가 있으면 도움이 된다.

https://icthuman.tistory.com/entry/OAuth-20-Flow

 

OAuth 2.0 Flow

1. Authorization Code - 권한 부여 승인을 위해서 자체생성한 Authorization Code를 전달하는 방식 - 기본이 되는 방식 - Refresh Token 사용이 가능 2. Client Credentials - 클라이언트의 자격증명만으로 Access Token을

icthuman.tistory.com

 

6. 앱 클라이언트

- 앱 클아이언트 부분을 좀 더 자세히 살펴보면 다음과 같다.

- 클라이언트 보안키 생성유무 선택에 따라서 추후 토큰발급 엔드포인트를 호출할 때 방법에 차이가 있다.

- 세션, 토큰( Id/Access/Refresh )별 만료시간을 선택할 수 있으며 Revoke Token에 대해서 선택사항으로 정할 수 있다.

- 사용자를 찾을 수 없다고 응답을 하면 해킹에 취약해질 수 있기 때문에 최근 로그인 시스템에서는 이름 또는 암호가 잘못되었다고 묶어서 표현하고 있다.

 

6. 호스팅 UI

- 자체적으로 로그인 UI 를 제공해준다.

- 주의할 점은 사용자 풀에서 연동자격증명을 선택하였다면 여기서도 선택을 해주어야 UI 상에서 버튼이 활성화 된다.

- 해당 호스팅UI는 내가 등록한 App에서만 사용하는 것이다. 따라서 호출할 때 다음 파라미터들을 전달해주어야 정상적으로 동작한다.

- client_id, response_type, scope, redirect_uri

 

<정리>

- AWS Cognito에서 User Pool을 생성하고 Identity Providers를 추가한 뒤 호스팅 UI 를 통해서 로그인/가입까지 완료했다.

- 다음 글에서는 OAuth 2.0 기본 Flow를 따라서 Cognito 사용자 풀과 호스팅 UI를 연동하여 Authorization Code를 발급받고

- 이를 기반으로 엔드포인트에 접근하여 Token발급, UserInfo 조회하는 기능을 살펴본다.

 

<참고>

- https://aws.amazon.com/ko/cognito/details/

 

기능 | Amazon Cognito | Amazon Web Services(AWS)

 

aws.amazon.com

- https://docs.aws.amazon.com/ko_kr/cognito/latest/developerguide/what-is-amazon-cognito.html

 

Amazon Cognito란 무엇입니까? - Amazon Cognito

Amazon Cognito란 무엇입니까? Amazon Cognito는 웹 및 모바일 앱에 대한 인증, 권한 부여 및 사용자 관리를 제공합니다. 사용자는 사용자 이름과 암호를 사용하여 직접 로그인하거나 Facebook, Amazon, Google

docs.aws.amazon.com

 

1. Authorization Code

Authorization Code

- 권한 부여 승인을 위해서 자체생성한 Authorization Code를 전달하는 방식

- 기본이 되는 방식

- Refresh Token 사용이 가능

 

2. Client Credentials

- 클라이언트의 자격증명만으로 Access Token을 획득하는 방식

- 가장 간단한 방식

- 자격증명을 안전하게 보관할 수 있는 클라이언트에서만 사용되어야 함

- Refresh Token 사용 불가능

 

3. Implicit Grant

- 자격증명을 안전하게 저장하기 힘든 클라이언트에게 최적화된 방식

- Access Token이 바로 발급되기 때문에 만료기간을 짧게 설정할 필요가 있음

- Refresh Token 사용 불가능

 

4. Resource Owner Password Credentials Grant

- username, password로 Access Token을 받는 방식

- 클라이언트가 외부 프로그램일 경우 사용하면 안됨

- 권한서버, 리소스서버, 클라이언트가 모두 같은 시스템에 속해 있을때 사용해야 함 (대부분 비권장)

- 요청이 성공한 클라이언트는 메모리에서 자격증명을 폐기해야 함

- Refresh Token 사용 가능

 

<정리>

  Authorization Code Client Credentials Implicit Grant ROPC Grant
App 유형 SPA, Web App, Mobile Web Server Browser User / Client
권장사항 일반적     비권장
Refresh Token O X X O
Redirect Uri 필수 필수 권장 X

 

 

<참조>

- https://learn.microsoft.com/ko-kr/azure/active-directory/develop/v2-oauth2-auth-code-flow

- https://www.rfc-editor.org/rfc/rfc6749

<개요>

- Spring Security 의 경우 Filter Chain 의 형태로 기능을 제공하고 있으며 필요에 따라서 추가/수정/삭제가 가능하다.

 

<내용>

1. Spring Security - Filter Chain

-  Spring 에서 모든 호출은 DispatcherServlet을 통과하게 되고 이후에 각 요청을 담당하는 Controller 로 분배된다.

-  이 때 각 Request 에 대해서 공통적으로 처리해야할 필요가 있을 때 DispatcherServlet 이전에 단계가 필요하며 이것이 Filter이다.

Filter 와 Interceptor의 차이

- Spring Security는 FilterChainProxy를 통해서 상세로직을 구현하고 있다.

Spring Security Filters

 

- 현재 Application에서 사용중인 Filter Chain은 Debug를 통해서 쉽게 확인할 수 있다.

11개의 Filter를 사용중이다.

- FilterChain이름이 의미하듯 Filter는 순서가 매우 중요하다.

 

2. 우리가 가장 많이 사용하는 UserNamePassword 구조에 대해서 좀 더 살펴보겠다.

(적당한 그림을 찾지 못해서 손으로 그려보았다.)

AuthenticationFilter

- Request가 들어왔을때 Filter를 거치게 되고, 적절한 AuthenticationToken이 존재하지 않는다면 AuthenticationProviders를 거쳐서 생성하게 되며 UserDeatilsService를 입맛에 맞게 구현하여 사용자 정보를 가져오는 부분을 구현할 수 있다.

- Spring Securty에서는 사람들이 가장 많이 사용하는 DB인증을 다음과 같이 미리 구현해 두었다. (오른쪽 파란색 박스)

 

3.  인증방식 변경 ( JWT Token)

최근에는 MSA구조의 효율성을 높이기 위해서 JWT Token 방식을 많이 사용하고 있다.  Request에 대한 인증을 별도 서버를 거치지 않고 검증가능하고 기본로직에 필요한 내용을 담을 수 있어서 편리하다.

현재 서비스에 적용중인 대략적인 구조이다.

JWT Token Filter

- JWT AuthTokenFilter에서 해당 처리를 마친 후 나머지 FilterChain을 수행하는 구조이다.

- Token을 통해서 인증 및 인가를 위한 정보를 생성하여 SecurityContextHolder를 통해서 세팅한다.

- JWT Filter의 내용을 간단히 살펴보면 아래와 같다. (보안상 TokenProvider 로직은 개별구현하는 것을 권장한다.)

public class JwtTokenFilter extends GenericFilterBean {
    private JwtTokenProvider jwtTokenProvider;
    public JwtTokenFilter(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }
    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain filterChain)
            throws IOException, ServletException {
        String token = jwtTokenProvider.resolveToken((HttpServletRequest) req);
        try {
            if (null == token) {
                filterChain.doFilter(req, res);
            } else {
                if(jwtTokenProvider.validateToken(token)) {
                    Authentication auth = token != null ? jwtTokenProvider.getAuthentication(token) : null;
                    SecurityContextHolder.getContext().setAuthentication(auth);
                    filterChain.doFilter(req, res);
                } else {
                    HttpServletResponse httpResponse = (HttpServletResponse) res;
                    httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid jwt token");
                }
            }
        } catch(IllegalArgumentException | JwtException e) {
            HttpServletResponse httpResponse = (HttpServletResponse) res;
            httpResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid jwt token");
        }
    }

 

- JwtTokenFilter를 작성 후 해당 Filter를 적절한 위치에 configure하는 것이 중요하다.

public class JwtConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
    private JwtTokenProvider jwtTokenProvider;
    public JwtConfigurer(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }
    @Override
    public void configure(HttpSecurity http) throws Exception {
        JwtTokenFilter customFilter = new JwtTokenFilter(jwtTokenProvider);
        http.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class);
	}
}

 

<정리>

- Spring Security는 분량이 많고 내용이 복잡하기 때문에 기본개념을 이해하고 요건에 따라서 적절히 추가/삭제/수정하는 것이 필요하다.

전체적인 흐름을 알지 못하고 사용하는 경우 의도치 않게 동작할 수 있기 때문에 아래 내용은 반드시 기억하는 것이 좋다.

 1. Filter 는 DispatcherServlet 앞에 존재한다.

 2. 여러 개의 Filter를 동시에 적용할 수 있으며 순서에 주의해야 한다. 

 3. Custom Filter를 개발하였다면 로직 처리 후 FilterChain.doFilter 를 호출하여 이후단계를 수행해야 한다. (AOP @Around와 동일)

 4.Filter / Interceptor 를 필요이상으로 넣을 경우 성능에 영향을 줄 수 있다.

 

<참조>

https://devlog-wjdrbs96.tistory.com/352

https://www.fatalerrors.org/a/in-depth-understanding-of-filterchainproxy.html

+ Recent posts