개요
현대 웹 애플리케이션에서는 사용자가 한 번 로그인한 후에는 요청마다 다시 로그인하지 않고도 인증 상태를 유지해야 한다. 이를 구현하는 대표적인 방법으로 세션(Session) 기반 인증, JWT(Json Web Token) 기반 인증, OAuth 2.0 기반 인증(제3자 인증 위임)이 있다. 각각의 개념과 동작 흐름, 장단점, 보안 고려사항을 살펴보고 간단한 코드 예시를 통해 이해해보자.
세션 기반 인증 (Session-Based Authentication)
개념
세션 기반 인증은 서버가 사용자 인증 정보를 직접 저장하고, 클라이언트(브라우저)는 이 세션에 대응하는 세션 ID를 쿠키로 가지고 있는 방식이다. 브라우저가 요청을 보낼 때마다 쿠키에 담긴 세션 ID를 보내주면, 서버는 해당 세션 ID에 매핑된 사용자 정보를 확인하고 인증을 판단한다.
동작 흐름 예시
- 사용자가 로그인 요청(아이디/비밀번호)을 서버로 보낸다.
- 서버는 인증에 성공하면 세션을 생성하고, 그 세션에 사용자 정보를 저장한다. 그리고 세션 ID를 쿠키로 내려보낸다.
- 클라이언트(브라우저)는 쿠키를 저장하고, 이후 요청마다 쿠키(세션 ID)를 포함해 전송한다.
- 서버는 세션 ID를 보고, 해당 세션에 연결된 사용자 정보를 확인한다. 사용자 정보가 있으면 인증 성공 처리를 하고 없다면 인증 실패 처리한다.
- 로그아웃 시에는 서버 측 세션을 삭제해서 인증이 만료되도록 한다.


장점
- 단순하고 이해하기 쉽다. 전통적인 방식이라서 이미 많은 웹 프레임워크에 구현이 되어 있다.
- 서버가 사용자의 상태를 직접 관리한다. 로그아웃 처리나 세션 무효화 등을 쉽게 할 수 있다.
- 쿠키에 세션 ID만 담으면 되므로 민감 정보가 직접 노출될 위험이 적다. (대신 세션 ID가 유출되면 위험)
단점
- 서버 확장성이 떨어진다. 세션 정보를 서버가 가지고 있어야 하므로 서버가 여러 대일 때 세션 동기화 작업(예: Redis)에 신경써야 한다.
- 쿠키 기반 보안 이슈(세션 하이재킹, CSRF 등)을 고려해야 한다.
- 모바일 앱, 외부 클라이언트와 통신할 때는 쿠키를 쓰기 불편할 수 있다.
코드 예시
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// CSRF를 REST API에서 비활성화 (토큰 기반 CSRF 방어 등 별도 구현 가능)
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
// 로그인 API는 인증 없이 접근 가능
.requestMatchers("/api/login").permitAll()
.anyRequest().authenticated()
)
// 폼 로그인 사용 안 함 (formLogin 제거)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
);
return http.build();
}
}
- SessionCreationPolicy.IF_REQUIRED로 설정하면, 인증이 필요한 요청에서 로그인 성공 시 세션을 생성하게 된다.
@RestController
@RequestMapping("/api")
public class SessionAuthController {
// 실제로는 UserDetailsService나 PasswordEncoder 등을 통해 검증
@PostMapping("/login")
public ResponseEntity<?> login(@RequestParam String username,
@RequestParam String password,
HttpServletRequest request) {
// 예시: username=test, password=1234 인 경우만 인증 성공
if ("test".equals(username) && "1234".equals(password)) {
// 세션 생성 (스프링 시큐리티 컨텍스트를 채워야 진짜 인증됨)
// 하지만 여기서는 간단히만 세션에 사용자 정보를 저장해보자
request.getSession().setAttribute("USER", username);
return ResponseEntity.ok("로그인 성공");
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("로그인 실패");
}
}
@GetMapping("/userinfo")
public ResponseEntity<?> userInfo(HttpServletRequest request) {
String user = (String) request.getSession().getAttribute("USER");
if (user != null) {
return ResponseEntity.ok("현재 로그인 사용자: " + user);
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("로그인이 필요합니다");
}
}
@PostMapping("/logout")
public ResponseEntity<?> logout(HttpServletRequest request) {
request.getSession().invalidate();
return ResponseEntity.ok("로그아웃 완료");
}
}
JWT(Json Web Token) 기반 인증
개념
JWT 방식은 토큰에 사용자 정보를 담고, 클라이언트에게 토큰을 주는 방식이다. 서버는 별도의 세션 정보를 가지지 않고, 클라이언트가 요청할 때마다 전달되는 토큰을 검증해서 인증 여부를 확인한다.
동작 흐름 예시
- 사용자가 아이디/비밀번호로 로그인 요청을 보낸다.
- 서버는 사용자 인증에 성공하면 JWT를 발급한다. JWT에는 만료 시간, 사용자 ID 같은 정보를 담고, 서버 비밀키로 서명한다.
- 서버는 JWT를 JSON 응답으로 클라이언트에게 전달한다.
- 클라이언트는 이 JWT를 localStorage나 쿠키에 저장한다(적절한 보안 정책 하에).
- 이후 보호가 필요한 API를 호출할 때, Authorization: Bearer <JWT> 헤더로 토큰을 보내보낸다.
- 서버는 해당 토큰이 유효한지(서명, 만료 여부)를 검증한 뒤, 유효하면 요청을 처리한다.

장점
- 서버 측에서 상태를 보관하지 않아도 된다. 확장성이 좋아지고, 분산 환경에서 유리하다.
- 다양한 클라이언트(웹, 앱 등)에서 통합적으로 사용하기 쉽다.
- 토큰 구조가 표준화되어 있어, 권한이나 사용자 정보를 함께 담을 수 있다.
단점
- 토큰 철회가 어렵다. 한 번 발급한 토큰은 서버가 별도 관리를 안하므로, 만료 전까지 임의로 무효화하기 어렵다.
- 토큰 탈취에 매우 취약하다. 만약 토큰이 노출되면 만료될 때까지 악용될 수 있다.
- 토큰이 길어질 수 있어 트래픽 부담이 조금 늘어난다.
보안 고려사항
- 짧은 만료 시간(exp)을 설정해보자. 토큰이 탈취되어도 유효 시간이 짧으면 피해를 줄일 수 있다.
- 민감 정보를 토큰에 직접 담지 말자(누구나 디코딩이 가능하므로).
- 토큰을 보관할 때 XSS/CSRF 공격에 대비해보자. (HttpOnly 쿠키 vs localStorage 논쟁이 있으니 상황에 맞게)
코드 예시
@RestController
@RequestMapping("/api")
public class AuthController {
private final JwtProvider jwtProvider; // JWT 생성/검증 로직을 담은 별도 클래스
public AuthController(JwtProvider jwtProvider) {
this.jwtProvider = jwtProvider;
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
// TODO: 아이디/비밀번호 검증 로직 (UserDetailsService 등 이용)
boolean isValidUser = (request.getUsername().equals("test")
&& request.getPassword().equals("1234"));
if (!isValidUser) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("로그인 실패");
}
// 로그인 성공 -> JWT 발급
String token = jwtProvider.generateToken(request.getUsername());
return ResponseEntity.ok(new TokenResponse(token));
}
}
// DTO 예시
class LoginRequest {
private String username;
private String password;
// getters, setters...
}
class TokenResponse {
private String token;
public TokenResponse(String token) { this.token = token; }
// getter...
}
모든 요청 전에 JWT를 검증하기 위해, 스프링 시큐리티에서는 필터를 만든 후 시큐리티 필터 체인에 등록해볼 수 있다.
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtProvider jwtProvider;
public JwtAuthenticationFilter(JwtProvider jwtProvider) {
this.jwtProvider = jwtProvider;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
// 토큰 유효성 검증
if (jwtProvider.validateToken(token)) {
// 토큰에서 사용자 정보 추출
String username = jwtProvider.getUsernameFromToken(token);
// 스프링 시큐리티 인증 객체 생성
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null,
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")));
// 컨텍스트에 인증 정보 저장
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtFilter;
public SecurityConfig(JwtAuthenticationFilter jwtFilter) {
this.jwtFilter = jwtFilter;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // JWT 사용 시 세션을 안 쓰므로 CSRF 비활성화가 일반적
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/login").permitAll()
.anyRequest().authenticated()
);
// 커스텀 JWT 필터를 UsernamePasswordAuthenticationFilter 이전에 삽입
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
OAuth 2.0 인증 방식
개념
OAuth 2.0은 제3자 서비스(구글, 페이스북 등)를 통해 인증을 대신 받고, 그 결과(액세스 토큰, 사용자 정보)를 우리 애플리케이션이 넘겨받는 방식이다. 소셜 로그인이라고도 부른다.
동작 흐름 예시 (Authorization Code Grant)
- 사용자가 "구글 로그인" 버튼을 누르면, 구글 OAuth 서버로 리다이렉트한다.
- 구글 로그인/동의 과정을 거쳐서 인가 코드(Authorization Code)가 발급된다.
- 우리 서버는 이 인가 코드를 가지고 구글에 "액세스 토큰"을 요청한다.
- 구글이 액세스 토큰을 발급해주면, 이 토큰으로 구글 사용자 정보를 가져온다.
- 우리 서버는 가져온 사용자 정보(이메일 등)와 매핑해서 자체적으로 로그인 처리를 한다(세션 생성 혹은 JWT 발급).
장점
- 사용자 편의성: 별도 회원가입 없이 소셜 계정을 활용할 수 있다.
- 보안 이점: 비밀번호를 우리 서버가 직접 관리하지 않아도 된다.
- 범용성: 구글, 페이스북, 깃허브, 카카오, 네이버 등 다양한 OAuth 제공자가 있다.
단점
- 구현이 복잡하다. 인가 코드 교환, 토큰 발급, 사용자 정보 조회 로직이 필요하다.
- 외부 서비스 의존: 해당 OAuth 제공자가 장애가 나면 로그인에 영향을 받는다.
- 동의 화면 진입 장벽: 사용자가 소셜 로그인 시 권한 동의를 거부하면 로그인 실패.
보안 고려사항
- Redirect URI 검증을 철저히 해보자.
- state 파라미터로 CSRF 방어를 하자.
- PKCE(Proof Key for Code Exchange)도 적극 고려해보자(특히 SPA나 모바일 앱).
코드 예시
spring:
security:
oauth2:
client:
registration:
github:
client-id: YOUR_GITHUB_CLIENT_ID
client-secret: YOUR_GITHUB_CLIENT_SECRET
scope: user:email
redirect-uri: "{baseUrl}/login/oauth2/code/github"
client-name: GitHub
provider:
github:
authorization-uri: https://github.com/login/oauth/authorize
token-uri: https://github.com/login/oauth/access_token
user-info-uri: https://api.github.com/user
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth -> oauth
.loginPage("/login") // 커스텀 로그인 페이지
.defaultSuccessUrl("/home") // 로그인 성공 후 이동
)
.logout(logout -> logout
.logoutSuccessUrl("/").permitAll()
);
return http.build();
}
}
참고 자료
모든 개발자를 위한 HTTP 웹 기본 지식 강의 | 김영한 - 인프런
김영한 | , [사진] 📣 확인해주세요!본 강의는 자바 스프링 완전 정복 시리즈의 세 번째 강의입니다. 우아한형제들 최연소 기술이사 김영한의 스프링 완전 정복 로드맵을 먼저 확인해주세요. (바
www.inflearn.com
JWT tokens and security - working principles and use cases
JWT tokens are legion on modern web applications. It is there important to clearly understand their impact in terms of security, before using them.
www.vaadata.com
'컴퓨터 공학' 카테고리의 다른 글
(코딩 컨벤션) Predicate Methods에서 'should', 'must', 'need' 는 어떻게 써야 좋을지에 대한 고찰 (0) | 2024.11.24 |
---|---|
(컴퓨터 공학) Java로 보는 메모리 구조 (3) | 2024.09.15 |
개요
현대 웹 애플리케이션에서는 사용자가 한 번 로그인한 후에는 요청마다 다시 로그인하지 않고도 인증 상태를 유지해야 한다. 이를 구현하는 대표적인 방법으로 세션(Session) 기반 인증, JWT(Json Web Token) 기반 인증, OAuth 2.0 기반 인증(제3자 인증 위임)이 있다. 각각의 개념과 동작 흐름, 장단점, 보안 고려사항을 살펴보고 간단한 코드 예시를 통해 이해해보자.
세션 기반 인증 (Session-Based Authentication)
개념
세션 기반 인증은 서버가 사용자 인증 정보를 직접 저장하고, 클라이언트(브라우저)는 이 세션에 대응하는 세션 ID를 쿠키로 가지고 있는 방식이다. 브라우저가 요청을 보낼 때마다 쿠키에 담긴 세션 ID를 보내주면, 서버는 해당 세션 ID에 매핑된 사용자 정보를 확인하고 인증을 판단한다.
동작 흐름 예시
- 사용자가 로그인 요청(아이디/비밀번호)을 서버로 보낸다.
- 서버는 인증에 성공하면 세션을 생성하고, 그 세션에 사용자 정보를 저장한다. 그리고 세션 ID를 쿠키로 내려보낸다.
- 클라이언트(브라우저)는 쿠키를 저장하고, 이후 요청마다 쿠키(세션 ID)를 포함해 전송한다.
- 서버는 세션 ID를 보고, 해당 세션에 연결된 사용자 정보를 확인한다. 사용자 정보가 있으면 인증 성공 처리를 하고 없다면 인증 실패 처리한다.
- 로그아웃 시에는 서버 측 세션을 삭제해서 인증이 만료되도록 한다.


장점
- 단순하고 이해하기 쉽다. 전통적인 방식이라서 이미 많은 웹 프레임워크에 구현이 되어 있다.
- 서버가 사용자의 상태를 직접 관리한다. 로그아웃 처리나 세션 무효화 등을 쉽게 할 수 있다.
- 쿠키에 세션 ID만 담으면 되므로 민감 정보가 직접 노출될 위험이 적다. (대신 세션 ID가 유출되면 위험)
단점
- 서버 확장성이 떨어진다. 세션 정보를 서버가 가지고 있어야 하므로 서버가 여러 대일 때 세션 동기화 작업(예: Redis)에 신경써야 한다.
- 쿠키 기반 보안 이슈(세션 하이재킹, CSRF 등)을 고려해야 한다.
- 모바일 앱, 외부 클라이언트와 통신할 때는 쿠키를 쓰기 불편할 수 있다.
코드 예시
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// CSRF를 REST API에서 비활성화 (토큰 기반 CSRF 방어 등 별도 구현 가능)
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
// 로그인 API는 인증 없이 접근 가능
.requestMatchers("/api/login").permitAll()
.anyRequest().authenticated()
)
// 폼 로그인 사용 안 함 (formLogin 제거)
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
);
return http.build();
}
}
- SessionCreationPolicy.IF_REQUIRED로 설정하면, 인증이 필요한 요청에서 로그인 성공 시 세션을 생성하게 된다.
@RestController
@RequestMapping("/api")
public class SessionAuthController {
// 실제로는 UserDetailsService나 PasswordEncoder 등을 통해 검증
@PostMapping("/login")
public ResponseEntity<?> login(@RequestParam String username,
@RequestParam String password,
HttpServletRequest request) {
// 예시: username=test, password=1234 인 경우만 인증 성공
if ("test".equals(username) && "1234".equals(password)) {
// 세션 생성 (스프링 시큐리티 컨텍스트를 채워야 진짜 인증됨)
// 하지만 여기서는 간단히만 세션에 사용자 정보를 저장해보자
request.getSession().setAttribute("USER", username);
return ResponseEntity.ok("로그인 성공");
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("로그인 실패");
}
}
@GetMapping("/userinfo")
public ResponseEntity<?> userInfo(HttpServletRequest request) {
String user = (String) request.getSession().getAttribute("USER");
if (user != null) {
return ResponseEntity.ok("현재 로그인 사용자: " + user);
} else {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("로그인이 필요합니다");
}
}
@PostMapping("/logout")
public ResponseEntity<?> logout(HttpServletRequest request) {
request.getSession().invalidate();
return ResponseEntity.ok("로그아웃 완료");
}
}
JWT(Json Web Token) 기반 인증
개념
JWT 방식은 토큰에 사용자 정보를 담고, 클라이언트에게 토큰을 주는 방식이다. 서버는 별도의 세션 정보를 가지지 않고, 클라이언트가 요청할 때마다 전달되는 토큰을 검증해서 인증 여부를 확인한다.
동작 흐름 예시
- 사용자가 아이디/비밀번호로 로그인 요청을 보낸다.
- 서버는 사용자 인증에 성공하면 JWT를 발급한다. JWT에는 만료 시간, 사용자 ID 같은 정보를 담고, 서버 비밀키로 서명한다.
- 서버는 JWT를 JSON 응답으로 클라이언트에게 전달한다.
- 클라이언트는 이 JWT를 localStorage나 쿠키에 저장한다(적절한 보안 정책 하에).
- 이후 보호가 필요한 API를 호출할 때, Authorization: Bearer <JWT> 헤더로 토큰을 보내보낸다.
- 서버는 해당 토큰이 유효한지(서명, 만료 여부)를 검증한 뒤, 유효하면 요청을 처리한다.

장점
- 서버 측에서 상태를 보관하지 않아도 된다. 확장성이 좋아지고, 분산 환경에서 유리하다.
- 다양한 클라이언트(웹, 앱 등)에서 통합적으로 사용하기 쉽다.
- 토큰 구조가 표준화되어 있어, 권한이나 사용자 정보를 함께 담을 수 있다.
단점
- 토큰 철회가 어렵다. 한 번 발급한 토큰은 서버가 별도 관리를 안하므로, 만료 전까지 임의로 무효화하기 어렵다.
- 토큰 탈취에 매우 취약하다. 만약 토큰이 노출되면 만료될 때까지 악용될 수 있다.
- 토큰이 길어질 수 있어 트래픽 부담이 조금 늘어난다.
보안 고려사항
- 짧은 만료 시간(exp)을 설정해보자. 토큰이 탈취되어도 유효 시간이 짧으면 피해를 줄일 수 있다.
- 민감 정보를 토큰에 직접 담지 말자(누구나 디코딩이 가능하므로).
- 토큰을 보관할 때 XSS/CSRF 공격에 대비해보자. (HttpOnly 쿠키 vs localStorage 논쟁이 있으니 상황에 맞게)
코드 예시
@RestController
@RequestMapping("/api")
public class AuthController {
private final JwtProvider jwtProvider; // JWT 생성/검증 로직을 담은 별도 클래스
public AuthController(JwtProvider jwtProvider) {
this.jwtProvider = jwtProvider;
}
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest request) {
// TODO: 아이디/비밀번호 검증 로직 (UserDetailsService 등 이용)
boolean isValidUser = (request.getUsername().equals("test")
&& request.getPassword().equals("1234"));
if (!isValidUser) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("로그인 실패");
}
// 로그인 성공 -> JWT 발급
String token = jwtProvider.generateToken(request.getUsername());
return ResponseEntity.ok(new TokenResponse(token));
}
}
// DTO 예시
class LoginRequest {
private String username;
private String password;
// getters, setters...
}
class TokenResponse {
private String token;
public TokenResponse(String token) { this.token = token; }
// getter...
}
모든 요청 전에 JWT를 검증하기 위해, 스프링 시큐리티에서는 필터를 만든 후 시큐리티 필터 체인에 등록해볼 수 있다.
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final JwtProvider jwtProvider;
public JwtAuthenticationFilter(JwtProvider jwtProvider) {
this.jwtProvider = jwtProvider;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
// 토큰 유효성 검증
if (jwtProvider.validateToken(token)) {
// 토큰에서 사용자 정보 추출
String username = jwtProvider.getUsernameFromToken(token);
// 스프링 시큐리티 인증 객체 생성
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(username, null,
Collections.singleton(new SimpleGrantedAuthority("ROLE_USER")));
// 컨텍스트에 인증 정보 저장
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
filterChain.doFilter(request, response);
}
}
@Configuration
@EnableWebSecurity
public class SecurityConfig {
private final JwtAuthenticationFilter jwtFilter;
public SecurityConfig(JwtAuthenticationFilter jwtFilter) {
this.jwtFilter = jwtFilter;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // JWT 사용 시 세션을 안 쓰므로 CSRF 비활성화가 일반적
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/login").permitAll()
.anyRequest().authenticated()
);
// 커스텀 JWT 필터를 UsernamePasswordAuthenticationFilter 이전에 삽입
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
OAuth 2.0 인증 방식
개념
OAuth 2.0은 제3자 서비스(구글, 페이스북 등)를 통해 인증을 대신 받고, 그 결과(액세스 토큰, 사용자 정보)를 우리 애플리케이션이 넘겨받는 방식이다. 소셜 로그인이라고도 부른다.
동작 흐름 예시 (Authorization Code Grant)
- 사용자가 "구글 로그인" 버튼을 누르면, 구글 OAuth 서버로 리다이렉트한다.
- 구글 로그인/동의 과정을 거쳐서 인가 코드(Authorization Code)가 발급된다.
- 우리 서버는 이 인가 코드를 가지고 구글에 "액세스 토큰"을 요청한다.
- 구글이 액세스 토큰을 발급해주면, 이 토큰으로 구글 사용자 정보를 가져온다.
- 우리 서버는 가져온 사용자 정보(이메일 등)와 매핑해서 자체적으로 로그인 처리를 한다(세션 생성 혹은 JWT 발급).
장점
- 사용자 편의성: 별도 회원가입 없이 소셜 계정을 활용할 수 있다.
- 보안 이점: 비밀번호를 우리 서버가 직접 관리하지 않아도 된다.
- 범용성: 구글, 페이스북, 깃허브, 카카오, 네이버 등 다양한 OAuth 제공자가 있다.
단점
- 구현이 복잡하다. 인가 코드 교환, 토큰 발급, 사용자 정보 조회 로직이 필요하다.
- 외부 서비스 의존: 해당 OAuth 제공자가 장애가 나면 로그인에 영향을 받는다.
- 동의 화면 진입 장벽: 사용자가 소셜 로그인 시 권한 동의를 거부하면 로그인 실패.
보안 고려사항
- Redirect URI 검증을 철저히 해보자.
- state 파라미터로 CSRF 방어를 하자.
- PKCE(Proof Key for Code Exchange)도 적극 고려해보자(특히 SPA나 모바일 앱).
코드 예시
spring:
security:
oauth2:
client:
registration:
github:
client-id: YOUR_GITHUB_CLIENT_ID
client-secret: YOUR_GITHUB_CLIENT_SECRET
scope: user:email
redirect-uri: "{baseUrl}/login/oauth2/code/github"
client-name: GitHub
provider:
github:
authorization-uri: https://github.com/login/oauth/authorize
token-uri: https://github.com/login/oauth/access_token
user-info-uri: https://api.github.com/user
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/login").permitAll()
.anyRequest().authenticated()
)
.oauth2Login(oauth -> oauth
.loginPage("/login") // 커스텀 로그인 페이지
.defaultSuccessUrl("/home") // 로그인 성공 후 이동
)
.logout(logout -> logout
.logoutSuccessUrl("/").permitAll()
);
return http.build();
}
}
참고 자료
모든 개발자를 위한 HTTP 웹 기본 지식 강의 | 김영한 - 인프런
김영한 | , [사진] 📣 확인해주세요!본 강의는 자바 스프링 완전 정복 시리즈의 세 번째 강의입니다. 우아한형제들 최연소 기술이사 김영한의 스프링 완전 정복 로드맵을 먼저 확인해주세요. (바
www.inflearn.com
JWT tokens and security - working principles and use cases
JWT tokens are legion on modern web applications. It is there important to clearly understand their impact in terms of security, before using them.
www.vaadata.com
'컴퓨터 공학' 카테고리의 다른 글
(코딩 컨벤션) Predicate Methods에서 'should', 'must', 'need' 는 어떻게 써야 좋을지에 대한 고찰 (0) | 2024.11.24 |
---|---|
(컴퓨터 공학) Java로 보는 메모리 구조 (3) | 2024.09.15 |