내배캠)Week9_TIL/WIL

Dev.Shinny·2022년 12월 27일
0

내배캠

목록 보기
7/7

221227

인증과 인가 개념 이해

인증(Authentication)이란?
(식별 가능한 정보로) 서비스에 등록된 유저의 신원을 입증하는 과정 ex) 로그인

인가(Authorization)란?
인증된 사용자에 대한 자원 접근 권한 확인 ex) 관리자 권한

클라이언트와 서버는 Http 프로토콜을 이용하여 통신을 하고, 이 통신은 비연결성(Connectionless) 무상태(Stateless)로 이루어진다.

비연결성(Connectionless)

비연결성은 클라이언트와 서버가 한 번 연결을 맺은 후, 클 라이언트 요청에 대해 서버가 응답을 마치면 맺었던 연결을 끊어 버리는 성질을 말한다.

이유: 서버에서 다수의 클라이언트가 연결을 계속 유지하면 많은 리소스가 발생하고 서버의 비용이 기하급수적으로 늘어난다.

단점: 서버는 클라이언트를 기억하고 있지 않으므로 동일한 클라이언트의 모든 요청에 대해, 매번 새로운 연결을 시도/해제의 과정을 거쳐야하므로 연결/해제에 대한 오버헤드가 발생한다.

무상태(Stateless)

HTTP에서 서버가 클라이언트의 상태를 보존하지 않는 무상태 프로토콜이다.

장점 :
1. 서버 확장성이 높다(스케일 아웃),
2. 특정 서버에 장애가 생겨도 다른 서버에서 응답할 수 있다.
3. 어떤 서버든 호출 가능하다 : 무상태 프로토콜이라면 클라이언트A가 요청할 때 이미 필요한 데이터를 다 담아서 보내기 때문에 아무 서버나 호출해도 된다.

단점 : 클라이언트가 추가 데이터를 전송해야한다.

  • 스케일 아웃: 서버를 여러 대 추가하여 시스템을 확장하는 방법

Stateful vs Stateless
카페에서 음료를 주문한다고 가정할 때, 상태 유지와 무상태가 어떻게 작동하는지 재밌는 예시가 있어 가져왔다.

정리하면 무상태는 항상 같은 서버가 클라이언트의 요청을 전부 해결할 필요 없이 보내온 요청에만 응답하고 다른 서버 또한 새로운 요청에 응답할 수 있기 때문에 클라이언트 요청이 증가해도 서버를 대거 투입할 수 있다.

but 매번 새로운 인증을 해야하는 번거로움이 발생한다. 서버가 클라이언트를 기억하지 못하기 때문이다.

그럼 어떻게 비연결성, 무상태 프로토콜에서 “유저가 인증되었다”라는 정보를 유지시킬까?

인증 방식

1. 쿠키

쿠키란?
클라이언트가 어떠한 웹사이트를 방문할 경우, 그 사이트가 사용하고 있는 서버를 통해 클라이언트의 브라우저에 설치되는 작은 기록 정보 파일

서버는 클라이언트의 로그인 요청에 대해 클라이언트측에 저장하고 싶은 정보를 응답 헤더 set-cookie에 담는다. 이후 해당 클라이언트가 요청을 보낼 때마다, 매번 저장된 쿠키를 요청 헤더의 cookie에 담아 보낸다. 서버는 쿠키에 담긴 정보를 바탕으로 해당 요청의 클라이언트가 누군지 식별한다.

단점
1) 보안 취약

  • 요청 시 쿠키 값을 그대로 보낸다.
  • 유출 및 조작 위험성이 높다.

2) 쿠키에는 용량 제한이 있어 많은 정보를 담을 수 없다.
3) 웹 브라우저마다 쿠키에 대한 지원 형태가 달라 브라우저간 공유가 불가능하다.
4) 쿠키의 사이즈가 커질수록 네트워크 부하가 심해진다.

2. 쿠키-세션

세션은 비밀번호 등 클라이언트 인증 정보를 쿠키가 아닌 서버 측에 저장하고 관리한다. 서버는 클라이언트의 로그인 요청에 대한 응답으로 인증 정보는 서버에 저장하고 클라이언트 식별자인 JSESSIONID를 쿠키에 담는다. 이후 클라이언트가 요청을 보낼 때마다, JSESSIONID 쿠키를 함께 보낸다 서버는 JSESSIONID 유효성을 판별해 클라이언트를 식별한다.

장단점
1) 쿠키를 포함한 요청이 외부에 노출되더라도 세션 ID 자체는 유의미한 개인정보를 담고 있지 않는다.

2) 각 사용자마다 고유한 세션 ID가 발급되기 때문에, 요청이 들어올 때마다 회원정보를 확인할 필요가 없다.

3) 서버에서 세션 저장소를 사용하므로 요청이 많아지면 서버에 부하가 심해진다.

3. JWT

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

JWT 구조

https://jwt.io/

JWT는 .을 구분자로 나누어지는 세 가지 문자열의 조합이다. 실제 디코딩된 JWT는 다음과 같은 구조를 지닌다.

  1. Header
    alg와 typ는 각각 정보를 암호화할 해싱 알고리즘 및 토근의 타입을 지정한다.

  2. Playload
    Playload는 토큰에 담을 정보를 지닌다. 주로 클라리언트의 고유 ID 값 및 유효 기간 등이 포함된다. key-value 형식으로 이루어진 한 쌍의 정보를 Claim이라고 칭한다.

  3. Signature
    Signature는 인코딩된 Header와 Payload를 더한 뒤 비밀키로 해싱하여 생성한다. Header와 Payload는 단순히 인코딩된 값이기 때문에 제 3자가 복호화 및 조작할 수 있지만, Signature는 서버 측에서 관리하는 비밀키가 유출되지 않는 이상 복호화할 수 없다. 따라서 Signature는 토큰의 위변조 여부를 확인하는데 사용된다.

JWT 인증 과정

  1. 클라이언트 로그인 요청이 들어오면, 서버는 검증 후 클라이언트 고유 ID 등의 정보를 Payload에 담는다.

  2. 암호화할 비밀키를 사용해 Access Token(JWT)을 발급한다.

  3. 클라이언트는 전달받은 토큰을 저장해두고, 서버에 요청할 때 마다 토큰을 요청 헤더 Authorization에 포함시켜 함께 전달한다.

  4. 서버는 토큰의 Signature를 비밀키로 복호화한 다음, 위변조 여부 및 유효 기간 등을 확인한다. 유효한 토큰이라면 요청에 응답한다.

장점

  1. Header와 Payload를 가지고 Signature를 생성하므로 데이터 위변조를 막을 수 있다.

  2. 인증 정보에 대한 별도의 저장소가 필요없다.

  3. JWT는 토큰에 대한 기본 정보와 전달할 정보 및 토큰이 검증됬음을 증명하는 서명 등 필요한 모든 정보를 자체적으로 지니고 있다.

  4. 클라이언트 인증 정보를 저장하는 세션과 다르게, 서버는 무상태가 된다.

  5. 확장성이 우수하다.

  6. 토큰 기반으로 다른 로그인 시스템에 접근 및 권한 공유가 가능하다.

  7. OAuth의 경우 Facebook, Google 등 소셜 계정을 이용하여 다른 웹서비스에서도 로그인을 할 수 있다.

  8. 모바일 어플리케이션 환경에서도 잘 동작한다.

단점

  1. 쿠키/세션과 다르게 JWT는 토큰의 길이가 길어, 인증 요청이 많아질수록 네트워크 부하가 심해진다.

  2. Payload 자체는 암호화되지 않기 때문에 유저의 중요한 정보는 담을 수 없다.

  3. 토큰을 탈취당하면 대처하기 어렵다. 토큰은 한 번 발급되면 유효기간이 만료될 때 까지 계속 사용이 가능하기 때문이다.

  4. 특정 사용자의 접속을 강제로 만료하기 어렵지만, 쿠키/세션 기반 인증은 서버 쪽에서 쉽게 세션을 삭제할 수 있다.

보안

  1. 짧은 만료 기한 설정
    토큰의 만료 시간을 짧게 설정할 수 있다. 토큰이 탈취돼도 빠르게 만료되기 때문에 피해를 최소화할 수 있다. 그러나 사용자가 자주 로그인 해야 하는 불편함이 있다.

2. Refresh Token
클라이언트가 로그인 요청을 보내면 서버는 Access Token 및 그보다 긴 만료 기간을 가진 Refresh Token을 발급하는 전략이다. 클라이언트는 Access Token이 만료되었을 때 Refresh Token을 사용하여 Access Token의 재발급을 요청한다. 서버는 DB에 저장된 Refresh Token과 비교하여 유효한 경우 새로운 Access Token을 발급하고, 만료된 경우 사용자에게 로그인을 요구한다.

그러나 검증을 위해 서버는 Refresh Token을 별도의 storage에 저장해야 한다. 이는 추가적인 I/O 작업이 발생함을 의미하기 때문에 JWT의 장점(I/O 작업이 필요 없는 빠른 인증 처리)을 완벽하게 누릴 수 없다. 클라이언트도 탈취 방지를 위해 Refresh Token을 보안이 유지되는 공간에 저장해야 한다.

출처
테코블

좀 더 읽어보기
JWT (JSON Web Token) 이해와 활용

221228

Model

  • Model의 .addAttribute()를 통해 Controller에서 생성된 데이터를 저장해 view에 전달할 수 있다.
  • Model은 스프링이 지원하는 기능으로써, key와 value로 이루어져있는 HashMap이다.
  • Servlet의 request.setAttribute()와 비슷한 역할을 한다.

ModelAttribute의 장점
Model의 사용 방법

ModelAndView

Model의 기능이 확장된 버전, 데이터와 이동하고자 하는 View Page를 같이 저장한다.

Controller 처리 결과 후 응답할 view와 view에 전달할 값을 저장

스프링 숙련 _ Auth 강의 코드 분석

UserService

// 서비스 단에서 어드민 토큰을 보유하고 있다. 
private static final String ADMIN_TOKEN = "AAABnvxRVklrnYxKZ0aHgTBcXukeZygoC";

	// 회원 가입 
    @Transactional
    public void signup(SignupRequestDto signupRequestDto) {
        String username = signupRequestDto.getUsername();
        String password = signupRequestDto.getPassword();
        String email = signupRequestDto.getEmail();

        // 회원(회원 아이디_username) 중복 확인
        // 요청한 requestDto가 담고있는 username으로 동일한 객체가 있는지
        // 확인하여 이를 found에 담고 만약 found가 존재한다면 Exception을 던진다. 
        Optional<User> found = userRepository.findByUsername(username);
        if (found.isPresent()) { 
            throw new IllegalArgumentException("중복된 사용자가 존재합니다.");
        }

        // 사용자 ROLE 확인
        // 일반적으로 role은 user로 초기화한다. 
        // 만약 요청한객체의 role이 admin이고, 저장된 토큰 값이(getAdminToken) 어드민 토큰 값이 아니라면 
        // "암호가 틀렸다"는 exception을 토큰 값이 맞다면 어드민으로 로그인한다. 
        UserRoleEnum role = UserRoleEnum.USER;
        if (signupRequestDto.isAdmin()) {
            if (!signupRequestDto.getAdminToken().equals(ADMIN_TOKEN)) {
                throw new IllegalArgumentException("관리자 암호가 틀려 등록이 불가능합니다.");
            }
            role = UserRoleEnum.ADMIN;
        }

//  요청 객체의 정보를 user에 담고 db에 저장한다. 
        User user = new User(username, password, email, role);
        userRepository.save(user);
    }
    
    // 로그인 메서드 
    @Transactional(readOnly = true)
    public void login(LoginRequestDto loginRequestDto) {
        String username = loginRequestDto.getUsername();
        String password = loginRequestDto.getPassword();

        // 사용자 확인
        // db에 요청한 username으로 가입된 사용자가 있는지 확인한다. 없으면 exception 
        User user = userRepository.findByUsername(username).orElseThrow(
                () -> new IllegalArgumentException("등록된 사용자가 없습니다.")
        );

        // 비밀번호 확인 
        if(!user.getPassword().equals(password)){
            throw  new IllegalArgumentException("비밀번호가 일치하지 않습니다.");
        }
    }

@Transactional이란

@Transactional이 붙은 메서드는 메서드가 포함하고 있는 작업 중에 하나라도 실패할 경우 전체 작업을 취소한다.

221229

spring 숙련 JWT 강의 코드 분석

JWT dependency 추가

compileOnly group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'

application.properties

jwt.secret.key=7ZWt7ZW0OTntmZTsnbTtjIXtlZzqta3snYTrhIjrqLjshLjqs4TroZzrgpjslYTqsIDsnpDtm4zrpa3tlZzqsJzrsJzsnpDrpbzrp4zrk6TslrTqsIDsnpA=

Base64 디코더 에서 강의에서 제공된 키 값을 디코딩 해봤다.

결과 : "항해99화이팅한국을너머세계로나아가자훌륭한개발자를만들어가자"
또 항해 99 찾았다 ㅋㅋ 거진 보물찾기

JwtUtil.java


@Slf4j
@Component
@RequiredArgsConstructor
public class JwtUtil {

// 토큰 생성에 필요한 값 
// Authorization Header key 값 
    public static final String AUTHORIZATION_HEADER = "Authorization";
    // 사용자 권한 값의 key 
    public static final String AUTHORIZATION_KEY = "auth";
    // Token 식별자 
    private static final String BEARER_PREFIX = "Bearer ";
    // 토큰 만료 시간 _ 1시간으로 설정
    private static final long TOKEN_TIME = 60 * 60 * 1000L;

	// application.properties에 적어둔 jwt.secret.key 값 참조하기 
    @Value("${jwt.secret.key}")
    // 참조된 값이 저장됨 
    private String secretKey;
    // 토큰을 만들 때 넣어줄 key 값 
    private Key key;
    // 사용할 알고리즘 정의 
    private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

// 객체가 생성될 때 초기화하는 메서드 
    @PostConstruct
    public void init() {
    	// 값을 디코딩하는 과정 
        byte[] bytes = Base64.getDecoder().decode(secretKey);
        key = Keys.hmacShaKeyFor(bytes);
    }

    // header에서 토큰을 가져오기
    public String resolveToken(HttpServletRequest request) {
    	//  파라미터에 작성된 AUTHORIZATION_HEADER의 키에 들어있는 토큰을 가져오기(request.getHeader)
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
        // 가지고 온 코드가 있는지 bearer로 시작하는지 확인하고 
        // 코드 중 필요없는 앞에 7글자 지워준다. 
        // 왜냐면 bearer 6글자고 한 칸 띄워져 있기 때문.
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
            return bearerToken.substring(7);
        }
        return null;
    }

    // String 형식의 JWT 토큰 생성
    public String createToken(String username, UserRoleEnum role) {
        Date date = new Date();

        return BEARER_PREFIX +
        // 실제 만들어지는 부분 
                Jwts.builder()
                .setSubject(username) // 토큰 용도
                .claim(AUTHORIZATION_KEY, role) // 사용자 권한을 지정하고 그 권한을 가져올 땐 위에서 지정한대로 auth키를 이용한다.
                .setExpiration(new Date(date.getTime() + TOKEN_TIME)) // 토큰 유효시간, 현재시간(date.getTime())+앞서 지정한 토큰 유효시간 
                .setIssuedAt(date) // 토큰 생성시기
                .signWith(key, signatureAlgorithm) // HS256과 Key(위에서 정의해둔 키)로 Sign
                .compact();
    }

    // 토큰 검증
    public boolean validateToken(String token) {
        try {
   // 내부적으로 토큰(token)을 검증해준다.          Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (SecurityException | MalformedJwtException e) {
            log.info("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
        } catch (ExpiredJwtException e) {
            log.info("Expired JWT token, 만료된 JWT token 입니다.");
        } catch (UnsupportedJwtException e) {
            log.info("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
        } catch (IllegalArgumentException e) {
            log.info("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
        }
        return false;
    }

    // 토큰에서 사용자 정보 가져오기(getBody()) _ 이 자체도 토큰 검증이 이루어진다고 생각해도 무방하다. try catch가 없는 건 위에서 이미 다 정의되어 있기 때문. 
    public Claims getUserInfoFromToken(String token) {
        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
    }
}
  1. @Slf4j은 무엇인가?

The Simple Logging Facade for Java
다양한 로깅 프레임 워크에 대한 추상화(인터페이스) 역할을 하는 라이브러리

SLF4j 이용한 Logging
SLF4J란

  1. Bearer이란??
    토큰 기반 인증 Bearer Authentication

Bearer 토큰은 토큰을 소유한 사람에게 액세스 권한을 부여하는 일반적인 토큰 클래스이다. 액세스 토큰, ID 토큰, 자체 서명 JWT는 모두 Bearer 토큰이다.

인증에 Bearer 토큰을 사용하려면 HTTPS와 같은 암호화된 프로토콜로 제공되는 보안이 필요하다. Bearer 토큰을 가로채면 악의적인 행위자가 이를 사용하여 액세스 권한을 얻을 수 있다.

2-1 토큰에 대해서
토큰이란 무엇인가_구글 문서

  1. Enum Class SignatureAlgorithm

디지털 서명에 사용되는 암호화 알고리즘 열거

  1. @PostConstruct

    의존성 주입이 이루어진 후 초기화를 수행하는 메서드. @PostConstruct가 붙은 메서드는 클래스가 service를 수행하기 전에 발생한다. 이 메서드는 다른 리소스에서 호출되지 않아도 수행된다.

장점)
1. 생성자가 호출 되었을 때, 빈은 초기화 되지 않았다.(의존성 주입이 이루어지지 않았다.) 이 때, @PostConstruct를 사용하면, 빈이 초기화 됨과 동시에 의존성을 확인할 수 있다.

2) bean 의 생애주기에서 오직 한 번만 수행된다는 것을 보장한다. (어플리케이션이 실행될 때 한번만 실행됨)

-> 아직 DI, Bean 등에 대한 이해도가 높지 않아서 @PostConstruct 장점에 대해서도 와닿지 않는다...

UserController

// Auth 강의 (기존)코드에서 login은 Form 태그로 넘어왔기 때문에 
// ModelAttribute 형식으로 받아와져서 @RequestBody를 넣어주지 않았다. 
// 그러나 이제는 ajax에서 body에 값이 넘어가기 때문에 @RequestBody를 써줘야 한다. 

// HttpServletResponse는 HttpRequest에서 Header가 넘어와 받아 오는 것처럼 client 쪽으로 반환할 때는 
// Response 객체를 반환한다. 미리 파라미터로 받아와 반환할 reponse header에 만들어준 token을 넣어준다. 
   @ResponseBody
    @PostMapping("/login")
    public String login(@RequestBody LoginRequestDto loginRequestDto, HttpServletResponse response) {
        userService.login(loginRequestDto, response);
        return "success";
    }

1. HttpServletResponse
유튜브 강의 : HttpServletRequest와 HttpServletResponse

HTTP 응답 정보(요청 처리 결과)를 제공하는 인터페이스
Servlet은 HttpServletResponse객체에 content-type, 응답 코드, 응답 메세지 등을 담아서 전송한다.

UserService

// jwtUtil을 사용하기 때문에 의존성 주입 
private final JwtUtil jwtUtil;

 @Transactional(readOnly = true)
    public void login(LoginRequestDto loginRequestDto, HttpServletResponse response) {

		..생략 기존과 동일...

  // 들고 온 response에 addHeader를 사용하면 Header쪽에 값을 넣어줄 수 있다. 키 값에는 AUTHORIZATION_HEADER와 만들어둔 createToken 메소드를 사용해서 유저의 이름과 유저의 권한을 넣어준 Toeken을 발급해 넣어준다.
  response.addHeader(JwtUtil.AUTHORIZATION_HEADER, jwtUtil.createToken(user.getUsername(), user.getRole()));
    }

ProductController

private final ProductService productService;

    // 관심 상품 등록하기
    @PostMapping("/products")
    public ProductResponseDto createProduct(@RequestBody ProductRequestDto requestDto, HttpServletRequest request) {
        // 응답 보내기
        return productService.createProduct(requestDto, request);
    }

    // 관심 상품 조회하기
    @GetMapping("/products")
    public List<ProductResponseDto> getProducts(HttpServletRequest request) {
        // 응답 보내기
        return productService.getProducts(request);
    }

    // 관심 상품 최저가 등록하기
    @PutMapping("/products/{id}")
    public Long updateProduct(@PathVariable Long id, @RequestBody ProductMypriceRequestDto requestDto, HttpServletRequest request) {
        // 응답 보내기 (업데이트된 상품 id)
        return productService.updateProduct(id, requestDto, request);
    }

ProductService

@Service
@RequiredArgsConstructor
public class ProductService {

	private final ProductRepository productRepository;
    private final UserRepository userRepository;
    private final JwtUtil jwtUtil;

	// 관심 상품 추가하기 
    @Transactional
    public ProductResponseDto createProduct(ProductRequestDto requestDto, HttpServletRequest request) {
        // Request에서 Token 가져오기
        String token = jwtUtil.resolveToken(request);
        Claims claims;

        // 토큰이 있는 경우에만 관심상품 추가 가능
        if (token != null) {
            if (jwtUtil.validateToken(token)) {
                // 토큰에서 사용자 정보 가져오기
                claims = jwtUtil.getUserInfoFromToken(token);
            } else {
                throw new IllegalArgumentException("Token Error");
            }

            // 토큰에서 가져온 사용자 정보를 사용하여 DB 조회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
            );

            // 요청받은 DTO 로 DB에 저장할 객체 만들기 _ 상품과 회원은 관계를 맺고 있음 
            Product product = productRepository.saveAndFlush(new Product(requestDto, user.getId()));

            return new ProductResponseDto(product);
        } else {
            return null;
        }
    }

    @Transactional(readOnly = true)
    public List<ProductResponseDto> getProducts(HttpServletRequest request) {
        // Request에서 Token 가져오기
        String token = jwtUtil.resolveToken(request);
        Claims claims;

        // 토큰이 있는 경우에만 관심상품 조회 가능
        if (token != null) {
            // Token 검증
            if (jwtUtil.validateToken(token)) {
                // 토큰에서 사용자 정보 가져오기
                claims = jwtUtil.getUserInfoFromToken(token);
            } else {
                throw new IllegalArgumentException("Token Error");
            }

            // 토큰에서 가져온 사용자 정보를 사용하여 DB 조회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
            );

            // 사용자 권한 가져와서 ADMIN 이면 전체 조회, USER 면 본인이 추가한 부분 조회
            UserRoleEnum userRoleEnum = user.getRole();
            System.out.println("role = " + userRoleEnum);

            List<ProductResponseDto> list = new ArrayList<>();
            List<Product> productList;

            if (userRoleEnum == UserRoleEnum.USER) {
                // 사용자 권한이 USER일 경우
                // 유저 아이디로 모든 상품 객체 가져와서 productList에 담는다.
                productList = productRepository.findAllByUserId(user.getId());
            } // 관리자인 경우 모든 상품 정보를 조회한다.  
            else {
                productList = productRepository.findAll();
            }

            for (Product product : productList) {
                list.add(new ProductResponseDto(product));
            }

            return list;

        } else {
            return null;
        }
    }

    @Transactional
    public Long updateProduct(Long id, ProductMypriceRequestDto requestDto, HttpServletRequest request) {
        // Request에서 Token 가져오기
        String token = jwtUtil.resolveToken(request);
        Claims claims;

        // 토큰이 있는 경우에만 관심상품 최저가 업데이트 가능
        if (token != null) {
            // Token 검증
            if (jwtUtil.validateToken(token)) {
                // 토큰에서 사용자 정보 가져오기
                claims = jwtUtil.getUserInfoFromToken(token);
            } else {
                throw new IllegalArgumentException("Token Error");
            }

            // 토큰에서 가져온 사용자 정보를 사용하여 DB 조회
            User user = userRepository.findByUsername(claims.getSubject()).orElseThrow(
                    () -> new IllegalArgumentException("사용자가 존재하지 않습니다.")
            );

            Product product = productRepository.findByIdAndUserId(id, user.getId()).orElseThrow(
                    () -> new NullPointerException("해당 상품은 존재하지 않습니다.")
            );

            product.update(requestDto);

            return product.getId();

        } else {
            return null;
        }
    }
	
        @Transactional
        public void updateBySearch (Long id, ItemDto itemDto){
            Product product = productRepository.findById(id).orElseThrow(
                    () -> new NullPointerException("해당 상품은 존재하지 않습니다.")
            );
            product.updateByItemDto(itemDto);
        }

    }

221230

스프링 숙련 _ JPA 강의

페이징

아직 작성 못함.. 할 거임 진짜 할거임 ㅜ

221231

스프링 프로젝트 시작.

  1. API 작성
    !! 노션에 작성해둠(임시 api라 확정되면 올릴 예정)

  2. 로그인 구현을 위한 와이어 프레임(임시)

  1. ERD 작성

  2. 협업과 프로젝트 편의를 위한 노션

  • 날짜 : 일별 진행 사항을 정리해두었다.
  • API : REST API가 작성되어 있다.
  • Git : Git 주소와 Git 관련 공유 사항을 명시해둔다.
  • 요구사항 : 프로젝트 요구 사항 링크가 있다.
  • 유용한 정보 모음 : 프로젝트에 필요한 기술 정보를 공유하고 있다.
  • 발표 자료 : 차후 발표에 쓰일 곳이다.
  • 명세서 : 구현 전에 클래스와 메서드를 정의해둔 명세서가 있다.
  • 업무 분담 : 업무 분담과 각 개인별 진행률을 확인할 수 있다.

명세서
지난 번 팀프로젝트의 협업을 말아먹고, 다른 팀 조장님께서 하신 방법이 좋아보여 차용했다.

루시드차트 사이트를 통해 팀원들이 실시간 패키지, 클래스와 메서드를 정의해두었다. 일반적인 UML 보다 더 자세하다. 이 틀을 바탕으로 좀 더 세밀한 틀을 작성하고 구현을 시작하려고 노션에 명세 페이지를 만들었다.

샘플용 작성인데, 어노테이션, 필요한 필드값, 메서드 반환 타입, 메서드명, 파라미터를 정의해두고 구현할 때는 메서드의 내부만 작성할 수 있도록 할 예정이다.

프로젝트에 필요한 기본 설정값 등도 담았다.

WIL 09

드디어 프로젝트가 시작되었다. 난 아직 Security도 못 봤는데ㅜ 이제서야 숙련과정 좀 이해한 것 같은데 심화 과정을 해야하니까 팀원들에게 민폐끼칠까 걱정이다. security 1강만 들어봤는데...what the hell? 진짜 이해 1도 안됨. 강의 집중도 안되고ㅜㅠ 게다가 알고리즘 버려두고 있다.
이번 목표는 어떻게든 프로젝트 잘 끝내고 security 박살...까지는 아니어도 이런식으로 사용하면 되겠구나~ 정도는 되고 싶다.

profile
Hello I'm Shinny. A developer who try to enjoy the challenge.

0개의 댓글