묻고 답해요
160만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
async await 함수 사용에 대해서 문의드립니다.
아직 노드버드 ch10 진행중입니다.진행하면서 다른 글들도 참고하면서 진행중인데요async await 사용 방식에 대해 문의드립니다.DB 데이타 조회, 생성 시 async 로 진행하는데코딩 방식1try{ const user = await User.findOne(...); ... await user.addFollowing(...);}catch(err){ next(err);}코딩 방식2User.findOne().then((result) => { user.addFollowing(...);}).then((result) => {...}).catch((err) => { next(err)});방식1은 await 키워드로 비동기 함수 호출하는 방식이구아래는 then 을 통해 비동기 함수 호출 하는 방식인듯 한데요.둘중 많이 쓰는 방식이 있을까요..?
-
해결됨[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
checkAuction() 호출 위치 질문
const checkAuction = require("./checkAuction"); const app = express(); passportConfig(); checkAuction(); app.set("port", process.env.PORT || 8010); app.set("view engine", "html"); nunjucks.configure("views", { express: app, watch: true, }); sequelize .sync({ force: false }) .then(() => { console.log("데이터베이스 연결 성공"); }) .catch((err) => { console.error(err); });이렇게 checkAuction함수의 호출이 sequelize.sync호출보다 위에있는데 어떻게 시퀄라이즈를 쓰는checkAuction함수가 에러없이 돌아가는게 가능한 것이죠?
-
해결됨[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
api 서버 만들기: 도메인주소, 클라이언트비밀키가 화면에 표시되지 않습니다.
무료/프리미엄 고르고 도메인주소 입력하면 밑에 화면에 주소와 타입 비밀키가 표시되지 않습니다. 로그는 계속 이렇게만 뜨고, 워크벤치 들어가보면 키는 보입니다.키가 발급은 되었는데, 브라우저로 못 띄우는 것 같다고 판단했습니다.어떤 부분을 확인해봐야 될까요?
-
미해결따라하며 배우는 NestJS
XML파일도 링크 공유해주시면 감사하겠습니다.
안녕하세요?인프런 수업자료 페이지에서 받은 파일이 모두 비어있어커뮤니티에서 검색을 해보니강사님께서 PDF파일 링크를 올려주신 답변을 찾았는데PDF파일은 중간중간 글자가 짤리는부분이 좀 있는 듯 하여 DrawIo로 XML파일 불러와서 봐보고싶은데 XML파일은 링크를 찾을수가 없더라구요~XML파일도 링크 공유해주시면 감사하겠습니다. Nest 처음 공부하는데 강사님 강의 정말 도움 많이 되고 있습니다. 감사합니다!
-
미해결스프링부트 시큐리티 & JWT 강의
권한 인증 403가 뜹니다
https://github.com/bgseong/Security-test public class JwtAuthorizationFilter extends BasicAuthenticationFilter { private TokenService tokenService; public JwtAuthorizationFilter(AuthenticationManager authenticationManager, TokenService tokenService) { super(authenticationManager); this.tokenService = tokenService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { super.doFilterInternal(request, response, chain); String token = tokenService.resolveToken(request); if(token == null){ chain.doFilter(request, response); return; } if (tokenService.validateToken(token)) { Authentication authentication = tokenService.getAuthentication(token); SecurityContextHolder.getContext().setAuthentication(authentication); System.out.println(SecurityContextHolder.getContext().getAuthentication()); } chain.doFilter(request,response); } }@EnableWebSecurity @Configuration @RequiredArgsConstructor @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfig { @Autowired TokenService tokenService; @Autowired CorsConfig corsConfig; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ http .csrf().disable() .httpBasic().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .formLogin().disable() .apply(new MyCustomDsl()) .and() .authorizeHttpRequests(authorize -> authorize .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // 특정 정적 리소스 허용 .requestMatchers("/api/v1/user/**").hasAnyRole("ADMIN", "MANAGER") .requestMatchers("/api/v1/manager/**").hasRole("ADMIN") .requestMatchers("/api/v1/admin/**").hasRole("ROLE_ADMIN") .anyRequest().permitAll()); return http.build(); } public class MyCustomDsl extends AbstractHttpConfigurer<MyCustomDsl, HttpSecurity> { @Override public void configure(HttpSecurity http) throws Exception { AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); http .addFilter(corsConfig.corsFilter()) .addFilter(new LoginFilter(authenticationManager,tokenService)) .addFilter(new JwtAuthorizationFilter(authenticationManager,tokenService)); } } }@Component public class TokenService implements InitializingBean { private final UserRepository usersrepository; private final Logger logger = LoggerFactory.getLogger(TokenService.class); private static final String AUTHORITIES_KEY = "auth"; private final String secret; private final long accessTokenValidityInMilliseconds; private final long refreshTokenValidityInMilliseconds; public static final String AUTHORIZATION_HEADER = "Authorization"; public static final String REFRESHTOKEN_HEADER = "RefreshToken"; private Key key; public TokenService( UserRepository usersrepository, @Value("${spring.jwt.secret}") String secret, @Value("${spring.jwt.token-validity-in-seconds}") long tokenValidityInSeconds) { this.usersrepository = usersrepository; this.secret = secret; this.accessTokenValidityInMilliseconds = tokenValidityInSeconds * 500; this.refreshTokenValidityInMilliseconds = tokenValidityInSeconds * 1000 * 336; } @Override public void afterPropertiesSet() { byte[] keyBytes = Decoders.BASE64.decode(secret); this.key = Keys.hmacShaKeyFor(keyBytes); } public String createAccessToken(PrincipalDetails principalDetails) { return createAccessToken(principalDetails.getUser().getEmail(), principalDetails.getAuthorities()); } public String createRefreshToken(PrincipalDetails principalDetails) { return createRefreshToken(principalDetails.getUser().getEmail(), principalDetails.getAuthorities()); } public String createAccessToken(String email, Collection<? extends GrantedAuthority> inputAuthorities) { String authorities = inputAuthorities.stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); long now = (new Date()).getTime(); String accessToken = Jwts.builder() .setSubject(email) .claim(AUTHORITIES_KEY, authorities) .signWith(key, SignatureAlgorithm.HS512) .setExpiration(new Date(now + this.accessTokenValidityInMilliseconds)) .compact(); return accessToken; } public String createRefreshToken(String email, Collection<? extends GrantedAuthority> inputAuthorities) { String authorities = inputAuthorities.stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); long now = (new Date()).getTime(); String Token = Jwts.builder() .setSubject(email) .claim(AUTHORITIES_KEY, authorities) .signWith(key, SignatureAlgorithm.HS512) .setExpiration(new Date(now + this.refreshTokenValidityInMilliseconds)) .compact(); return Token; } public Authentication getAuthentication(String token) { Claims claims = Jwts .parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); User user = usersrepository.findByEmail(claims.get("sub",String.class)); Collection<? extends GrantedAuthority> authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(",")) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); PrincipalDetails principal = new PrincipalDetails(user); return new UsernamePasswordAuthenticationToken(principal, null, authorities); } public String resolveToken(HttpServletRequest request) { String bearerToken = request.getHeader(AUTHORIZATION_HEADER); if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } return null; } public boolean validateToken(String token) { try { Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); return true; } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { logger.info("worng JWT sign"); } catch (ExpiredJwtException e) { logger.info("expire JWT"); } catch (UnsupportedJwtException e) { logger.info("No support JWT"); } catch (IllegalArgumentException e) { logger.info("JWT is worng"); } return false; } }이렇게 구성해 놨습니다. 그런데 모든 권한이 적용된 url에 접근을 하면 403 에러가 뜹니다.필터에서 SecurityContextHolder를 출력하면 아래와 같이 출력이 되는 걸 확인했고[Principal=com.securitytest.Securitytest.auth.PrincipalDetails@45095607, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_ADMIN]]컨트롤러에서 PrincipalDetails를 호출해보니, null이라서 오류가 난다고 뜹니다. 무엇이 문제일까요ㅠㅠ..
-
해결됨[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
room계층 소켓 연결 후 소통 질문
서버쪽에선 socket.on("join", (data) => { socket.join(data); // 방 참가 socket.to(data).emit("join", { user: "system", chat: `${socket.request.session.color}님이 입장하셨습니다.`, }); });이렇게 room계층 소켓으로 연결했고, 해당 room으로 join이벤트를 보냈는데 브라우저 쪽에선const socket = io.connect("http://localhost:8005/chat", { path: "/socket.io", }); socket.emit("join", new URL(location).pathname.split("/").at(-1)); socket.on("join", function (data) {...}/chat 네임스페이스 계층 소켓으로 밖에 연결하지 못했는데 어떻게 해당 room계층으로 온 join이벤트인지 구별하는 게 가능한지 궁금합니다. room계층보다 상위인 네임스페이스 계층에 연결했으니 어느 room으로 오든 네임스페이스만 같으면 모든 이벤트를 다 감지하게 되는 것 아닌가요?
-
미해결스프링부트 시큐리티 & JWT 강의
1강 환경설정을 따라하는데 run하면 에러가 나요
설정 후 run 하면If you want an embedded database (H2, HSQL or Derby), please put it on the classpath. 라는 문구가 뜹니다. 그래서 mysql에 다시 한번 sql문구를 쓰고 다시 처음부터 강의대로 해봤는데도 안되서 구글링해도 모르겠어서요ㅠㅠ
-
미해결스프링부트 시큐리티 & JWT 강의
소셜 로그인 후 JWT 발급
아무리 해도 해결이 안되네요...package com.example.project1.config.oauth2; import com.example.project1.config.auth.PrincipalDetails; import com.example.project1.config.oauth2.provider.GoogleUserInfo; import com.example.project1.config.oauth2.provider.NaverUserInfo; import com.example.project1.config.oauth2.provider.OAuth2UserInfo; import com.example.project1.domain.member.UserType; import com.example.project1.entity.member.MemberEntity; import com.example.project1.repository.member.MemberRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; 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; @Service @Slf4j @RequiredArgsConstructor public class PrincipalOauth2UserService extends DefaultOAuth2UserService { private final BCryptPasswordEncoder bCryptPasswordEncoder; private final MemberRepository memberRepository; // 구글로부터 받은 userReuest 데이터에 대한 후처리되는 함수 @Override public PrincipalDetails 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(); // 예) google_109742856182916427686 String userName = provider + "_" + providerId; String password = bCryptPasswordEncoder.encode("get"); // 사용자의 이메일 주소를 가져옵니다. 소셜 서비스에서 제공하는 이메일 정보를 사용합니다. String email = oAuth2UserInfo.getEmail(); // 사용자의 권한 정보를 설정합니다. UserType. // 여기서는 소셜로그인으로 가입하면 무조건 User로 권한을 주는 방식으로 했습니다. UserType role = UserType.USER; // 이메일 주소를 사용하여 이미 해당 이메일로 가입된 사용자가 있는지 데이터베이스에서 조회합니다. MemberEntity member = memberRepository.findByUserEmail(email); if(member == null) { log.info("OAuth 로그인이 최초입니다."); log.info("↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓"); log.info("OAuth 자동 회원가입을 진행합니다."); member = MemberEntity.builder() .userName(userName) .userPw(password) .userEmail(email) .userType(role) .provider(provider) .providerId(providerId) .build(); log.info("userEmail in PrincipalOauth2UserService : " + member.getUserEmail()); log.info("userName in PrincipalOauth2UserService : " + member.getUserName()); log.info("userPw in PrincipalOauth2UserService : " + member.getUserPw()); log.info("userType in PrincipalOauth2UserService : " + member.getUserType()); log.info("provider in PrincipalOauth2UserService : " + member.getProvider()); log.info("providerId in PrincipalOauth2UserService : " + member.getProviderId()); memberRepository.save(member); } else { log.info("로그인을 이미 한적이 있습니다. 당신은 자동회원가입이 되어 있습니다."); MemberEntity findUser = memberRepository.findByUserEmail(email); log.info("findUser in PrincipalOauth2UserService : " + findUser); } OAuth2User oAuth2User1 = super.loadUser(userRequest); log.info("getAttributes in PrincipalOauth2UserService : " + oAuth2User1.getAttributes()); // attributes가 있는 생성자를 사용하여 PrincipalDetails 객체 생성 // 소셜 로그인인 경우에는 attributes도 함께 가지고 있는 PrincipalDetails 객체를 생성하게 됩니다. PrincipalDetails principalDetails = new PrincipalDetails(member, oAuth2User.getAttributes()); log.info("principalDetails in PrincipalOauth2UserService : " + principalDetails); return principalDetails; } }package com.example.project1.config.oauth2.provider; public interface OAuth2UserInfo { String getProviderId(); String getProvider(); String getEmail(); String getName(); }package com.example.project1.config.oauth2.provider; import java.util.Map; public class GoogleUserInfo implements OAuth2UserInfo{ // getAttributes()를 받음 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.project1.config.oauth2.provider; import java.util.Map; public class NaverUserInfo implements OAuth2UserInfo{ // oauth2User.getAttributes()를 받음 private Map<String,Object> attributes; // PrincipalOauth2UserService에서 new NaverUserInfo((Map)oAuth2User.getAttributes().get("response"))로 // Oauth2 네이버 로그인 정보를 받아온다. // → {id=5SN-ML41CuX_iAUFH6-KWbuei8kRV9aTHdXOOXgL2K0, email=zxzz8014@naver.com, name=전혜영} 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.project1.config.auth; import com.example.project1.entity.member.MemberEntity; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import lombok.ToString; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.oauth2.core.user.OAuth2User; import java.util.ArrayList; import java.util.Collection; import java.util.Map; @Setter @Getter @ToString @NoArgsConstructor @Slf4j public class PrincipalDetails implements UserDetails, OAuth2User { // 일반 로그인 정보를 저장하기 위한 필드 private MemberEntity member; // OAuth2 로그인 정보를 저장하기 위한 필드 // attributes는 Spring Security에서 OAuth2 인증을 수행한 후에 사용자에 대한 추가 정보를 저장하는 데 사용되는 맵(Map)입니다. // OAuth2 인증은 사용자 인증 후에 액세스 토큰(Access Token)을 발급받게 되는데, // 이 토큰을 사용하여 OAuth2 서비스(provider)로부터 사용자의 프로필 정보를 요청할 수 있습니다. // 예를 들어, 소셜 로그인을 사용한 경우에는 attributes에는 사용자의 소셜 서비스(provider)에서 제공하는 프로필 정보가 담겨 있습니다. // 소셜 로그인 서비스(provider)마다 제공하는 프로필 정보가 다를 수 있습니다. // 일반적으로 attributes에는 사용자의 아이디(ID), 이름, 이메일 주소, 프로필 사진 URL 등의 정보가 포함됩니다. /* * 구글의 경우 * { "sub": "100882758450498962866", // 구글에서 발급하는 고유 사용자 ID "name": "John Doe", // 사용자 이름 "given_name": "John", // 이름(이름 부분) "family_name": "Doe", // 성(성(성) 부분) "picture": "https://lh3.googleusercontent.com/a/AAcHTtdzQomNwZCruCcM0Eurcf8hAgBHcgwvbXEBQdw3olPkSg=s96-c", // 프로필 사진 URL "email": "johndoe@example.com", // 이메일 주소 "email_verified": true, // 이메일 주소 인증 여부 "locale": "en" // 지역 설정 } * */ private Map<String, Object> attributes; // 일반 로그인 public PrincipalDetails(MemberEntity member) { this.member = member; } // OAuth2 로그인 public PrincipalDetails(MemberEntity member, Map<String, Object> attributes) { this.member = member; this.attributes = attributes; } // 해당 유저의 권한을 리턴하는 곳 @Override public Collection<? extends GrantedAuthority> getAuthorities() { Collection<GrantedAuthority> collection = new ArrayList<>(); collection.add(new SimpleGrantedAuthority("ROLE_" + member.getUserType().toString())); log.info("collection : " + collection); return collection; } // 사용자 패스워드를 반환 @Override public String getPassword() { log.info("password : "+ member.getUserPw()); return member.getUserPw(); } // 사용자 이름 반환 @Override public String getUsername() { log.info("id : " + member.getUserEmail()); return member.getUserEmail(); } // 계정 만료 여부 반환 @Override public boolean isAccountNonExpired() { // 만료되었는지 확인하는 로직 // true = 만료되지 않음 return true; } // 계정 잠금 여부 반환 @Override public boolean isAccountNonLocked() { // true = 잠금되지 않음 return true; } // 패스워드의 만료 여부 반환 @Override public boolean isCredentialsNonExpired() { // 패스워드가 만료되었는지 확인하는 로직 // true = 만료되지 않음 return true; } // 계정 사용 가능 여부 반환 @Override public boolean isEnabled() { // 계정이 사용 가능한지 확인하는 로직 // true = 사용 가능 return true; } @Override public Map<String, Object> getAttributes() { log.info("attributes : " + attributes); return attributes; } @Override // OAuth2 인증에서는 사용되지 않는 메서드이므로 null 반환 public String getName() { return null; } } // Oauth2 google로 JWT 발급 @GetMapping("/success-oauth") public ResponseEntity<?> createTokenForGoogle(@AuthenticationPrincipal OAuth2User oAuth2User , @RequestBody MemberDTO member) { Object email = oAuth2User.getAttribute("email"); log.info("oAuth2User : " + email); if(oAuth2User == null) { log.info("받아올 정보가 없습니다 ㅠㅠ"); return ResponseEntity.status(HttpStatus.NOT_FOUND).body("정보가 없어...."); } else { // OAuth2User에서 필요한 정보를 추출하여 UserDetails 객체를 생성합니다. ResponseEntity<TokenDTO> token = memberService.createToken((String) email, member); log.info("token : " + token); return ResponseEntity.ok().body(token); } } // 소셜 로그인 성공시 jwt 반환 // OAuth2User에서 필요한 정보를 추출하여 UserDetails 객체를 생성하는 메서드 public ResponseEntity<TokenDTO> createToken(String userEmail, MemberDTO member) { MemberEntity findUser = memberRepository.findByUserEmail(userEmail); log.info("findUser in MemberService : " + findUser); findUser = MemberEntity.builder() // id를 식별해서 수정 // 이거 없으면 새로 저장하기 됨 // findUser꺼를 쓰면 db에 입력된거를 사용하기 때문에 // 클라이언트에서 userEmail을 전달하더라도 서버에서 기존 값으로 업데이트가 이루어질 것입니다. // 이렇게 하면 userEmail을 수정하지 못하게 할 수 있습니다. .userId(findUser.getUserId()) .userEmail(findUser.getUserEmail()) .userPw(passwordEncoder.encode(findUser.getUserPw())) .userName(findUser.getUserName()) .nickName(member.getNickName()) .userType(findUser.getUserType()) .address(AddressEntity.builder() .userAddr(member.getAddressDTO().getUserAddr()) .userAddrDetail(member.getAddressDTO().getUserAddrDetail()) .userAddrEtc(member.getAddressDTO().getUserAddrEtc()) .build()) .build(); memberRepository.save(findUser); // 권한 정보 추출 List<GrantedAuthority> authorities = getAuthoritiesForUser(findUser); // UserDetails 객체 생성 (사용자의 아이디 정보를 활용) // 첫 번째 인자 : username 사용자 아이디 // 두 번째 인자 : 사용자의 비밀번호 // 세 번째 인자 : 사용자의 권한 정보를 담은 컬렉션 UserDetails userDetails = new User(userEmail, null, authorities); log.info("userDetails in MemberService : " + userDetails); TokenDTO token = jwtProvider.createToken2(userDetails); log.info("token in MemberService : " + token); return ResponseEntity.ok().body(token); } // 소셜 로그인 성공시 JWT 발급 public TokenDTO createToken2(UserDetails userDetails) { long now = (new Date()).getTime(); Date now2 = new Date(); // userDetails.getAuthorities()는 사용자의 권한(authorities) 정보를 가져오는 메서드입니다. // claims.put("roles", userDetails.getAuthorities()) 코드는 사용자의 권한 정보를 클레임에 추가하는 것입니다. // 클레임에는 "roles"라는 키로 사용자의 권한 정보가 저장되며, 해당 권한 정보는 JWT의 페이로드 부분에 포함됩니다. Claims claims = Jwts.claims().setSubject(userDetails.getUsername()); claims.put(AUTHORITIES_KEY, userDetails.getAuthorities()); log.info("claims : " + claims); // access token Date accessTokenExpire = new Date(now + this.accessTokenTime); String accessToken = Jwts.builder() .setSubject(userDetails.getUsername()) .setClaims(claims) .setIssuedAt(now2) .setExpiration(accessTokenExpire) .signWith(key,SignatureAlgorithm.HS256) .compact(); // RefreshToken 생성 Date refreshTokenExpire = new Date(now + this.refreshTokenTime); String refreshToken = Jwts.builder() .setIssuedAt(now2) .setClaims(claims) .setExpiration(refreshTokenExpire) .signWith(key, SignatureAlgorithm.HS256) .compact(); TokenDTO tokenDTO = TokenDTO.builder() .grantType("Bearer ") .accessToken(accessToken) .refreshToken(refreshToken) .userEmail(userDetails.getUsername()) .build(); log.info("tokenDTO in JwtProvider : " + tokenDTO); return tokenDTO; } http // oauth2Login() 메서드는 OAuth 2.0 프로토콜을 사용하여 소셜 로그인을 처리하는 기능을 제공합니다. .oauth2Login() // OAuth2 로그인 성공 이후 사용자 정보를 가져올 때 설정 담당 .userInfoEndpoint() // OAuth2 로그인 성공 시, 후작업을 진행할 서비스 .userService(principalOauth2UserService) .and() .defaultSuccessUrl("/success-oauth");소셜 로그인 후 쇼핑몰 프로젝트라 주소가 필 수라서 JSON을 추가로 받아서 주소를 DB에 저장 후 JWT를 반환해주는 로직을 구성하려고 합니다. 소셜 로그인을 했을 때 PrincipalOauth2UserService여기서 모든 값이 제대로 들어갔고 PrincipalDetails principalDetails = new PrincipalDetails(member, oAuth2User.getAttributes()); log.info("principalDetails in PrincipalOauth2UserService : " + principalDetails); return principalDetails;로그를 찍어본 결과 제대로 값이 로그에 찍혔습니다. 이거를 principalDetails에 보내줬으니 principalDetails 클래스에서도 제대로 받아졌는지 확인해봤습니다. Oaut2User를 상속받아서 오버라이드 한 @Override public Map<String, Object> getAttributes() { log.info("attributes : " + attributes); return attributes; }여기서도 로그에 제대로 값이 나왔습니다.여기서부터가 문제인데 컨트롤러에서 @AuthenticationPrincipal OAuth2User oAuth2User로 소셜 로그인한 정보를 받아와서 토큰을 만들려고 하는데 null이 뜹니다. 별방법을 다했는데 다른 부분은 다 해결을 했는데 이부분이 해결이 안되네요 ㅠㅠ
-
해결됨[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
pending 질문입니다.
메인화면으로가는 버튼을 빠르게 반복해서 누르면 로딩중만 나오길래 개발자모드에서 network를 보면 (pending)이렇게 나오면서 응답을 못하는상태인데 왜 그러는걸까요? 모든 함수에서 mysql에 연결하고 쿼리문 사용할때 async/await 사용하고있습니다.로그인 미들웨어는 promise로 사용하고있는데 정확히 어떤점때문에 이러한 오류가 발생하는지 모르겠습니다.저pending 오류를 어떻게해야 해결이 될까요?
-
해결됨Spring Boot JWT Tutorial
mysql 사용시 SQLSyntaxErrorException에러발생
엔티티 생성후 실행을 해보니 Caused by: java.sql.SQLSyntaxErrorException: Table 'onehour.user_authority' doesn't exist에러가 납니다이유가 궁금합니다 유저랑 authority는 생성이 잘 된 것 같은데...그리고 mysql사용시 따로 커넥션을 허용해주는 설정을 추가 안해줘도 될까요?
-
미해결스프링부트 시큐리티 & JWT 강의
마지막 강의 질문드립니다.
여기서 super.doFilterInternal(request, response, chain);위 문장을 지워주셨는데 해당 줄을 지우면 회원가입 로직이 컨트롤러를 타지 않습니다.회원가입은 /join으로 매핑되어 있는데 JwtAuthorizationFilter의 doFilterInternal() 메소드를 타고jwtHeader 값이 없기 때문에 return을 만나 컨트롤러를 타지 않는 것 같습니다. 반대로 super.doFilterInternal(request, response, chain); 주석 해제하면 회원가입 로직은 진행됩니다만 마지막 강의에서 인증이 되지 않는 문제가 계속 일어나고 있습니다! SecurityConfig 클래스 코드입니다! 부족한 지식으로 제 생각이 다를 수 있지만 문제를 해결하지 못하고 있어 질문 드립니다ㅠㅠ
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
실제 서버 배포
안녕하세요 선생님 ! 강의 정말 잘 보고 있습니다 ! 다름이 아니라 현재 Node Js 교과서를 참고해서 웹 프로젝트를 진행 중입니다. AWS를 이용해 lightsail과 도메인을 구입해 실제 배포를 하려 하는데요. 큰 프로젝트가 아니라 사용자가 100~200명쯤 되는 블라인드 매칭 서비스입니다. 무료로 진행하는 프로그램이라 큰 걱정은 없지만, 실제 서버 배포를 함에 있어 15장에 나오는 서비스 운영을 위한 패키지 만으로 보안이 가능할까 걱정이 돼서 글을 작성합니다. 추가 적으로 추천하시는 패키지들이 있으시면 추천 받고 싶습니다 !
-
해결됨
react-spring 파일 업로드/ react에서 이미지 뷰(jwt)
개인 프로젝트로 블로그 만들기를 구현하는 중입니다.모든 프로젝트에 대한 설명은 할 수 없지만, 보안으로 jwt token을 사용하고있고, multipartfile을 통해서 게시글 내에 이미지, 영상, 기타 파일 등을 함께 첨부하여 게시글을 작성할 수 있도록 기능을 구현하였습니다.파일 업로드 단계까진 잘 되었고, 우선 로컬 저장소를 사용하여 파일은 스프링 부트 프로젝트 내 resources/static/ ~ 이하 이미지:img, 영상:video, 기타 파일:file 로 세부 디렉토리를 나눠서 접근하도록 하였습니다. 우선 사진으로만 테스트 해보는 중인데, 리액트에서 파일을 보여주는 데에 어려움을 겪고있습니다.export const getImageApi = async (token: string | null, imageName: string) => { const url = `http://localhost:4000/api/images/${imageName}.jpg`; try { const response = await axios.get(url, { headers: { Authorization: `Bearer ${token}`, }, }); const result = response.data; return result; } catch (error) { console.error("Error fetching board data:", error); return null; } };토큰을 이용해야만 파일에 접근이 가능하기에, 이런식으로 따로 api를 설정하고 접근하는 방식으로 해보려고 하는데 제 생각처럼 잘 되지 않습니다. 해당 api를 통한 요청을 포스트맨으로 조회해보니 이진화 된 파일이 응답으로 나오곤 하는데, 이를 다시 원본으로 복구한 후에 보여줘야하는 것 같은데,, 아직 많이 부족해서 어떤식으로 해야할지 잘 모르겠습니다. 리액트 - 스프링(부트)를 사용하면서 파일 업로드/다운로드(조회)를 할 때, 이런 방식을 사용하는 것이 바람직한 것인지, 단순히 사진을 업로드 하고 조회할 때에 이정도 복잡한 수준으로 구현하는 것이 일반적인 것인지 궁금하고 더 간단하거나 쉽게 할 수 있는 방법이 있다면 알려주시면 감사하겠습니다. 첫 질문이라 소스코드를 많이 올리는 것이 번잡할까 싶어 많이 올려두지 않았습니다. 혹시 도움을 주실 때 관련 소스코드를 필요로 하신다면 보내드리도록 하겠습니다. 감사합니다.
-
해결됨[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
프로미스 함수 모킹할 때 질문
Promise.reject를 쓰는 경우가 아니라면 바로 반환하거나 Promise객체를 사용해서 반환하거나 똑같은 것 같은데 크게 상관없나요?
-
해결됨Spring Boot JWT Tutorial
이게 무슨 에러일까요..
yml파일 두 개 사진대로 둘 다 실행 해봤는데 안되네요.. 뭐가 잘못된 걸까요..
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
MySQL 3306 포트 PID 관련 질문
강의에서 알려주신대로 MySQL 설치 및 정상적으로 비밀번호도 설정하였습니다. 그리고, workbench 및 콘솔로도 잘 실습하였고요. (*Table 세팅 등)그런데, 컴퓨터를 껐다가 재부팅했더니 MySQL 로그인이 안됩니다.패스워드도 정상적으로 다 입력하였으나, 계속 오류 메세지가 나와서cmd에 접속해 현재 mysql이 사용중인 프로세스 id가 컴퓨터 재부팅 전에설정되었던 pid와 다름을 확인하였고, 어쩔 수 없이 컴퓨터에 설치된MySQL 프로그램 삭제 후 재설치하면서 임시방편으로 문제해결하긴 했습니다. 혹시 몰라서 또 다시 컴퓨터를 재부팅한 이후 MySQL pid를 보았는데,또 바뀌어 있더라고요. 그럼.... 컴퓨터를 매번 재부팅할 때마다 MySQL 프로그램을 계속삭제했다가 재설치를 해야하나요?ㅜ
-
미해결[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
Lightsail에서 배포시 Error: Cannot find module './KakaoStrategy'에 대한 오류 질문입니다.
안녕하세요 강사님,현재 Lightsail에서 배포를 진행하며$ sudo NODE_ENV=production PORT=80 pm2 start server.js -i 0 명령어 작성후$ sudo pm2 monit으로 확인해 보니 아래와 같은 오류가 발생합니다.KakaoStrategy 모듈을 못찾는다는데passport-kakao 1.0.0 버전으로 npm i를 했었습니다. 어떤게 문제인걸까요..?로컬에서는 문제없이 잘 돌아갔습니다. package.json 입니다.{ ... "dependencies": { "@aws-sdk/client-s3": "^3.377.0", "bcrypt": "^5.1.0", "connect-redis": "^4.0.4", "cookie-parser": "^1.4.6", "cors": "^2.8.5", "cross-env": "^7.0.3", "csurf": "^1.11.0", "dotenv": "^16.3.1", "express": "^4.18.2", "express-session": "^1.17.3", "helmet": "^7.0.0", "hpp": "^0.2.3", "morgan": "^1.10.0", "multer": "^1.4.5-lts.1", "multer-s3": "^3.0.1", "mysql2": "^3.4.2", "nunjucks": "^3.2.4", "passport": "^0.6.0", "passport-kakao": "1.0.0", "passport-local": "^1.0.0", "pm2": "^5.3.0", "redis": "^3.0.2", "sanitize-html": "^2.11.0", "sequelize": "^6.32.1", "sequelize-cli": "^6.6.1", "winston": "^3.10.0" }, "devDependencies": { "nodemon": "^2.0.22" } } passport > kakaoStrategy.js 부분입니다.const passport = require('passport'); const KakaoStrategy = require('passport-kakao').Strategy; const User = require('../models/user'); module.exports = () => { passport.use(new KakaoStrategy({ clientID: process.env.KAKAO_ID, callbackURL: '/auth/kakao/callback', }, async (accessToken, refreshToken, profile, done) => { // accessToken, refreshToken은 카카오 api를 호출하는데 사용되나, 여기선 사용하지 않음 console.log('kakao profile', profile); try { const exUser = await User.findOne({ where: { snsId: profile.id, provider: 'kakao' }, }); if (exUser) { //login done(null, exUser); } else { //join const newUser = await User.create({ email: profile._json?.kakao_account?.email, // 이 구조가 자주 바뀌므로, profile을 console.log를 통해 계속 확인하자 nick: profile.displayName, snsId: profile.id, provider: 'kakao', }); done(null, newUser); } } catch (error) { console.error(error); done(error); } })); };
-
해결됨[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
프록시 서버 질문
이전 강의에서 만든 서버에서 서버로 요청하는 nodecat서버를 프록시 서버라고 봐도 되나요?브라우저에서 nodecat과 같은 도메인인 localhost:4000/myposts 요청을 보냈고 nodecat 서버가 이를 받아 nodebird서버로 localhost:8002/v1/post/my 요청을 보냈으니 이게 뭐 http-proxy-middleware같은 패키지를 써서 따로 구현해야 하는 프록시 서버랑 뭐가 다른지 모르겠습니다.
-
해결됨[개정3판] Node.js 교과서 - 기본부터 프로젝트 실습까지
axios.defaults.headers.origin 질문
이전 강의에서 갑자기 axios.defaults.headers.origin을 넣은 것이 이해가 되지 않았었는데 이번 강의에서 corsWhenDomainMatches 미들웨어에 사용하기 위해 넣은 것이라 봐도 되나요? 근데 axios.defaults.headers.origin = process.env.ORIGIN;코드를 주석처리해도 Origin 헤더는 정상적으로 찍히던데 저게 있는 것과 없는 것과 뭔 차이인가요?
-
미해결따라하며 배우는 NestJS
회원가입 기능 구현 중 오류가 났습니다.
No repository for "UserRepository" was found. Looks like this entity is not registered in current "default" connection? service와 module에 UserRepository 모두 설정했고 repository에도 생성을 했는데 왜 못찾는지 모르겠습니다.이런 오류 발생했을때는 어느 부분을 확인해봐야 할까요?