묻고 답해요
148만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
상품등록 클릭시 WhiteLabel 오류
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]상품등록 코드를 작성했고 전부 알맞게 친것 같은데 상품등록 버튼을 누르면 위 Whitelabel Page가 뜹니다 이유를 알 수 있을까요?
-
해결됨실전! 스프링 데이터 JPA
기초적인 질문입니다...
public void changeTeam(Team team) { this.team = team; team.getMembers().add(this);}강의를 들었을 때 양방향 연관관계에서는 한쪽에만 작업을 해주면 안되고 양쪽을 작업해줘야 하기 때문에 위와 같은 코드를 작성한다고 알고 있습니다.위 코드에서 팀 변경으로 인해 새로운 팀에 member를 추가해주고 기존의 팀의 list에서 제거해주는 작업은 별도로 진행하지 않아도 되는지 궁금하여 질문 남깁니다...한쪽에 설정해주면 JPA가 알아서 양방향 연관관계에 대해서 정리해주는건가 싶기도 하고 잘모르겠어서 질문 드립니다
-
미해결스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
JapMemberRepository 클래스에서 메서드의 리턴값에 관한 질문입니다.
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예)[질문 내용]@Overridepublic Member save(Member member) {em.persist(member); return member;}여기에서 스펙을 맞추기 위해 return값을 넣어준다고 하셨는데요, 이게 무슨 의미인지 궁금합니다!혼자 코딩을 하게 될 경우 return값이 필요한지, 어떤 값으로 리턴해야 하는지 판단하려면 어떻게 해야 하나요?
-
미해결실전! 스프링 데이터 JPA
JOIN 관련해서 질문드릴게 있어요!
안녕하세요. 수업 듣다가 갑자기헷갈려서 여쭤볼게 생겼어요.@Query("select new study.datajpa.dto.MemberDto(m.id, m.username, t.name) " + "from Member m join m.team t")위 같은 쿼리에서 Member entity에 t.name이란게 없으니 저는 fetch join을 써야 한다고 처음에 생각했거든요.일반 join은 조회시 join 대상이 되는 entity는 조회 대상이 되는 칼럼에서 불러지지 않고, 불러진다면, 따로 쿼리가 동작하기 떄문에 N+1 이 발생하는 걸로 알고있었는데, 수업에서 log보니깐 select 쿼리에서 t.name 도 같이 한 쿼리에 조회되는게 보여서 갑자기.. 멍해지더라구요 ㅋㅋ;;;저는 member 따로 1번, team따로 1번 돌거라고 예상했었는데... 제가 놓친 부분이 있을까요?? 답변 미리 감사드리겠습니다.
-
해결됨실전! 스프링 데이터 JPA
isNew() 메서드의 구현체 문의
안녕하세요! isNew() 메서드의 구현체가 정확히 무엇인지 확인하고 싶어 질문드립니다!SimpleJpaRepository의 save() 메서드 내부에서 isNew() 메서드가 호출될 때,if (entityInformation.isNew(entity)) {처럼 호출되는데요, 이 때 isNew 가 org.springframework.data.repository.core.EntityInformation 인터페이스의 추상 메서드인 것을 확인했습니다.그런데 이 isNew 의 구현체가 여러개라서 하나씩 확인해보니 강의에서 설명해주신 내용이 추상 클래스 org.springframework.data.repository.core.support.AbstractEntityInformation 에 구현된 내용과 같더라고요.// AbstractEntityInformation.isNew() public boolean isNew(T entity) { ID id = getId(entity); Class<ID> idType = getIdType(); if (!idType.isPrimitive()) { return id == null; } if (id instanceof Number) { return ((Number) id).longValue() == 0L; } throw new IllegalArgumentException(String.format("Unsupported primitive id type %s", idType)); }그런데 AbstractEntityInformation 역시 추상 클래스이다 보니 정확한 구현체를 찾기가 힘든 상태입니다. 질문) SimpleJpaRepository의 save() 내부에서 사용되는 isNew() 의 구현체는 정확히 어떤 클래스인가요? 또, 이러한 구현체가 정확히 무엇인지 알 수 있는 방법이 있을까요?확인해주셔서 감사합니다. :D
-
미해결스프링 시큐리티 OAuth2
프론트와 백으로 나누어진 형태에선, Authorization Code Grant Type을 어떻게 구현하나요?
안녕하세요 강의 잘 듣고있습니다.저는 강의에서 사용하고있는 Spring + Thymeleaf 구조를 사용하고 있지않습니다.spring + react와 같이 백엔드와 프론트로 나누어져 있고 rest api방식으로 통신하는 구조입니다. 구글링해본 결과. rest api 구조에서 소셜로그인을 시도할경우.대부분 소셜로그인을 전부 프론트에서만 처리하고있었습니다.즉, Implict Grant Type이죠.하지만 강의에서 강사님께서 이 방법은 Deprecated되었으며 바람직하지 않다고 하셨습니다. 그래서 저는 Authorization Code Grant Type으로 인증을 받으려 시도했습니다만.구글링을 해봐도 죄다 Implict 방식만나오고 Code방식은 나오지 않아 어려움을 겪고있습니다. 그냥 모바일앱이나 react와 같은 프론트앱에서는 Implicit Type으로 하는게 최선일까요? 이 강의의 다른 질문글들을 읽어보았는데도 정확한 해결방법은 제시되어 있지 않는것 같아재차 질문글을 작성하게 되었습니다. 읽어주셔서 감사합니다.
-
미해결스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
unsupported Post method [405 error]
스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술섹션 5. 회원 웹 기능 -등록 을 수강하고 있습니다. 저는 VSCODE 로 IDE를 활용하고 있고, 코드서버를 쓰고 있습니다. 강의에서 배운 내용 그대로 코드를 쳤는데도 불구하고 위의 그림과 같이 Unsupported POST method 라고 뜹니다.혹시 몰라서 마지막에 IDE 화면도 첨부했습니다.package donghun.donghunspring.Controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import donghun.donghunspring.domain.Member; import donghun.donghunspring.service.MemberService; @Controller public class MemberController { //memberService new 객체를 만드는 게 아니고, 연결 시켜줘야 한다 : 생성자 이용. private final MemberService memberService; //스프링이 뜰 때 Controller 에 service와 repository 연결한다. annotation 붙여 (service, repository) //연결하려면 service 와 repository 를 스프링 빈에 등록해야 하므로 service, repository 각각에 @Service, @repository 등록. //또는 직접 자바코드 작성하여 Bean 등록할 수 있음. @Autowired public MemberController(MemberService memberService) { this.memberService = memberService; } @GetMapping("/members") public String membersForm(){ return "members/mF"; } @PostMapping("/members/new") public String create(MemberForm form){ Member member = new Member(); member.setName(form.getName()); memberService.join(member); return "redirect:/"; } @GetMapping("/members/new") public String createForm() { return "members/createMemberForm"; } //post 방식으로 form 입력하므로 postmapping 이 선택된다. } ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ template : createMemberForm.html //두 개의 코드가 첨부가 안되므로 절취선 이용해 붙이겠습니다. <!DOCTYPE HTML> <html xmlns:th="http://www.thymeleaf.org"> <body> <div class="container"> <form action="/members/new" method="post"> <div class="form-group"> <label for="name">이름</label> <input type="text" id="name" name="name" placeholder="이름을 입력하세요"> </div> <button type="submit">등록</button> </form> </div> <!-- /container --> </body> </html>
-
미해결스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
clearStore() 메서드의 작성 위치 의문
=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오) 예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오) 예3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오) 예[질문 내용]MemoryMemberRepository 클래스에서public void clearStore() 메서드를 추가해서 테스트 실행 시 사용하는데,Repository클래스의 목적에 맞지 않게 테스트에서만 사용하기 위한 clearStore() 메서드를 Repository클래스에 정의하여 적어 주는 게 일반적이고 흔히 쓰는 방식인가요 ?테스트 케이스에서 따로 save된 정보를 삭제하는 방식으로 하는 게 더 올바른 방식은 없고 그 방식을 안하는 이유가 있을까요 ? 궁금해서 질문드립니다.
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
jpa 테이블 생성 시 질문
로컬서버에서 appication.yml 에jpa:hibernate:ddl-auto: create이렇게 설정하면 테이블이 전체 지워지고 다시 생성되는 것까지 이했습니다. 그런데 만들어놓은 테이블은 그대로 두고 새로운 컬럼이나 테이블을 추가한거만 업데이트를 하면 좋을 거같은데 구글링 해봐도 잘모르겠네요 ㅜ 자꾸 테이블을 지우고 다시 생성하니까 테스트하기가 좀 힘든거같아요. 혹시 방법이 있을까요?
-
해결됨Spring Boot JWT Tutorial
JWT에 생성할 때 질문
JwtProviderpackage com.example.project1.config.jwt; import com.example.project1.config.auth.PrincipalDetails; import com.example.project1.domain.jwt.TokenDTO; import com.example.project1.domain.member.UserType; import io.jsonwebtoken.*; import lombok.extern.slf4j.Slf4j; 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 io.jsonwebtoken.security.Keys; import javax.xml.bind.DatatypeConverter; import java.security.Key; import java.util.*; import java.util.stream.Collectors; @Slf4j @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 secret_key) { byte[] secretByteKey = DatatypeConverter.parseBase64Binary(secret_key); this.key = Keys.hmacShaKeyFor(secretByteKey); } // 유저 정보를 가지고 AccessToken, RefreshToken 을 생성하는 메소드 public TokenDTO createToken(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)를 가져와 문자열 형태로 변환한 후, // 쉼표로 구분하여 조인한 결과를 authorities 변수에 저장합니다. 따라서 authorities는 권한 정보를 문자열 형태로 가지게 됩니다. // 권한 정보를 문자열로 변환하여 클레임에 추가하는 방식 // String authorities = authentication.getAuthorities().stream() // .map(GrantedAuthority::getAuthority) // .collect(Collectors.joining(",")); Map<String, Object> claims = new HashMap<>(); claims.put(AUTHORITIES_KEY, authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList())); log.info("claims in JwtProvider : " + claims); log.info("authentication.getName() in JwtProvider : " + authentication.getName()); long now = (new Date()).getTime(); Date now2 = new Date(); // AccessToken 생성 Date accessTokenExpire = new Date(now + this.accessTokenTime); String accessToken = Jwts.builder() // 내용 sub : 유저의 이메일 // 토큰 제목 // JWT의 "sub" 클레임을 설정하는 메서드입니다. // "sub" 클레임은 일반적으로 사용자를 식별하는 용도로 사용되며, // 이메일과 같은 사용자의 고유한 식별자를 담고 있을 수 있습니다. .setSubject(authentication.getName()) .setIssuedAt(now2) // 클레임 id : 유저 ID // .claim(AUTHORITIES_KEY, authorities) .setClaims(claims) // 내용 exp : 토큰 만료 시간, 시간은 NumericDate 형식(예: 1480849143370)으로 하며 // 항상 현재 시간 이후로 설정합니다. .setExpiration(accessTokenExpire) // 서명 : 비밀값과 함께 해시값을 ES256 방식으로 암호화 .signWith(key, SignatureAlgorithm.HS256) .compact(); Claims claims2 = Jwts.parser().setSigningKey(key).parseClaimsJws(accessToken).getBody(); String subject = claims2.getSubject(); log.debug("claims subject 확인 in JwtProvider : " + subject); // Claims claim = Jwts.parserBuilder() // .setSigningKey(key) // .build() // .parseClaimsJws(accessToken).getBody(); // accessToken in JwtProvider : eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ6eHp6NDVAbmF2ZXIuY2 // 9tIiwiaWF0IjoxNjg5OTk1MzM3LCJhdXRoIjoiUk9MRV9VU0VSIiwiZXhwIjoxNjkzNTk1MzM3fQ.2_2PR-A // X9N0jKDyA7LpK7xRRBZBYZ17_f8Jq2TY4ny8 log.info("accessToken in JwtProvider : " + accessToken); // claim에서 auth 확인 in JwtProvider : ROLE_USER log.info("claim에서 accessToken에 담김 auth 확인 in JwtProvider : " + claims); // RefreshToken 생성 Date refreshTokenExpire = new Date(now + this.refreshTokenTime); String refreshToken = Jwts.builder() .setSubject(authentication.getName()) .setClaims(claims) .setIssuedAt(now2) .setExpiration(refreshTokenExpire) .signWith(key, SignatureAlgorithm.HS256) .compact(); log.info("refreshToken in JwtProvider : " + refreshToken); log.info("claim에서 refreshToken에 담긴 auth 확인 in JwtProvider : " + claims); TokenDTO tokenDTO= TokenDTO.builder() .grantType("Bearer ") .accessToken(accessToken) .refreshToken(refreshToken) .accessTokenTime(accessTokenExpire) .refreshTokenTime(refreshTokenExpire) // principalDeatails에서 getUserName 메소드가 반환한 것을 담아준다. // 이메일을 반환하도록 구성했으니 이메일이 반환됩니다. .userEmail(authentication.getName()) .build(); log.info("tokenDTO in JwtProvider : " + tokenDTO); return tokenDTO; } // 소셜 로그인 성공시 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("auth", 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; } // 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); log.info("authorities : " + authorities); Map<String, Object> claims = new HashMap<>(); claims.put(AUTHORITIES_KEY, authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList())); log.info("claims : " + claims); String accessToken = Jwts.builder() .setIssuedAt(now2) .setSubject(userEmail) .setExpiration(accessTokenExpire) .setClaims(claims) .signWith(key, SignatureAlgorithm.HS256) .compact(); log.info("accessToken in JwtProvider : " + accessToken); // log.info("claim에서 accessToken에 담김 auth 확인 in JwtProvider : " + auth); TokenDTO tokenDTO = TokenDTO.builder() .grantType("Bearer ") .accessToken(accessToken) .userEmail(userEmail) .accessTokenTime(accessTokenExpire) .build(); log.info("tokenDTO in JwtProvider : " + tokenDTO); return tokenDTO; } // JWT 토큰을 복호화하여 토큰에 들어있는 정보를 꺼내는 코드 // 토큰으로 클레임을 만들고 이를 이용해 유저 객체를 만들어서 최종적으로 authentication 객체를 리턴 // 인증 정보 조회 public Authentication getAuthentication(String token) { // 토큰 복호화 메소드 Claims claims = parseClaims(token); log.info("claims in JwtProvider : " + claims); if(claims.get("auth") == null) { throw new RuntimeException("권한 정보가 없는 토큰입니다."); } Object auth = claims.get("auth"); // [ROLE_USER] log.info("auth in JwtProvider : " + auth); // 클레임 권한 정보 가져오기 List<String> authorityStrings = (List<String>) claims.get(AUTHORITIES_KEY); // [ROLE_USER] log.info("authorityStrings in JwtProvider : " + authorityStrings); Collection<? extends GrantedAuthority> authorities = authorityStrings.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); // [ROLE_USER] log.info("authorities in JwtProvider : " + authorities); // UserDetails 객체를 만들어서 Authentication 리턴 // User principal = new User(claims.getSubject(), "", authorities); // log.info("principal in JwtProvider : " + principal); log.info("claims.getSubject() in JwtProvider : " + claims.getSubject()); return new UsernamePasswordAuthenticationToken(claims.getSubject(), token, authorities); } private Claims parseClaims(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); return true; } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { log.info("잘못된 JWT 서명입니다."); } catch (ExpiredJwtException e) { log.info("만료된 JWT 토큰입니다."); } catch (UnsupportedJwtException e) { log.info("지원되지 않는 JWT 토큰입니다."); }catch (IllegalArgumentException e) { log.info("JWT 토큰이 잘못되었습니다."); } return false; } }JwtAuthenticationFilterpackage com.example.project1.config.jwt; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StringUtils; import org.springframework.web.filter.GenericFilterBean; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import java.io.IOException; import java.net.URI; // 클라이언트 요청 시 JWT 인증을 하기 위해 설치하는 커스텀 필터로 // UsernamePasswordAuthenticationFiler 이전에 실행된다. // 이전에 실행된다는 뜻은 JwtAuthenticationFilter 를 통과하면 // UsernamePasswordAuthenticationFilter 이후의 필터는 통과한 것으로 본다는 뜻이다. // 쉽게 말해서, Username + Password 를 통한 인증을 Jwt 를 통해 수행한다는 것이다. // JWT 방식은 세션과 다르게 Filter 하나를 추가해야 합니다. // 이제 사용자가 로그인을 했을 때, Request 에 가지고 있는 Token 을 해석해주는 로직이 필요합니다. // 이 역할을 해주는것이 JwtAuthenticationFilter입니다. // 세부 비즈니스 로직들은 TokenProvider에 적어둡니다. 일종의 service 클래스라고 생각하면 편합니다. // 1. 사용자의 Request Header에 토큰을 가져옵니다. // 2. 해당 토큰의 유효성 검사를 실시하고 유효하면 // 3. Authentication 인증 객체를 만들고 // 4. ContextHolder에 저장해줍니다. // 5. 해당 Filter 과정이 끝나면 이제 시큐리티에 다음 Filter로 이동하게 됩니다. @RequiredArgsConstructor @Slf4j public class JwtAuthenticationFilter extends GenericFilterBean { public static final String HEADER_AUTHORIZATION = "Authorization"; private final JwtProvider jwtProvider; // doFilter는 토큰의 인증정보를 SecurityContext에 저장하는 역할 수행 @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; // Request Header에서 JWT 토큰을 추출 // 요청 헤더에서 JWT 토큰을 추출하는 역할 String jwt = resolveToken(httpServletRequest); // jwt : // eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ6eHp6NDVAbmF2ZXIuY29tIiwiaWF // 0IjoxNjg5OTQ0OTk0LCJhdXRoIjoiIiwiZXhwIjoxNjg5OTQ1MzU0fQ.qyR2bJMDmNb1iv // q6a4W55dGBmyFEzaENN1-F7qPlJKw log.info("jwt : " + jwt); String requestURI = httpServletRequest.getRequestURI(); // requestURI/api/v1/users/1 log.info("requestURI" + requestURI); if(StringUtils.hasText(jwt) && jwtProvider.validateToken(jwt)){ // 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext에 저장 Authentication authentication = jwtProvider.getAuthentication(jwt); // UsernamePasswordAuthenticationToken // [Principal=null, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_USER]] log.info("authentication in JwtAuthenticationFilter : " + authentication); SecurityContextHolder.getContext().setAuthentication(authentication); log.info("Security Context에 인증 정보를 저장했습니다. 정보 : {}",authentication.getName()); } else { log.debug("유효한 JWT 토큰이 없습니다. uri : {}", requestURI); } chain.doFilter(request, response); } // Request Header 에서 토큰 정보를 꺼내오기 위한 메소드 // HEADER_AUTHORIZATION로 정의된 헤더 이름을 사용하여 토큰을 찾고, // 토큰이 "Bearer "로 시작하는 경우에만 실제 토큰 값을 반환 private String resolveToken(HttpServletRequest httpServletRequest) { String bearerToken = httpServletRequest.getHeader(HEADER_AUTHORIZATION); if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { return bearerToken.substring(7); } else { return null; } } }PrincipalDetailsServicepackage com.example.project1.config.auth; 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.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; // http://localhost:8080/login ← 이 때 동작을 함 @Service @RequiredArgsConstructor @Slf4j public class PrincipalDetailsService implements UserDetailsService { private MemberRepository memberRepository; // 시큐리티 session = Authentication = UserDetails // 함수 종료시 @AuthenticationPrincipal 어노테이션이 만들어진다. @Override public UserDetails loadUserByUsername(String userEmail) throws UsernameNotFoundException { MemberEntity member = memberRepository.findByUserEmail(userEmail); log.info("user in PrincipalDetailsService : " + member); return new PrincipalDetails(member); } } PrincipalDetailspackage 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.beans.factory.annotation.Autowired; 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() { return member.getUserPw(); } // 사용자 이름 반환 @Override public String getUsername() { 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() { return attributes; } @Override // OAuth2 인증에서는 사용되지 않는 메서드이므로 null 반환 public String getName() { return null; } }controller // 로그인 @PostMapping("/api/v1/users/login") public ResponseEntity<?> login(@RequestBody MemberDTO memberDTO) throws Exception { log.info("member : " + memberDTO); try { log.info("-----------------"); ResponseEntity<TokenDTO> login = memberService.login(memberDTO.getUserEmail(), memberDTO.getUserPw()); log.info("login : " + login); return ResponseEntity.ok().body(login); } catch (Exception e) { return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(e.getMessage()); } } // refresh로 access 토큰 재발급 // @RequsetHeader"Authorization")은 Authorization 헤더에서 값을 추출합니다. // 일반적으로 리프레시 토큰은 Authorization 헤더의 값으로 전달되며, // Bearer <token> 형식을 따르는 경우가 일반적입니다. 여기서 <token> 부분이 실제 리프레시 토큰입니다 // 로 추출하면 다음과 같이 문자열로 나온다. // Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ0ZXN0IiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c @PostMapping("/refresh") public ResponseEntity<?> refreshToken(@RequestHeader("Authorization") String token) throws Exception { try { ResponseEntity<TokenDTO> accessToken = refreshTokenService.createAccessToken(token); return ResponseEntity.ok().body(accessToken); } catch (Exception e) { throw new RuntimeException(e); } }MemberServicepackage com.example.project1.service.member; import com.example.project1.config.jwt.JwtAuthenticationFilter; import com.example.project1.config.jwt.JwtProvider; import com.example.project1.domain.jwt.TokenDTO; import com.example.project1.domain.member.MemberDTO; import com.example.project1.domain.member.UserType; import com.example.project1.entity.jwt.TokenEntity; import com.example.project1.entity.member.MemberEntity; import com.example.project1.entity.member.embedded.AddressEntity; import com.example.project1.repository.jwt.TokenRepository; import com.example.project1.repository.member.MemberRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 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.security.crypto.password.PasswordEncoder; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.stereotype.Service; import java.util.*; @Service @RequiredArgsConstructor @Slf4j public class MemberService { private final MemberRepository memberRepository; private final PasswordEncoder passwordEncoder; private final AuthenticationManagerBuilder authenticationManagerBuilder; private final JwtProvider jwtProvider; private final TokenRepository tokenRepository; // 회원가입 public String signUp(MemberDTO memberDTO) throws Exception { try { MemberEntity byUserEmail = memberRepository.findByUserEmail(memberDTO.getUserEmail()); if (byUserEmail != null) { return "이미 가입된 회원입니다."; } else { // 아이디가 없다면 DB에 넣어서 등록 해준다. MemberEntity member = MemberEntity.builder() .userEmail(memberDTO.getUserEmail()) .userPw(passwordEncoder.encode(memberDTO.getUserPw())) .userName(memberDTO.getUserName()) .nickName(memberDTO.getNickName()) .userType(memberDTO.getUserType()) .provider(memberDTO.getProvider()) .providerId(memberDTO.getProviderId()) .address(AddressEntity.builder() .userAddr(memberDTO.getAddressDTO().getUserAddr()) .userAddrDetail(memberDTO.getAddressDTO().getUserAddrDetail()) .userAddrEtc(memberDTO.getAddressDTO().getUserAddrEtc()) .build()) .build(); log.info("member : " + member); memberRepository.save(member); // MemberDTO memberDTO1 = MemberDTO.toMemberDTO(Optional.of(save)); return "회원가입에 성공했습니다."; } } catch (Exception e) { log.error(e.getMessage()); throw e; // 예외를 던져서 예외 처리를 컨트롤러로 전달 } } // 아이디 조회 public MemberDTO search(Long userId) { Optional<MemberEntity> searchId = memberRepository.findById(userId); MemberDTO memberDTO = MemberDTO.toMemberDTO(searchId); return memberDTO; } // 회원 삭제 public String remove(Long userId) { MemberEntity member = memberRepository.deleteByUserId(userId); if(member == null) { return "회원 탈퇴 완료!"; } else { return "회원 탈퇴 실패!"; } } // 로그인 public ResponseEntity<TokenDTO> login(String userEmail, String userPw) throws Exception { 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 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 token = jwtProvider.createToken(authentication, findUser.getUserType()); TokenDTO token = jwtProvider.createToken(authentication, authoritiesForUser); log.info("tokenEmail in MemberService : "+ token.getUserEmail()); TokenEntity checkEmail = tokenRepository.findByUserEmail(token.getUserEmail()); log.info("checkEmail in MemberService : " + checkEmail); // 사용자에게 이미 토큰이 할당되어 있는지 확인합니다. if (checkEmail != null) { log.info("이미 발급한 토큰이 있습니다."); // // 기존 토큰을 업데이트할 때 사용할 임시 객체로 TokenDTO token2를 생성합니다. TokenDTO token2 = TokenDTO.builder() .id(checkEmail.getId()) .grantType(token.getGrantType()) .accessToken(token.getAccessToken()) .refreshToken(token.getRefreshToken()) .userEmail(token.getUserEmail()) .nickName(findUser.getNickName()) .userId(findUser.getUserId()) .accessTokenTime(token.getAccessTokenTime()) .refreshTokenTime(token.getRefreshTokenTime()) .userType(findUser.getUserType()) .build(); // 기존 토큰을 업데이트할 때 사용할 임시 객체로 TokenEntity tokenEntity2를 생성합니다. TokenEntity updateToken = TokenEntity.builder() .id(token2.getId()) .grantType(token2.getGrantType()) .accessToken(token2.getAccessToken()) .refreshToken(token2.getRefreshToken()) .userEmail(token2.getUserEmail()) .nickName(token2.getNickName()) .userId(token2.getUserId()) .accessTokenTime(token2.getAccessTokenTime()) .refreshTokenTime(token2.getRefreshTokenTime()) .userType(token2.getUserType()) .build(); log.info("token in MemberService : " + updateToken); tokenRepository.save(updateToken); } else { log.info("발급한 토큰이 없습니다."); token = TokenDTO.builder() .grantType(token.getGrantType()) .accessToken(token.getAccessToken()) .refreshToken(token.getRefreshToken()) .userEmail(token.getUserEmail()) .nickName(findUser.getNickName()) .userId(findUser.getUserId()) .accessTokenTime(token.getAccessTokenTime()) .refreshTokenTime(token.getRefreshTokenTime()) .userType(findUser.getUserType()) .build(); // 새로운 토큰을 DB에 저장할 때 사용할 임시 객체로 TokenEntity tokenEntity를 생성합니다. TokenEntity newToken = TokenEntity.builder() .grantType(token.getGrantType()) .accessToken(token.getAccessToken()) .refreshToken(token.getRefreshToken()) .userEmail(token.getUserEmail()) .nickName(token.getNickName()) .userId(token.getUserId()) .accessTokenTime(token.getAccessTokenTime()) .refreshTokenTime(token.getRefreshTokenTime()) .userType(token.getUserType()) .build(); log.info("token in MemberService : " + newToken); tokenRepository.save(newToken); } HttpHeaders headers = new HttpHeaders(); // response header에 jwt token을 넣어줌 headers.add(JwtAuthenticationFilter.HEADER_AUTHORIZATION, "Bearer " + token); return new ResponseEntity<>(token, headers, HttpStatus.OK); } } else { return null; } return null; } private List<GrantedAuthority> getAuthoritiesForUser(MemberEntity member) { // 예시: 데이터베이스에서 사용자의 권한 정보를 조회하는 로직을 구현 // member 객체를 이용하여 데이터베이스에서 사용자의 권한 정보를 조회하는 예시로 대체합니다. UserType role = member.getUserType(); // 사용자의 권한 정보를 가져오는 로직 (예시) List<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("ROLE_" +role.name())); log.info("role in MemberService : " + role.name()); log.info("authorities in MemberService : " + authorities); return authorities; } // 회원정보 수정 public MemberDTO update(MemberDTO memberDTO) { MemberEntity findUser = memberRepository.findByUserEmail(memberDTO.getUserEmail()); if(findUser != null) { findUser = MemberEntity.builder() .userPw(passwordEncoder.encode(memberDTO.getUserPw())) .userType(memberDTO.getUserType()) .userName(memberDTO.getUserName()) .nickName(memberDTO.getNickName()) .address(AddressEntity.builder() .userAddr(memberDTO.getAddressDTO().getUserAddr()) .userAddrDetail(memberDTO.getAddressDTO().getUserAddrDetail()) .userAddrEtc(memberDTO.getAddressDTO().getUserAddrEtc()) .build()).build(); memberRepository.save(findUser); MemberDTO modifyUser = MemberDTO.toMemberDTO(Optional.of(findUser)); return modifyUser; } else { MemberEntity member = MemberEntity.builder() .userEmail(memberDTO.getUserEmail()) .userPw(passwordEncoder.encode(memberDTO.getUserPw())) .userName(memberDTO.getUserName()) .nickName(memberDTO.getNickName()) .userType(memberDTO.getUserType()) .address(AddressEntity.builder() .userAddr(memberDTO.getAddressDTO().getUserAddr()) .userAddrDetail(memberDTO.getAddressDTO().getUserAddrDetail()) .userAddrEtc(memberDTO.getAddressDTO().getUserAddrEtc()) .build()) .build(); memberRepository.save(member); // 제대로 DTO 값이 엔티티에 넣어졌는지 확인하기 위해서 // 엔티티에 넣어주고 다시 DTO 객체로 바꿔서 리턴을 해줬습니다. MemberDTO memberDto = MemberDTO.toMemberDTO(Optional.of(member)); log.info("memberDto : " + memberDto); return memberDto; } } // 소셜 로그인 성공시 jwt 반환 // OAuth2User에서 필요한 정보를 추출하여 UserDetails 객체를 생성하는 메서드 public ResponseEntity<TokenDTO> createToken(OAuth2User oAuth2User) { String userEmail = oAuth2User.getAttribute("email"); log.info("userEmail in MemberService : " + userEmail); MemberEntity findMember = memberRepository.findByUserEmail(userEmail); // 권한 정보 추출 List<GrantedAuthority> authorities = getAuthoritiesForUser(findMember); // 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); } }RefreshTokenServicepackage com.example.project1.service.jwt; import com.example.project1.config.jwt.JwtAuthenticationFilter; import com.example.project1.config.jwt.JwtProvider; import com.example.project1.domain.jwt.TokenDTO; import com.example.project1.domain.member.UserType; import com.example.project1.entity.jwt.TokenEntity; import com.example.project1.entity.member.MemberEntity; import com.example.project1.repository.jwt.TokenRepository; import com.example.project1.repository.member.MemberRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; 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.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service @RequiredArgsConstructor @Slf4j public class RefreshTokenService { private final TokenRepository tokenRepository; private final JwtProvider jwtProvider; private final MemberRepository memberRepository; public ResponseEntity<TokenDTO> createAccessToken(String refreshToken) { // refreshToken 유효성 검사하고 true면 넘어감 if(jwtProvider.validateToken(refreshToken)) { TokenEntity findRefreshTokenEmail = tokenRepository.findByRefreshToken(refreshToken); // 아이디 추출 String userEmail = findRefreshTokenEmail.getUserEmail(); log.info("userEmail : " + userEmail); MemberEntity member = memberRepository.findByUserEmail(userEmail); log.info("member : " + member); // 사용자의 권한 정보를 가져옴 List<GrantedAuthority> authoritiesForUser = getAuthoritiesForUser(member); TokenDTO accessToken = jwtProvider.createAccessToken(userEmail, authoritiesForUser); log.info("accessToken : " + accessToken); accessToken = TokenDTO.builder() .grantType(accessToken.getGrantType()) .accessToken(accessToken.getAccessToken()) .userEmail(accessToken.getUserEmail()) .nickName(member.getNickName()) .userId(member.getUserId()) .accessTokenTime(accessToken.getAccessTokenTime()) .build(); TokenEntity tokenEntity = TokenEntity.builder() .grantType(accessToken.getGrantType()) .accessToken(accessToken.getAccessToken()) .userEmail(accessToken.getUserEmail()) .nickName(accessToken.getNickName()) .userId(accessToken.getUserId()) .accessTokenTime(accessToken.getAccessTokenTime()) .build(); log.info("token : " + tokenEntity); tokenRepository.save(tokenEntity); HttpHeaders headers = new HttpHeaders(); // response header에 jwt token을 넣어줌 headers.add(JwtAuthenticationFilter.HEADER_AUTHORIZATION, "Bearer " + accessToken); return new ResponseEntity<>(accessToken, headers, HttpStatus.OK); } else { throw new IllegalArgumentException("Unexpected token"); } } // 주어진 사용자에 대한 권한 정보를 가져오는 로직을 구현하는 메서드입니다. // 이 메서드는 데이터베이스나 다른 저장소에서 사용자의 권한 정보를 조회하고, // 해당 권한 정보를 List<GrantedAuthority> 형태로 반환합니다. private List<GrantedAuthority> getAuthoritiesForUser(MemberEntity member) { // 예시: 데이터베이스에서 사용자의 권한 정보를 조회하는 로직을 구현 // member 객체를 이용하여 데이터베이스에서 사용자의 권한 정보를 조회하는 예시로 대체합니다. UserType role = member.getUserType(); // 사용자의 권한 정보를 가져오는 로직 (예시) log.info("role : " + role.name()); List<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("ROLE_" + role.name())); return authorities; } }이런식으로 코드를 작성했습니다. 로그인 시 JWT는 제대로 생성해주는 것을 볼 수 있고 권한이 주어지고 auth에 ROLE_USER이런식으로 들어가는 것을 확인했습니다. 근데 .setSubject(authentication.getName())를 넣었는데 헤더에 토큰을 담아서 보내는 테스트를 할 때 public Authentication getAuthentication(String token) { // 토큰 복호화 메소드 Claims claims = parseClaims(token); log.info("claims in JwtProvider : " + claims); if(claims.get("auth") == null) { throw new RuntimeException("권한 정보가 없는 토큰입니다."); } Object auth = claims.get("auth"); // [ROLE_USER] log.info("auth in JwtProvider : " + auth); // 클레임 권한 정보 가져오기 List<String> authorityStrings = (List<String>) claims.get(AUTHORITIES_KEY); // [ROLE_USER] log.info("authorityStrings in JwtProvider : " + authorityStrings); Collection<? extends GrantedAuthority> authorities = authorityStrings.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); // [ROLE_USER] log.info("authorities in JwtProvider : " + authorities); // UserDetails 객체를 만들어서 Authentication 리턴 // User principal = new User(claims.getSubject(), "", authorities); // log.info("principal in JwtProvider : " + principal); log.info("claims.getSubject() in JwtProvider : " + claims.getSubject()); return new UsernamePasswordAuthenticationToken(claims.getSubject(), token, authorities); } private Claims parseClaims(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(); } }auth는 제대로 나오는데 claims.getSubject() 이 부분이 null이 나옵니다. 즉 권한을 제대로 주어지는데 new UsernamePasswordAuthenticationToken(claims.getSubject(), token, authorities); 인증하는 이부분에서 인자가 (이메일, 토큰, 권한)이렇게 주어져야 하는데 (null, 토큰, 권한) 이렇게 갑니다. JwtAuthenticationFilter에서 Authentication authentication = jwtProvider.getAuthentication(jwt); authentication 담아주는 것을 로그로 찍어본 결과 // [Principal=null, Credentials=[PROTECTED], Authenticated=true, Details=null, Granted Authorities=[ROLE_USER]] 이렇게 로그가 찍혔습니다. 로그를 보면 인증은 true니까 된거 같은데 log.info("Security Context에 인증 정보를 저장했습니다. 정보 : {}", authentication.getName()); 여기서도는 null도 아니고 앞에 문자열만 뜨고 안뜹니다. 근데 JwtProvider에서 log로 찍어보면 이메일이 제대로 나오고 있습니다....
-
미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
컬렉션의 필요성에 대해 질문 좀 드리겠습니다
[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예[질문 내용]먼저 다 대 다 관계라고 하겠습니다.A라는 엔티티와 B라는 엔티티가 다대다로 묶여져 있고 그 중간테이블을 C라고 하겠습니다.이럴 경우에 걍 컬렉션 배치size으로 하는게 아니라 중간 테이블 B로 하면 안되나요?요약하면 강의에서는 페이징이 필요하지 않으면 join fetch로 페이징이 필요하다면 batch size를 활용하라 라고 되어있는데 역으로 @ManyToOne이 있는 다대 일의 다 쪽에서 jon fetch로 모든 것을 해결하면 안되는 건가요?? 아 그리고 질문이 또 있는데 위에서 B에서 A와 C를 fetch join으로 모두 가져오게 설계해도 괜찮죠??
-
해결됨실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
OrderServiceTest 시 OrderItem 을리스트로 넘길때
안녕하세요. 강의 기준으로 응용하고 싶어서 orderitem을 한개가 아닌 여러개생성하고 주문하려고 하고 있는데 테스트java @Test @Rollback(value = false) public void 상품주문() throws Exception{ Member member = new Member(); member.setName("회원1"); member.setLoginId("admin"); member.setPassword("admin"); member.setEmail("admin"); em.persist(member); Item item = new Item(); item.setItemName("티셔츠1"); item.setPrice(100000); em.persist(item); OrderItem order1 = OrderItem.createOrderItem(item, item.getPrice(), 2,"blue","L"); OrderItem order2 = OrderItem.createOrderItem(item, item.getPrice(), 4,"blue","L"); List<OrderItem> orders = new ArrayList<>(); orders.add(order1); orders.add(order2); Long orderId = orderService.order(member.getId(), item.getId(), orders); Order findOrder = orderRepository.findOne(orderId); Assert.assertEquals("상품 주문시 상태는 : ", OrderStatus.ORDER,findOrder.getOrderStatus()); }OrderService.java @Transactional public Long order(Long memberId, Long itemId, OrderItem... orderItems) { //엔티티 조회 Member member = memberRepository.findOne(memberId); Item item = itemRepository.findOne(itemId); List<OrderItem> orders = new ArrayList<>(); //주문상품 생성 for(OrderItem order : orderItems){ OrderItem orderItem = OrderItem.createOrderItem(item,item.getPrice(),order.getQuantity(),order.getColor(),order.getSize()); orders.add(orderItem); } // 주문 생성 Order order = Order.createOrder(member, (OrderItem) orders); //주문 저장 orderRepository.save(order); return order.getId(); }OrderItem... orderItems 파라미터가 list를 받지 못하는거같은데 혹시 어떻게 해야할까요?
-
미해결자바와 스프링 부트로 생애 최초 서버 만들기, 누구나 쉽게 개발부터 배포까지! [서버 개발 올인원 패키지]
블로그 포스팅 관련 문의
안녕하세요 강사님먼저 좋은 강의 만들어주셔서 감사드립니다. 다름이 아니라 혹시 강의 내용과 pdf 강의 자료를 개인 블로그에 정리해서올려도 되는지 여쭙고자 글 남겨드렸습니다.다른 분들과 공유보다는 스스로 복습하는 용도로 글을 작성하는 목적이 크지만 인터넷에 올라오는 내용인 만큼 먼저 허락을 구하고자 합니다.최대한 지양하겠지만 가끔씩 강의 혹은 pdf 파일 속 그림이나 사진을 올리는 경우가 있을 수도 있을 것 같습니다만 이 때는 당연히 출처를 남기겠습니다.만약 불가하시다면 비공개 처리하여 공유 및 열람이 안 되도록 조치해놓겠습니다.감사합니다.
-
미해결스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
MemberController 러닝 에러
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG) 질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]4분 쯤에 강사님께서 @Autowired 설명하셨을 때 생성자의 파라미터인 memberService에 빨간 밑줄 에러표시가 떴는데 저는 그게 뜨지도 않았고, HelloSpringApplication을 실행하면 강사님과는 다르게 붉은 글씨들이 뜨네요.. 뭐가 잘못된 건지 모르겠습니다..
-
미해결실습으로 배우는 선착순 이벤트 시스템
MySql Lock을 사용하지 않는 이유
강의에서 설명해주시기로는 쿠폰 개수를 가져오는 것부터 쿠폰 생성까지 lock을 걸어야 한다고 설명 주셨는데 이전 강의인 재고 관리 이슈와는 다르게 row가 아닌 table에 lock을 걸기 때문에 성능 이슈가 발생한다고 보면 될까요?
-
미해결스프링 입문 - 코드로 배우는 스프링 부트, 웹 MVC, DB 접근 기술
회원서비스테스트강의중 회원가입 테스트에 대한 궁금증이 생겨서 질문드립니다.
회원가입 테스트를 진행할때 밑에 검증하는 부분에서 궁금증이 생겼습니다. 강의상에서는 제가 작성한 부분에서는 findOneMember라는 회원의 이름을 이용하여 검증을 하고 있는걸로 알고 있습니다. 하지만 제가 추가로 작성한 마지막 줄에 있는 검증 코드처럼 findOneMember라는 회원의 이름으로 접근하지 않고 when부분에서 도출한 joinId와 given부분에서 생성한 기대하는 회원의 id를 이용하여 검증을 하면 혹시 안되는지 여쭈어보고 싶습니다. 추가로 이렇게 검증을 할 때 어떤 것을 검증하냐는 개발자의 마음인지도 궁금합니다. 감사합니다.
-
미해결스프링 시큐리티
스프링 시큐리티 멀티 로그인 관련 질문드립니다.
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain adminfilterChain(HttpSecurity http) throws Exception { http.authorizeRequests() .requestMatchers("/admin/**").hasRole("ADMIN") .anyRequest().authenticated() .and() .httpBasic() ; return http.build(); } @Bean static final public InMemoryUserDetailsManager kk() { //DB연동을 안할 경우, 테스트 용으로 하는 것이다. UserDetails user = User.withDefaultPasswordEncoder() .username("user") .password("1111") .roles("USER") .build(); UserDetails admin = User.withDefaultPasswordEncoder() .username("admin") .password("1111") .roles("ADMIN") .build(); return new InMemoryUserDetailsManager(user, admin); } } @Configuration class config2 { @Bean public SecurityFilterChain userfilterChain(HttpSecurity http) throws Exception { http.authorizeRequests() .requestMatchers("/user/**").hasRole("USER") .anyRequest().authenticated() .and() .formLogin() ; return http.build(); } } 스프링 시큐리티가 최근부터 Bean으로 설정을 해야하며, 강의에서 나온 antmatchers()와 같은 함수명이 바뀌어 구글링을 하며 만들어보고자 도전해보고 있습니다. 해당 강의 중 "다중 보안 설정"에 대한 내용과 유사하게 구현해보고자 하였으나, 마음처럼 되지 않아 질문을 남김니다.제가 구현하고 싶은 형태는 localhost:8080/user >> formLogin() 페이지로 이동하게 되고, 이와 다르게 localhost:8080/admin >> httpBasic() 페이지로 이동하도록 구현하고자 합니다. 하지만, 생각과는 다르게 admin 경로가 처리가 안되는 것을 확인하였습니다. 이에 대해 조언을 구하고자 합니다.
-
미해결스프링 시큐리티 OAuth2
소셜 로그인 후 JWT 발급과 요청 시 access token담아 보내기 에러
https://github.com/YuYoHan/project_study1 전체 링크입니다. 일단, 시큐리티 컨피그 중 Oauth2에 관련된 부분입니다. 방식은 REST 방식입니다. http // oauth2Login() 메서드는 OAuth 2.0 프로토콜을 사용하여 소셜 로그인을 처리하는 기능을 제공합니다. .oauth2Login() // .defaultSuccessUrl("/success-oauth") // OAuth2 로그인 성공 이후 사용자 정보를 가져올 때 설정 담당 .userInfoEndpoint() // OAuth2 로그인 성공 시, 후작업을 진행할 서비스 .userService(principalOauth2UserService) .and() .defaultSuccessUrl("/success-oauth"); 저는 현재 생각하는 방식이 소셜 로그인을 성공하면 PrincipalOauth2UserService 에서 Member 테이블에 넣어주고 save까지 해서 로직을 구현했습니다. 그리고 실행해본 결과 로그로 제대로 값이 등록되어 있는 것을 볼 수 있었습니다.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 : " + userRequest.getClientRegistration() ); log.info("accessToken : " + userRequest.getAccessToken().getTokenValue() ); OAuth2User oAuth2User = super.loadUser(userRequest); // 구글 로그인 버튼 클릭 →구글 로그인 창 → 로그인 완료 → code 를 리턴(OAuth-Client 라이브러리) → AccessToken 요청 // userRequest 정보 → 회원 프로필 받아야함(loadUser 함수 호출) → 구글로부터 회원 프로필을 받아준다. log.info("getAttributes : " + 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 : " + member.getUserEmail()); log.info("userName : " + member.getUserName()); log.info("userPw : " + member.getUserPw()); log.info("userType : " + member.getUserType()); log.info("provider : " + member.getProvider()); log.info("providerId : " + member.getProviderId()); memberRepository.save(member); } else { log.info("로그인을 이미 한적이 있습니다. 당신은 자동회원가입이 되어 있습니다."); log.info("userEmail : " + member.getUserEmail()); log.info("userName : " + member.getUserName()); log.info("userPw : " + member.getUserPw()); log.info("userType : " + member.getUserType()); log.info("provider : " + member.getProvider()); log.info("providerId : " + member.getProviderId()); } // attributes가 있는 생성자를 사용하여 PrincipalDetails 객체 생성 // 소셜 로그인인 경우에는 attributes도 함께 가지고 있는 PrincipalDetails 객체를 생성하게 됩니다. return new PrincipalDetails(member, oAuth2User.getAttributes()); } } 이제 @AuthenticationPrincipal OAuth2User oAuth2User를 사용하면 소셜 로그인한 정보를 가져와서 JWT 발급하는데 사용할 수 있다고 들었는데 @GetMapping("/success-oauth") public ResponseEntity<?> createTokenForGoogle(@AuthenticationPrincipal OAuth2User oAuth2User) { if(oAuth2User == null) { log.info("받아올 정보가 없습니다 ㅠㅠ"); return ResponseEntity.status(HttpStatus.NOT_FOUND).body("정보가 없어...."); } else { log.info("oauth2User 정보를 받아오자 : " + oAuth2User); // OAuth2User에서 필요한 정보를 추출하여 UserDetails 객체를 생성합니다. ResponseEntity<TokenDTO> token = memberService.createToken(oAuth2User); log.info("token : " + token); return ResponseEntity.ok().body(token); } }@AuthenticationPrincipal OAuth2User oAuth2User를 사용해서 정보를 받아와서 null이 아닐 경우MemberService // 소셜 로그인 성공시 jwt 반환 // OAuth2User에서 필요한 정보를 추출하여 UserDetails 객체를 생성하는 메서드 public ResponseEntity<TokenDTO> createToken(OAuth2User oAuth2User) { String userEmail = oAuth2User.getAttribute("email"); log.info("userEmail in MemberService : " + userEmail); MemberEntity findMember = memberRepository.findByUserEmail(userEmail); // 권한 정보 추출 List<GrantedAuthority> authorities = getAuthoritiesForUser(findMember); // 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); } private List<GrantedAuthority> getAuthoritiesForUser(MemberEntity member) { // 예시: 데이터베이스에서 사용자의 권한 정보를 조회하는 로직을 구현 // member 객체를 이용하여 데이터베이스에서 사용자의 권한 정보를 조회하는 예시로 대체합니다. UserType role = member.getUserType(); // 사용자의 권한 정보를 가져오는 로직 (예시) List<GrantedAuthority> authorities = new ArrayList<>(); authorities.add(new SimpleGrantedAuthority("ROLE_" +role.name())); log.info("role in MemberService : " + role.name()); log.info("authorities in MemberService : " + authorities); return authorities; }JwtProvider 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("auth", 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(); return TokenDTO.builder() .grantType("Bearer ") .accessToken(accessToken) .refreshToken(refreshToken) .userEmail(userDetails.getUsername()) .build(); }이런식으로 로직을 짰는데 지금 생각하는 방식이 소셜 로그인이 성공했을 경우 주소, 권한(USER or ADMIN) 를 추가적으로 넣고 싶은데 소셜 로그인으로 어떻게 해야할지 감이 안잡힙니다 ㅠㅠ 그리고 컨트롤러에서 제대로 정보를 못가지고 오는거 같습니다.PrincipalOauth2UserService에서 return new PrincipalDetails(member, oAuth2User.getAttributes());이렇게 보내줬는데 PrincipalDetails에서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.beans.factory.annotation.Autowired; 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() { return member.getUserPw(); } // 사용자 이름 반환 @Override public String getUsername() { 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() { return attributes; } @Override // OAuth2 인증에서는 사용되지 않는 메서드이므로 null 반환 public String getName() { return null; } }생성자를 구현했지만 컨트롤러에서 못받고 있습니다. 추가 질문)postman에서 소셜로그인 테스트를 해서 성공해서 accesstoken을 받았는데 거기서 뭘 어떻게 진행해야 하나요? 거기서 소셜 로그인을 성공하면 바로 토큰을 발급해줘서 요청시 header에 담아서 보내주것을 어떤식으로 해야하나요??추가 질문2)소셜 로그인은 아니지만 발급 받은 access token을 header에 담아서 보내주는 것을 하는 도중java.lang.IllegalArgumentException: Cannot pass null or empty values to constructor at org.springframework.util.Assert.isTrue(Assert.java:121) ~[spring-core-5.3.28.jar:5.3.28] at org.springframework.security.core.userdetails.User.<init>(User.java:110) ~[spring-security-core-5.7.9.jar:5.7.9] at org.springframework.security.core.userdetails.User.<init>(User.java:87) ~[spring-security-core-5.7.9.jar:5.7.9] at com.example.project1.config.jwt.JwtProvider.getAuthentication(JwtProvider.java:231) ~[classes/:na] at com.example.project1.config.jwt.JwtAuthenticationFilter.doFilter(JwtAuthenticationFilter.java:62) ~[classes/:na] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:223) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.28.jar:5.3.28] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.28.jar:5.3.28] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:112) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:82) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.28.jar:5.3.28] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.28.jar:5.3.28] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354) ~[spring-web-5.3.28.jar:5.3.28] at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) ~[spring-web-5.3.28.jar:5.3.28] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.28.jar:5.3.28] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.28.jar:5.3.28] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.28.jar:5.3.28] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.28.jar:5.3.28] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:661) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:427) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:357) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:294) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:373) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:237) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:319) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na] 2023-07-22 18:49:55.024 ERROR 11616 --- [nio-8080-exec-6] o.a.c.c.C.[Tomcat].[localhost] : Exception Processing ErrorPage[errorCode=0, location=/error] java.lang.IllegalArgumentException: Cannot pass null or empty values to constructor at org.springframework.util.Assert.isTrue(Assert.java:121) ~[spring-core-5.3.28.jar:5.3.28] at org.springframework.security.core.userdetails.User.<init>(User.java:110) ~[spring-security-core-5.7.9.jar:5.7.9] at org.springframework.security.core.userdetails.User.<init>(User.java:87) ~[spring-security-core-5.7.9.jar:5.7.9] at com.example.project1.config.jwt.JwtProvider.getAuthentication(JwtProvider.java:231) ~[classes/:na] at com.example.project1.config.jwt.JwtAuthenticationFilter.doFilter(JwtAuthenticationFilter.java:62) ~[classes/:na] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:223) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:217) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.28.jar:5.3.28] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.28.jar:5.3.28] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:112) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:82) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.28.jar:5.3.28] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.28.jar:5.3.28] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186) ~[spring-security-web-5.7.9.jar:5.7.9] at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354) ~[spring-web-5.3.28.jar:5.3.28] at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) ~[spring-web-5.3.28.jar:5.3.28] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.28.jar:5.3.28] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.28.jar:5.3.28] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.28.jar:5.3.28] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.28.jar:5.3.28] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:661) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:427) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:357) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:294) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:373) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:237) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:319) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:390) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.76.jar:9.0.76] at java.base/java.lang.Thread.run(Thread.java:829) ~[na:na] 이런 에러가 발생합니다....보니까 public Authentication getAuthentication(String token) { // 토큰 복호화 메소드 Claims claims = parseClaims(token); log.info("claims in JwtProvider : " + claims); if(claims.get("auth") == null) { throw new RuntimeException("권한 정보가 없는 토큰입니다."); } Object auth = claims.get("auth"); log.info("auth in JwtProvider : " + auth); // 클레임 권한 정보 가져오기 List<String> authorityStrings = (List<String>) claims.get(AUTHORITIES_KEY); Collection<? extends GrantedAuthority> authorities = authorityStrings.stream() .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); // UserDetails 객체를 만들어서 Authentication 리턴 UserDetails principal = new User(claims.getSubject(), "", authorities); return new UsernamePasswordAuthenticationToken(principal, token, authorities); } 여기서 오류가 생기는거 같습니다. log.info("auth in JwtProvider : " + auth); 찍으면 auth in JwtProvider : [ROLE_USER] 이렇게 나옵니다
-
미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
default_batch_fetch_size 크기 질문
강의에서default_batch_fetch_size가 100이고,OrderItem의 크기는 2개,Item의 크기는 4개 입니다default_batch_fetch_size를 정할때 OrderItem의 크기와 Item의 크기를 더한 값인가요? 아니면 OrderItem의 크기만 인건가요?
-
미해결실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발
메인 오류
학습하는 분들께 도움이 되고, 더 좋은 답변을 드릴 수 있도록 질문전에 다음을 꼭 확인해주세요.1. 강의 내용과 관련된 질문을 남겨주세요.2. 인프런의 질문 게시판과 자주 하는 질문(링크)을 먼저 확인해주세요.(자주 하는 질문 링크: https://bit.ly/3fX6ygx)3. 질문 잘하기 메뉴얼(링크)을 먼저 읽어주세요.(질문 잘하기 메뉴얼 링크: https://bit.ly/2UfeqCG)질문 시에는 위 내용은 삭제하고 다음 내용을 남겨주세요.=========================================[질문 템플릿]1. 강의 내용과 관련된 질문인가요? (예/아니오)2. 인프런의 질문 게시판과 자주 하는 질문에 없는 내용인가요? (예/아니오)3. 질문 잘하기 메뉴얼을 읽어보셨나요? (예/아니오)[질문 내용]여기에 질문 내용을 남겨주세요.실행시키면 실행이 되다가 갑자기 이런 오류가 뜹니다... 설정이나 설치 등은 그대로 따라했습니다.