@Configuration
public class PasswordConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
Chicken과 Pizza는 Food라는 interface를 상속받아 Food라는 같은 타입의 Bean으로 등록되었다.
@SpringBootTest
public class BeanTest {
@Autowired
Food food;
👿 문제 : Food 타입으로 Bean 객체를 2개(Pizza, Chicken) 등록했기 때문에 자동 주입할 수 없다.
🍀 해결방안: 골라줘야함
해결 방안 1) Bean의 이름 명시하기
@Autowired
Food pizza;
Chicken, Pizza 클래스이지만 Bean으로 등록하면 chicken, pizza로 등록되므로 chicken, pizza가 Bean의 이름이다. 따라서, Food pizza or Food chicken으로 선언한다.
해결 방안 2) @Primary 사용
Chicken or Pizza에 @Primary를 붙여준다. Ex) Chicken에 @Primary를 붙여주어 자동으로 Chicken을 선택한다.
@Component
@Primary
public class Chicken implements Food {
@Autowired
Food food;
해결 방안 3) @Qualifier("Bean 이름")
Chicken or Pizza에 @Qualifier("Bean 이름")을 해준다. Ex) Pizza에 @Qualifier("pizza")를 붙여주어 자동으로 Pizza를 선택한다.
@Component
@Qualifier("pizza")
public class Pizza implements Food {
@Autowired
@Qualifier("pizza")
Food food;
Primary와 Qualifier 중 누가 더 우선순위가 있을까? Qualifier가 우선순위가 높다.
: 인증은 해당 유저가 실제 유저인지 인증하는 개념이다. 실제 그 유저가 맞는지를 확인하는 절차이다.
: 인가는 해당 유저가 특정 리소스에 접근이 가능한지 허가를 확인하는 개념이다. 예를들어 관리자 페이지-관리자 권한 같은 것들이 있다.
: 쿠키-세션 방식은 서버가 ‘특정 유저가 로그인 되었다’는 상태를 저장하는 방식이다.
인증과 관련된 아주 약간의 정보만 서버가 가지고 있게 되고 유저의 이전 상태의 전부는 아니더라도 인증과 관련된 최소한의 정보는 저장해서 로그인을 유지시킨다는 개념이다.

- 사용자가 로그인 요청을 보냅니다.
- 서버는 DB의 유저 테이블을 뒤져서 아이디 비밀번호를 대조해봐야겠죠?
- 실제 유저테이블의 정보와 일치한다면 인증을 통과한 것으로 보고 “세션 저장소”에 해당 유저가 로그인 되었다는 정보를 넣습니다.
- 세션 저장소에서는 유저의 정보와는 관련 없는 난수인 session-id를 발급합니다.
- 서버는 로그인 요청의 응답으로 session-id를 내어줍니다.
- 클라이언트는 그 session-id를 쿠키라는 저장소에 보관하고 앞으로의 요청마다 세션아이디를 같이 보냅니다. (주로 HTTP header에 담아서 보냅니다!)
- 클라이언트의 요청에서 쿠키를 발견했다면 서버는 세션 저장소에서 쿠키를 검증합니다.
- 만약 유저정보를 받아왔다면 이 사용자는 로그인이 되어있는 사용자겠죠?
- 이후에는 로그인 된 유저에 따른 응답을 내어줍니다.
: JWT(JSON Web Token)란 인증에 필요한 정보들을 암호화시킨 토큰을 의미한다. JWT 기반 인증은 쿠키/세션 방식과 유사하게 JWT 토큰(Access Token)을 HTTP 헤더에 실어 서버가 클라이언트를 식별한다.

- 사용자가 로그인 요청을 보냅니다.
- 서버는 DB의 유저 테이블을 뒤져서 아이디 비밀번호를 대조해봐야겠죠?
- 실제 유저테이블의 정보와 일치한다면 인증을 통과한 것으로 보고 유저의 정보를 JWT로 암호화 해서 내보냅니다.
- 서버는 로그인 요청의 응답으로 jwt 토큰을 내어줍니다.
- 클라이언트는 그 토큰을 저장소에 보관하고 앞으로의 요청마다 토큰을 같이 보냅니다.
- 클라이언트의 요청에서 토큰을 발견했다면 서버는 토큰을 검증합니다.
- 이후에는 로그인 된 유저에 따른 응답을 내어줍니다.
: 쿠키와 세션 모두 HTTP 에 상태 정보를 유지(Stateful)하기 위해 사용된. 즉, 쿠키와 세션을 통해 서버에서는 클라이언트 별로 인증 및 인가를 할 수 있게 된다.




Response Headers를 보면 Set-Cookie 부분에 addCookie에서 설정했던 값들이 들어가있다. 이 Response Headers를 보고 브라우저에서 첫번째 사진처럼 값을 넣어준다.
@GetMapping("/create-cookie")
public String createCookie(HttpServletResponse res) {
addCookie("Robbie Auth", res);
return "createCookie";
}
public static void addCookie(String cookieValue, HttpServletResponse res) {
try {
cookieValue = URLEncoder.encode(cookieValue, "utf-8").replaceAll("\\+", "%20");
// Cookie Value 에는 공백이 불가능하다
// => encoding 진행
Cookie cookie = new Cookie(AUTHORIZATION_HEADER, cookieValue); // Name-Value
cookie.setPath("/");
cookie.setMaxAge(30 * 60); // 만료 기한
// Response 객체에 Cookie 추가
// 만든 쿠키를 response 객체에 넣어주는 것
res.addCookie(cookie);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e.getMessage());
}
}


Request Headers에 Cookie에 Authorization라는 이름을 가진 쿠키의 쿠키 값이 들어있어 다음 코드 중 getCookie(@CookieValue(AUTHORIZATION_HEADER) String value)이 해당 값을 가져온다.
@GetMapping("/get-cookie")
public String getCookie(@CookieValue(AUTHORIZATION_HEADER) String value) {
// @CookieValue(AUTHORIZATION_HEADER)
// AUTHORIZATION_HEADER은 Cookie의 이름
// 쿠키 이름에 해당하는 쿠키 값을 가져온다.
// "Robbie Auth"가 쿠키 값이다.
System.out.println("value = " + value);
return "getCookie : " + value;
}
@CookieValue("Cookie의 Name")
: Cookie의 Name 정보를 전달해주면 해당 정보를 토대로 Cookie의 Value를 가져온다.
: Servlet에서는 유일무이한 '세션 ID'를 간편하게 만들수 있는 HttpSession을 제공 해준다.
createSession
@GetMapping("/create-session")
public String createSession(HttpServletRequest req) {
// 세션이 존재할 경우 세션 반환, 없을 경우 새로운 세션을 생성한 후 반환
HttpSession session = req.getSession(true);
// 세션에 저장될 정보 Name - Value 를 추가합니다.
session.setAttribute(AUTHORIZATION_HEADER, "Robbie Auth");
return "createSession";
}
req.getSession(true)

Request로 cookie를 보냈고 Response로 자동으로 만들어진 JSESSIONID=...를 받았다. 그리고 이를 브라우저가 읽어 첫번째 사진처럼 세션 아이디와 값을 저장한다.
getSession
@GetMapping("/get-session")
public String getSession(HttpServletRequest req) {
// 세션이 존재할 경우 세션 반환, 없을 경우 null 반환
HttpSession session = req.getSession(false);
String value = (String) session.getAttribute(AUTHORIZATION_HEADER);
// 가져온 세션에 저장된 Value 를 Name 을 사용하여 가져옵니다.
System.out.println("value = " + value);
return "getSession : " + value;
}
req.getSession(false)session.getAttribute(”세션에 저장된 정보 Name”)

위에서 cookie를 생성하였기 때문에 뒤에 Session을 생성한 뒤에도 2개가 동시에 뜬다. Request Headers를 보면 2개의 Cookie가 저장되어있다.
: JWT(Json Web Token)란 JSON 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token이다. 즉, 토큰의 한 종류라고 생각하면 된다. 일반적으로 쿠키 저장소를 사용하여 JWT를 저장한다.


Client 가 username, password 로 로그인 성공 시
a. 서버에서 "로그인 정보" → JWT 로 암호화 (Secret Key 사용)
application.properties
jwt.secret.key=7Iqk7YyM66W07YOA7L2U65Sp7YG065+9U3ByaW5n6rCV7J2Y7Yqc7YSw7LWc7JuQ67mI7J6F64uI64ukLg==
b. 서버에서 직접 쿠키를 생성해 JWT를 담아 Client 응답에 전달
Response Header에 아래 형태로 JWT 전달
Cookie cookie = new Cookie(AUTHORIZATION_HEADER, token); // Name-Value
cookie.setPath("/");
// Response 객체에 Cookie 추가
res.addCookie(cookie);
JwtUtil - JWT 토큰 생성
// 1. JWT 데이터
// Header KEY 값, Cookie의 이름
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분
// Base64 Encode 한 SecretKey, application.properties에서 가져옴
@Value("${jwt.secret.key}")
private String secretKey;
// jwt.secret.key를 관리할 때 사용하는 객체
private Key key;
// 암호화 알고리즘
private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
// 로그 설정
// Application이 동작하는 동안에 상태나 동작을 시간순으로 기록하는 것
public static final Logger logger = LoggerFactory.getLogger("JWT 관련 로그");
@PostConstruct
public void init() {
byte[] bytes = Base64.getDecoder().decode(secretKey);
// jwt.secret.key가 Base64 인코딩되어있어서 디코딩을 하는 것이다.
key = Keys.hmacShaKeyFor(bytes);
}
// 2. JWT 토큰 생성
public String createToken(String username, UserRoleEnum role) {
Date date = new Date();
// 암호화 되어 JWT 토큰을 만든다.
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();
}
c. 브라우저 쿠키 저장소에 자동으로 JWT 저장됨

