유익한 강의 감사드립니다. 질문이 있습니다. 권한처리를 위해 세션을 사용하게 되면 서버가 여러대일 경우 어떤 서버에는 권한정보가 있고 어떤 서버에는 없을 수도 있지 않나요? 이런 경우 스프링 시큐리티가 자동으로 알아서 처리를 해주는건가요? 세션을 사용하지 않고 JWT 를 사용하는 이유중에 하나가 서버가 여러대 일 경우의 세션 문제를 해결하기 위함도 있다고 하셨는데 마찬가지로 서버가 여러대라면 권한처리를 위한 권한정보를 동일하게 맞춰(?) 주는 무엇인가가 필요하지 않나요?
안녕하세요 강사님 강의를 보고 토이 프로젝트에서 주문 시 재고 감소 및 메뉴 주문량 증가 로직에서 낙관적락 기법을 적용해 보았는데요, 강의에서 해주신 내용 그대로 파사드 패턴 까지 적용을 해보면서 시도했는데 무한 루프가 돌았습니다. hikariCP pool을 40으로 설정해주니 그제서야 해결이 되었는데요, 강의에선 네임드락에서 커넥션풀을 지정해주었는데요 저는 낙관적락인데도 해당 설정을 해서 해결된 이유가 있을까요? @Service @RequiredArgsConstructor public class OrderService { @Transactional public Order orderWithOptimisticLock(Long memberId, LocalDateTime now) { Cart cart = cartRepository.findByMember(memberId); List<CartItem> cartItems = cart.getCartItems(); cartItems.stream() .map(CartItem::getMenu) .forEach(menu -> { decreaseStockWithOptimisticLock(menu.getId(), 1); increaseMenuOrderCountWithOptimisticLock(menu.getId(), 1); }); Money money = calculator.calculateMenus(cart.getMember(), cart.convertToMenus()); Order order = Order.createOrder(cart, money, now); return orderRepository.save(order); } public void decreaseStockWithOptimisticLock(Long menuId, int quantity) { Menu menu = menuRepository.findByIdForOptimisticLock(menuId); menu.decrease(quantity); } public void increaseMenuOrderCountWithOptimisticLock(Long menuId, int quantity) { Menu menu = menuRepository.findByIdForOptimisticLock(menuId); menu.increaseOrderCount(quantity); } } ``` @Component @RequiredArgsConstructor public class OptimisticLockStockFacade { private final OrderService orderService; public Order order(Long memberId, LocalDateTime localDateTime) throws InterruptedException { while (true) { try { return orderService.orderWithOptimisticLock(memberId, localDateTime); } catch (Exception e) { Thread.sleep(50); } } } } ``` public interface JpaMenuRepository extends JpaRepository<Menu, Long> { @NotNull @Lock(LockModeType.OPTIMISTIC) @Query("select m from Menu m where m.id = :id") Optional<Menu> findByIdForOptimisticLock(@NotNull @Param("id") Long id); }
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요. 1. 강의 내용과 관련된 질문을 남겨주세요. 2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요. (자주 하는 질문 링크: https://bit.ly/3fX6ygx) 3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요. (질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요. ========================================= [질문 템플릿] 1. 강의 내용과 관련된 질문인가요? (예) 2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예) 3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예) [질문 내용] 1. 스프링 데이터 JPA 가 지원하는 save를 호출하면 flush()가 나가는건가요 아니면 save를 하고 bulkAgePlus()를 호출하면서 bulkAgePlus() 에 있는 JQPL이 실행되서 flush() 가 나가는건가요 ? bulkAgePlus()를 실행하면 JPQL 실행이되고, JPQL 이 실행이 되면 flush() 가 호출이 되는데 이때 SQL쿼리문이 DB에 전송이대서 DB에는 업데이트가 되어있지만 영속성 컨텍스트는 업데이트가 안되기 때문에 clear()를 해주는게 맞는건가요 ?
질문은 아니지만 최근에 이 강의를 들으시는 분들에게 조금이나마 팁이 될까 적어봅니다. 강사님께서 강의하신 3년전에는 2.3.* 버전이고, 강의자료 github version 3에서도 2.7.* 버전이라 최신 버전인 3.1.*에는 안맞는 것들이 조금은 많았습니다. 대부분 Spring Boot 3.*대로 업데이트 되면서 많은게 바뀌었더라고요. 그래서 작업하면서 최신 버전에서는 이렇게 하면 오류가 해결되는구나에 대해서 기억나는대로 설명해드리고자 합니다. <강사님 github Version3 SecurityConfig.java 에서 filterChain 발췌> @Bean SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .formLogin().disable() .httpBasic().disable() .apply(new MyCustomDsl()) // 커스텀 필터 등록 .and() .authorizeRequests() .antMatchers("/api/v1/user/**") .access("hasRole('USER') or hasRole('MANAGER') or hasRole('ADMIN')") .antMatchers("/api/v1/manager/**") .access("hasRole('MANAGER') or hasRole('ADMIN')") .antMatchers("/api/v1/admin/**") .access("hasRole('ADMIN')") .anyRequest().permitAll() .and().build(); } 람다 표현식 사용 권장 httpServlet 오브젝트에 처음 적용시키는 csrf부터 빨간줄이 떴습니다. 설명줄을 확인해보니 스프링 시큐리티 6.1 버전부터 deprecated되었다고 하네요. 자동완성을 확인해보니 밑에 있던 기존 csrf는 밑줄이 그어져있고 대신 안에 파라미터를 넣어줘야 한다고 되어있네요. 이런식으로 파라미터를 요구하는 식에는 모두 람다식 표현을 사용했습니다. http.csrf(cs-> cs.disable()) .sessionManagement(s->s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .formLogin(f->f.disable()) .httpBasic(h->h.disable()) .apply(new MyCustomDs1()) 람다식 표현은 매개변수->{매개변수 표현식} 으로 표현할 수가 있습니다. 자세한 내용은 구글링 하시면 잘 나오실 겁니다. .and() Method 삭제 http의 csrf, sessionManagement, formLogin, httpBasic을 disable로 하고 .and()로 한번 끊고 나서 다음 설정을 하는 구문입니다. 자동완성을 확인해보니 and() 메소드는 완전히 삭제가 된 모양이군요. and()의 역할은 다중 보안 설정 시에 사용하는 메소드입니다. SecurityConfig.java 파일에서는 처음으로 보안 설정을 한 후에 권한 설정을 하는 방식으로 진행되었습니다. 이에 따라 and() 구문으로 보안설정과 권한설정을 나누었으나, and() 메소드가 삭제됨에 따라 나누는 방법에 대해서 많은 고민을 했던 것 같습니다. <수정 내용> 인프런 AI 인턴으로부터 받은 답변의 내용을 살펴보면 apply() 메소드 뒤에 메소드 체이닝으로 붙여서 람다 표현식으로 하면 권한이 생성된다고 되어있습니다. 한 번 해보시죠. 빨간색 줄을 보면 authorizeHttpRequests 메소드가 'MyCustomDs1' 이라는 커스텀 필터 클래스의 메소드라고 인식하고, MyCustomDs1 필터 내에 authorizeHttpRequests라는 메소드가 존재하지 않음으로 오류를 띄워주는 것이라고 할 수 있겠습니다. 이에 대해, 제 방식이 정답인지는 모르겠으나 이런식으로 해결했습니다. http.csrf(cs-> cs.disable()) // 보안 설정 .sessionManagement(s->s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .formLogin(f->f.disable()) .httpBasic(h->h.disable()) .apply(new MyCustomDs1()); http.authorizeHttpRequests(authorize-> { // 권한 부여 // authorizeRequests가 deprecated됨에 따라 authorizeHttpRequests 사용 권장 ... // /user, /manager, /admin으로 들어가도 /loginForm으로 접근하도록 return http.build(); ① 보안 설정에 대한 내용을 HttpServlet 오브젝트인 http에 한번 추가를 시키고, ② 메소드 체이닝을 끊어낸 다음, ③ 권한 부여에 대한 내용을 추가했습니다. 권한 부여 방식 ① authorizeRequests deprecated authorizeRequests 메소드도 설명란을 보면 어노테이션으로 Deprecated가 걸린 것을 볼 수 있습니다. 자동완성으로 확인을 해보자면 authorizeHttpRequests() 메소드를 람다식 표현으로 쓰라고 되어 있네요. ② antMatchers deprecated antMatchers는 흔적도 없이 사라졌나 봅니다.. requestMatchers() 메소드를 사용하시면 되겠습니다. ③ hasAnyRole() 내의 parameter format 변경 기존에는 hasAnyRole() 파라미터로 ROLE_USER, ROLE_ADMIN 이런식으로 앞에 ROLE_을 붙여서 권한을 부여했지만, Spring Security가 업데이트 되면서 hasAnyRole 메소드에서 권한을 부여할 때 각 role마다 앞에 자동으로 'ROLE_'을 붙여줍니다. 즉, 기존 방식대로 사용하다 보면 httpServlet 입장에서는 'ROLE_ROLE_USER', 'ROLE_ROLE_ADMIN' 이런식으로 인식하게 되어서 권한을 부여받지 못하는 부분이 있었습니다. 대략 이렇게 정리를 마치겠습니다. Spring같은 Framework의 큰 장점이자 단점은 업데이트가 수시로 된다는 점인데요. 업데이트가 되면서 사용하는 데에 조금 더 편안해지겠지만, 이에 대해 인지하지 못한다면 사용할 수 없다는 점이 아닐까 생각됩니다. 긴 글 읽어주셔서 감사하고, 저와 이 글을 읽으신 모든 분들의 코딩 실력이 한 발자국 더 앞설 수 있기를 기도하겠습니다. 제가 작업한 프로젝트도 github에 올려놨으니 확인이 필요하시다면 한번씩 방문해주세요~ http://github.com/msun0215/jwt.git 오늘의 결론 ① Spring 공식 홈페이지 업데이트 될때마다 찾아가서 확인해보자 ② 영어 공부 열심히 하자
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()); 여기서 사용한 Token 은 그저 로그인 정보를 담아서 authenticationManager에 담기 위한 토큰인가요 ??
안녕하십니까 영한님. 영한님 덕분에 JAVA Spring 기반 백엔드 개발의 시작을 할 수 있었고, 이제는 5개월 차 스타트업 백엔드 개발자로써 커리어를 시작하게 되었습니다. 현업에 와서는 오히려 취준생 때 보다 더 많은 고민을 하게 되느데요, 질문 1. 그중 최근에 CRUD의 각 상황별 API 응답을 어떻게 보내는게 적절할지, 질문 2. 그리고 ResponseDto를 어떻게 구성하는것이 적절할지에 대한 고민을 팀장님과 함께 하고있어서, 영한님의 생각을 여쭙고자 질문글을 올리게 되었습니다. 답변 해주시면 정말 감사할 거 같습니다! Q1. 생성, 조회 , 수정, 삭제 API의 응답을 각각 어떻게 보내시는지 여쭤보고 싶습니다. 가장 먼저 조회의 경우는 말 그대로 path의 Entity 및 관련된 Entity 정보를 조합하여 응답 DTO로 변환하여 보내고 있습니다. 그런데 나머지 Write Operation에 대한 응답을 어디까지 보내야 하냐가 이슈 입니다. 예를들면 엔티티의 생성의 경우 엔티티의 응답 DTO를 보내면 - 프론트에서 별도의 조회 API 호출 없이 바로 프론트가 화면에 뿌려줄 수 있으니깐 저는 생성의 경우에도 조회와 마찬가지로 Entity의 정보를 조합하여 응답 DTO로 변환하여 보내고 있었습니다. 그런데 이러한 부분이 Command Query Sperate 원칙에 어긋나는것 같아, 영한님께서는 혹시 생성한 Entity의 Key만 보내시는지, 아니면 Entity의 정보를 DTO로 변환하여 보내시는지 궁금합니다. 만약 Entity의 Id만 보내신다면 이후에 별도로 조회API를 호출해야 하고 그 또한 비용일텐데 이러한 부분은 어떻게 하시는지 여쭤보고 싶습니다. 이제 수정 API인데요, 제가 다룬 비즈니스 로직의 경우 수정 비즈니스 로직이 다양하고 , 각 비즈니스 로직의 경우 다뤄지는 Entity의 종류가 다른 경우였습니다. (중심 Entity는 동일하지만, 연관된 Entity를 누구까지 건드리냐의 차이) 그래서 하나의 통일된 응답으로 보내기 모호한 점이 첫 번째 이유이고, 애초에 수정 후에 프론트 화면에서 그 엔티티의 정보를 보여줄 필요가 없어서 라는 두번째 이유에 의해서 에초에 엔티티의 Id값도 보내지 않고 있었는데요, 이 수정 API의 응답을 영한님은 어떻게 진행하지는지 그 이유가 궁금합니다. 마지막으로 삭제의 경우는 정말, 프론트에게 보낼 응답이 없어도 되는 경우 라고 생각했는데요, 팀장님의 의견은 만약에 나중에 삭제한 Entity를 복구하는 요구사항이 추가되는것을 고려하여 Id 정도는 넘기자는 의견을 내어주셨습니다. 마찬가지로 삭제의 경우도 어떤식으로 수행하시는지 그 이유가 궁금합니다. Q3. 마지막으로 Entity의 ResponseDto의 필드를 어떤식으로 구성하시는지 궁금합니다 예를들면 저의 경우는 API는 프론트와 서버 간의 스펙이라고 생각하고, Entity의 단건조회의 경우는 단건 조회용 ResponseDto를, 전체조회의 경우는 전체 조회용 SummaryResponseDto를 별도로 만들어서 사용하고 있었습니다. 저희 팀장님 께서는 프론트쪽도 일을 해오시다가 , 백엔드쪽 분야로 전향하신 케이스 인데요, 그렇다 보니 어떻게 해야 프론트의 생산성이 올라가는지를 고려하시는 분이셨고, 팀장님의 생각은 서버에서 넘겨주는 응답에 일관성이 있어야 그 응답을 사용하는 프론트 측도 학습이 되고 놓치는 부분 없이 생산성이 올라간다 는 의견이셨습니다. 그래서 Entity별로 당장 사용하지 않더라도 가능한 모든 필드를 담은 ResponseDto를 하나만 만들고, 해당 ResponseEntity의 조합으로 각 API별 응답 Dto를 만들어서 사용하면 , 프론트 측 에서는 일관성 있는 응답값을 사용할 수 있다는 의견이셨습니다. 물론 이 방법이 네트워크 패킷의 양을 쓸데없이 증가시킨다는 것을 알고 계시면서도, 생산성에 큰 영향을 미치는 부분이라고 생각하셨습니다. 예를들어 다음과 같이 각 Entity의 응답 Dto의 조합별로 API의 ResponseDto를 만들 수 있습니다. ResponseDto{ UserDto{ id : 1, name : “aaa” … // User엔티티의 거의 모든 필드 } ItemDto{ id : 2, name : “bbb”, … // Item엔티티의 거의 모든 필드 } } 저는 이러한 부분에 대해 생각해 본 적이 없이, 그냥 제가 “해당 API를 호출하는 화면에서 필요한 정보들만을 담아 (혹은 여러 화면에서 쓰인다면 여러개를 고려) ResponseDto를 각각 만들어서” 넘겼는데요 영한님께서는 이러한 ResponseDto를 구성하는 부분에 있어서 상황별로 필드를 재구성 하여 ResponseDto를 정의하여 사용하시는 편 인지 (SummaryResponseDto 등의 별도 Dto에 사용될 Entity의 필드들을 풀어서 정의하시는지) 아니면 생산성을 고려하여 각 Entity별 Dto를 만들고, 이들을 조합하여 ReponseDto를 정의하시는 편 이신지 , 혹은 다른 규칙이 있으신지 궁금합니다. 물론 그렇다고 , 팀장님의 의견에서 전체조회시 사용하는 DTO와 단건조회시 사용하는 DTO가 동일하더라도, 전체조회 후 단건조회를 할때 단건조회 API를 호출하지 말고 기존 Front가 가지고 있는 값을 쓰지는 말자는 의견 이십니다 (단건조회API는 별도로 호출해야 한다) 그저 핵심은 프론트가 다루는 ResponseDto의 일관성을 위해서 입니다 (결론은 생산성을 위해) 긴글 읽어주셔서 감사합니다. 항상 건강하셨으면 좋겠고, 다음 강의들도 손꼽아 기다리고 있습니다!
Spring Boot 3.0.0에서 최신판인 Spring Boot 3.1.2로 업데이트 되면서 Spring Security도 6.1.2로 업데이터 되었습니다. 이에 따라서 강사님 github - Version3 branch에 있던 프로젝트의 SecurityConfig.java 에서 중간중간에 .and()로 묶어주신 부분도 .and()가 deprecated 되면서 사용할 수 없게 되었습니다. 이에 따라 // 이전 생략 return http.csrf(CsrfConfigurer::disable) .sessionManagement(s->s.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .formLogin(f->f.disable()) .httpBasic(h->h.disable()) .apply(new MyCustomDs1()) // custom Filter .authorizeHttpRequests(authorize-> { // 권한 부여 // authorizeRequests가 deprecated됨에 따라 authorizeHttpRequests 사용 권장 authorize .requestMatchers("/api/v1/user/**").hasAnyRole("hasRole('ROLE_USER') or hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')") .requestMatchers("/api/v1/manager/**").hasAnyRole("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')") .requestMatchers(("/api/v1/admin/**")).hasAnyRole("hasRole('ROLE_ADMIN')") .anyRequest().permitAll(); }); apply(new MyCustomDs1()) 이후에 authorizeRequest 메소드를 람다식으로 변환시켜서 이어줄려고 하는데, 위 캡쳐 이미지와 같이 에러가 떴습니다. 내용을 보자 하니 http에 custom Filter(new MyCustomDs1())를 apply시키고 이후에 authorizeHttpRequests를 실행시켜야 하는데, authorizeHttpRequest가 MyCustomDs1 내부에 있는 함수로 인식하는거 같은데,, 어떻게 해결해야 하는지 방법을 공유해주시면 감사하겠습니다.
당연히 구글링 해보셨져? 원하는 결과를 못찾으셨나요? 어떤 검색어를 입력했는지 알려주세요. - 검색해보았으나, 원하는 결과를 찾지 못했습니다. 검색어: 서비스 계층에서의 @Transactional 사용 기준 문제가 발생한 코드(프로젝트)를 Github에 올리시고 링크를 알려주세요. ``` java @Transactional public void write(Long postId , CommentCreate request) { Post post = postRepository.findById(postId).orElseThrow(PostNotFound:: new ) ; Comment comment = Comment.builder() .post(post) .author(request.getAuthor()) .password(request.getPassword()) .content(request.getContent()) .build() ; post.addComment(comment) ; } public void delete(Long commentId , CommentDelete request) { Comment comment = commentRepository.findById(commentId).orElseThrow(CommentNotFound:: new ) ; commentRepository.delete(comment) ; } ``` 서비스 계층에서 댓글 작성의 경우 @Transational 적용했지만, 삭제의 경우 적용하지 않았는데 강의에서 이 두 경우의 차이에 대해 이해해야 하고, 찾아보라고 이야기해주셨습니다. 지금까지 제가 공부한 내용에 비추어보면 서비스 게층의 생성 및 삭제의 경우모두 @Transcational 어노테이션을 항상 사용해 왔습니다. 왜냐하면, 수정 삭제의 경우 하나의 트랜잭션에서 시작 및 종료되어야 하기 때문이라고 생각했습니다. 구글링도 해보았는데, 납득할만한 이유를 찾지 못하여 호돌맨님 및 수강생분들에게 질문드립니다.
강의 내용을 최대한 놓치지 않고 다 작성한 것 같은데 포스트맨에 나오는 내용이 다릅니다.. [22:12:33.504][WARN ][org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver.logException:line207] - Resolved [jakarta.validation.ConstraintDefinitionException: HV000243: Constraint ConSilkTea.SmallRecordServer.common.annotation.ValidEnum references constraint validator type ConSilkTea.SmallRecordServer.core.annotation.ValidEnumValidator, but this validator is defined for constraint type ConSilkTea.SmallRecordServer.core.annotation.ValidEnum.] 이건 로그에 나온겁니다.. 전에 했던 블로그 api랑 충돌이 일어나는 것 같은데 서버 하나당 api는 1개 밖에 사용을 못할까요? 다중으로 사용하고 싶다면 어떻게 변경을 하면 좋을지 몰라 질문 남깁니다
안녕하세요, 강의 너무 잘 보고있습니다. `JwtTokenProvider.kt` 생성 중, 궁금한 것이 있어 질문 남깁니다. createToken에서 signWith에 사용하는 key를 private val key by lazy { Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)) } yml파일에 쓴 key 그대로 사용하는 것이 아닌, 아래와 같이 한번 더 decode를 거치는 것으로 보여지는데요. signwith를 통해 또 다시 HS256으로 인코딩 하는데, 그 전에 한번 더 디코딩한 키를 넣는 이유가 궁금합니다!
- 학습 관련 질문을 남겨주세요. 상세히 작성하면 더 좋아요! - 먼저 유사한 질문이 있었는지 검색해보세요. - 서로 예의를 지키며 존중하는 문화를 만들어가요. - 잠깐! 인프런 서비스 운영 관련 문의는 1:1 문의하기를 이용해주세요. remember-me 인증이 토큰 기반 인증을 사용해 유효성을 검사한다고 하셨는데, 토큰 기반 인증은 statelesss한 방식 알고있습니다. remember-me 인증은 세션을 이용한 서버 기반 인증 방식으로 사용자 상태를 저장하고, 토큰 기반 인증 방식은 그 반대라고 생각되는데, 강사님은 토큰 기반 인증 방식을 좀 더 넓은 범위로 보신건지 궁금합니다!