묻고 답해요
130만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
몽구스 쿼리 remove 에러 문제
Room.remove 이 부분에서 계속해서 TypeError: Room.remove is not a function 발생하길래 docs를 살펴봤는데 deleteMany나 deleteOne만 있더라구요! 현재 코드를 deleteMany로 수정해서 잘 동작이 됩니다!그런데 remove가 deleteOne이나 deleteMany로 분리된것은 꽤 오래전 일이라서 docs를 봐도 왜 나는 remove가 안되는데 불과 몇개월 전에 코드에서는 remove가 잘 돌아간건지 궁금해서 질문 남깁니다!! ㅜㅜ
-
미해결윤재성의 자바 기반 안드로이드 앱개발 Part 2 - 메뉴와 4대 구성요소
31강 서비스 를 듣고있는데요~
서비스하고 그냥 일반 자바 클래스 파일 만드는것하고 차이를 모르겠습니다.일반 자바 클래스 파일 따로 만들어도 어차피 화면없는 기능구현이 가능한 것 아닌가요?서비스라고 매니페스트에 추가하면서까지 서비스로 만드는 이유를 모르겠습니다 ㅠㅠ(정말 이해가 안돼서요)
-
미해결[2024 최신] [코드팩토리] [초급] Flutter 3.0 앱 개발 - 10개의 프로젝트로 오늘 초보 탈출!
구글지도 사용하기 중 코드 질문..
아래 코드에서 _HomeScreenState 클래스에서 appBar와 Body를 나누신 후에, 코드 정리중 appBar는 함수로, _CustomGoogleMap과 ChooCheckButton은 위젯으로 분리하여 만들어주셨는데, 그 이유가 뭔가요? appBar는 위젯으로 관리를 하면 안되는건가요? import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @override State<HomeScreen> createState() => _HomeScreenState(); } class _HomeScreenState extends State<HomeScreen> { static final LatLng companyLatLng = LatLng(37.5233273, 126.921252); static final CameraPosition initialPosition = CameraPosition( target: companyLatLng, zoom: 15, ); @override Widget build(BuildContext context) { return Scaffold( appBar: renderAppbar(), body: Column( children: [ _CustomGoogleMap(initialPosition: initialPosition), _ChoolCheckButton(), ], ), ); } AppBar renderAppbar() { return AppBar( title: Text( '오늘도 출근', style: TextStyle( color: Colors.blue, fontWeight: FontWeight.w700, ), ), backgroundColor: Colors.white, ); } } class _CustomGoogleMap extends StatelessWidget { final CameraPosition initialPosition; const _CustomGoogleMap({ required this.initialPosition, Key? key, }) : super(key: key); @override Widget build(BuildContext context) { return Expanded( flex: 5, child: GoogleMap( mapType: MapType.normal, initialCameraPosition: initialPosition, ), ); } } class _ChoolCheckButton extends StatelessWidget { const _ChoolCheckButton({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return Expanded( child: Text( '출근', ), ); } }
-
미해결HAL, CubeMX, TrueSTUDIO를 이용한 STM32F4 무료 강좌
파일 생성이 되지 않습니다
강의 그대로 세팅 다 하고 난 후 강의처럼 OK버튼이 없어 오른쪽 상단의 GENERATE CODE를 눌렀습니다.이런 메세지 창이 뜨는데 어떻게 해야하나요??GENERATE CODE 버튼을 누르는게 아닌가요?
-
미해결장고 설계철학으로 시작하는 파이썬 장고 입문
django.conf.settings
아래와 같이작성후 setting을 바꾸려고 하는데 참조가 안되는데 왜그러는 걸 까요??
-
미해결
스프링부트 소셜로그인 JWT반환
소셜 로그인 후 프론트가 accessToken을 받고 그거를 서버에 헤더에 담아서 보내주면 서버에서는 accessToken을 검증하고 accessToken에서 소셜로그인한 유저의 정보를 가져올 수 있는 걸로 알고 있는데 계속 실패해서 찾아보니 소셜로그인시 발급해주는 accessToken의 구조가 JWT 구조와 다르다고 기존의 JWT 검증하는 코드는 실패가 뜬다고 나오더군요. 그러면 구글, 네이버의 경우 accessToken을 어떻게 검증해서 정보를 빼와서 JWT를 발급해줄 수 있을 까요? JWT 발급 로직은 구성이 되어 있지만 검증하는 부분이 막혔습니다 ㅠㅠ코드는 다음과 같습니다.package com.example.shoppingmall.config.oauth2; import com.example.shoppingmall.config.auth.PrincipalDetails; import com.example.shoppingmall.config.oauth2.provicer.GoogleUserInfo; import com.example.shoppingmall.config.oauth2.provicer.NaverUserInfo; import com.example.shoppingmall.config.oauth2.provicer.OAuth2UserInfo; import com.example.shoppingmall.dto.member.Role; import com.example.shoppingmall.entity.member.MemberEntity; import com.example.shoppingmall.repository.member.MemberRepository; import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import java.util.Map; import java.util.UUID; // 소셜 로그인하면 사용자 정보를 가지고 온다. // 가져온 정보와 PrincipalDetails 객체를 생성합니다. @Service @Log4j2 @RequiredArgsConstructor public class PrincipalOauth2UserService extends DefaultOAuth2UserService { private final BCryptPasswordEncoder bCryptPasswordEncoder; private final MemberRepository memberRepository; @Override public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { // registrationId로 어떤 OAuth로 로그인 했는지 확인가능 log.info("clientRegistration in PrincipalOauth2UserService : " + userRequest.getClientRegistration()); log.info("accessToken in PrincipalOauth2UserService : " + userRequest.getAccessToken().getTokenValue()); OAuth2User oAuth2User = super.loadUser(userRequest); // 구글 로그인 버튼 클릭 →구글 로그인 창 → 로그인 완료 → code 를 리턴(OAuth-Client 라이브러리) → AccessToken 요청 // userRequest 정보 → 회원 프로필 받아야함(loadUser 함수 호출) → 구글로부터 회원 프로필을 받아준다. log.info("getAttributes in PrincipalOauth2UserService : " + oAuth2User.getAttributes()); // 회원가입 강제 진행 OAuth2UserInfo oAuth2UserInfo = null; if (userRequest.getClientRegistration().getRegistrationId().equals("google")) { log.info("구글 로그인 요청"); oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes()); } else if (userRequest.getClientRegistration().getRegistrationId().equals("naver")) { log.info("네이버 로그인 요청"); // 네이버는 response를 json으로 리턴을 해주는데 아래의 코드가 받아오는 코드다. // response={id=5SN-ML41CuX_iAUFH6-KWbuei8kRV9aTHdXOOXgL2K0, email=zxzz8014@naver.com, name=전혜영} // 위의 정보를 NaverUserInfo에 넘기면 oAuth2UserInfo = new NaverUserInfo((Map) oAuth2User.getAttributes().get("response")); } else { log.info("지원하지 않는 소셜 로그인입니다."); } // 사용자가 로그인한 소셜 서비스(provider)를 가져옵니다. // 예를 들어, "google" 또는 "naver"와 같은 값을 가질 수 있습니다. String provider = oAuth2UserInfo.getProvider(); // 사용자의 소셜 서비스(provider)에서 발급된 고유한 식별자를 가져옵니다. // 이 값은 해당 소셜 서비스에서 유니크한 사용자를 식별하는 용도로 사용됩니다. String providerId = oAuth2UserInfo.getProviderId(); String userName = oAuth2UserInfo.getName(); String password = bCryptPasswordEncoder.encode("get"); // 사용자의 이메일 주소를 가져옵니다. 소셜 서비스에서 제공하는 이메일 정보를 사용합니다. String email = oAuth2UserInfo.getEmail(); // 사용자의 권한 정보를 설정합니다. UserType. // 여기서는 소셜로그인으로 가입하면 무조건 User로 권한을 주는 방식으로 했습니다. Role role = Role.USER; // UUID를 사용하여 랜덤한 문자열 생성 UUID uuid = UUID.randomUUID(); // External User 줄임말 : EU String randomNickName = "EU" + uuid.toString().replace("-", "").substring(0, 9); // 이메일 주소를 사용하여 이미 해당 이메일로 가입된 사용자가 있는지 데이터베이스에서 조회합니다. MemberEntity member = memberRepository.findByUserEmail(email); if (member == null) { log.info("OAuth 로그인이 최초입니다."); log.info("OAuth 자동 회원가입을 진행합니다."); member = MemberEntity.builder() .userName(userName) .userPw(password) .userEmail(email) .role(role) .provider(provider) .nickName(randomNickName) .providerId(providerId) .build(); log.info("member : " + member); MemberEntity save = memberRepository.save(member); log.info("save : " + save); } else { log.info("로그인을 이미 한적이 있습니다. 당신은 자동회원가입이 되어 있습니다."); } // attributes가 있는 생성자를 사용하여 PrincipalDetails 객체 생성 // 소셜 로그인인 경우에는 attributes도 함께 가지고 있는 PrincipalDetails 객체를 생성하게 됩니다. PrincipalDetails principalDetails = new PrincipalDetails(member, oAuth2User.getAttributes()); log.info("principalDetails in PrincipalOauth2UserService : " + principalDetails); return principalDetails; } }package com.example.shoppingmall.config.oauth2.provicer; public interface OAuth2UserInfo { String getProviderId(); String getProvider(); String getEmail(); String getName(); }package com.example.shoppingmall.config.oauth2.provicer; import java.util.Map; public class GoogleUserInfo implements OAuth2UserInfo{ private Map<String, Object> attributes; public GoogleUserInfo(Map<String, Object> attributes) { this.attributes = attributes; } @Override public String getProviderId() { return (String) attributes.get("sub"); } @Override public String getProvider() { return "google"; } @Override public String getEmail() { return (String) attributes.get("email"); } @Override public String getName() { return (String) attributes.get("name"); } }package com.example.shoppingmall.config.oauth2.provicer; import java.util.Map; public class NaverUserInfo implements OAuth2UserInfo{ private Map<String, Object> attributes; // PrincipalOauth2UserService에서 new NaverUserInfo((Map)oAuth2User.getAttributes().get("response"))로 // Oauth2 네이버 로그인 정보를 받아온다. public NaverUserInfo(Map<String,Object> attributes) { this.attributes = attributes; } @Override public String getProviderId() { return (String) attributes.get("id"); } @Override public String getProvider() { return "naver"; } @Override public String getEmail() { return (String) attributes.get("email"); } @Override public String getName() { return (String) attributes.get("name"); } }package com.example.shoppingmall.config.security; import com.example.shoppingmall.config.jwt.JwtAccessDeniedHandler; import com.example.shoppingmall.config.jwt.JwtAuthenticationEntryPoint; import com.example.shoppingmall.config.jwt.JwtProvider; import com.example.shoppingmall.config.oauth2.PrincipalOauth2UserService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.DelegatingPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import java.util.HashMap; import java.util.Map; @Configuration @RequiredArgsConstructor @EnableWebSecurity // @EnableGlobalMethodSecurity 어노테이션은 Spring Security에서 메서드 수준의 보안 설정을 활성화하는데 사용되는 어노테이션입니다. @EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true) public class SecurityConfig { private final JwtProvider jwtProvider; private final PrincipalOauth2UserService principalOauth2UserService; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .httpBasic().disable() .csrf().disable() .formLogin().disable() .logout().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); http .authorizeRequests() .antMatchers("/api/v1/boards/**") .access("hasRole('ROLE_USER') or hasRole('ROLE_ADMIN')") .antMatchers("/api/v1/admin/**") .access("hasRole('ROLE_ADMIN')") .antMatchers("/api/v1/items/**") .access("hasRole('ROLE_ADMIN')") // /success-oauth 엔드포인트에 대해 인증된 사용자만 접근 가능하도록 설정 // .antMatchers("/success-oauth").authenticated() .antMatchers("/swagger-resources/**").permitAll() .antMatchers("/swagger-ui/**").permitAll() .antMatchers("/api/v1/users/**").permitAll(); http // JWT Token을 위한 Filter를 아래에서 만들어 줄건데, // 이 Filter를 어느위치에서 사용하겠다고 등록을 해주어야 Filter가 작동이 됩니다. .apply(new JwtSecurityConfig(jwtProvider)); // 에러 방지 http .exceptionHandling() .authenticationEntryPoint(new JwtAuthenticationEntryPoint()) .accessDeniedHandler(new JwtAccessDeniedHandler()); // oauth2 http // oauth2Login() 메서드는 OAuth 2.0 프로토콜을 사용하여 소셜 로그인을 처리하는 기능을 제공합니다. .oauth2Login() // OAuth2 로그인 성공 이후 사용자 정보를 가져올 때 설정 담당 .userInfoEndpoint() // OAuth2 로그인 성공 시, 후작업을 진행할 서비스 .userService(principalOauth2UserService); return http.build(); } @Bean PasswordEncoder passwordEncoder() { String idForEncode = "bcrypt"; Map<String, PasswordEncoder> encoders = new HashMap<>(); encoders.put(idForEncode, new BCryptPasswordEncoder()); return new DelegatingPasswordEncoder(idForEncode, encoders); } }package com.example.social.config.security; import com.example.social.config.jwt.JwtAuthenticationFilter; import com.example.social.config.jwt.JwtProvider; import org.springframework.security.config.annotation.SecurityConfigurerAdapter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.DefaultSecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; public class JwtSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> { private final JwtProvider jwtProvider; public JwtSecurityConfig(JwtProvider jwtProvider) { this.jwtProvider = jwtProvider; } @Override public void configure(HttpSecurity builder) throws Exception { // JwtAuthenticationFilter가 일반 로그인에 대한 토큰 검증을 처리 JwtAuthenticationFilter customFilter = new JwtAuthenticationFilter(jwtProvider); builder.addFilterBefore(customFilter, UsernamePasswordAuthenticationFilter.class); } }package com.example.social.config.jwt; import com.example.social.domain.TokenDTO; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import lombok.extern.log4j.Log4j2; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import javax.xml.bind.DatatypeConverter; import java.security.Key; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; // PrincipalDetails의 정보를 가지고 JWT를 만들어준다. // 이곳에서 JWT를 검증하는 메소드도 존재한다. @Log4j2 @Component public class JwtProvider { private static final String AUTHORITIES_KEY = "auth"; @Value("${jwt.access.expiration}") private long accessTokenTime; @Value("${jwt.refresh.expiration}") private long refreshTokenTime; private Key key; public JwtProvider(@Value("${jwt.secret_key}") String secretKey) { byte[] secretByte = DatatypeConverter.parseBase64Binary(secretKey); this.key = Keys.hmacShaKeyFor(secretByte); } // 유저 정보를 가지고 AccessToken, RefreshToken 을 생성하는 메소드 public TokenDTO crateToken(Authentication authentication, List<GrantedAuthority> authorities) { // UsernamePasswordAuthenticationToken // [Principal=zxzz45@naver.com, Credentials=[PROTECTED], Authenticated=false, Details=null, Granted Authorities=[]] // 여기서 Authenticated=false는 아직 정상임 // 이 시점에서는 아직 실제로 인증이 이루어지지 않았기 때문에 Authenticated 속성은 false로 설정 // 인증 과정은 AuthenticationManager와 AuthenticationProvider에서 이루어지며, // 인증이 성공하면 Authentication 객체의 isAuthenticated() 속성이 true로 변경됩니다. log.info("authentication in JwtProvider : " + authentication); // userType in JwtProvider : ROLE_USER log.info("userType in JwtProvider : " + authorities); // 권한 가져오기 // authentication 객체에서 권한 정보(GrantedAuthority)를 가져와 문자열 형태로 변환한 후, // 권한 정보를 문자열로 변환하여 클레임에 추가하는 방식 Map<String, Object> claims = new HashMap<>(); claims.put(AUTHORITIES_KEY, authorities.stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList())); // 유저의 이메일을 클레임에 넣어줍니다. claims.put("sub", authentication.getName()); log.info("claims in JwtProvider : " + claims); long now = (new Date()).getTime(); Date now2 = new Date(); Date accessTokenExpire = new Date(now + this.accessTokenTime); String accessToken = Jwts.builder() .setIssuedAt(now2) .setClaims(claims) .setExpiration(accessTokenExpire) .signWith(key, SignatureAlgorithm.HS256) .compact(); // claims subject 확인 in JwtProvider : zxzz45@naver.com log.info("클레임 확인 : " + checkToken(accessToken)); Date refreshTokenExpire = new Date(now + this.refreshTokenTime); String refreshToken = Jwts.builder() .setIssuedAt(now2) .setClaims(claims) .setExpiration(refreshTokenExpire) .signWith(key, SignatureAlgorithm.HS256) .compact(); log.info("클레임 확인 : " + checkToken(refreshToken)); return TokenDTO.builder() .grantType("Bearer ") .accessToken(accessToken) .accessTokenTime(accessTokenExpire) .refreshToken(refreshToken) .refreshTokenTime(refreshTokenExpire) // principalDeatails에서 getUserName 메소드가 반환한 것을 담아준다. // 이메일을 반환하도록 구성했으니 이메일이 반환됩니다. .userEmail(authentication.getName()) .build(); } // accessToken 만료시 refreshToken으로 accessToken 발급 public TokenDTO createAccessToken(String userEmail, List<GrantedAuthority> authorities) { long now = (new Date()).getTime(); Date now2 = new Date(); Date accessTokenExpire = new Date(now + this.accessTokenTime); Map<String, Object> claims = new HashMap<>(); claims.put(AUTHORITIES_KEY, authorities.stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList())); claims.put("sub", userEmail); String accessToken = Jwts.builder() .setIssuedAt(now2) .setClaims(claims) .setExpiration(accessTokenExpire) .signWith(key, SignatureAlgorithm.HS256) .compact(); return TokenDTO.builder() .grantType("Bearer ") .accessToken(accessToken) .accessTokenTime(accessTokenExpire) .userEmail(userEmail) .build(); } // 소셜 로그인 성공시 JWT 발급 // 위의 코드와 비슷하지만 차이점은 // 위에서는 accessToken만 발급하지만 여기에서는 // accessToken과 refreshToken 모두 발급 public TokenDTO createTokenForOAuth2(String userEmail, List<GrantedAuthority> authorities) { log.info("email in JwtProvicer : " + userEmail); log.info("authorities in JwtProvicer : " + authorities); // 권한 가져오기 Map<String, Object> claims = new HashMap<>(); claims.put(AUTHORITIES_KEY, authorities.stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.toList())); claims.put("sub", userEmail); long now = (new Date()).getTime(); Date now2 = new Date(); // accessToken 생성 Date accessTokenExpire = new Date(now + this.accessTokenTime); String accessToken = Jwts.builder() .setIssuedAt(now2) .setClaims(claims) .setExpiration(accessTokenExpire) .signWith(key, SignatureAlgorithm.HS256) .compact(); log.info("claims subject 확인 in JwtProvider : " + checkToken(accessToken)); Date refreshTokenExpire = new Date(now + this.refreshTokenTime); String refreshToken = Jwts.builder() .setIssuedAt(now2) .setClaims(claims) .setExpiration(refreshTokenExpire) .signWith(key, SignatureAlgorithm.HS256) .compact(); log.info("claims subject 확인 in JwtProvider : " + checkToken(refreshToken)); return TokenDTO.builder() .grantType("Bearer ") .accessToken(accessToken) .refreshToken(refreshToken) .accessTokenTime(accessTokenExpire) .refreshTokenTime(refreshTokenExpire) .userEmail(userEmail) .build(); } // JWT 토큰을 복호화하여 토큰에 들어있는 정보를 꺼내는 코드 // 토큰으로 클레임을 만들고 이를 이용해 유저 객체를 만들어서 최종적으로 authentication 객체를 리턴 // 인증 정보 조회 // 이거는 일반 로그인 시 JWT 발급받은 것을 헤더에 보낼 때 작용한다. // 소셜 로그인은 여기서 작동하지 않는다. 그 이유는 소셜 로그인에서 보내주는 토큰은 // 토큰 형식하고 다릅니다. public Authentication getAuthentication(String token) throws Exception { // 토큰 복호화 메소드 Claims claims = paresClaims(token); log.info("claims : " + claims); if(claims.get("auth") == null) { throw new Exception("권한이 없는 토큰입니다."); } // 클레임 권한 정보 가져오기 List<String> authStrings = (List<String>) claims.get(AUTHORITIES_KEY); // [ROLE_USER] log.info("authorityStrings in JwtProvider : " + authStrings); Collection<? extends GrantedAuthority> authorities = authStrings.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); // [ROLE_USER] log.info("authorities in JwtProvider : " + authorities); // UserDetails 객체를 만들어서 Authentication 리턴 /* UserDetails를 사용하는 이유는 다음과 같습니다: * 1. 인증과 권한 정보 분리: Spring Security에서는 인증(Authentication)과 권한(Authorization)을 분리하는 것이 중요합니다. 즉, 사용자의 인증 정보(아이디, 비밀번호 등)와 사용자의 권한 정보(역할, 권한)을 분리하여 관리하고 처리합니다. UserDetails는 사용자의 인증 정보를 담고 있으며, GrantedAuthority 객체들을 통해 사용자의 권한 정보를 나타낼 수 있습니다. 2. 유연성: Spring Security는 다양한 인증 방식과 인증 제공자(Authentication Provider)를 지원합니다. UserDetails를 사용함으로써 각각의 인증 방식에 따라 사용자 정보를 일반화하여 처리할 수 있습니다. JWT 토큰을 사용하는 경우에도 UserDetails를 활용하면 일반적인 Spring Security의 흐름을 따르며, JWT 토큰에 포함된 사용자 정보와 권한을 UserDetails 객체로 추상화하여 처리할 수 있습니다. * */ // Spring Security의 일반적인 원칙을 따르고, 인증 정보를 효율적이고 안전하게 관리하기 위한 방법 중 하나 // PrincipalDetails에 유저 정보가 있는데 밑의 작업을 하는 이유는 다음과 같다. // Spring Security의 내부 동작 및 일관성 유지를 위해 필요한 작업입니다. // PrincipalDetails 클래스는 UserDetails 인터페이스를 구현하여 사용자의 정보와 권한을 저장하는 역할을 하고 있습니다. // 그리고 UsernamePasswordAuthenticationToken은 Spring Security에서 인증을 나타내는 객체이며, // 인증된 사용자 정보와 해당 사용자의 권한 정보를 포함합니다. // JWT를 사용하여 인증을 처리할 때에는 토큰 검증 과정에서 사용자의 권한 정보를 추출하여 // UsernamePasswordAuthenticationToken을 생성합니다. // 하지만 토큰 검증을 통해 가져온 권한 정보는 SimpleGrantedAuthority 객체의 리스트 형태로 제공됩니다. // 이 때, Spring Security가 기대하는 UserDetails 타입의 객체로 변환하여야 합니다. // 요약하면, 토큰 검증을 통해 가져온 권한 정보를 UserDetails 타입으로 변환하여 // UsernamePasswordAuthenticationToken에 담아서 저장하는 것은 Spring Security의 일관성과 내부 동작을 따르는 방식입니다. UserDetails userDetails = new User(claims.getSubject(), "", authorities); log.info("claims.getSubject() in JwtProvider : " + claims.getSubject()); // 일반 로그인 시 주로 이거로 인증처리해서 SecurityContext에 저장한다. // Spring Security에서 인증을 나타내는 객체로 사용됩니다. // 일반적인 경우, 사용자 이름과 비밀번호를 사용하여 인증을 처리하고 해당 사용자의 권한 정보를 포함 // 원래는 사용자 이름과 비밀번호를 사용하여 인증을 처리하고, 해당 사용자의 권한 정보를 포함합니다. // 일반적으로 이 객체를 사용하여 사용자가 입력한 인증 정보를 처리하고, // 인증이 성공하면 해당 사용자의 권한을 포함한 Authentication 객체가 생성되어 SecurityContext에 저장됩니다. // 그러나 JWT를 사용하는 경우에는 비밀번호 대신에 JWT 토큰을 사용하여 인증을 처리합니다. return new UsernamePasswordAuthenticationToken(userDetails, token, authorities); } private Claims paresClaims(String token) { try { return Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); } catch (ExpiredJwtException e) { log.info("ExpiredJwtException : " + e.getMessage()); log.info("ExpiredJwtException : " + e.getClaims()); return e.getClaims(); } } // 토큰 검증하기 위한 메소드 public boolean validateToken(String token) { try { Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); return true; } catch (SecurityException | MalformedJwtException e) { log.error("잘못된 JWT 설명입니다. \n info : " + e.getMessage()); } catch (ExpiredJwtException e) { log.error("만료된 JWT입니다. \n info : " + e.getMessage()); } catch (UnsupportedJwtException e) { log.error("지원되지 않는 JWT입니다. \n info : " + e.getMessage()); } catch (IllegalArgumentException e) { log.error("JWT가 잘못되었습니다. \n info : " + e.getMessage()); } return false; } // 클레임에 제대로 등록되어 있나 확인하기 위해서 메소드를 만듬 private String checkToken(String token) { Claims claims = Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); String subject = claims.getSubject(); return subject; } }package com.example.social.config.jwt; // 여기는 토큰이 header에 담겨서 오면 검증을 하는 곳이다. // 클라이언트 요청 시 JWT 인증을 하기 위해 설치하는 커스텀 필터로 // UsernamePasswordAuthenticationFiler 이전에 실행된다. // 이전에 실행된다는 뜻은 JwtAuthenticationFilter 를 통과하면 // UsernamePasswordAuthenticationFilter 이후의 필터는 통과한 것으로 본다는 뜻이다. // 쉽게 말해서, Username + Password 를 통한 인증을 Jwt 를 통해 수행한다는 것이다. import lombok.RequiredArgsConstructor; import lombok.extern.log4j.Log4j2; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.util.HashMap; import java.util.Map; // JWT 방식은 세션과 다르게 Filter 하나를 추가해야 합니다. // 이제 사용자가 로그인을 했을 때, Request 에 가지고 있는 Token 을 해석해주는 로직이 필요합니다. // 이 역할을 해주는것이 JwtAuthenticationFilter입니다. // 세부 비즈니스 로직들은 TokenProvider에 적어둡니다. 일종의 service 클래스라고 생각하면 편합니다. /* * 순서 * 1. 사용자의 Request Header에 토큰을 가져옵니다. * 2. 해당 토큰의 유효성 검사를 실시하고 유효하면 * 3. Authentication 인증 객체를 만들고 * 4. ContextHolder에 저장해줍니다. * 5. 해당 Filter 과정이 끝나면 이제 시큐리티에 다음 Filter로 이동하게 됩니다. * * 이렇게 거치고 나면 컨트롤러에서 정보를 가져올 수 있습니다. * */ @Log4j2 @RequiredArgsConstructor public class JwtAuthenticationFilter extends OncePerRequestFilter { public static final String HEADER_AUTHORIZATION = "Authorization"; private final JwtProvider jwtProvider; // doFilter는 토큰을 검증하고 // 토큰의 인증정보를 SecurityContext에 담아주는 역할을 한다. @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; // request header에서 JWT를 추출 // 요청 헤더에서 JWT 토큰을 추출하는 역할 String jwt = resovleToken(httpServletRequest); log.info("jwt in JwtAuthenticationFilter : " + jwt); // 어떤 경로로 요청을 했는지 보여줌 String requestURI = httpServletRequest.getRequestURI(); log.info("uri JwtAuthenticationFilter : " + requestURI); if(StringUtils.hasText(jwt) && jwtProvider.validateToken(jwt)) { // 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext에 저장 Authentication authentication = null; try { authentication = jwtProvider.getAuthentication(jwt); } catch (Exception e) { throw new RuntimeException(e); } log.info("authentication in JwtAuthenticationFilter : " + authentication); // Spring Security의 SecurityContextHolder를 사용하여 현재 인증 정보를 설정합니다. // 이를 통해 현재 사용자가 인증된 상태로 처리됩니다. // 위에서 jwtProvider.getAuthentication(jwt)가 반환이 UsernamePasswordAuthenticationToken로 // SecurityContext에 저장이 되는데 SecurityContextHolder.getContext().setAuthentication(authentication); // 처리를 하는 이유는 다음과 같다. /* * 1. 인증 정보 검증: JWT 토큰이나 다른 인증 정보를 사용하여 사용자를 식별하고 * 권한을 확인하기 위해서는 토큰을 해독하여 사용자 정보와 권한 정보를 추출해야 합니다. * 이 역할은 jwtProvider.getAuthentication(jwt)에서 수행됩니다. * 이 메서드는 JWT 토큰을 분석하여 사용자 정보와 권한 정보를 추출하고, 해당 정보로 인증 객체를 생성합니다. * * 2. 인증 정보 저장: * 검증된 인증 객체를 SecurityContextHolder.getContext().setAuthentication(authentication);를 * 사용하여 SecurityContext에 저장하는 이유는, Spring Security에서 현재 사용자의 인증 정보를 * 전역적으로 사용할 수 있도록 하기 위함입니다. 이렇게 하면 다른 부분에서도 현재 사용자의 인증 정보를 사용할 수 있게 되며, * Spring Security가 제공하는 @AuthenticationPrincipal 어노테이션을 통해 현재 사용자 정보를 편리하게 가져올 수 있습니다. * */ SecurityContextHolder.getContext().setAuthentication(authentication); } else { log.error("유효한 JWT가 없습니다. : " + requestURI); } filterChain.doFilter(request, response); } // 토큰을 가져오기 위한 메소드 // Authorization로 정의된 헤더 이름을 사용하여 토큰을 찾고 // 토큰이 "Bearer "로 시작하거나 "Bearer "로 안온 것도 토큰 반환 private String resovleToken(HttpServletRequest httpServletRequest) { String token = httpServletRequest.getHeader(HEADER_AUTHORIZATION); log.info("token : " + token); if(StringUtils.hasText(token) && token.startsWith("Bearer ")) { return token.substring(7); } else if(StringUtils.hasText(token)) { return token; } else { return null; } } }package com.example.social.domain; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; import java.util.Date; @Getter @ToString @NoArgsConstructor public class TokenDTO { private Long id; private String grantType; private String accessToken; private Date accessTokenTime; private String refreshToken; private Date refreshTokenTime; private String userEmail; @Builder public TokenDTO(Long id, String grantType, String accessToken, Date accessTokenTime, String refreshToken, Date refreshTokenTime, String userEmail) { this.id = id; this.grantType = grantType; this.accessToken = accessToken; this.accessTokenTime = accessTokenTime; this.refreshToken = refreshToken; this.refreshTokenTime = refreshTokenTime; this.userEmail = userEmail; } }package com.example.social.entity; import com.example.social.domain.TokenDTO; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; import javax.persistence.*; import java.util.Date; @Entity(name = "token") @Getter @Table @NoArgsConstructor @ToString public class TokenEntity { @Id @GeneratedValue @Column(name = "token_id") private Long id; private String grantType; private String accessToken; private Date accessTokenTime; private String refreshToken; private Date refreshTokenTime; private String userEmail; @Builder public TokenEntity(Long id, String grantType, String accessToken, Date accessTokenTime, String refreshToken, Date refreshTokenTime, String userEmail) { this.id = id; this.grantType = grantType; this.accessToken = accessToken; this.accessTokenTime = accessTokenTime; this.refreshToken = refreshToken; this.refreshTokenTime = refreshTokenTime; this.userEmail = userEmail; } public static TokenEntity toTokenEntity(TokenDTO token) { return TokenEntity.builder() .id(token.getId()) .grantType(token.getGrantType()) .accessToken(token.getAccessToken()) .accessTokenTime(token.getAccessTokenTime()) .refreshToken(token.getRefreshToken()) .refreshTokenTime(token.getRefreshTokenTime()) .userEmail(token.getUserEmail()) .build(); } }package com.example.social.repository; import com.example.social.entity.TokenEntity; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface TokenRepository extends JpaRepository<TokenEntity, Long> { TokenEntity findByRefreshToken(String refreshToken); TokenEntity findByUserEmail(String userEmail); }로그인의 경우 JWT 발급이 제대로 되는것을 확인 // 로그인 @PostMapping("/api/v1/users/login") public ResponseEntity<?> login(@RequestBody MemberDTO member) throws Exception { log.info("member : " + member); try { String userEmail = member.getUserEmail(); String userPw = member.getUserPw(); return memberService.login(userEmail, userPw); } catch (EntityNotFoundException e) { return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage()); } } // 로그인 public ResponseEntity<?> login(String userEmail, String userPw) { MemberEntity findUser = memberRepository.findByUserEmail(userEmail); log.info("findUser : " + findUser); if(findUser != null) { // 사용자가 입력한 패스워드를 암호화하여 사용자 정보와 비교 if(passwordEncoder.matches(userPw, findUser.getUserPw())) { // UsernamePasswordAuthenticationToken은 Spring Security에서 // 사용자의 이메일과 비밀번호를 이용하여 인증을 진행하기 위해 제공되는 클래스 // 이후에는 생성된 authentication 객체를 AuthenticationManager를 이용하여 인증을 진행합니다. // AuthenticationManager는 인증을 담당하는 Spring Security 의 중요한 인터페이스로, 실제로 사용자의 인증 과정을 처리합니다. // AuthenticationManager를 사용하여 사용자가 입력한 이메일과 비밀번호가 올바른지 검증하고, // 인증에 성공하면 해당 사용자에 대한 Authentication 객체를 반환합니다. 인증에 실패하면 예외를 발생시킵니다. // 인증은 토큰을 서버로 전달하고, 서버에서 해당 토큰을 검증하여 사용자를 인증하는 단계에서 이루어집니다. // 즉, Authentication 객체를 생성하고, 해당 객체를 SecurityContext에 저장하게 되면, // 인증이 완료되지 않은 상태에서 사용자 정보를 가지는 인증 객체가 저장됩니다. // 이후 검증 시에는 해당 인증 객체를 기반으로 다시 UsernamePasswordAuthenticationToken을 생성하여 // 인증 상태를 true로 설정하는 것이 가능합니다. Authentication authentication = new UsernamePasswordAuthenticationToken(userEmail, userPw); // UsernamePasswordAuthenticationToken // [Principal=zxzz45@naver.com, Credentials=[PROTECTED], Authenticated=false, Details=null, Granted Authorities=[]] // 여기서 Authenticated=false는 아직 정상임 // 이 시점에서는 아직 실제로 인증이 이루어지지 않았기 때문에 Authenticated 속성은 false로 설정 // 인증 과정은 AuthenticationManager와 AuthenticationProvider에서 이루어지며, // 인증이 성공하면 Authentication 객체의 isAuthenticated() 속성이 true로 변경됩니다. log.info("authentication in MemberService : " + authentication); List<GrantedAuthority> authoritiesForUser = getAuthoritiesForUser(findUser); TokenDTO tokenDTO = jwtProvider.crateToken(authentication, authoritiesForUser); TokenEntity findToken = tokenRepository.findByUserEmail(tokenDTO.getUserEmail()); // 사용자에게 이미 토큰이 할당되어 있는지 확인 if(findToken != null) { log.info("이미 토큰이 발급되어 있습니다."); // 기존의 토큰을 업데이트 합니다. tokenDTO = TokenDTO.builder() .id(findToken.getId()) .grantType(tokenDTO.getGrantType()) .accessToken(tokenDTO.getAccessToken()) .accessTokenTime(tokenDTO.getAccessTokenTime()) .refreshToken(tokenDTO.getRefreshToken()) .refreshTokenTime(tokenDTO.getRefreshTokenTime()) .build(); TokenEntity tokenEntity = TokenEntity.toTokenEntity(tokenDTO); tokenRepository.save(tokenEntity); } else { log.info("발급한 토큰이 없습니다."); tokenDTO = TokenDTO.builder() .grantType(tokenDTO.getGrantType()) .accessToken(tokenDTO.getAccessToken()) .accessTokenTime(tokenDTO.getAccessTokenTime()) .refreshToken(tokenDTO.getRefreshToken()) .refreshTokenTime(tokenDTO.getRefreshTokenTime()) .build(); TokenEntity tokenEntity = TokenEntity.toTokenEntity(tokenDTO); tokenRepository.save(tokenEntity); } HttpHeaders headers = new HttpHeaders(); headers.add(JwtAuthenticationFilter.HEADER_AUTHORIZATION, "Bearer " + tokenDTO); return new ResponseEntity<>(tokenDTO, headers, HttpStatus.OK); } } else { return ResponseEntity.notFound().build(); } return ResponseEntity.notFound().build(); }문제는 소셜로그인 시 JWT발급이 문제입니다. // 소셜 로그인 // 소셜 로그인시 발급받은 accessToken에서 정보를 가져올 때는 // @AuthenticationPrincipal OAuth2User oAuth2User이거를 사용한다. @GetMapping("/success-oauth") public ResponseEntity<?> getOAuth2UserInfo(@AuthenticationPrincipal OAuth2User oAuth2User) throws Exception{ try { String email = oAuth2User.getAttribute("email"); log.info("email : " + email); ResponseEntity<?> tokenForOAuth2 = memberService.createTokenForOAuth2(email); return ResponseEntity.ok().body(tokenForOAuth2); } catch (Exception e) { throw new RuntimeException(e); } }여기서 소셜로그인시 받은 accessToken 검증이 실패해서 정보가 null이 뜹니다.
-
미해결
스프링부트 EC2 배포 시 소셜로그인, JWT, S3 처리
스프링부트를 EC2에 배포할 때 OAuth2, JWT나 S3를 이미지 넣는 설정 파일(yml)을 보안상 git에 안올라가게 막아주는데 그러면 배포할 때는 어떻게 처리를 해줘야 배포상태에서 OAuth2, JWT나 S3를 이미지 넣는 기능을 사용할 수 있나요??
-
해결됨처음 만난 리덕스(Redux)
getDefaultMiddleware 질문 드립니다
const store = configureStore({ reducer:rootReducer, middleware: (getDefaultMiddleware)=>{ const defaultMiddleware = getDefaultMiddleware(); return [...defaultMiddleware]; } });강사님 마지막 실습코드에서여기서 기본미들 웨어를 가져 오는 이유를 잘 모르겠습니다
-
해결됨[코드캠프] 부트캠프에서 만든 고농축 백엔드 코스
우분투 사용법
강의 듣다가 윈도우에서 우분투로 바꿨는데사용법이 익숙치 않아요터미널에서 bash 라는 창을 어떻게 켜는지도 모르고 , 단축키도 몰라요. 전체적으로 미숙해요.이런건 어디서 배워야하나요?
-
미해결PM을 위한 IT SI프로젝트 전 과정 알아가기
교육 완료했는데.. 오토에버 사이트에는 0%로 나옵니다.
교육 완료했는데.. 오토에버 사이트에는 0%로 나옵니다.
-
미해결타입스크립트의 모든 것
변수의 타입에 클래스를 지정해준 것과 지정 안한 것 과의 차이가 어떻게 되는지 궁금해서 질문을 남깁니다.
안녕하세요? 현 강의에서 user에 new UserInfo() 클래스를 할당하고 동시에 해당 변수에 타입으로 클래스를 할당할 수 있다고 하셨는데 그렇게 되면 본래 UserInfo 상단에 지정된 인자들의 타입을 한번 더 점검해준다는 뜻인가요?? 해당 부분이 이해가 안되서 질문을 남깁니다.
-
해결됨처음 만난 리덕스(Redux)
강사님 질문이 있어요
serializableCheck: { ignoredActions: [ REHYDRATE, FLUSH, PAUSE, PERSIST, PURGE, REGISTER, ], },강사님 마지막 실습 코드에서 질문드려요 여기 코드는 검색을 하니 직렬화,역질렬화 검사 할때 사용 하는거라는데 마지막 실습 코드에서는 어떻게 사용 되는 건가요? 어떤 연관성? 이 있는 건가요?
-
미해결[아파치 카프카 애플리케이션 프로그래밍] 개념부터 컨슈머, 프로듀서, 커넥트, 스트림즈까지!
로그와 세그먼트
안녕하세요. 강의듣다 헷갈려서 질문드립니다.로그와 세그먼트에서 설명이 로그에서 갑자기 세그먼트로 넘어가는 느낌을 받았는데, 어떻게 이해하면될까요 로그 = 세그먼트 로그 = 세그먼트 들을 저장하는 로그파일세그먼트 = 오프셋 1ea의 명칭
-
해결됨[퇴근후딴짓] 빅데이터 분석기사 실기 (작업형1,2,3)
가설검정과정
가설 검정 과정 강의에서 가설검정 오류 부분에 일반적으로 1종 오류를 2종 오류보다 더 중요하게 생각함 이라고 적혀있습니다.강사님 강의 내용에서는 2종이 더 심각하게 받아들여진다 라고 말씀주셨는데,두 개 중 어떤 부분이 맞는건가요?
-
해결됨10주완성 C++ 코딩테스트 | 알고리즘 코딩테스트
5-V 문제 질문
안녕하세요 큰돌선생님 매번 좋은강의 감사합니다해당문제 풀이에서 다른부분은 모두 이해 가는데,make함수에서 if(interval == n) break; 부분이 왜 필요한지 잘 모르겠습니다. 어차피 for 루프가 끝나면 자동으로 종료되기때문에 필요없다고 생각하여 제출했는데 틀렸다고 나오네요 혹시 무엇때문에 필요한 것인가요?
-
해결됨10주완성 C++ 코딩테스트 | 알고리즘 코딩테스트
5 - B stack 풀이 질문
안녕하세요, 강사님의 강의를 수강하며 코딩테스트를 준비하고 있는 수강생입니다.강사님의 좋은 코드 설명과 양질의 코드로 항상 감사하게 생각하고 있는데요,다름이 아니라 제가 5 - B 문제를 풀다가 질문이 생겨서 글을 올리게 되었습니다.저는 해당 <문자열 폭발> 문제를 읽자마자 '아 여느 괄호 연쇄 폭발 문제랑 비슷하구나' 라는 생각이 들어서스택으로 문제 풀이 가닥을 잡게 되었는데, 그 와중에 전과는 다르게 폭발하는 string의 길이가 길어 졌으니매번 탐색을 해주어야겠다, 시간 복잡도도 괜찮을 것 같다! 라는 생각에 코드를 작성해봤습니다.생각보다 정답 풀이가 저의 풀이와 비슷해서 기분도 좋았는데, 왠지 모르게 시간초과가 계속 발생합니다.<질문>어느 부분을 고치면 시간 초과를 없앨 수 있을까요?그리고 이러한 시간 초과를 겪지 않으려면 어떤 코딩 방식을 지향해야할까요? (이건 개인적으로 지금 발생하고 있는 문제가 제 코드 작성 습관과 관련이 있디고 생각해서 적었습니다.)#include<iostream> #include<stack> #include<queue> #include<vector> #include<string> #include<climits> #include<algorithm> using namespace std; int main() { ios_base::sync_with_stdio(0); cin.tie(0); string str; cin >> str; string bomb; cin >> bomb; stack<char> s; int len = str.length(); int bomb_len = bomb.length(); for(int i = 0; i < len; i++) { char now = str[i]; // 폭탄을 확인할 만큼 stack이 큰지 확인 // 폭탄을 넣을 만큼 크지 않다면 그냥 stack에 문자 넣기 if(s.size() >= bomb_len - 1 && now == bomb[bomb_len - 1]) { // 폭탄 글자 길이 만큼 스택에서 글자 우선 뽑기 string token; token = token + now; for(int j = 1; j < bomb_len; j++) { token = token + s.top(); s.pop(); } reverse(token.begin(), token.end()); // 뽑은 문자열이 폭탄 글자인지 확인 // 폭탄이 아니라면 다시 넣어주고 폭탄이면 뺀 문자열 그냥 버림 if(token != bomb) { for(int j = 0; j < bomb_len; j++) { s.push(token[j]); } } } else { s.push(now); } } if(s.empty()) { cout << "FRULA"; } else { string answer; int ans_len = s.size(); for(int i = 0; i < ans_len; i++) { answer = answer + s.top(); s.pop(); } reverse(answer.begin(), answer.end()); cout << answer; } }
-
해결됨처음 만난 리액트(React)
useEffect() 안에 함수를 정의하는 이유가 무엇인가요?
안녕하세요, 소플님.챕터 7 나만의 훅 만들기를 공부하다가 궁금한 점이 있어서 질문드립니다. 228쪽 커스텀 훅 추출하기 예제 코드에서function useUserStatus(userId) { //... useEffect(() => { function handleStatusChange(status) { //... } //... }) //... }이런 식으로 useEffect 안에 handleStatusChange 함수를 정의하셨는데,useUserStatus 바로 아래에 정의하지 않고 useEffect 안에 정의하신 이유가 있을까요?어떤 상황에서 useEffect 안에 함수를 정의해야 하는 것인지 궁금합니다. 감사합니다.
-
해결됨선형대수학개론
echelon form과 row echelon form에 대한 질문
현재 대학교에서 선형대수학개론 배우고 있습니다. 대학교에서 배울 때는 echelon form과 reduced echelon form 대신 row echelon form과 reduced row echelon form을 배웠습니다. row echelon form은 이 수업에서 배운 echelon form 조건에서 leading entry가 1이라는 조건이 추가로 붙습니다. 그리고 reduced echelon form은 echelon form이랑 조건이 같은 거 같아요.그래서 제 질문은 row echelon form은 echelon form에서 조금 더 엄격한 버전이라고 생각하면 될까요?입니다.
-
미해결
vivado 실행 오류
vivado에서 코드를 입력하고 RTL ANALYSIS에서 Open Elaborated Design을 눌러 실행하면 비바도 프로그램이 아예 나가져요ㅜㅜ용량은 충분히 남았고, 윈도우 이름, 경로 모두 영어로 되어있습니다.vivado 2022.2 사용하다가 오류가 해결되지 않아 삭제 후 2023.1 설치하였는데도 같은 문제가 발생합니다. 해결 방법을 알 수 있을까요?
-
미해결TensorFlow Object Detection API 가이드 Part1 - 코드 10줄 수정으로 물체검출하기
python -m pip install . 이 아예 안되서 전체 다 수행할 수 없습니다.
어제 구매했는데 조금 속상하네요ㅠ설치 부터가 안 되니.. 뭘 시도할 수가 없습니다.