| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- securitycontextholderfilter
- 지연 로딩
- @ComponentScan
- 서블릿 컨테이너
- DI
- kafka
- AWS
- DLQ
- dockerhub
- MSA
- Spring Data JPA
- JWT
- JdbcTemplate
- 스프링 부트
- docker compose
- Spring
- docker
- Spring Container
- CORS
- 쿠버네티스
- Web
- @Transactional
- Dead Letter Queue
- JPA
- mybatis
- Routing Key
- 페이징
- 컨테이너
- redis
- JPQL
- Today
- Total
look-forest
스프링 OAuth2 Client (JWT 방식, CSR/SPA 구조) 본문
스프링 OAuth2 Client (JWT 방식, CSR/SPA 구조)
studyHub 2024. 11. 16. 16:04OAuth2.0 클라이언트와 스프링 시큐리티 6 프레임워크를 활용하여,
신뢰할 수 있는 외부 사이트(구글, 네이버)로 부터 인증을 받고,
전달 받은 유저 데이터를 활용하여 JWT를 발급하고 인가를 진행하는 방법.
※ 프로젝트 컨셉
- OAuth2 클라이언트 JWT방식을 구현하기 위한 가장 기본적인 뼈대 코드
- 프론트/백엔드 서버가 나눠진 상황 가정
- OAuth2.0 코드 방식 인증을 활용
- 인증 후 발급된 정보로 JWT를 발급, JWT는 단일 토큰으로 진행
JWT 방식에서 OAuth2 클라이언트 구성시 고민점
JWT 방식에서는 로그인(인증)이 성공하면 JWT 발급 문제와 웹/하이브리드/네이티브앱별 특징에 의해 OAuth2 Code Grant 방식 동작의 책임을 프론트엔드 측에 둘 것인지 백엔드 측에 둘 것인지 결정해야 한다.
1. 프론트가 책임을 담당하는 경우 (앱)
프론트단에서 로그인 → 코드 발급 → Access 토큰 → 유저 정보 획득 과정을 모두 수행한 뒤,
백엔드단에서 유저 정보 → JWT 발급 방식으로 주로 네이티브앱에서 사용하는 방식. (앱에서 쿠키를 다루기가 힘드므로)
→ 프론트에서 보낸 유저 정보의 진위 여부를 따지기 위해 추가적인 보안 로직이 필요
2. 프론트와 백엔드가 나눠 담당하는 경우 (잘못된 방식)
프론트단에서 로그인 → 코드 발급 후 코드를 백엔드로 전송, (혹은 Access 토큰까지 프론트에서 발급)
백엔드단에서 코드 → 토큰 발급 → 유저 정보 획득 → JWT 발급
→ CODE/Access 토큰을 전송하는 방법은 지양해야 한다. (네트워크 스니핑 등 보안상 위험)
3. 백엔드가 책임을 담당하는 경우 (웹)
프론트단에서 백엔드의 OAuth2 로그인 경로로 하이퍼링킹을 진행 후
백엔드단에서 로그인 페이지 요청 → 코드 발급 → Access 토큰 → 유저 정보 획득 → JWT 발급 방식으로 주로 웹앱/모바일앱 통합 환경 서버에서 사용하는 방식.
→ 백엔드에서 JWT를 발급하는 방식의 고민과 프론트측에서 받는 로직을 처리해야 한다.

동작 모식도

각각의 필터가 동작하는 주소
- JWTFilter : 모든 주소에서 동작
- OAuth2AuthorizationRequestRedirectFilter : /oauth2/authorization/서비스명 (관습)
- OAuth2LoginAuthenticationFilter : /login/oauth2/code/서비스명 (관습)
구현해야 할 부분
- OAuth2 클라이언트 커스터마이징
- OAuth2UserDetailsService
- OAuth2UserDetails
- LoginSuccessHandler
- JWT 관련
- JWTFilter
- JWTUtil : JWT를 발급 및 검증하는 클래스
구현
사전 준비 사항
- 기본 SecurityConfig 설정
- 소셜 로그인 신청
- OAuth2 소셜 로그인을 위한 변수 설정 (application.properties)
- JWT 발급, 검증 Util 클래스 생성
1. 응답 데이터 처리 - OAuth2UserService 구현 및 등록
서비스 별 응답받은 사용자 정보를 처리하고, 롤 값을 부여한 후 응답. DB에 저장하는 과정 포함.
- 응답 데이터 포맷이 서비스 마다 다르므로, 획일화해서 사용할 수 있도록 인터페이스 타입을 만들어 처리
*세션 방식과 동일
2. 로그인 성공 시 JWT 응답
OAuth2 로그인이 성공하면 실행될 성공 핸들러를 커스텀해서 내부에 JWT 발급 구현을 진행.
프론트측에 JWT를 전달하는 방법으로는 쿠키를 이용한다.
Spring Security JWT 방식에서는 header에 JWT 발급 토큰을 응답하지만, 헤더로 발급 진행하면 하이퍼 링크로 받을 수 없다.
브라우저는 하이퍼링크 요청에 대해 응답 헤더를 개발자가 직접 확인하거나 사용할 수 없도록 설계되어 있다. location.href는 페이지 이동이나 리디렉션을 위한 목적으로 사용되며, 응답 본문은 렌더링되지만 응답 헤더를 자바스크립트로 읽을 방법은 없다.
보안 및 사용자 경험 보호 때문이다.
- 보안 유지
응답 헤더는 서버와 브라우저 간에 민감한 정보(예: 인증 토큰, 서버 설정 등)를 포함할 수 있다. 이러한 정보를 브라우저가 제한 없이 노출한다면 악의적인 스크립트가 이를 탈취할 위험이 있다. 이를 방지하기 위해 브라우저는 하이퍼링크 요청의 응답 헤더에 대한 접근을 차단한다. - 기능의 목적 차이
하이퍼링크(location.href)는 사용자가 페이지를 이동하거나 콘텐츠를 렌더링하는 것이 주목적이다. 이에 반해 API 요청은 데이터 전송과 처리를 목적으로 설계되었다. 이런 차이를 유지하기 위해, 하이퍼링크 요청에서는 헤더에 접근할 수 없게 하고 API 클라이언트를 통해 명시적으로 데이터를 처리하도록 권장한다. - CORS 및 안전한 데이터 흐름
Cross-Origin Resource Sharing(CORS)은 브라우저가 서로 다른 도메인 간의 데이터 교환을 안전하게 관리하는 기술이다. 응답 헤더를 제한하면 데이터가 의도치 않게 공유되지 않도록 제어할 수 있다
@Component
@RequiredArgsConstructor
public class CustomSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final JWTUtil jwtUtil;
//로그인이 성공하면 JWT 발급하여 쿠키로 프론트 측에 전달
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
CustomOAuth2User principal = (CustomOAuth2User) authentication.getPrincipal();
String username = principal.getUsername();
String role = principal.getAuthorities().iterator().next().getAuthority();
String token = jwtUtil.createJwt(username, role, 60 * 60 * 60L);
response.addCookie(createCookie("Authorization", token));
response.sendRedirect("http://localhost:3000/");
}
private Cookie createCookie(String key, String value) {
Cookie cookie = new Cookie(key, value);
cookie.setMaxAge(60 * 60 ^ 60);
//cookie.setSecure(true); //https 에서만 사용
cookie.setPath("/"); //전역에 쿠키가 보인다
cookie.setHttpOnly(true); //javascript 가 해당 쿠키를 가져가지 못하게
return cookie;
}
}
완료 후 SecurityConfig에 SuccessHandler 를 등록하면 된다.
http
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint(userInfoEndpointConfig -> userInfoEndpointConfig.userService(oAuth2UserService))
.successHandler(customSuccessHandler)
);
3. JWT 검증 필터 등록
요청에 담긴 JWT를 검증하기 위한 커스텀 필터를 만들어 시큐리티 filter chain에 등록해야 한다.
해당 필터를 통해 요청 쿠키에 JWT가 존재하는 경우 JWT를 검증하고 SecurityContextHolder에 Authentication을 생성
(이 세션은 STATELESS 상태로 관리되기 때문에 해당 요청이 끝나면 소멸된다)
@RequiredArgsConstructor
public class JWTFilter extends OncePerRequestFilter {
private final JWTUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
//JWT 에 해당하는 쿠키 찾기
Optional<Cookie> jwtCookie = Arrays.stream(request.getCookies())
.filter(c -> c.getName().equals("Authorization"))
.findFirst();
if (jwtCookie.isEmpty()) {
filterChain.doFilter(request, response);
return;
}
String jwt = jwtCookie.get().getValue();
if (jwtUtil.isExpired(jwt)) {
filterChain.doFilter(request, response);
return;
}
UserDTO userDTO = UserDTO.builder()
.username(jwtUtil.getUsername(jwt))
.role(jwtUtil.getRole(jwt))
.build();
//UserDetails 에 회원 정보 객체 담기
CustomOAuth2User customOAuth2User = new CustomOAuth2User(userDTO);
//스프링 시큐리티 인증 토큰 생성
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(customOAuth2User, null, customOAuth2User.getAuthorities());
//세션에 사용자 등록
SecurityContextHolder.getContext().setAuthentication(authToken);
filterChain.doFilter(request, response);
}
}
완료 후 SecurityConfig에 해당 필터를 등록하면 된다.
//JWTFilter 추가
http
.addFilterBefore(new JWTFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class);
추가 설정
- CORS 설정 : Security Config와 MVC Config
참고 자료 & 이미지 출처
https://www.devyummi.com/page?id=669296ed4b5dc5675c8737be
개발자 유미 | 커뮤니티
www.devyummi.com
'Spring > Spring Security (feat. JWT, OAuth2)' 카테고리의 다른 글
| 스프링 OAuth2 인가/리소스 서버 (1) | 2024.11.17 |
|---|---|
| 스프링 OAuth2 Client (세션 방식, SSR 구조) (0) | 2024.11.14 |
| 스프링 JWT 심화 (0) | 2024.11.14 |
| 스프링 시큐리티 JWT (0) | 2024.11.14 |
| 기타 : 메소드 시큐리티, 연동(웹 MVC, 타임리프, 스프링 데이터 JPA) (1) | 2024.11.10 |