본문 바로가기

프로그래밍/JAVA & SPRING

[기타] 봄이네집 스프링 - (1) Spring Security - Auth0 JWT Library

반응형

Spring Security 레퍼런스 문서 함께 읽기

Spring Security 의 내부 구현체들 알아보기

jwt 맛보기

 

모든 인증은 AJAX 요청 기반으로 이뤄진다.

HS-256 기반으로 암호화된 JWT를 발급한 후 HttpServletResponse로 돌려줄 수 있어야 한다.

Controller에 method security를 적용한다.

소셜공급자(naver, kakao, facebook...)를 통한 로그인을 구현하기 위해 기초 작업을 한다. 이때 FE에서의 인증 플로우는 Implicit Crant Flow를 따른다.

 

스프링시큐리티가 실제로 어렵고 복잡하다.

==> 구현체도 많고 다양한 스펙들을 지원하기 때문에 방대해지고 알아야 할게 많다.

레퍼런스가 jwt같다.

==> django의 레퍼런스는 읽기에는 좋다.

스프링은 레퍼런스 문서가 친절하지 않고 난해하다.

 

필요한 만큼 이해하고 적용해본다.

일단 구현하고 리팩토링과 추가학습을 통해 정교함을 더해간다.

 

개념도

 

 

필터체인이라 해서 여러개의 필터가 있고

가장앞단의 username password authontiation filter calls filter, 커스텀인증 filter를 구현해서

체인으로 구현해서 앞단에 두어서 앞단에 놓는다.

 

필터를 거친 req는 인증 요청 객체로 변환이 될거다.

req의 body에 우리가 구상해 놓은 인증객체들이 묻어 있을거다. json형태로.

이런 인증객체들을 dto클래스로 빼고

 dto클래스를 다시한번 스프링시큐리티에서 제공하는 authotication을 상속받은 그런 객체로 만들어서

provider manager에 authenticate() 메소드로 전달 할 거다.

 

provider manager는 여러개의 authenticate provider를 제공할 수 있다.

내부구현을 보면 이터레이션을 돌면서 이 authentication 클래스에 맞는 provider를 자동으로 찾아서 인증처리를 하고 돌려주는데

이 인증된 객체, authentication 을 상속받은 객체인데

시큐리티 콘텍스트 (쓰레드당 하나씩 부여된다)라는게 있다.

인증콘텍스트 관리자는 인증객체를 관리하는 관리자다

security context를 관리하는 security context holder라는 것이 있다.

 

인증객체는 콘트롤러에서 주입을 받을 수도 있다.

 

인증관리자가 들고 있다가 필요한 시점에 뿌려준다.

 

인증이 끝나면 res객체를 통해서 사용자에게 알려준다.

 

 

AuthenticationManager: AuthenticationProvider 주머니

- Builder 패턴으로 구현

- 등록된 Authentication Provider 들에 접근하는 유일한 객체

- 단순 인터페이스에 불과하다. 내장 구현체:

- 구현하라고 널허준 인터페이스가 아니다.

 

이렇게 만들어주면 된다.

 

@Configuration
public class TestSecurity extends WebSecurityConfigurerAdapter{

 @Autowired
 public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

  auth
   .inMemoryAuthentication()
   .withUser("gksmf").password("1234").roles("ADMIN");
 }
}



AuthenticationProvider : 진짜 인증이 일어나는 곳

- Spring Security의 알파와 오메가

- 인증 "전" 객체를 받아 인증 가능 여부를 체크한 후, 예외를 던지는지 인증 "후" 객체를 만들어 돌려준다. (비번비교 또는 소셜 공급자 서버에 컨텍해서 사용자 검색...)

- 구현하라고 널허준 인터페이스다.

- 필요에 맞게 정교하게 구현하고 인증 관리자에 등록시키자.

 

객체지향 설계를 할 수 없고 아래처럼 안 예쁘게 코딩된다.

Clean Code를 구현할 수 없는 곳이 시큐리티와 JPA쪽이다.

 

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {
 @Autowired private MemberDetailsService userDetailService;
 @Autowired private PasswordEncoder passwordEncoder;
  
 @Override
 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
  UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken)authentication;
  FormLoginDocument document = (FormLoginDocument)token.getPrincipal();
  SecurityMember attemptedUser = getUserInfo(document);
  if(isValidCredentials(document, attempedUser)) {
   return new CustomLoginToken(attempedUser);
  }
  throw new BadCredentialsException("계정 정보가 일치하지 않습니다.", HttpStatus.FORBIDDEN);
 }
 @Override
 public boolean supports(Class<?> authentication) {
  // TODO Auto-generated method stub
  return false;
 }
}



그럼 인증 객체는 뭔가?

Authentication의 모든 서브객체이다.

 

public interface Authentication extends Principal, Serializable {
 Collection<? extends GrantedAuthority> getAuthorities();
 Object getCredentials();
 Object getDetails();
 Object getPrincipal();
 boolean isAuthenticated();
 void setAuthenticated(boolean var1) throws IllegalArgumentException;
}



스프링에서 인증된 사용자는 권한을 가지고 있다고 본다.

 

UsernamePasswordAuthenticationToken -> java doc 문서를 확인해보자

[Contructor Summary]

UsernamePasswordAuthenticationToken(java.lang.Object principal,

                               java.lang.Object credentials)

 

Principal 객체 : 인증의 주체가 되는 객체를 이렇게 부른다. 인증을 시작할 시점을 명시할 객체가 와야 한다. username으로 user객체를 불러와서 인증처리를 할 것이다.

결국 Principal 이 인증 프로세서의 시작을 알려주는 역할을 해줘야 한다.

그럼 Principal 은 username에 걸려야 한다. 실제 userid가 처음에 와야 한다는 것.

(필자: username과 userid는 여기서는 혼용해서 쓰는것 같음)

username이 principal 위치에 와야 한다.

password는 credentials에 와야 한다.

username과 password를 들고 있는 객체를 만들어주는 생성자인 것이다.

 

그리고 UsernamePasswordAuthenticationToken() 생성자를 통해 만들어진 객체는 인증 전 객체이여서 false를 리턴한다.

AbstractAuthenticationToken.isAuthenticated() ==> false

프로바이더의 손을 타지 않은 객체이다.

 

[Contructor Summary]

UsernamePasswordAuthenticationToken(java.lang.Object principal

                               ,java.lang.Object credentials

                               ,java.util.Collection<? extends GrantedAuthority>authorites)

 

manager나 provider의 인증과정을 거처서 인증이 끝난 객체만 담아라.

이 객체는 authorites가 들어 있다.

권한정보가 들어 있으면 이 사용자는 인증을 받았구나 하는 것이다.

 

결국 우리가 구현해야 할 것

​- 요청을 받아낼 필터 (AbstractAuthenticationFilter)

​이 필터를 상속한 요청을 받아낼 필터를 구현해야 한다.

​이 필터는 인증을 의미 단위(FLOW단위별)로 구분해서 만들어야 한다.

폼로그인 지원 소셜로그인 지원한다 => 2개를 만든다.

 

- Manager에 등록시킬 Auth Provider을 구현

인증을 처리해야 될 객체가 분리될 필요가 있을때.

인증 처리되는 과정이 다른 인증과 달라서 다른 클래스의 기능을 가져야 할때

프로바이더를 새로 등록한다. Manager에 얼마든지 담을 수 있다.

- AJAX 방식이라면, 인증 정보를 담을 DTO

- 각 인증에 따른 추가 구현체. 기본적으로 성공/실패 핸들러.

​인증이 성공했을 때 어떤 RES에 인증정보 DTO를 보내주거나

실패했을때 실패과정에서 튀어나오는 EXCEPTION을 매핑해서 DTO로 돌려주는 핸들러 필요

 

- 소셜 인증의 경우 각 소셜 공급자의 규격에 맞는 DTO와 HTTP req 객체. (RestTemplate?)

- 인증 시도 / 인증 성공시에 각각 사용할 ​Authentication 객체 (이건 선택이다)

 ==> 지금까지는 스프링 시큐리티 자체에 대한 FLOW에 대해 설명했다.

 

Implicit Grant Flow

프론트엔드 개발의 대세는 Implicit grant flow~ 이다 .

각 소셜공급자들이 제공하는 js sdk를 이용해서 프론트단에서 인증을 받고

이 인증결과물을 서버로 보내서 다시 토큰을 발급받는다.

1차로 firebase에서 인증받은 토큰을 서버로 보내서

서버에서 firebase로의 인증결과를

우리만의 JWT 토큰을 만들어서 클라이언트에 내려준다.

6번부터는 FRONT와  BACK의 통신이 된다.

 

 

우리가 만들어준 토큰을 가지고  앞으로 REST API 콜이 발생할 때마다 헤더의 Authorization 필드에 넣어서 토큰을 보내준다.

그러면 토큰을 해독만 하면 된다.

전혀 디비에 오버헤더가 발생하지 않고 firebase에 contact할 이유가 없다.

네트워크 비용, 디비 조회 비용을 아낄 수 있다.

 

우리가 Spring Security Auth0 JWT Library를 사용하면 좋은 점은,,,

기본적으로 모든 요청이 백엔드 서버쪽에서 나가기 때문에

사용자가 로그인 요청을 할때마다 백엔드 서버쪽가 구글, 페이스북의 o auth 공급자에게 갔다가 와야 한다.

사용자가 많은 서비스에서는 문제가 될 수 있다.

이런 귀찮은 작업을 프론트 단에서 맡기고 우리는 jwt만 만들면 된다.

Implicit grant flow

 

유저 정보 저장을 위한 DB설계

 

유저 객체 설계

- 유저 인증을 위해 필요한 정보, 서비스 제공을 위한 정보를 필요한 만큼만 저장한다.

- 비밀번호를 비롯한 민감한 정보는 암호화하는 것이 원칙(BCcryptPasswordEncoder)

- 소셜 회원도 담을 수 있게 확장성있게 구현한다.

- @ElementCollection, Enum 등을 활용하자.

 

라이브 코딩

 

 

 


봄이네집 개발 블로그
https://tech.wheejuni.com/2018/06/04/spring-questions/
https://github.com/wheejuni/spring-jwt

 

스프링에 관한 질문들

스프링에 관한 질문들기술 면접을 2주 앞두고 면접에 필요한 내용들을 정리하며 “…질문들” 시리즈를 연재해본다. 스프링의 구조, 요청 / 응답 스프링은 어떻게 요청을 처리하는가? 요청을 처

tech.wheejuni.com

 

wheejuni/spring-jwt

봄이네집 스프링 - (1) Spring Security - Auth0 JWT Library 방송을 위한 저장소 - wheejuni/spring-jwt

github.com