1. 지연 로딩과 즉시 로딩
- JPA는 연관관계가 설정된 Entity의 정보를 바로 가져올지, 필요할 때 가져올지 가져오는 방법을 정할 수 있다.
- 이런 가져오는 방법을 JPA에서는 Fetch Type이라 부른다.
- Fetch Type의 종류에는 2가지가 있는데 하나는 LAZY, 다른 하나는 EAGER 이다.
- LAZY는 지연 로딩으로 필요한 시점에 정보를 가져온다.
- EAGER는 즉시 로딩으로 이름의 뜻처럼 조회할 때 연관된 모든 Entity의 정보를 즉시 가져온다.
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface OneToMany {
Class targetEntity() default void.class;
CascadeType[] cascade() default {};
FetchType fetch() default FetchType.LAZY;
String mappedBy() default "";
boolean orphanRemoval() default false;
}
- @OneToMany 애너테이션은 Fetch Type의 default 값이 LAZY로 되어있다.
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ManyToOne {
Class targetEntity() default void.class;
CascadeType[] cascade() default {};
FetchType fetch() default FetchType.EAGER;
boolean optional() default true;
}
- @ManyToOne 애너테이션은 EAGER로 되어있다.
대체적으로 @OOOToMany의 연관관계는 설정된 필드가 Java 컬렉션 타입이다. 컬렉션 타입이라는 뜻은 엔티티가 여러개 들어있을 수 있다는 뜻이다. 이 많은 엔티티를 한번에 즉시 불러오려면 서버의 자원 소모가 심해질 수 있기 때문에 효율적으로 정보를 조회하기 위해 지연 로딩이 default로 설정되어 있다.
반대로 @OOOToOne의 연관관계는 대체적으로 설정된 필드가 Entity 한 개이다. 따라서 즉시 정보를 가져와도 무리가 없어 즉시 로딩이 default로 설정되어 있다.
음식(외래키 주인):
@Entity
@Getter
@Setter
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
고객:
@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user")
private List<Food> foodList = new ArrayList<>();
}
아보카도 피자를 주문한 회원 이름 조회코드:
@Test
@DisplayName("아보카도 피자 조회")
void test1() {
Food food = foodRepository.findById(2L).orElseThrow(NullPointerException::new);
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getPrice() = " + food.getPrice());
System.out.println("아보카도 피자를 주문한 회원 정보 조회");
System.out.println("food.getUser().getName() = " + food.getUser().getName());
}
user는 eager타입으로 조회해서 이름만 조회하려 해도 id와 이름 전부 불러오는 모습이다.
JPA의 영속성 컨텍스트에는 다음과 같은기능들이 있다.
- 1차 캐시
- 쓰기 지연 저장소
- 변경 감지
지연 로딩도 마찬가지로 영속성 컨텍스트의 기능 중 하나이다. 지연 로딩된 Entity의 정보를 조회하려고 할 때는 반드시 영속성 컨텍스트가 존재해야하고 그 말은 즉, ‘트랜잭션이 적용되어있어야 한다’라는 뜻이다.
@Entity
@Getter
@Setter
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
}
user를 지연 로딩 스타일로 불러오도록 코드를 수정했다.
@Test
@DisplayName("아보카도 피자 조회")
void test1() {
Food food = foodRepository.findById(2L).orElseThrow(NullPointerException::new);
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getPrice() = " + food.getPrice());
System.out.println("아보카도 피자를 주문한 회원 정보 조회");
System.out.println("food.getUser().getName() = " + food.getUser().getName());
}
똑같이 회원 정보를 조회하려 하면 트랜잭션이 적용되지 않아서 지연 로딩 스타일로 엔티티를 가져올 수 없다.
@Test
@Transactional
@DisplayName("아보카도 피자 조회")
void test1() {
Food food = foodRepository.findById(2L).orElseThrow(NullPointerException::new);
System.out.println("food.getName() = " + food.getName());
System.out.println("food.getPrice() = " + food.getPrice());
System.out.println("아보카도 피자를 주문한 회원 정보 조회");
System.out.println("food.getUser().getName() = " + food.getUser().getName());
}
트랜잭션을 적용하면 정상적으로 조회가 가능하다. 이때 회원 이름을 조회할 때만 추가적으로 select문을 호출하는 모습을 볼 수 있다. 이는 지연 로딩의 특징인 필요할 때 데이터를 가져오는 모습이다.
2. 영속성 전이
userRepository.save(user);
foodRepository.save(food);
orderRepository.save(order);
}
테이블에 엔티티의 내용들을 저장하기 위해서는 위의 코드처럼 일일이 관련된 레포지토리의 save메소드를 호출해야 한다.
JPA에서는 이를 간편하게 처리할 수 있는 방법으로 영속성 전이(CASCADE)의 PERSIST 옵션을 제공한다.
- 영속성 전이
- 영속 상태의 Entity에서 수행되는 작업들이 연관된 Entity까지 전파되는 상황을 의미한다.
- 영속성 전이를 적용하여 해당 Entity를 저장할 때 연관된 Entity까지 자동으로 저장하기 위해서는 자동으로 저장하려고 하는 연관된 Entity에 추가한 연관관계 애너테이션에 CASCADE의 PERSIST 옵션을 설정해야 한다.
음식(외래키 주인):
@Entity
@Getter
@Setter
@Table(name = "food")
public class Food {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private double price;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
}
고객:
@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST)
private List<Food> foodList = new ArrayList<>();
public void addFoodList(Food food) {
this.foodList.add(food);
food.setUser(this);// 외래 키(연관 관계) 설정
}
}
여기서는 users의 엔티티를 조작할 때 food의 엔티티도 같이 조작되도록 하기 위해 user엔티티에 cascade속성을 걸어줬다.
영속성 전이 저장 코드:
@Test
@DisplayName("영속성 전이 저장")
void test2() {
// 고객 Robbie 가 후라이드 치킨과 양념 치킨을 주문합니다.
User user = new User();
user.setName("Robbie");
// 후라이드 치킨 주문
Food food = new Food();
food.setName("후라이드 치킨");
food.setPrice(15000);
user.addFoodList(food);
Food food2 = new Food();
food2.setName("양념 치킨");
food2.setPrice(20000);
user.addFoodList(food2);
userRepository.save(user);
}
영속성 전이 기능으로 저장뿐만 아니라 삭제도 할 수 있다.
// 주문한 음식 데이터 삭제
foodRepository.deleteAll(user.getFoodList());
// Robbie 탈퇴
userRepository.delete(user);
원래는 이렇게 N:1에서 N쪽의 데이터를 모두 지우고 1쪽의 데이터를 지워야 하는 번거로움이 있다.
JPA에서는 이를 간편하게 처리할 수 있는 방법으로 영속성 전이(CASCADE)의 REMOVE 옵션을 제공한다.
고객 테이블에 persist옵션 뿐만 아니라 remove옵션도 같이 걸어준다.
고객:
@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<Food> foodList = new ArrayList<>();
public void addFoodList(Food food) {
this.foodList.add(food);
food.setUser(this); // 외래 키 설정
}
}
영속성 전이 삭제 코드:
@Test
@Transactional
@Rollback(value = false)
@DisplayName("영속성 전이 삭제")
void test4() {
// 고객 Robbie 를 조회합니다.
User user = userRepository.findByName("Robbie");
System.out.println("user.getName() = " + user.getName());
// Robbie 가 주문한 음식 조회
for (Food food : user.getFoodList()) {
System.out.println("food.getName() = " + food.getName());
}
// Robbie 탈퇴
userRepository.delete(user);
}
3. 고아 Entity 삭제
JPA에서는 연관된 Entity를 삭제하는것 만으로도 delete sql을 실행해서 실제 테이블에서도 연관 관계가 있는 레코드를 삭제하는 기능이 있다. orphanRemoval 옵션으로 레포지토리에서 delete를 실행하지 않아도 연관된 데이터를 지울 수 있다.
orphanRemoval이 적용된 고객 테이블:
@Entity
@Getter
@Setter
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST, orphanRemoval = true)
private List<Food> foodList = new ArrayList<>();
public void addFoodList(Food food) {
this.foodList.add(food);
food.setUser(this); // 외래 키 설정
}
}
orphanRemoval 테스트:
@Test
@Transactional
@Rollback(value = false)
@DisplayName("연관관계 제거")
void test1() {
// 고객 Robbie 를 조회합니다.
User user = userRepository.findByName("Robbie");
System.out.println("user.getName() = " + user.getName());
// 연관된 음식 Entity 제거 : 후라이드 치킨
Food chicken = null;
for (Food food : user.getFoodList()) {
if(food.getName().equals("후라이드 치킨")) {
chicken = food;
}
}
if(chicken != null) {
user.getFoodList().remove(chicken);
}
// 연관관계 제거 확인
for (Food food : user.getFoodList()) {
System.out.println("food.getName() = " + food.getName());
}
}
4. JWT
JWT(Json Web Token)란 JSON 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token 이다. 일반적으로 쿠키 저장소를 사용하여 JWT를 저장한다.
JWT를 사용하는 이유:
만약 서버의 대용량 트래픽 처리가 필요하면 서버를 2대 이상 운영해야 할 수도 있다.
이렇게 서버가 분산되어 있으면 각 세션마다 다른 로그인 정보가 들어있을 수 있다.(세션1에는 클라이언트1,2,3, 세션2에는 4, 세션3에는 5,6 ...) 이렇게 세션에 소속된 서버가 아닌 다른 서버에(예를 들어, 클라이언트 1이 세션 2에 요청) api를 요청을 하면 문제가 생길 수 있다.
이 문제를 해결하려면 클라이언트는 각자가 소속된 서버에 api를 요청하도록 고정하거나 별도의 세션 저장소 생성하여 모든 세션을 저장하게 하는 방법이 있다. 이 중 후자는 별도의 저장소를 생성하고 저장하는 기능 때문에 추가적인 서버 비용이 발생할 수 있다는 단점이 있다.
JWT를 사용하면 로그인 정보를 Server 에 저장하지 않고, Client 에 로그인 정보를 JWT 로 암호화하여 저장한 다음, JWT 통해 인증/인가하는 방법으로 세션 스토리지 없이 2대 이상의 서버를 운용할 수 있다.
JWT를 사용할 때는 모든 서버에서 동일한 Secret Key를 보유하고 Secret Key 통해 암호화 / 위조 검증 (복호화 시)을 한다.
JWT의 장/단점:
- 장점:
- 다중 서버이기 때문에 동시 접속자가 많을 때 서버 측 부하를 낮춰줄 수 있다.
- Client, Sever 가 다른 도메인을 사용할 때에도 인증이 가능하다.(예시, 카카오 OAuth2 로그인 시 JWT Token 사용)
- 단점:
- 구현의 복잡도 증가한다.
- JWT 에 담는 내용이 커질 수록 네트워크 비용 증가한다.
- 미리 생성된 JWT 를 일부만 만료시킬 방법이 없다.
- Secret key 유출 시 JWT를 조작할 수 있다.
JWT의 사용흐름:
1. Client 가 username, password 로 로그인 성공 시
- 서버에서 "로그인 정보" → JWT 로 암호화 (Secret Key 사용)
- 서버에서 직접 쿠키를 생성해 JWT를 담아 Client 응답에 전달(JWT 전달방법은 개발자가 정함)
Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
cookie.setPath("/");
// Response 객체에 Cookie 추가
res.addCookie(cookie);
- 브라우저 쿠키 저장소에 자동으로 JWT 저장됨
2. Client 에서 JWT 통해 인증방법
- 서버에서 API 요청 시마다 쿠키에 포함된 JWT를 찾아서 사용
- 코드 예시:
// HttpServletRequest 에서 Cookie Value : JWT 가져오기
public String getTokenFromRequest(HttpServletRequest req) {
Cookie[] cookies = req.getCookies();
if(cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals(AUTHORIZATION_HEADER)) {
try {
return URLDecoder.decode(cookie.getValue(), "UTF-8"); // Encode 되어 넘어간 Value 다시 Decode
} catch (UnsupportedEncodingException e) {
return null;
}
}
}
}
return null;
}
쿠키에 담긴 정보가 여러 개일 수 있기 때문에 그 중 이름이 JWT가 담긴 쿠키의 이름과 동일한지 확인하여 JWT를 가져온다.
- 서버에서의 역할:
- Client 가 전달한 JWT 위조 여부 검증 (Secret Key 사용)
- JWT 유효기간이 지나지 않았는지 검증
- 검증 성공시, JWT → 에서 사용자 정보를 가져와 확인(예시: GET /api/products : JWT 보낸 사용자의 관심상품 목록 조회)
JWT의 구조:
Payload:실제 데이터, Header, Verify Signature: 암호화 관련 양식
5. JWT 예시:
JWT 토큰 생성에 필요한 데이터 정의:
// Header KEY 값
public static final String AUTHORIZATION_HEADER = "Authorization";
// 사용자 권한 값의 KEY
public static final String AUTHORIZATION_KEY = "auth";
// Token 식별자
public static final String BEARER_PREFIX = "Bearer ";
// 토큰 만료시간
private final long TOKEN_TIME = 60 * 60 * 1000L; // 60분
@Value("${jwt.secret.key}") // Base64 Encode 한 SecretKey
private String secretKey;
private Key key;
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 로그 설정
public static final Logger logger = LoggerFactory.getLogger("JWT 관련 로그");
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
key = Keys.hmacShaKeyFor(bytes);
}
시크릿 키는 application.properities에 미리 저장해 놓은 상태로 작업한다. @Value 어노테이션을 사용하면 application.properities에 정의해놓은 변수를 가져올 수 있다.
이 프로젝트에서는 시크릿 키를 base64방식으로 암호화된 상태로 정의해 놓았기 때문에 복호화 과정이 필요하다.
jwt 토큰도 여러가지 암호화 방식을 사용해서 토큰을 주고 받을 수 있는데 여기서는 HS256방식을 사용했다. SignatureAlgorithm.HS256이 HS256방식의 암호화를 의미한다. Bearer 란 JWT 혹은 OAuth에 대한 토큰을 사용한다는 표시이다.
JWT생성:
// 토큰 생성
public String createToken(String username, UserRoleEnum role) {
Date date = new Date();
return BEARER_PREFIX +
Jwts.builder()
.setSubject(username) // 사용자 식별자값(ID)
.claim(AUTHORIZATION_KEY, role) // 사용자 권한
.setExpiration(new Date(date.getTime() + TOKEN_TIME)) // 만료 시간
.setIssuedAt(date) // 발급일
.signWith(key, signatureAlgorithm) // 암호화 알고리즘
.compact();
}
JWT의 subject에 사용자의 식별값 즉, id를 넣는다.
JWT에 사용자의 권한 정보를 넣는다. key-value 형식으로 key 값을 통해 확인할 수 있다.
signWith에 secretKey 값을 담고있는 key와 암호화 알고리즘을 값을 넣어줍니다. 암호화 방식은 바로 위에 signatureAlgorithm이라는 변수에 담겨있다.
JWT Cookie에 저장:
// JWT Cookie 에 저장
public void addJwtToCookie(String token, HttpServletResponse res) {
try {
token = URLEncoder.encode(token, "utf-8").replaceAll("\\+", "%20"); // Cookie Value 에는 공백이 불가능해서 encoding 진행
Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
cookie.setPath("/");
// Response 객체에 Cookie 추가
res.addCookie(cookie);
} catch (UnsupportedEncodingException e) {
logger.error(e.getMessage());
}
}
쿠키의 value에는 공백을 넣을 수 없어서 공백을 없애는 작업을 해줘야 저장할 수 있다.
받아온 Cookie의 Value인 JWT 토큰 substring:
// JWT 토큰 substring
public String substringToken(String tokenValue) {
if (StringUtils.hasText(tokenValue) && tokenValue.startsWith(BEARER_PREFIX)) {
return tokenValue.substring(7);
}
logger.error("Not Found Token");
throw new NullPointerException("Not Found Token");
}
현재 저장된 jwt토큰에는 Bearer라는 문자열이 붙은 채로 저장되어 있다. jwt토큰을 활용하기 위해서는 Bearer를 잘라야 한다. StringUtils.hasText를 사용하여 공백, null을 확인하고 startsWith을 사용하여 토큰의 시작값이 Bearer이 맞는지 확인한다. 확인이 됐으면 substring()메소드로 제거한다.
JWT 검증:
// 토큰 검증
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (SecurityException | MalformedJwtException | SignatureException e) {
logger.error("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
} catch (ExpiredJwtException e) {
logger.error("Expired JWT token, 만료된 JWT token 입니다.");
} catch (UnsupportedJwtException e) {
logger.error("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
} catch (IllegalArgumentException e) {
logger.error("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
}
return false;
}
JWT에서 사용자 정보 가져오기:
// 토큰에서 사용자 정보 가져오기
public Claims getUserInfoFromToken(String token) {
return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
}
JWT의 구조 중 Payload 부분에는 토큰에 담긴 정보가 들어있다. 여기에 담긴 정보의 한 ‘조각’ 을 클레임(claim) 이라고 부르고, 이는 key-value 의 한 쌍으로 이뤄져있다. 토큰에는 여러개의 클레임 들을 넣을 수 있다.
6. 필터
필터(Filter)란 Web 애플리케이션에서 관리되는 영역으로 Client로 부터 오는 요청과 응답에 대해 최초/최종 단계의 위치이며 이를 통해 요청과 응답의 정보를 변경하거나 부가적인 기능을 추가할 수 있다. 쉽게 말해 필터는 비즈니스 로직을 수행하기 전에 실행되는 로직이고 비즈니스 로직을 실행하고 나서 반환하기 전에 마지막으로 실행되는 로직이다.
주로 범용적으로 처리해야 하는 작업들, 예를들어 로깅 및 보안 처리에 활용한다.
필터는 여러 개의 필터가 Chain형식으로 묶여서 처리될 수 있다.
필터예시:
uri을 로그에 출력하는 기능의 필터
@Slf4j(topic = "LoggingFilter")
@Component
@Order(1) //필터의 실행순서
public class LoggingFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
// 전처리
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String url = httpServletRequest.getRequestURI();
log.info(url);
chain.doFilter(request, response); // 다음 Filter 로 이동
// 후처리
log.info("비즈니스 로직 완료"); //실행 대기중인 필터가 모두 실행된 뒤에 실행
}
}
권한 인증을 담당하는 필터
@Slf4j(topic = "AuthFilter") // 로그의 제목을 정할 수 있다.
@Component
@Order(2)
public class AuthFilter implements Filter {
private final UserRepository userRepository;
private final JwtUtil jwtUtil;
public AuthFilter(UserRepository userRepository, JwtUtil jwtUtil) {
this.userRepository = userRepository;
this.jwtUtil = jwtUtil;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
String url = httpServletRequest.getRequestURI();
if (StringUtils.hasText(url) &&
(url.startsWith("/api/user") || url.startsWith("/css") || url.startsWith("/js"))
) {
// 회원가입, 로그인 관련 API 는 인증 필요없이 요청 진행
chain.doFilter(request, response); // 다음 Filter 로 이동
} else {
// 나머지 API 요청은 인증 처리 진행
// 토큰 확인
String tokenValue = jwtUtil.getTokenFromRequest(httpServletRequest);
if (StringUtils.hasText(tokenValue)) { // 토큰이 존재하면 검증 시작
// JWT 토큰 substring
String token = jwtUtil.substringToken(tokenValue);
// 토큰 검증
if (!jwtUtil.validateToken(token)) {
throw new IllegalArgumentException("Token Error");
}
// 토큰에서 사용자 정보 가져오기
Claims info = jwtUtil.getUserInfoFromToken(token);
User user = userRepository.findByUsername(info.getSubject()).orElseThrow(() ->
new NullPointerException("Not Found User")
);
request.setAttribute("user", user);
chain.doFilter(request, response); // 다음 Filter 로 이동
} else {
throw new IllegalArgumentException("Not Found Token");
}
}
}
}
jwtUtil의 내용은 위의 Jwt 예시에 간략하게 적어놓았다.
7. Spring Security
'Spring Security' 프레임워크는 Spring 서버에 필요한 인증 및 인가를 위한 기능을 제공하는 프레임워크이다.
앞에서 필터는 비즈니스 로직을 수행하기 전에 실행되는 로직이고 비즈니스 로직을 실행하고 나서 반환하기 전에 마지막으로 실행되는 로직이라고 했는데, 정확히는 DispatcherServlet을 통과하기 전에 실행되는 모듈이다. DispatcherServlet은 Spring에서 모든 호출이 거쳐가는 모듈이다. DispatcherServlet을 거치고 나면 비로소 컨트롤러에서 사용자의 요청을 처리한다.
Spring Security도 필터의 한 종류이다. FilterChainProxy를 상속받아서 상세 로직을 구현한 필터이다.
Form Login 기반 인증은 인증이 필요한 URL 요청이 들어왔을 때 인증이 되지 않았다면 로그인 페이지를 반환하는 형태이다.
UsernamePasswordAuthenticationFilter는 Spring Security의 필터인 AbstractAuthenticationProcessingFilter를 상속한 Filter이다. UsernamePasswordAuthenticationFilter는 Form Login 기반 인증을 사용할 때 사용자의 username과 password를 확인해서 인증한다.
사용자가 username과 password를 제출하면 인증된 사용자의 정보가 담기는 인증 객체인 UsernamePasswordAuthenticationToken을 만들어 AuthenticationManager에게 넘겨 인증을 시도한다.
인증에 실패하면 SecurityContextHolder를 비우고 성공하면 SecurityContextHolder에 Authentication를 세팅한다.
SecurityContextHolder는 SecurityContext가 담긴 클래스이다. SecurityContext는 인증이 완료된 사용자의 상세 정보(Authentication)를 저장한다.
// 예시코드
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
context.setAuthentication(authentication); // SecurityContext 에 인증 객체 Authentication 를 저장합니다.
SecurityContextHolder.setContext(context);
SecurityContext에는 인증된 사용자의 정보가 담긴 Authentication이 있는데 여기에는 세 종류의 사용자 정보가 있다.
principal은 사용자를 식별하는 멤버다. credentials은 주로 사용자의 비밀번호이다. 대부분 사용자 인증에 사용한 후 내용물을 지운다.
authorities는 사용자에게 부여한 권한을 GrantedAuthority로 추상화하여 사용한다.
'내일배움캠프' 카테고리의 다른 글
뉴스피드 프로젝트 2일차 (0) | 2024.06.07 |
---|---|
뉴스피드 프로젝트 1일차 (0) | 2024.06.04 |
3진법 뒤집기 (0) | 2024.05.31 |
최대공약수와 최소공배수 구하기 (0) | 2024.05.31 |
MySQL에서의 if문과 case문 (0) | 2024.05.30 |