소개
게시글
질문&답변
2021.03.10
안녕하세요. 선생님 강의를 보고 현재 OAuth2 + JWT 인증 인가를 구현 중인데... 질문을 드려도 될까요?
정말 감사합니다. 아직 테스트는 더 해봐야겠지만, 알려주시고 조언 해주신 것들을 바탕으로 드디어 구현에 성공 했습니다. 구현에는 성공 하였지만 시행 착오를 겪었던 과정에서 이해하지 못한 것들이 있어 몇 가지 질문을 드리고 싶습니다. (아마도 마지막 질문이 될거 같습니다.) 1. 앞선 질문에서 2번 질문에 대한 답변에서 AbstractAuthenticationToken을 상속하여 커스텀 토큰을 만드는 방법을 알려 주셨습니다. 그래서 다음과 같이 AuthenticationToken을 추가 했는데요. public class CustomJwtAuthenticationToken extends AbstractAuthenticationToken { private final OAuthAccount oAuthAccount; private final String jwtToken; @Builder private CustomJwtAuthenticationToken(OAuthAccount oAuthAccount, String jwtToken) { super(oAuthAccount.getAuthorities()); this.oAuthAccount = oAuthAccount; this.jwtToken = jwtToken; } @Override public Object getCredentials() { return jwtToken; } @Override public Object getPrincipal() { return oAuthAccount; }} 이를 커스텀 필터에서 UsernamePasswordAuthenticationToken 대신 생성 하고 SecurityContextHolder에 저장 하였는데, 요청 테스트를 해보니 StackoverflowError Exception이 발생 하였습니다. 이전 3번 질문에 대한 답변에 AuthenticationManager 는 부모 AuthenticationManager 객체를 가질 수 있고 재귀해서 적절한 AuthenticationProvider 를 찾는다고 되어 있습니다. 라는 내용이 있었습니다. SecurityConfig에 임의로 생성한 AuthenticationManager를 넣어 주었지만, 인증 과정에서 AuthenticationProvider를 찾지 못하여 재귀가 발생하여 생긴 예외라 추측을 하고 있는데, 이 것이 맞을까요? Custom 토큰이 아닌 기존에 있는 UsernamePasswordAuthenticationToken을 사용하면 해당 예외가 발생하지 않습니다. 2. JWT를 인증하는 커스텀 필터에서 OAuthAccount oAuthAccount = OAuthAccount.from(account);Authentication authentication = new UsernamePasswordAuthenticationToken(oAuthAccount, jwtToken, oAuthAccount.getAuthorities());SecurityContextHolder.getContext().setAuthentication(authentication); 위와 같이 UsernamePasswordAuthenticationToken에 credentials에 jwtToken을 넣어주고 있지만, 이전 처럼 이 부분을 비워두어도 문제가 생기지 않는데요. (값을 바꿔 보아도 정상 작동 하는 것을 확인할 수 있었습니다.) 이와 같이 토큰을 직접 생성하는 과정에서 credentials 필드가 어떤 의미를 가지는건지 알고 싶습니다. 3. 기존에는 커스텀 인증 필터가 BasicAuthenticationFilter를 상속하도록 구현을 했었는데, 해당 필터 안에서는 authenticationManager의 역할이 전혀 없다고 생각되어 OncePerRequestFilter를 상속 하도록 수정 해놓은 상태 입니다. 이 역시 코드가 문제 없이 작동을 하는데, 엄연히 인증 처리를 대신하는 필터이기 때문에 BasicAuthenticationFilter를 상속하도록 구현 하는게 맞는건지 판단에 어려운 점이 있습니다. BasicAuthenticationFilter가 OncePerRequestFilter를 상속하는 필터라는 것은 알고 있는데, 어떤 필터를 상속하도록 하는게 더 옳은 쪽일까요? 여기까지 아마도 마지막으로 드리는 질문이 될거 같습니다. 시큐리티가 공부할 자료도 충분하지 않고 공식 문서도 친절한편이 아니라 어려움이 많았는데요. 제공해주신 강의를 통해 적용을 시도해볼 수 있었고, 인증 / 인가의 개념을 다지는데에도 정말 많은 도움을 받았습니다. 다시 한번 감사를 드립니다. 여전히 어렵고 모르는게 많지만 이번 구현 과정에서 좀 더 학습이 된 느낌인데요. 프로젝트를 마무리 하면 남은 실전 강의도 끝까지 완강하고 수강평도 남기겠습니다 :) 도움 주셔서 정말 감사합니다.
- 0
- 6
- 753
질문&답변
2021.03.09
안녕하세요. 선생님 강의를 보고 현재 OAuth2 + JWT 인증 인가를 구현 중인데... 질문을 드려도 될까요?
선생님, 먼저 자세한 답변 해주셔서 정말 감사합니다 :) 알려주신 것을 바탕으로 코드를 분석하고 다시 수정하려 시도를 하고 있는데요. 잘 해결 되지 않는 부분들이 있어 부득이하게 추가 질문을 드리게 되었습니다. 먼저 시큐리티가 내부적으로 OAuth2LoginAuthenticationProvider를 통해 OAuth2LoginAuthenticationToken을 생성하여, 이를 SecurityContextHolder에 저장 하는 과정을 디버거를 통해 확인 하였습니다. 문제는 말씀 하신 것처럼 JWT 인증방식은 아래와 같은 설정으로 http.sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); 세션을 사용하지 않기 때문에, 인가 프로세스 이전에 SecurityContext에 Authentication을 별도로 저장해주어야 한다는 점입니다. 그래서 제가 처음에 시도 했던 방법은 UsernamePasswordAuthenticationToken을 수동으로 생성해서 SecurityContext에 넣어주는 방법이었습니다. 사실 이 방법을 택했던 이유는, OAuth2LoginAuthenticationToken에 대해 숙지하고 있지 못하기 때문이었던거 같습니다. 기존 인증 / 인가 처리 흐름에 문제는 없다고 말씀을 해주셨고, 제가 최종적으로 구현하고 싶은 것은 다음과 같습니다. 1. 다음과 같이 인증이 끝난 사용자가 BasicAuthenticationFilter 를 거쳐갈 때 (인가 처리 필터에 도달하기 이전에) @Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { AccountClaims accountClaims = jwtTokenProvider.parseJwtToken(request.getHeader(HttpHeaders.AUTHORIZATION)); Account account = accountRepository.findByEmail(accountClaims.getEmail()).orElseThrow(AccountNotFoundException::new); OAuthAccount oAuthAccount = OAuthAccount.from(account); Authentication authentication = new UsernamePasswordAuthenticationToken(oAuthAccount, null, oAuthAccount.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response);} JWT를 검증하고, 검증된 사용자라면 UserDetails 타입 혹은 OAuth2User 를 implement 한 객체(Authentication)를 생성하여 SecurityContextHolder에 저장한다. 2. 사용자에 대한 인가 처리를 한다.3. 인가 처리가 끝난 사용자가 컨트롤러에 도달하면, SecurityContextHolder에서 꺼내어 이후 처리를 진행한다. 입니다. 여기서 궁금한 점이 있습니다. 1. 처음 질문에서 설명 드린것과 같이 OAuth2로 사용자를 인증한 뒤 이후에는 JWT 토큰을 통해 인증 / 인가 처리를 하는 흐름 인데요.SecurityContext에 Authentication을 넣어주는 과정에서 OAuth2LoginAuthenticationToken이 아닌 UsernamePasswordAuthenticationToken을 사용해도 문제가 없을까요? 2. 만약 UsernamePasswordAuthenticationToken을 사용해도 괜찮다면, UserDetails 타입으로 넘기지 않고 OAuth2User 타입의 객체를 넘겨도 상관이 없을까요? (UserDetails로 생성하는 것이 더 간단할 듯 하지만, Password를 사용하지 않는다는 것이 걸리는 점 같습니다.)3. UsernamePasswordAuthenticationToken을 사용해도 괜찮을 경우, 여기서 궁금한 것이 AuthenticationManager의 역할 입니다. 기존 코드에 아래와 같이 시큐리티 설정이 되어 있습니다. @RequiredArgsConstructor@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { // ... @Override protected void configure(HttpSecurity http) throws Exception { // ... http.addFilterBefore(JwtAuthorizationFilter.of(authenticationManager(), accountRepository, jwtTokenProvider), UsernamePasswordAuthenticationFilter.class); // ... } @Override @Bean protected AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); }} BasicAuthenticationFilter를 extends 하려면 반드시 authenticationManager 를 생성자에 넣어주어야 하기 때문에 @Override@Beanprotected AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager();} 위의 코드를 추가 했던 것인데요. 선생님 강의를 통해 AuthenticationManager의 역할이 인증처리를 위임하는 인터페이스라 배웠었습니다.문제는 소셜 로그인이기 때문에 사용자가 OAuth를 통해 인증을 하는 과정에서 AuthenticationManager를 별도로 사용하지 않았는데, 이렇게 임의로 넣어 필터를 생성한 뒤 다음과 같이 @Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { AccountClaims accountClaims = jwtTokenProvider.parseJwtToken(request.getHeader(HttpHeaders.AUTHORIZATION)); Account account = accountRepository.findByEmail(accountClaims.getEmail()).orElseThrow(AccountNotFoundException::new); OAuthAccount oAuthAccount = OAuthAccount.from(account); Authentication authentication = new UsernamePasswordAuthenticationToken(oAuthAccount, null, oAuthAccount.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); chain.doFilter(request, response);} 구현해도 문제가 없을까요? 4. 만약 반드시 OAuth2LoginAuthenticationToken을 사용해야 한다면 public OAuth2LoginAuthenticationToken(ClientRegistration clientRegistration, OAuth2AuthorizationExchange authorizationExchange, OAuth2User principal, Collectionextends GrantedAuthority> authorities, OAuth2AccessToken accessToken) { this(clientRegistration, authorizationExchange, principal, authorities, accessToken, null);} 이 생성자를 통해 생성을 하는 듯 보이는데요. (OAuth2AccessToken이 있는 것으로 봐서는 OAuth Authorization 서버와 통신을 하는 과정에서 쓰이는 것 같기도 한데...) 이 것들을 필터에서 생성하고 활용할 수 있는 방법이 있는지 궁금합니다. 프로젝트를 시작하면서부터 시큐리티를 통한 OAuth2 + JWT 사용자 인증 / 인가에 대해 정말 많이 찾아봤습니다. 그런데 자료도 많이 없고 여전히 답을 찾지 못하고 있네요ㅠㅠ 시큐리티의 인증 / 인가 처리가 매우 견고하고 강력하다고 느끼고 있기 때문에 JWT를 적용하는 과정에서 꼭 사용 해보고 싶은데... 쉽지 않네요. 조언을 부탁 드립니다. 감사합니다.
- 0
- 6
- 753
질문&답변
2020.09.28
스프링 데이터 JPA와 JPQL을 함께 사용할 경우 Repository에 대해 질문 드립니다.
강의를 듣고 배우면서 머릿속에 항상 있던 의문이었는데 영한님 답변으로 많은 부분이 해소가 되었습니다. 말씀 해주신 방향으로 개인 프로젝트도 개선 해보겠습니다! 조언 해주셔서 감사합니다^^
- 0
- 2
- 341