1. 들어가기 전
새로운 프로젝트에 들어가게 되면서 Security와 JWT를 이용해 AccessToken, RefreshToken(미정)을 사용하여 로그인을 구현하는 작업을 맡게 되어 나의 프로젝트 회고를 작성해보기로 하였다,
2. 인증 vs 인가
🔑 인증(Authentication) vs 인가(Authorization)
- 인증 (Authentication)
👉 "너 누구야?"
사용자가 누구인지 신원을 확인하는 과정- 예: 아이디/비밀번호 로그인, OAuth 로그인, 생체인증 등
- 성공하면 Access Token 같은 인증 수단을 발급
- 인가 (Authorization)
👉 "너한테 이거 할 권한 있어?"
인증된 사용자가 특정 자원이나 기능을 사용할 수 있는지 권한을 확인하는 과정- 예: 일반 유저는 게시물 작성 가능, 관리자는 회원 강제 탈퇴 가능
➡️ 쉽게 말하면:
- 인증 = 신원 확인 (로그인)
- 인가 = 권한 확인 (권한 검증)
3. 내가 생각한 방법
강사님과 이야기 후, 생각해 본 방법을 정리해보았다.
로그인,비밀번호 -> 확인여부 + JWT 토큰 같이 보내기 -> 다시 한 번 더 요청
로그인 -> 아이디 비밀번호입력 -> 리액트가 이미 발급받은 토큰 여부 체크 -> (사전에 알고있는)클라이언트 id, secret 받은 후 db에서 일치 여부 확인 -> 맞으면 토큰 생성 -> 토큰 프론트로 전송 -> 토큰이랑 (자사 서비스의) 사용자 아이디 비밀번호 같이 보냄 -> 필터에서 토큰 생성
사용자는 API키를 사용하겠다고 한 사용자 서비스 (별도의 프로그램, 서비스 자체)
회원은 자사 서비스를 이용하는 사람들
기존 방식 : 사용자ID, 비밀번호 방식으로 secret id password를 설정하고, 해당 아이디 비번으로 회원 가입 시 DB저장
이후 로그인 시 사용자 아이디, 비밀 번호 DB에 존재하는 경우 토큰 발급
문제점 > API 인증을 허가하는 부분의 토큰 방식과, 아이디 비번 인증 전용 토큰 방식은 다르다. (API 사용자와 회원을 혼동하면 안됨) => 하지만, 같다고 생각했었음
변경 방식 : 둘 모두 분리하여 토큰 사용 VS 아이디 비번은 단순하게 체킹하고, API 사용 권한 여부만 토큰으로 판별
변경 로직 (예상) :
- 회원 가입
- 기존 방식 유지하되, ID, PW, 추가 회원 데이터(자사 서비스 로그인 용) 저장 및 SecrectKey, password (API 인증용) 발급해준 뒤 저장
- 로그인
1. 사용자가 ID PW 입력
2. 리액트는 이전 JWT 토큰 발급이력이 있는지 발급 여부 확인
2-1. 있다면 사용자가 입력한 ID, PW와 함께 토큰을 서버로 보내 바로 로그인 시도(6번) (6으로 이동)
2-2. 없다면 3으로 이동
3. secertkey, password 리액트에서 전송, DB에서 확인 후에, API 인증 허가용 엑세스 토큰 발급
=> (만료 여부 확인하여 refreshToken 까지 사용 가능하나 이 부분은 생략)
4. 프론트에서 받은 토큰 브라우저 세션에 저장
5. 세션에 저장한 엑세스 토큰과 ID, PW를 이용하여 전송 (2-1과 같음)
6. 토큰 일치 여부 확인 후 로그인
중요한 점은, 사용자(클라이언트, 개발자, 툴 등 API를 실제로 구현하는 주체)와 회원(실제 서비스를 이용하는 사람들)을 같은 개념으로 두고 생각하면 혼용하기 쉽다는 것이다.
API키(토큰)를 승인해주는 주체와, 로그인하는 주체를 혼동하면 안된다.
나는, 두 개념을 합하여 jwt토큰 발급 및 시큐리티에 필요한 검증을 해주는 secret_id와 secret_password를 우리 서비스를 이용하는 가상의 회원 아이디, 비밀번호로 통합하기로 하였다. 다만 절대!!! 혼동하면 안된다. API인증과, 로그인은 엄연히 다르게 생각해야 한다.
API/회원 로그인 및 인증 로직 정리
1. 기존 방식
- 사용자 ID, 비밀번호 방식으로 회원 가입 시 DB 저장
- 로그인 시 입력한 ID, PW가 DB에 존재하면 토큰 발급
- 문제점
- API 인증용 토큰과 일반 회원 로그인용 토큰을 동일하게 사용
- API 사용자와 일반 회원 혼동 가능
2. 변경 방식
- 회원 로그인용과 API 인증용 토큰을 분리
- ID/PW 검증은 단순 확인 용도
- API 사용 권한 여부만 토큰으로 판별
3. 회원 가입
- 기존 방식 유지
- ID, PW, 추가 회원 데이터 저장
- API 인증용 SecretKey, password 생성 후 DB 저장
4. 로그인 흐름
- 사용자 ID/PW 입력
- React 측 JWT 토큰 발급 여부 확인
- 있음:
- 사용자가 입력한 ID/PW + 기존 토큰 → 서버로 전송 → 토큰 검증
- 없음:
- SecretKey, password 전송 → DB에서 확인 → API 인증용 Access Token 발급
- 있음:
- 서버에서 발급한 토큰을 React에서 브라우저 세션에 저장
- 이후 로그인 시
- 세션에 저장한 Access Token + ID/PW → 서버 전송
- 서버에서 토큰 일치 여부 확인 → 로그인 처리 완료
5. 흐름 요약
회원 가입
└─ ID, PW + API Secret 발급 → DB 저장
로그인
├─ React: 이전 토큰 확인
│ ├─ 토큰 있음 → ID/PW + 토큰 → 서버 확인
│ └─ 토큰 없음 → SecretKey/PW → 서버 확인 → Access Token 발급
└─ Access Token 브라우저 세션 저장
└─ 이후 요청 시 토큰 + ID/PW → 서버에서 검증
1️⃣ 회원/API 인증 비교 테이블
구분 기존 방식 변경 방식
| 목적 | 회원 로그인 + API 인증 동일 | 회원 로그인과 API 인증 분리 |
| 회원 가입 | ID, PW 저장 | ID, PW + API SecretKey, Password 발급 및 저장 |
| 로그인 | DB ID/PW 확인 → 토큰 발급 | ID/PW 단순 검증 + API 인증용 Access Token 발급 |
| 문제점 | API/회원 토큰 혼동 | 구분 명확, 보안 향상 |
2️⃣ 회원 가입 프로세스
단계 설명
| 1 | 사용자가 회원 가입 시 ID, PW 입력 |
| 2 | 추가 회원 데이터와 함께 DB 저장 |
| 3 | API 인증용 SecretKey, Password 생성 후 DB에 저장 |
3️⃣ 로그인 프로세스 (React + JWT)
flowchart TD
A[사용자 ID/PW 입력] --> B{React: 이전 JWT 토큰 존재?}
B -- Yes --> C[ID/PW + 기존 토큰 서버 전송]
C --> D[서버: 토큰 일치 여부 확인 → 로그인]
B -- No --> E[SecretKey + Password 서버 전송]
E --> F[DB 확인 → Access Token 발급]
F --> G[React 브라우저 세션에 토큰 저장]
G --> H[ID/PW + 토큰 → 서버 전송]
H --> D

4️⃣ 요약
- 회원 로그인: ID/PW 단순 확인
- API 인증: 토큰 기반, Access Token 사용
- React + JWT: 클라이언트에서 토큰 관리, 서버는 Stateless
- 로그아웃: 클라이언트에서 토큰 삭제 → 서버 세션 없음
구현 및 클래스 소개
1. DATA
- Users (사용자 Entity) - 서비스에 필요한 사용자 엔티티, 회원 가입 시 DB 저장용
package team.shdsesc.stocksimul.auth.entity;
import jakarta.persistence.*;
import lombok.*;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import team.shdsesc.stocksimul.auth.dto.UserDTO;
import team.shdsesc.stocksimul.auth.dto.UserRole;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString
public class Users {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;
@Column(nullable = false, unique = true)
private String phoneNumber;
private int level;
@ElementCollection(fetch = FetchType.LAZY)
@Builder.Default
private List<String> tickerList = new ArrayList<>();
@ElementCollection(fetch = FetchType.LAZY)
@Builder.Default
@Enumerated(EnumType.STRING)
private Set<UserRole> roleSet = new HashSet<>();
public static UserDTO toUsersEntity(Users users) {
return new UserDTO(
users.getUserId(),
users.getEmail(),
users.getPassword(),
users.getPhoneNumber(),
users.getLevel(),
users.getTickerList(),
users.getRoleSet().stream()
.map(role -> new SimpleGrantedAuthority(role.toString()))
.toList()
);
}
public void addMemberRole(UserRole role) {
roleSet.add(role);
}
}
- UserDTO - Security의 UserDetails를 상속받는 User를 확장한 DTO
package team.shdsesc.stocksimul.auth.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import java.util.Collection;
import java.util.List;
@Getter
@Setter
@ToString
public class UserDTO extends User {
private Long userId;
private String secretId;
private String email;
private String secretPassword;
private String phoneNumber;
private int level;
private List<String> tickerList;
public UserDTO(Long userId, String email, String password, String phoneNumber, int level, List<String> tickerList, Collection<? extends GrantedAuthority> authorities) {
super(email, password, authorities);
this.userId = userId;
this.secretId = email;
this.secretPassword = password;
this.phoneNumber = phoneNumber;
this.level = level;
this.tickerList = tickerList;
}
}
package team.shdsesc.stocksimul.auth.dto;
public enum UserRole {
USER, ADMIN
}
- UserRequestDTO - 회원 가입 시 요청 객체
package team.shdsesc.stocksimul.auth.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
@AllArgsConstructor
public class UserRequestDTO {
private String email;
private String password;
private String level;
private List<String> tickerList;
}
2. Config
- SecurityConfig - 시큐리티 설정을 제공하는 설정 파일
package team.shdsesc.stocksimul.auth.config;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import team.shdsesc.stocksimul.auth.filter.ApiLoginFilter;
import team.shdsesc.stocksimul.auth.filter.TokenCheckFilter;
import team.shdsesc.stocksimul.auth.handler.APILoginSuccessHandler;
import team.shdsesc.stocksimul.auth.service.UserDetailService;
import team.shdsesc.stocksimul.auth.util.JWTUtil;
@Configuration
@EnableWebSecurity
@Log4j2
@EnableMethodSecurity(securedEnabled = true)
public class SecurityConfig {
private final UserDetailService userDetailService;
private final PasswordEncoder passwordEncoder;
private final JWTUtil jwtUtil;
@Autowired
SecurityConfig(UserDetailService userDetailService, PasswordEncoder passwordEncoder, JWTUtil jwtUtil) {
this.userDetailService = userDetailService;
this.passwordEncoder = passwordEncoder;
this.jwtUtil = jwtUtil;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
log.info("security config...............");
http.authorizeHttpRequests(auth -> auth
// .requestMatchers("/boards/register").hasAnyRole("BASIC","MANAGER","ADMIN")
.requestMatchers("/auth/**").permitAll()
.anyRequest().authenticated());
http.csrf(AbstractHttpConfigurer::disable); // CSRF 토큰 미사용 설정
// CORS 설정
http.cors(cors -> cors.configurationSource(corsConfigurationSource()));
// 자체 로그인 설정으로 폼 로그인은 비활성화
http.formLogin(AbstractHttpConfigurer::disable);
http.httpBasic(AbstractHttpConfigurer::disable);
// JWT 관련 설정
// AuthenticationManager 설정
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(userDetailService).passwordEncoder(passwordEncoder);
// AuthenticationManager 객체 생성
AuthenticationManager authenticationManager = authenticationManagerBuilder.build();
http.authenticationManager(authenticationManager);
// 필터
// 토큰발급URL (http://localhost:8080/auth)
ApiLoginFilter apiLoginFilter = new ApiLoginFilter("/auth");
apiLoginFilter.setAuthenticationManager(authenticationManager);
apiLoginFilter.setAuthenticationSuccessHandler(new APILoginSuccessHandler(jwtUtil));
// 필터동작위치
http.addFilterBefore(apiLoginFilter, UsernamePasswordAuthenticationFilter.class);
// 토큰체크필터
TokenCheckFilter tokenCheckFilter = new TokenCheckFilter(jwtUtil);
http.addFilterBefore(tokenCheckFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
// configuration.addAllowedOriginPattern("http://localhost:3000/"); // 모든 도메인 허용
configuration.addAllowedOriginPattern("http://localhost:5173");
configuration.addAllowedHeader("*"); // 모든 헤더 허용
configuration.addAllowedMethod("*"); // 모든 HTTP 메서드 허용
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
securityFilterChain : 시큐리티를 사용할 떄, 접근 권한, authenticationManager에 유저 설정 및 유효성 검증 및 해당 경로로 들어오는 요청에 대해 시큐리티 필터 체인 적용, 폼 로그인 (서버 쪽) 설정
corsConfigurationSource : 도메인, HTTP 메서드, 헤더로 들어오는 요청 허용 여부 결정
- QueryDslConfig - 쿼리 DSL 설정
package team.shdsesc.stocksimul.auth.config;
import com.querydsl.jpa.impl.JPAQueryFactory;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class QueryDslConfig {
@PersistenceContext
private EntityManager em;
@Bean
public JPAQueryFactory jpaQueryFactory(){
return new JPAQueryFactory(em);
}
}
- PasswordEncoderConfig - 패스워드 인코딩 설정
package team.shdsesc.stocksimul.auth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class PasswordEncoderConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Repository
- UserRepository - JPA 레포지토리와 Query DSL을 상속 받음
package team.shdsesc.stocksimul.auth.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import team.shdsesc.stocksimul.auth.entity.Users;
public interface UserRepository extends JpaRepository<Users, String>, UserRepositoryCustom {
}
- UserRepositoryCustom - Query DSL 연동을 위한 인터페이스
package team.shdsesc.stocksimul.auth.repository;
import team.shdsesc.stocksimul.auth.entity.Users;
import java.util.Optional;
public interface UserRepositoryCustom {
Optional<Users> findUserWithRolesByUserId(String email);
}
- UserRepositoryImpl - Repository의 구현체
package team.shdsesc.stocksimul.auth.repository;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import team.shdsesc.stocksimul.auth.entity.QUsers;
import team.shdsesc.stocksimul.auth.entity.Users;
import java.util.Optional;
@RequiredArgsConstructor
public class UserRepositoryImpl implements UserRepositoryCustom {
private final JPAQueryFactory queryFactory;
@Override
public Optional<Users> findUserWithRolesByUserId(String email) {
QUsers users = QUsers.users ;
Users result = queryFactory
.selectFrom(users)
.leftJoin(users.roleSet).fetchJoin()
.where(users.email.eq(email))
.fetchOne();
return Optional.ofNullable(result);
}
}
Service
- UserDetailService - 계정 관련 서비스(비즈니스 로직)를 처리
package team.shdsesc.stocksimul.auth.service;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.ResponseCookie;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import team.shdsesc.stocksimul.auth.dto.UserDTO;
import team.shdsesc.stocksimul.auth.dto.UserRole;
import team.shdsesc.stocksimul.auth.dto.UserRequestDTO;
import team.shdsesc.stocksimul.auth.entity.Users;
import team.shdsesc.stocksimul.auth.repository.UserRepository;
import java.util.Optional;
@Log4j2
@Service
@RequiredArgsConstructor
public class UserDetailService implements UserDetailsService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
Optional<Users> result = userRepository.findUserWithRolesByUserId(email);
log.info("username:{}", email);
Users users = result.orElseThrow(() -> new UsernameNotFoundException("Wrong ClientId or ClientSecret"));
log.info("Users:{}", users);
UserDTO dto = Users.toUsersEntity(users);
log.info("loadUserByUsername:{}", dto);
return dto;
}
public UserDTO registerUser(UserRequestDTO request) {
Users users = Users.builder()
.email(request.getEmail())
.password(passwordEncoder.encode(request.getPassword()))
.phoneNumber("010-1234-5678")
.level(Integer.parseInt(request.getLevel()))
.tickerList(request.getTickerList())
.build();
users.addMemberRole(UserRole.USER);
userRepository.save(users);
return Users.toUsersEntity(users);
}
public void logoutUser(HttpServletResponse response){
// Refresh Token 쿠키 삭제
ResponseCookie cookie = ResponseCookie.from("refreshToken", "")
.path("/")
.httpOnly(true)
.secure(false) // 개발용
.maxAge(0) // 만료시킴
.build();
response.setHeader("Set-Cookie", cookie.toString());
}
}
Util
- 토큰 발급에 실질적으로 쓰이는 클래스
package team.shdsesc.stocksimul.auth.util;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Component
@Log4j2
public class JWTUtil {
@Value("${jwt.secret.key}")
private String key;
// Access Token 생성
public String generateAccessToken(Map<String, Object> valueMap, int minutes) {
return generateToken(valueMap, minutes);
}
// Refresh Token 생성 (DB 저장 가능)
public String generateRefreshToken(Map<String, Object> valueMap, int days) {
return generateToken(valueMap, days * 60 * 24); // 일 단위 -> 분
}
private String generateToken(Map<String, Object> valueMap, int minutes) {
Map<String, Object> headers = new HashMap<>();
headers.put("typ", "JWT");
headers.put("alg", "HS256");
Map<String, Object> payloads = new HashMap<>(valueMap);
return Jwts.builder()
.setHeader(headers)
.setClaims(payloads)
.setIssuedAt(Date.from(ZonedDateTime.now().toInstant()))
.setExpiration(Date.from(ZonedDateTime.now().plusMinutes(minutes).toInstant()))
.signWith(SignatureAlgorithm.HS256, key.getBytes())
.compact();
}
// 토큰 검증
public Map<String, Object> validateToken(String token) throws Exception {
log.info("토큰: " + token);
return Jwts.parser()
.setSigningKey(key.getBytes())
.build()
.parseClaimsJws(token)
.getBody();
}
}
Handler
- APILoginSuccessHandler - 로그인 성공 시 토큰 발급 로직을 처리하는 핸들러
package team.shdsesc.stocksimul.auth.handler;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.log4j.Log4j2;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import team.shdsesc.stocksimul.auth.util.JWTUtil;
import java.io.IOException;
import java.util.Map;
@Log4j2
public class APILoginSuccessHandler implements AuthenticationSuccessHandler {
private final JWTUtil jwtUtil;
public APILoginSuccessHandler(JWTUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
log.info("Login Success...."); // 로그인 성공하면
// 토큰생성해서 서블릿으로 응답
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
Map<String, Object> claim = Map.of("email", authentication.getName());
// Access Token 유효기간 1시간
String accessToken = jwtUtil.generateAccessToken(claim, 60);
// Refresh Token 유효기간 1일
String refreshToken = jwtUtil.generateRefreshToken(claim, 1);
// secure, httpOnly 옵션을 통해 XSS 공격 방지
// SameSite 옵션을 통해 CSRF 공격 방지
// - None : 도메인 검증 X 어디서든 사용 가능하나 secure 옵션 필수
// - Lax : 외부 링크도 접근 허용 하지만 get 요청만 OK
// - Strict : 같은 도메인에서만 쿠키 전송 가능
ResponseCookie cookie = ResponseCookie.from("refreshToken", refreshToken)
.maxAge(7 * 24 * 60 * 60)
.path("/")
// 브라우저에서 쿠키에 접근할 수 없도록 제한
.httpOnly(true)
// https 환경에서만 쿠키 발동
.secure(false)
// 동일 사이트과 크로스 사이트에 모두 쿠키 전송이 가능
.sameSite("None")
.build();
response.setHeader("Set-Cookie", cookie.toString());
Map<String, String> keyMap = Map.of("accessToken", accessToken);
ObjectMapper om = new ObjectMapper();
String json = om.writeValueAsString(keyMap);
response.getWriter().print(json);
}
}
httpOnly : 브라우저에서 접근 불가하게 하여, XSS(크로스 사이트 스크립팅) 공격방지
secure : https에서만 쿠키 허용, XSS 공격 방지
sameSite : CSRF 공격 방지
🍪 쿠키 보안 속성들
쿠키를 사용할 때 보안을 위해 설정하는 주요 옵션:
- Secure
- true로 설정 시, HTTPS 연결에서만 쿠키 전송 가능
- 평문 HTTP에서는 전송되지 않음
- 예: Set-Cookie: token=abc; Secure
- HttpOnly
- true로 설정 시, JavaScript에서 쿠키 접근 불가
- 즉, document.cookie로 가져올 수 없음 → XSS 공격 방어
- 예: Set-Cookie: token=abc; HttpOnly
- SameSite
- CSRF 공격 방지를 위한 옵션
- 동작 방식:
- Strict: 완전히 동일한 도메인에서만 쿠키 전송
(다른 사이트에서 링크 클릭해도 전송 안 됨) - Lax: 대부분의 경우 차단, 단 GET 요청(링크 클릭, 새 탭 열기 등) 은 허용
- None: 모든 cross-site 요청 허용, 단 Secure 필수
- Strict: 완전히 동일한 도메인에서만 쿠키 전송
- 예: Set-Cookie: token=abc; SameSite=Strict
간단한 클래스 설명 까지만 진행해 보았다.
'BackEnd' 카테고리의 다른 글
| CICD 구현해보기 (2) - DockerFile (0) | 2025.11.07 |
|---|---|
| CICD 구현해보기 (1) - GitAction (0) | 2025.11.06 |
| 6/5 - Mapper 클래스, 트랜잭션 제어 (5) | 2025.06.06 |
| 6/4 - MariaDB, Log4JDBC (0) | 2025.06.06 |
| 6/1 - 의존성 주입 (DI), 관점 지향 프로그래밍 (AOP) (0) | 2025.06.06 |