묻고 답해요
164만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
스프링부트 3.15버전인데 junit5 vs junit4 어떤 걸 사용해야 하나요?
스프링부트 3.15버전 사용하고 있는데강의 자료에 나와있는 것처럼 build.gradle에 내용을 추가해서 junit4를 사용하는 게 좋을까요?추가하지 않으면 junit5로 동작하는 건가요?
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
Kafka connector
window 사용 중이고, 서버는 전부 열려있습니다만, POSTMAN에서 JSON 형식으로 { "name" : "my-source-connect", "config" : { "connector.class" : "io.confluent.connect.jdbc.JdbcSourceConnector", "connection.url":"jdbc:mysql://localhost:3306/mydb", "connection.user":"root", "connection.password":"test1357", "mode": "incrementing", "incrementing.column.name" : "id", "table.whitelist":"users", "topic.prefix" : "my_topic_", "tasks.max" : "1" }를 보냈을 때 뜨는 오류가 "error_code": 500, "message": "Failed to find any class that implements Connector and which name matches io.confluent.connect.jdbc.JdbcSourceConnector, available connectors are: PluginDesc{klass=class org.apache.kafka.connect.file.FileStreamSinkConnector, name='org.apache.kafka.connect.file.FileStreamSinkConnector', version='3.6.0', encodedVersion=3.6.0, type=sink, typeName='sink', location='classpath'}, PluginDesc{klass=class org.apache.kafka.connect.file.FileStreamSourceConnector, name='org.apache.kafka.connect.file.FileStreamSourceConnector', version='3.6.0', encodedVersion=3.6.0, type=source, typeName='source', location='classpath'}, PluginDesc{klass=class org.apache.kafka.connect.mirror.MirrorCheckpointConnector, name='org.apache.kafka.connect.mirror.MirrorCheckpointConnector', version='3.6.0', encodedVersion=3.6.0, type=source, typeName='source', location='classpath'}, PluginDesc{klass=class org.apache.kafka.connect.mirror.MirrorHeartbeatConnector, name='org.apache.kafka.connect.mirror.MirrorHeartbeatConnector', version='3.6.0', encodedVersion=3.6.0, type=source, typeName='source', location='classpath'}, PluginDesc{klass=class org.apache.kafka.connect.mirror.MirrorSourceConnector, name='org.apache.kafka.connect.mirror.MirrorSourceConnector', version='3.6.0', encodedVersion=3.6.0, type=source, typeName='source', location='classpath'}" 입니다. 어떤오류인지 잘 모르겠네요
-
미해결스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
build 오류
이렇게 오류가 뜨는데 어떻게 해결해야 하는지 궁금합니다.
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
테스트 코드 작성
항상 강의 잘 듣고 있습니다!postman으로 테스트하다가, 이걸 테스트 코드로 작성해 자동화하면 어떨까? 라고 생각을 해서 테스트 코드를 작성 중입니다.문제는 테스트 코드에서 이벤트 관련 통합 테스트를 어떻게 작성해야 효율적인지 모르겠어요..기존의 테스트 코드로는 이벤트가 한 번씩 늦게 전달되는 경우도 있어서 간헐적으로 실패를 했습니다. 이를 해결하기 위해서 Timeout을 걸어 일정 시간 이내로 이벤트가 도착하는지 체크하고 있는데, 이렇게 되니 전체적인 테스트 속도가 느려지게 되더라고요. 보통 이벤트 관련 테스트는 어떻게 작성을 하나요?
-
미해결스프링 부트 - 핵심 원리와 활용
프로메테우스 병렬처리 클라이언트
안녕하세요 질문이 있습니다. 프로메테우스 라이브러리로 클라이언트를 만들때파이썬 언어의 병렬처리를 이용하면 프로메테우스가 잘 작동하지 않는다는 코멘트가 있는데혹시 관련하여 아시는바가 있나요? 파이썬 언어를 사용하여,프로메테우스 병렬처리 클라이언트를 만들려고 하는데해당 사항이 맞다면 작업을 고려해봐야 할 것 같아서 고민입니다. 강의와 직접적인 연관이 없으나관련 분야에 전문가이실 것이라 생각되어질문 드렸습니다. 관련하여 아시는 내용이 있으시다면코멘트 부탁 드리겠습니다.
-
미해결호돌맨의 요절복통 개발쇼 (SpringBoot, Vue.JS, AWS)
검증시 변수값이 아니라 상수값으로 비교하는 이유가 있나요?
문제가 발생한건 아니고 단순 질문입니다! 테스트 짜실 때 보면,// 1번 코드 assertEquals("상수값", findComment.getAuthor()); // 2번 코드 assertEquals(addComment.getAuthor, findComment.getAuthor());1번처럼 상수값과 비교를 많이 하시는데, 저는 2번처럼 저장하려고 했던 객체와 실제로 저장된 후 리턴된 객체간의 비교를 많이 하는 것 같습니다. 2번이 유지보수의 측면으로 봤을 때 더 좋아보이는데 1번 스타일로 테스트 코드를 짜는 이유가 있으실까요?
-
해결됨토비의 스프링 부트 - 이해와 원리
url만 희한하게 null이 나옵니다..
테스트 실패해서 디버깅 돌리는데,이상하게 다른 거 다 잘 들어오는 거 같은데 url만 null이 찍히네요.. 원인이 무엇인지 모르겠습니다.. DataSourceTest의 connect() 메소드입니다.
-
미해결스프링 시큐리티 OAuth2
authenticationEntryPoint 를 기본 설정된 /login이 아닌 react 웹 페이지로 설정 시 cors 문제가 지속해서 발생합니다.
authenticastion Entry Point로 지정 시 cors문제가 계속해서 진행되어 Authorization Code를 받아올 수 없습니다.코드 첨부Authorization Server Config@Bean @Order(Ordered.HIGHEST_PRECEDENCE) public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); http .getConfigurer(OAuth2AuthorizationServerConfigurer.class) .oidc(Customizer.withDefaults() ); http .exceptionHandling((exceptions) -> exceptions .authenticationEntryPoint( new LoginUrlAuthenticationEntryPoint("http://localhost:3000") // new LoginUrlAuthenticationEntryPoint("/login") )) http.oauth2ResourceServer((resourceServer) -> resourceServer .jwt(Customizer.withDefaults())); return http.build(); } @Bean public RegisteredClientRepository registeredClientRepository() { RegisteredClient oidcClient = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId("oidc-client") .clientSecret("{noop}secret") .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) .redirectUri("https://oidcdebugger.com/debug") .redirectUri("https://test-b821b.web.app") .postLogoutRedirectUri("http://127.0.0.1:8080/") .scope(OidcScopes.OPENID) .scope(OidcScopes.PROFILE) .clientSettings(ClientSettings.builder().requireAuthorizationConsent(true).build()) .build(); return new InMemoryRegisteredClientRepository(oidcClient); } @Bean public AuthorizationServerSettings authorizationServerSettings() { return AuthorizationServerSettings.builder().issuer("http://localhost:6080").build(); } } Default Web Config @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurer() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOrigins("http://localhost:3000") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .exposedHeaders("*"); } }; } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowCredentials(true); configuration.addAllowedHeader("*"); configuration.addAllowedMethod("*"); configuration.addAllowedOrigin("http://localhost:3000"); configuration.setExposedHeaders(Arrays.asList("*")); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", configuration); return source; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .cors(customCorsConfig -> customCorsConfig.configurationSource(corsConfigurationSource())) .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(authorizeHttpRequestsCustomizer -> authorizeHttpRequestsCustomizer.requestMatchers(AUTH_WHITELIST).permitAll() .anyRequest().authenticated() ) .formLogin( httpSecurityFormLoginConfigurer -> httpSecurityFormLoginConfigurer.loginPage("http://localhost:3000").loginProcessingUrl("/login") // Customizer.withDefaults() ) ; return http.build(); } @Bean public UserDetailsService userDetailsService() { PasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); UserDetails userDetails = User.builder() .username("user") .password(passwordEncoder.encode("password")) .roles("USER") .build(); return new InMemoryUserDetailsManager(userDetails); } @Bean public JWKSource<SecurityContext> jwkSource() { KeyPair keyPair = generateRsaKey(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); RSAKey rsaKey = new RSAKey.Builder(publicKey) .privateKey(privateKey) .keyID(UUID.randomUUID().toString()) .build(); JWKSet jwkSet = new JWKSet(rsaKey); return new ImmutableJWKSet<>(jwkSet); } private static KeyPair generateRsaKey() { KeyPair keyPair; try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); keyPairGenerator.initialize(2048); keyPair = keyPairGenerator.generateKeyPair(); } catch (Exception ex) { throw new IllegalStateException(ex); } return keyPair; } @Bean public JwtDecoder jwtDecoder(JWKSource<SecurityContext> jwkSource) { return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); } @Bean public PasswordEncoder encoder() { return new BCryptPasswordEncoder(); } 위 코드로 진행했을 시 발생하는 Cors Error입니다 Access to XMLHttpRequest at 'http://localhost:6080/oauth2/authorize?client_id=oidc-client&redirect_uri=https%3A%2F%2Foidcdebugger.com%2Fdebug&scope=openid&response_type=code&response_mode=form_post&state=3it6dl9kewr&nonce=hyq3pnronj7&continue' (redirected from 'http://localhost:6080/login') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.3. 이로 인해 CorsFilter를 적용하게 되면 해당 Cors는 해결되지만 authorization code 가 Redirect되는 시점에 Cors 에러가 새롭게 나옵니다.CorsFilter @Component @Slf4j @Order(Ordered.HIGHEST_PRECEDENCE) public class CorsFilter implements Filter { private static final Set<String> allowedOrigins = new HashSet<String>(); static { allowedOrigins.add( "http://localhost:3000" ); } @Override public void init(FilterConfig fc) throws ServletException { } @Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletResponse response = (HttpServletResponse) res; HttpServletRequest request = (HttpServletRequest) req; String origin = request.getHeader(HttpHeaders.ORIGIN); log.info("Origin: {}", origin); if (origin != null) { Optional<String> first = allowedOrigins.stream().filter(origin::startsWith).findFirst(); first.ifPresent(s -> response.setHeader("Access-Control-Allow-Origin", s)); } response.setHeader("Access-Control-Allow-Credentials", "true"); response.setHeader("Access-Control-Allow-Methods", "*"); response.setHeader("Access-Control-Max-Age", "3600"); response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Key, Authorization"); if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { response.setStatus(HttpServletResponse.SC_OK); } else { chain.doFilter(req, res); } } @Override public void destroy() { } Cors 오류Access to XMLHttpRequest at 'https://oidcdebugger.com/debug?code=1o_jSQQVu6EB4XDq-xCWP3qrzp2VDdGbH_AN7iBJGU5gQRC7BRFkQYdn2A6P-6G5Aoi8XLJLaVR4MSVktNzDvYLMmj_UXahcWfEwQdA-tk4GCw5Bff4mXn2q6mIYfs_d&state=3it6dl9kewr' (redirected from 'http://localhost:6080/login') from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.혹시 외부 로그인 페이지와 연동한 예시가 있거나, Cors 문제를 해결할 방안이 있는지 궁금합니다ㅠㅠ
-
해결됨실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
계층형 테이블에 매핑된 엔티티 컬렉션을 fetch join으로 가져올 때 쿼리 개수 질문드립니다.
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]코드는 마지막에 첨부되어 있습니다.게시글 엔티티 Post가 게시글에 달린 댓글 엔티티 Comment의 컬렉션을 프로퍼티로 갖습니다. 댓글 엔티티 Comment는 COMMENT, REPLY 타입으로 구분되며, 프로퍼티로 parentComment와 replies를 갖습니다. COMMENT 타입은 parentComment == null이며, REPLY 타입은 replies == null입니다. Post.comments는 Comment의 타입을 고려하지 않고 모두 갖고 있습니다.위 상황에서 게시물 상세 정보를 가져올 때, comments를 fetch join하여 Post 엔티티를 가져와 댓글들을 타입 계층에 따라 분리하는 작업을 합니다. 이때 REPLY 타입의 replies에 접근하지 않습니다. COMMENT 타입의 replies에는 접근하며 해당 replies는 모두 같은 게시물에 속해있습니다.예상했던 쿼리의 개수는 1 혹은 (1 + COMMENT 타입 수) 였습니다. 애초에 둘 중 무엇인지 궁금해서 일을 진행했었습니다.테스트를 실행해보면, post.comments의 요소에 처음 접근할 때 REPLY 타입을 가져오는 쿼리가 발생합니다. 그런데 이후에 다른 COMMENT 타입에 접근할 때는 REPLY를 가져오는 쿼리가 발생하지 않습니다. 만약 Post의 COMMENT 타입 Comment.replies에 접근할 때 쿼리가 발생하는 거라면 COMMENT 타입의 개수만큼 발생해야 하는게 아닌가요? 제가 무엇을 놓치고 있는지 궁금합니다.논외로, Spring data JPA의 @Query를 통해 작성한 단순한 정적 쿼리가 계속 반복된다면 QeuryDSL로 옮겨 반복을 줄이는게 합리적일까요? 아니면 spring data jpa의 편리함을 유지하는게 합리적인가요? 감사합니다.public class Post extends BaseEntity { public static final int TITLE_MAX_LENGTH = 50; public static final int CONTENT_MAX_LENGTH = 10000; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "post_id", nullable = false, updatable = false) private Long id; @Column(name = "title", nullable = false, length = TITLE_MAX_LENGTH, updatable = false) private String title; @Column(name = "content", nullable = false, length = CONTENT_MAX_LENGTH) private String content; @Column(name = "hits", nullable = false) private Long hits; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "writer_id") private Member writer; @OneToMany(fetch = FetchType.LAZY, mappedBy = "post", orphanRemoval = true) @ToString.Exclude private List<Comment> comments = new ArrayList<>(); }public class Comment extends BaseDeleteEntity { public static final int CONTENT_LENGTH = 1000; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "comment_id", nullable = false) private Long id; @Column(name = "content", nullable = false, length = CONTENT_LENGTH) private String content; @Column(name = "type", nullable = false, updatable = false) private CommentType type; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "post_id", nullable = false, updatable = false) private Post post; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "writer") private Member writer; @OneToMany(fetch = FetchType.LAZY, mappedBy = "parent", orphanRemoval = true) private List<Comment> replies = new ArrayList<>(); @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "parent_comment_id", updatable = false) @ToString.Exclude private Comment parent; } @Query(value = """ SELECT p FROM Post p LEFT JOIN FETCH p.writer AS w LEFT JOIN FETCH w.profile LEFT JOIN FETCH p.comments WHERE p.id = :postId """ ) Optional<Post> findPostById(@Param("postId") final Long id); @Autowired EntityManager em; @Autowired PostRepository postRepository; @Test public void 정상작동테스트_추가적인_쿼리_발생_x() throws Exception { //given Post post = PostTest.create("username", "nickname"); Member member = post.getWriter(); em.persist(member); em.persist(post); Long postId = post.getId(); int commentCount = 10; int replyCount = 2; createComment(post, member, commentCount, replyCount); em.flush(); em.clear(); //when int totalCommentCount = commentCount * (replyCount + 1); Post findPost = postRepository.findPostById(postId).get(); System.out.println(); //then for (Comment comment : findPost.getComments().stream().filter(Comment::isCommentType).toList()) { // 이때 REPLY 타입을 조회하는 쿼리가 1회 발 System.out.println("comment = " + comment); System.out.println("comment.getReplies().get(0) = " + comment.getReplies().get(0)); } Assertions.assertThat(post.getComments().size()).isEqualTo(totalCommentCount); }[테스트 실행 시 쿼리]2023-11-02T11:45:00.154+09:00 DEBUG 51844 --- [ main] org.hibernate.SQL :selectp1_0.post_id,c1_0.post_id,c1_0.comment_id,c1_0.content,c1_0.created_by,c1_0.created_date,c1_0.deleted_date,c1_0.is_deleted,c1_0.last_modified_by,c1_0.last_modified_date,c1_0.parent_comment_id,c1_0.type,c1_0.writer,p1_0.content,p1_0.created_by,p1_0.created_date,p1_0.hits,p1_0.last_modified_by,p1_0.last_modified_date,p1_0.title,w1_0.member_id,w1_0.created_by,w1_0.created_date,w1_0.last_modified_by,w1_0.last_modified_date,w1_0.login_id,w1_0.login_type,w1_0.profile_id,p2_0.profile_id,p2_0.created_by,p2_0.created_date,p2_0.last_modified_by,p2_0.last_modified_date,p2_0.nickname,w1_0.user_role,w1_0.social_login_id,w1_0.usernamefrompost p1_0left joinmember w1_0on w1_0.member_id=p1_0.writer_idleft joinprofile p2_0on p2_0.profile_id=w1_0.profile_idleft joincomment c1_0on p1_0.post_id=c1_0.post_idwherep1_0.post_id=?2023-11-02T11:45:00.162+09:00 INFO 51844 --- [ main] p6spy2023-11-02T11:45:01.221+09:00 DEBUG 51844 --- [ main] org.hibernate.SQL :selectr1_0.parent_comment_id,r1_0.comment_id,r1_0.content,r1_0.created_by,r1_0.created_date,r1_0.deleted_date,r1_0.is_deleted,r1_0.last_modified_by,r1_0.last_modified_date,r1_0.post_id,r1_0.type,r1_0.writerfromcomment r1_0wherearray_contains(?,r1_0.parent_comment_id)[반복되는 spring data jpa 쿼리]public interface PostRepository extends JpaRepository<Post, Long>, PostQueryRepository { /** * Post 반환 시 Member, Profile을 fetch join한다. ~ToOne 매핑관계에 대한 fetch join은 별명을 사용할 수 있고, 연계하여 fetch * join할 수 있다. * * @param id must not be {@literal null}. * @return */ @Query(value = """ SELECT p FROM Post p LEFT JOIN FETCH p.writer AS w LEFT JOIN FETCH w.profile LEFT JOIN FETCH p.comments WHERE p.id = :postId """ ) Optional<Post> findPostById(@Param("postId") final Long id); /** * Post를 페이징 처리하여 Page<Post>로 반환한다. 이때 Member와 Profile을 fetch join한다. * * @param pageable the pageable to request a paged result, * can be {@link Pageable#unpaged()}, * must not be {@literal null}. */ @Query(value = """ SELECT p FROM Post p LEFT JOIN FETCH p.writer AS w LEFT JOIN FETCH w.profile """, countQuery = "SELECT count(p) FROM Post p" ) @Override Page<Post> findAll(final Pageable pageable); /** * WriterId가 memberId와 같은 Post를 페이징 처리하여 Page<Post>로 반환한다. 이때 Member와 Profile을 fetch join한다. * * @param writerId writerId가 일치하는 Post들을 반환한다. * @param pageable 페이징 정보 */ @Query(value = """ SELECT p FROM Post p LEFT JOIN FETCH p.writer AS w LEFT JOIN FETCH w.profile WHERE w.id = :writerId """, countQuery = "SELECT count(p) FROM Post p WHERE p.writer.id = :writerId" ) Page<Post> findAllByWriterId(@Param("writerId") final Long writerId, final Pageable pageable); }
-
해결됨Spring Boot JWT Tutorial
8:45 spring security 3.1.5 설정 방법 (버전 안 맞춰서 안될때)
spring security 3.1.5 버전 방식입니다.기존에 implement 하지 않고 클래스를 @configuration 해서구성 파일로 인식하게 만들고 해당 메서드를 @bean 을 주입시켜 사용하는 방식입니다.처음 참조할 부분은 여기를 참조 하시면 됩니다. 처음 설정 방법 :: 3.1.5 방법 1SecurityConfig::SecurityFilterChain 메서드를 수정하기 @Bean public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { httpSecurity .csrf(AbstractHttpConfigurer::disable) .exceptionHandling((handling) -> handling.authenticationEntryPoint(jwtAuthenticationEntryPoint) .accessDeniedHandler(jwtAccessDeniedHandler) ) .headers((header) -> header.frameOptions( HeadersConfigurer.FrameOptionsConfig::sameOrigin ) ) .authorizeHttpRequests((registry) -> registry.requestMatchers("/api/hello").permitAll() .requestMatchers("/api/authentication").permitAll() .requestMatchers("/api/signup").permitAll() .anyRequest().authenticated() ); return httpSecurity.build(); } 방법 2JwtSecurityConfig에 메서드를 하나 추가 한다. public HttpSecurity configureAndReturn(HttpSecurity httpSecurity) { httpSecurity.addFilterBefore( new JwtFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class ); return httpSecurity; }SecurityConfig::SecurityFilterChain 메서드를 수정하기@Bean public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception { return new JwtSecurityConfig(tokenProvider).configureAndReturn( httpSecurity .csrf(AbstractHttpConfigurer::disable) .exceptionHandling((handling) -> handling.authenticationEntryPoint(jwtAuthenticationEntryPoint) .accessDeniedHandler(jwtAccessDeniedHandler) ) .headers((header) -> header.frameOptions( HeadersConfigurer.FrameOptionsConfig::sameOrigin ) ) .authorizeHttpRequests((registry) -> registry.requestMatchers("/api/hello").permitAll() .requestMatchers("/api/authentication").permitAll() .requestMatchers("/api/signup").permitAll() .anyRequest().authenticated() ) ).build(); } 개인적으로는 방법 1이 깔끔하다고 느낍니다.
-
해결됨스프링 시큐리티
웹기반 인가처리 실시간 DB 반영이 thread safe 한가? 질문
안녕하세요. 선생님.선생님 강의로 많이 배우고 있네요.UrlFilterInvocationSecurityMetadataSource 를 직접 구현하여 DB와 연동하는 것을 보여주셨는데.이 클래스에 Resouce 인가 설정을 위한 클래스의 멤버 변수로 LinkedHashMap<RequestMatcher, List<ConfigAttribute>> requestMap 을 사용하네요.만약 DB에 Resource, Role 인가 설정 변경 시 reload()가 불렸을 때 DB에서 모두 가져와 requestMap 에 put 하는데 이것이 multi thread 에 safe 한지요?만약 thread에 safe 하지 않다면 좋은 방법이 있는지요? lock을 쓰기에는 read 요청이 많아 부담스러울 것 같다는 느낌이 있네요.강의에서 Role 추가 시 실시간 설정 반영은 하지 않았지만 RoleHierarchy 또한 마찬가지 인 것 같아요.운영 시 거의 변경이 안되기 때문에 그냥 놔두는게 가장 좋은 방법인지? 선생님의 의견을 알고 싶어요.감사합니다.
-
미해결Spring Cloud로 개발하는 마이크로서비스 애플리케이션(MSA)
gateway와 userService 간의 인증 정보 공유 문제에 관해 질문 드립니다!
안녕하세요 선생님 강의 잘 듣고 있습니다.강의 수강 중 질문이 생겨서요! user-service는 mvc 기반의 app으로 spring security를 통해 인증을 처리하므로, tomcat의 JSESSIONID 를 기반으로 하여 Security Context에 Authentication 객체의 유무를 통해 인증이 된 사용자인지 안 된 사용자인지 Security Filter Chain에서 체크를 하는 것으로 알고 있습니다.그런데gateway에서 설정하신 application.yml 에 따르면 모든 요청 헤더에 존재하는 Cookie를 삭제하고 --> 서버에서 매번 새 요청으로 인식gateway 는 netty 서버를 사용하고 user-service는 tomcat 서버를 사용하잖아요.그래서 두 서버 사이 Context가 공유가 안 될것 같아서요... 그래서 gateway에서는 인증을 했다고 요청을 넘기지만 user-service에서는 그 요청을 받았을 때 해당하는 SESSIONID 의 Context에 Authentication 객체가 존재하지 않아 인증이 필요한 url에 접근할 수 없을 거라고 생각했습니다. 어떻게 인증 완료 정보를 두 서비스가 공유할 수 있는 건가요?? 또한 gateway도 이중화 구성이 가능한가요?.. userService를 2,3중화 한 것처럼 gateway도 여러 대의 인스턴스로 기동하면 eureka에서 다중 구성한 서버로 인식해주는 건가요??... 혹시 gateway에도 Spring Security를 적용시키는 경우도 있을까요?.. 권한 별 접근 제어를 구현하기 위해서는 해당 라이브러리를 사용해야 할 것 같아서요!또한 gateway에서 커스텀 필터를 만들 때, apply 메소드의 return 타입이 GatewayFilter 로 되어있는데, Mono<Void> 타입으로 return을 해도 되는 건가요?... Mono<Void>는 mvc에서 return 타입이 void 인 것과 같은 의미 아닌가요?... 열심히 강의해주셨는데 질문의 너무 많아 죄송합니다..ㅠ 답변 기다리겠습니다! 항상 감사드립니다!!
-
해결됨스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
private static Map<Long, Member> store = new HashMap<>(); 코드 이해가 안가요
Member.javapackage Hello.hellospring.domain; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @Entity public class Member { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } } MemoryMemberRepository.javapackage Hello.hellospring.repository; import Hello.hellospring.domain.Member; import java.util.*; public class MemoryMemberRepository implements MemberRepository { private static Map<Long, Member> store = new HashMap<>(); private static long sequence = 0L; @Override public Member save(Member member) { member.setId(++sequence); store.put(member.getId(), member); return member; } @Override public Optional<Member> findByid(Long id) { return Optional.ofNullable(store.get(id)); } @Override public Optional<Member> findByName(String name) { return store.values().stream() .filter(member -> member.getName().equals(name)) .findAny(); } @Override public List<Member> findAll() { return new ArrayList<>(store.values()); } public void clearStore() { store.clear(); } } MemoryMemberReposirory.java 코드에서 private static Map<Long, Member> store = new HashMap<>(); 부분이 이해가 가질 않습니다..ㅠㅠ Map으로 <키 , 값>을 받으려고 하는것까진 알겠는데 왜 키는 id의 데이터타입인 Long으로 들어가는데 값은 name의 데이터타입인 String으로 들어가는것이 아닌 Member 객체로 들어가게 되는건가요..??
-
미해결자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
deleteUser 관련 질문
현재 코드에서는 name을 기준으로 delete를 하는데요, findByName(name)을 이용한 코드에서 DB 안에 같은 이름의 회원이 2명 이상인 경우엔 오류가 생깁니다. 그 이유가 find는 return 개수가 1건이기 때문에 rollback 된다고 생각했습니다.이 버그를 수정하기 위해서 deleteUser의 파라미터는 Long id 로 수정했는데, 또 안 되더라고요...?findAll을 쓰면 같은 이름을 가진 모든 회원이 삭제될 것 같고... 어떻게 하면 동명이인의 회원 중에서 내가 원하는 한 회원만 삭제할 수 있나요? + 추가 방금 다른 학생분의 질문과 답변을 읽었습니다. 같은 내용의 질문인 것 같네요!그러면 파라미터를 Long id 로 변경하되, 현재 실습 중인 UI에서도 코드를 수정해야하는 부분이 있기에 삭제가 안 되는 게 맞다고 이해하면 될까요?
-
미해결스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
윈도우 gradlew build 오류
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]jdk 17 스프링부트 3.0
-
미해결스프링 부트 - 핵심 원리와 활용
springboot-web 사용하지 않고, springboot-actuator 사용하는 방법 있을까요?
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)예3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)예[질문 내용]제가 현재 액츄에이터를 적용하려는 프로젝트는 spring-boot-starter만 사용해서 ApplicationRunner를 상속받아서 사용하고 있습니다. 이 상태에서 spring-actuator를 붙여서 사용해보니 actuator port를 열지 않아서, springboot-starter-web을 추가하고 application port도 열고, actuator port도 열리는 것을 확인했습니다. 기존 상태대로 applicationRunner를 상속받아 사용하고, actuator 관련 서버만 따로 떠서 port를 열어주는 방법은 없을까요?
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
빌드가 안됩니다ㅜㅜ
강의 자료 코드 다운 받아서 실행하니깐 아래와 같이 나옵니다..사실 처음에 빌드 에러 발생했었는데 그건 영한님께서 항상 settings하시는대로 따라하니깐해결된 거 같은데, 이제는 아래와 같은 문제가 발생합니다ㅠ
-
해결됨쥬쥬와 함께 하루만에 시작하는 백엔드 - 스프링, 도커, AWS
pk, fk 연결
맛집 리뷰 서비스 만들어보는 부분 따라하고 있는데요궁금한점이 있어요. erd 보시면 맛집 id가 리뷰, 메뉴 테이블의 fk로 들어가 있잖아요그런데 코드단에서 이걸 연결해 주는 작업을 한 적이 없는 거 같은데h2에 생성된 디비에는 어떻게 연결이 되어있는 건가요?코드에서 이 작업을 해주는 부분이 어디인가요?
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
java.lang.IllegalStateException: Failed to load ApplicationContext 오류가 납니다.
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/)[질문 내용]여기에 질문 내용을 남겨주세요.다른 답변들을 보고 모두 따라해 봤는데 아무 소용 없었습니다. h2 콘솔로는 실행이 되고 접속도 다 됩니다 근데 test를 하면 자꾸 java.lang.IllegalStateException: Failed to load ApplicationContext 이 에러가 생깁니다. at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:98) at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:124) at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190) at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132) at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:248) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97) at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329) at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306) at org.junit.runners.ParentRunner.run(ParentRunner.java:413) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:69) at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38) at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11) at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35) at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235) at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)MVCC=TRUE 지우라고 한 답변들도 있어서 지우는데도 안되네요;;;
-
미해결스프링과 JPA 기반 웹 애플리케이션 개발
compile 시 에러
컴파일 시 아래와 같은 에러가 발생하는데 해결에 어려움이 있습니다. 도움부탁드립니다. "C:\Program Files\Java\jdk-11.0.17\bin\java.exe" -Dmaven.multiModuleProjectDirectory=D:\SpringPrj\cryptoWebService "-Dmaven.home=C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.1\plugins\maven\lib\maven3" "-Dclassworlds.conf=C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.1\plugins\maven\lib\maven3\bin\m2.conf" "-Dmaven.ext.class.path=C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.1\plugins\maven\lib\maven-event-listener.jar" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.1\lib\idea_rt.jar=49589:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.1\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.1\plugins\maven\lib\maven3\boot\plexus-classworlds-2.6.0.jar;C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.1\plugins\maven\lib\maven3\boot\plexus-classworlds.license" org.codehaus.classworlds.Launcher -Didea.version=2020.3.1 compile[INFO] Scanning for projects...[INFO] [INFO] ----------------------< com.studyolle:studyolle >-----------------------[INFO] Building studyolle 0.0.1-SNAPSHOT[INFO] --------------------------------[ jar ]---------------------------------[INFO] [INFO] --- apt-maven-plugin:1.1.3:process (default) @ studyolle ---[INFO] [INFO] --- frontend-maven-plugin:1.8.0:install-node-and-npm (install node and npm) @ studyolle ---[INFO] Installing node version v4.6.0[INFO] Unpacking C:\Users\sonth\.m2\repository\com\github\eirslett\node\4.6.0\node-4.6.0-win-x64.zip into D:\SpringPrj\cryptoWebService\src\main\resources\static\node\tmp[INFO] Copying node binary from D:\SpringPrj\cryptoWebService\src\main\resources\static\node\tmp\node-v4.6.0-win-x64\node.exe to D:\SpringPrj\cryptoWebService\src\main\resources\static\node\node.exe[INFO] Installed node locally.[INFO] [INFO] --- frontend-maven-plugin:1.8.0:npm (npm install) @ studyolle ---[INFO] Running 'npm install' in D:\SpringPrj\cryptoWebService\src\main\resources\static[INFO] npm WARN package.json static@1.0.0 No description[INFO] npm WARN package.json static@1.0.0 No repository field.[INFO] npm WARN package.json static@1.0.0 No README data[INFO] [INFO] --- maven-resources-plugin:3.2.0:resources (default-resources) @ studyolle ---[INFO] Using 'UTF-8' encoding to copy filtered resources.[INFO] Using 'UTF-8' encoding to copy filtered properties files.[INFO] Copying 2 resources[INFO] ------------------------------------------------------------------------[INFO] BUILD FAILURE[INFO] ------------------------------------------------------------------------[INFO] Total time: 35.495 s[INFO] Finished at: 2023-10-31T22:25:42+09:00[INFO] ------------------------------------------------------------------------[ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources) on project studyolle: Input length = 1 -> [Help 1][ERROR] [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.[ERROR] Re-run Maven using the -X switch to enable full debug logging.[ERROR] [ERROR] For more information about the errors and possible solutions, please read the following articles:[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoExecutionExceptionProcess finished with exit code 1