[내배캠 Java 6기] Spring 주특기 숙련 1주차(1~6)

ekkkyo_j·2024년 8월 21일

[내배캠 Java 6기] TIL

목록 보기
23/38

📌 Bean 수동으로 등록하기

  • Bean 수동 등록이란?
    • @Component를 사용하면 @ComponentScan에 의해 자동으로 스캔되어 해당 클래스를 Bean으로 등록 해준다.
    • 일반적으로 @Component를 사용하여 Bean을 자동으로 등록하는 것이 좋습니다.
    • 프로젝트의 규모가 커질 수록 등록할 Bean들이 많아지기 때문에 자동등록을 사용하면 편리하다.
    • 비즈니스 로직과 관련된 클래스들은 그 수가 많기 때문에 @Controller, @Service와 같은 애너테이션들을 사용해서 Bean으로 등록하고 관리하면 개발 생산성에 유리하다.

      👉🏻 그렇다면 Bean 수동 등록은 언제 사용할까?
    • 기술적인 문제나 공통적인 관심사를 처리할 때 사용하는 객체들을 수동으로 등록하는 것이 좋다.
    • 공통 로그처리와 같은 비즈니스 로직을 지원하기 위한 부가 적이고 공통적인 기능들을 기술 지원 Bean이라 부르고 수동등록 한다.
@Configuration
public class PasswordConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
  • Bean으로 등록하고자하는 객체를 반환하는 메서드를 선언하고 @Bean을 설정한다.
  • Bean을 등록하는 메서드가 속한 해당 클래스에 @Configuration을 설정한다.
  • Spring 서버가 뜰 때 Spring IoC 컨테이너에 'Bean'으로 저장된다.

📌 같은 타입의 Bean이 2개라면?

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가 우선순위가 높다.

  • 좁은 범위가 최우선 순위 => @Qualifier("Bean 이름")
  • 넓은 범위가 나중 순위 => @Primary

📌 인증과 인가

인증(Authentication)

: 인증은 해당 유저가 실제 유저인지 인증하는 개념이다. 실제 그 유저가 맞는지를 확인하는 절차이다.

인가(Authorization)

: 인가는 해당 유저가 특정 리소스에 접근이 가능한지 허가를 확인하는 개념이다. 예를들어 관리자 페이지-관리자 권한 같은 것들이 있다.

인증의 방식

✨ 쿠키 세션(Cookie - Session) 방식

: 쿠키-세션 방식은 서버가 ‘특정 유저가 로그인 되었다’는 상태를 저장하는 방식이다.
인증과 관련된 아주 약간의 정보만 서버가 가지고 있게 되고 유저의 이전 상태의 전부는 아니더라도 인증과 관련된 최소한의 정보는 저장해서 로그인을 유지시킨다는 개념이다.

  1. 사용자가 로그인 요청을 보냅니다.
  2. 서버는 DB의 유저 테이블을 뒤져서 아이디 비밀번호를 대조해봐야겠죠?
  3. 실제 유저테이블의 정보와 일치한다면 인증을 통과한 것으로 보고 “세션 저장소”에 해당 유저가 로그인 되었다는 정보를 넣습니다.
  4. 세션 저장소에서는 유저의 정보와는 관련 없는 난수인 session-id를 발급합니다.
  5. 서버는 로그인 요청의 응답으로 session-id를 내어줍니다.
  6. 클라이언트는 그 session-id를 쿠키라는 저장소에 보관하고 앞으로의 요청마다 세션아이디를 같이 보냅니다. (주로 HTTP header에 담아서 보냅니다!)
  7. 클라이언트의 요청에서 쿠키를 발견했다면 서버는 세션 저장소에서 쿠키를 검증합니다.
  8. 만약 유저정보를 받아왔다면 이 사용자는 로그인이 되어있는 사용자겠죠?
  9. 이후에는 로그인 된 유저에 따른 응답을 내어줍니다.

✨ JWT 기반 인증

: JWT(JSON Web Token)란 인증에 필요한 정보들을 암호화시킨 토큰을 의미한다. JWT 기반 인증은 쿠키/세션 방식과 유사하게 JWT 토큰(Access Token)을 HTTP 헤더에 실어 서버가 클라이언트를 식별한다.

  1. 사용자가 로그인 요청을 보냅니다.
  2. 서버는 DB의 유저 테이블을 뒤져서 아이디 비밀번호를 대조해봐야겠죠?
  3. 실제 유저테이블의 정보와 일치한다면 인증을 통과한 것으로 보고 유저의 정보를 JWT로 암호화 해서 내보냅니다.
  4. 서버는 로그인 요청의 응답으로 jwt 토큰을 내어줍니다.
  5. 클라이언트는 그 토큰을 저장소에 보관하고 앞으로의 요청마다 토큰을 같이 보냅니다.
  6. 클라이언트의 요청에서 토큰을 발견했다면 서버는 토큰을 검증합니다.
  7. 이후에는 로그인 된 유저에 따른 응답을 내어줍니다.

📌 쿠키와 세션(Cookie - Session)

: 쿠키와 세션 모두 HTTP 에 상태 정보를 유지(Stateful)하기 위해 사용된. 즉, 쿠키와 세션을 통해 서버에서는 클라이언트 별로 인증 및 인가를 할 수 있게 된다.

  1. 쿠키(Cookie)
    : 클라이언트에 저장될 목적으로 생성한 작은 정보를 담은 파일이다.
  • 구성요소
    • Name (이름): 쿠키를 구별하는 데 사용되는 키 (중복될 수 없음)
    • Value (값): 쿠키의 값
    • Domain (도메인): 쿠키가 저장된 도메인
    • Path (경로): 쿠키가 사용되는 경로
    • Expires (만료기한): 쿠키의 만료기한 (만료기한 지나면 삭제된다.)
  1. 세션(Session)
    : 서버에서 일정시간 동안 클라이언트 상태를 유지하기 위해 사용된다.
  • 서버에서 클라이언트 별로 유일무이한 '세션 ID' 를 부여한 후 클라이언트 별 필요한 정보를 서버에 저장한다.
  • 서버에서 생성한 '세션 ID' 는 클라이언트의 쿠키값('세션 쿠키' 라고 부름)으로 저장되어 클라이언트 식별에 사용된다.

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를 가져온다.

✨ Create session

: 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";
}
  • HttpServletRequest를 사용하여 세션을 생성 및 반환할 수 있다.
  • req.getSession(true)
    - 세션이 존재할 경우 세션을 반환하고 없을 경우 새로운 세션을 생성한다.
  • 세션에 저장할 정보를 Name-Value 형식으로 추가한다.
  • 반환된 세션은 브라우저 Cookie 저장소에 ‘JSESSIONID’라는 Name으로 Value에 저장된다.



Request로 cookie를 보냈고 Response로 자동으로 만들어진 JSESSIONID=...를 받았다. 그리고 이를 브라우저가 읽어 첫번째 사진처럼 세션 아이디와 값을 저장한다.

✨ Get session

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)
    • 세션이 존재할 경우 세션을 반환하고 없을 경우 null을 반환합니다.
  • session.getAttribute(”세션에 저장된 정보 Name”)
    • Name을 사용하여 세션에 저장된 Value를 가져옵니다.

위에서 cookie를 생성하였기 때문에 뒤에 Session을 생성한 뒤에도 2개가 동시에 뜬다. Request Headers를 보면 2개의 Cookie가 저장되어있다.

📌 JWT(Json Web Token)

: JWT(Json Web Token)란 JSON 포맷을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 Web Token이다. 즉, 토큰의 한 종류라고 생각하면 된다. 일반적으로 쿠키 저장소를 사용하여 JWT를 저장한다.

  • 로그인 정보를 Server 에 저장하지 않고, Client 에 로그인 정보를 JWT 로 암호화하여 저장 → JWT 통해 인증/인가
  • 모든 서버에서 동일한 Secret Key 소유한다.

  • Secret Key 통한 암호화 / 위조 검증 (복호화 시)

JWT 사용 흐름

  1. 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 저장됨

✨ Get jwt

profile
게으르지만 성실히 공부하기^_^

0개의 댓글