무료
다른 수강생들이 자주 물어보는 질문이 궁금하신가요?
- 미해결Spring Boot JWT Tutorial
이게 무슨 에러일까요..
yml파일 두 개 사진대로 둘 다 실행 해봤는데 안되네요.. 뭐가 잘못된 걸까요..
- 미해결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로 찍어보면 이메일이 제대로 나오고 있습니다....
- 미해결Spring Boot JWT Tutorial
AbstractHttpConfigurer, SecurityConfigurerAdapter 구분
먼저, 올려주신 소스와 강의 잘 봤습니다.감사합니다~!https://github.com/SilverNine/spring-boot-jwt-tutorial/blob/master/src/main/java/me/silvernine/tutorial/jwt/JwtSecurityConfig.java Spring Security Docs에서는 customFilter를 만들때의 예시를 AbstractHttpConfigurer로 하고 있습니다.그런데 작성하신 JwtSecurityConfig을 보면, extends를 SecurityConfigurerAdapter로 하셨는데 어떠한 이유가 있는지 궁금합니다.감사합니다.
- 미해결Spring Boot JWT Tutorial
JWT secretKey ECDSA error
[코드](https://github.com/YuYoHan/JWT)ECDSA signing keys must be PrivateKey instances.이런 오류가 뜨는데 JwtProvider입니다. @Slf4j @Component public class JwtProvider implements InitializingBean { private static final String AUTHORITIES_KEY = "auth"; private final String secret; private final long tokenValidityInMilliseconds; private Key key; public JwtProvider(@Value("${jwt.secret_key}") String secret, @Value("${jwt.access.expiration}") long tokenValidityInMilliseconds ) { this.secret = secret; this.tokenValidityInMilliseconds = tokenValidityInMilliseconds * 1000; } // 여기서 InitializingBean를 상속받고 이 메소드를 오버라이드한 이유는 // Bean이 생성이 되고 생성자를 통해서 secret 값을 Base64 Decode해서 key 변수에 할당하기 위함이다. @Override public void afterPropertiesSet() throws Exception { byte[] keyBytes = Decoders.BASE64.decode(secret); this.key = Keys.hmacShaKeyFor(keyBytes); } // 유저 정보를 가지고 AccessToken, RefreshToken을 생성하는 메서드 public TokenDTO createToken(Authentication authentication) { // 권한 가져오기 String authorities = authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) .collect(Collectors.joining(",")); long now = (new Date()).getTime(); // Access Token 생성 Date accessTokenExpire = new Date(now + this.tokenValidityInMilliseconds); String accessToken = Jwts.builder() .setSubject(authentication.getName()) .claim(AUTHORITIES_KEY, authorities) .setExpiration(accessTokenExpire) .signWith( key, SignatureAlgorithm.ES512) .compact(); // Refresh Token 생성 Date refreshTokenExpire = new Date(now + this.tokenValidityInMilliseconds + 86400); String refreshToken = Jwts.builder() .setSubject(authentication.getName()) .claim(AUTHORITIES_KEY, authorities) .setExpiration(refreshTokenExpire) .signWith(key, SignatureAlgorithm.ES512) .compact(); return TokenDTO.builder() .grantType("Bearer") .accessToken(accessToken) .refreshToken(refreshToken) .build(); } // JWT 토큰을 복호화하여 토큰에 들어있는 정보를 꺼내는 코드 // 토큰으로 클레임을 만들고 이를 이용해 유저 객체를 만들어서 최종적으로 authentication 객체를 리턴 // 인증 정보 조회 public Authentication getAuthentication(String token) { // 토큰 복호화 메소드 Claims claims = parseClaims(token); if(claims.get("auth") == null) { throw new RuntimeException("권한 정보가 없는 토큰입니다."); } // 클레임 권한 정보 가져오기 Collection<? extends GrantedAuthority> authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(",")) .map(SimpleGrantedAuthority::new) .collect(Collectors.toList()); // UserDetails 객체를 만들어서 Authentication 리턴 User principal = new User(claims.getSubject(), "", authorities); return new UsernamePasswordAuthenticationToken(principal, token, authorities); } private Claims parseClaims(String token) { try { return Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); } catch (ExpiredJwtException e) { 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; } } 강의랑 같은데 오류가 발생하네요 ㅠㅠ
- 미해결Spring Boot JWT Tutorial
@ManyToMany를 일대다 관계로 풀어서 작성중 문제가 생겼습니다...
public class User { @Id @Column(name = "user_id") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long userId; @Column(name = "username", length = 50, unique = true) private String username; @JsonIgnore @Column(name = "password", length = 100) private String password; @Column(name = "nickname", length = 50) private String nickname; @JsonIgnore @Column(name = "activated") private boolean activated; @OneToMany(mappedBy = "user") private Set<UserAuthority> authorities; }public class UserAuthority implements Serializable{ @Id @ManyToOne @JsonIgnore @JoinColumn(name = "user_id") private User user; @Id @ManyToOne @JoinColumn(name = "authority_name") private Authority authority; } public class Authority { @Id @Column(name = "authority_name", length = 50) private String authorityName; } 이렇게 Entity를 작성했고 @Transactional public User signup(UserDto userDto) { validationUser(userDto); Authority authority = Authority.builder() .authorityName("ROLE_USER") .build(); UserAuthority userAuthority = UserAuthority.builder() .authority(authority) .build(); if(userAuthority.getAuthority() == null){ throw new RuntimeException("권한정보가 없습니다"); } User user = User.builder() .username(userDto.getUsername()) .password(passwordEncoder.encode(userDto.getPassword())) .nickname(userDto.getNickname()) .authorities(Collections.singleton(userAuthority)) .activated(true) .build(); return userRepository.save(user); }UserService는 중간에 UserAuthority로 빌드하는 것을 추가해줬습니다. private org.springframework.security.core.userdetails.User createUser(String username, User user, UserAuthority userAuthority) { if (!user.isActivated()) { throw new RuntimeException(username + " -> 활성화되어 있지 않습니다."); } List<GrantedAuthority> grantedAuthorities = user.getAuthorities().stream() .map(authority -> new SimpleGrantedAuthority(authority.getAuthority().getAuthorityName())) .collect(Collectors.toList()); return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities); }CustomUserDetailService도 String 타입으로 일치 해야 함으로 authority.getAuthorityName()을 authority.getAuthority().getAuthorityName())로 변환해주었습니다. 이후 data.sql의 admin 아이디는 모든 메서드가 잘 되는데 signup으로 회원가입후 getUesr메서드시 권한정보가 넘어오지 못했다는 오류가 발생합니다.2023-06-02 00:43:56.952 ERROR 18668 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception java.lang.IllegalArgumentException: A granted authority textual representation is required at org.springframework.util.Assert.hasText(Assert.java:289) ~[spring-core-5.3.27.jar:5.3.27] at org.springframework.security.core.authority.SimpleGrantedAuthority.<init>(SimpleGrantedAuthority.java:39) ~[spring-security-core-5.7.8.jar:5.7.8] at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) ~[na:na] at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) ~[na:na] at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) ~[na:na] at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na] at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) ~[na:na] at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na] at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) ~[na:na] at project.hanseovill.security.TokenProvider.getAuthentication(TokenProvider.java:88) ~[main/:na] at project.hanseovill.security.JwtFilter.doFilter(JwtFilter.java:41) ~[main/:na] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:112) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:82) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.session.DisableEncodeUrlFilter.doFilterInternal(DisableEncodeUrlFilter.java:42) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) ~[spring-web-5.3.27.jar:5.3.27] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:481) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:130) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na] 2023-06-02 00:43:56.961 ERROR 18668 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] threw exception java.lang.IllegalArgumentException: A granted authority textual representation is required at org.springframework.util.Assert.hasText(Assert.java:289) ~[spring-core-5.3.27.jar:5.3.27] at org.springframework.security.core.authority.SimpleGrantedAuthority.<init>(SimpleGrantedAuthority.java:39) ~[spring-security-core-5.7.8.jar:5.7.8] at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) ~[na:na] at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) ~[na:na] at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) ~[na:na] at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na] at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) ~[na:na] at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na] at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) ~[na:na] at project.hanseovill.security.TokenProvider.getAuthentication(TokenProvider.java:88) ~[main/:na] at project.hanseovill.security.JwtFilter.doFilter(JwtFilter.java:41) ~[main/:na] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:112) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:82) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) ~[spring-web-5.3.27.jar:5.3.27] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.27.jar:5.3.27] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.27.jar:5.3.27] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:661) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:427) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:357) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:294) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:373) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:237) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:319) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na] 2023-06-02 00:43:56.962 ERROR 18668 --- [nio-8080-exec-7] o.a.c.c.C.[Tomcat].[localhost] : Exception Processing ErrorPage[errorCode=0, location=/error] java.lang.IllegalArgumentException: A granted authority textual representation is required at org.springframework.util.Assert.hasText(Assert.java:289) ~[spring-core-5.3.27.jar:5.3.27] at org.springframework.security.core.authority.SimpleGrantedAuthority.<init>(SimpleGrantedAuthority.java:39) ~[spring-security-core-5.7.8.jar:5.7.8] at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:195) ~[na:na] at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948) ~[na:na] at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:484) ~[na:na] at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:474) ~[na:na] at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:913) ~[na:na] at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na] at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:578) ~[na:na] at project.hanseovill.security.TokenProvider.getAuthentication(TokenProvider.java:88) ~[main/:na] at project.hanseovill.security.JwtFilter.doFilter(JwtFilter.java:41) ~[main/:na] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:112) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:82) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:346) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:221) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:186) ~[spring-security-web-5.7.8.jar:5.7.8] at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:354) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:267) ~[spring-web-5.3.27.jar:5.3.27] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.27.jar:5.3.27] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117) ~[spring-web-5.3.27.jar:5.3.27] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.27.jar:5.3.27] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:102) ~[spring-web-5.3.27.jar:5.3.27] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:178) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:153) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:661) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:427) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:357) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:294) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.StandardHostValve.custom(StandardHostValve.java:373) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.StandardHostValve.status(StandardHostValve.java:237) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.StandardHostValve.throwable(StandardHostValve.java:319) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:164) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:926) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1791) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.74.jar:9.0.74] at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na] 근데 postman 확인시 authorities가 들어갔다고 나옵니다하지만 getUserName을 통해 확인해보면안들어간게 맞습니다...회원가입후 반환하는 값이 authorities안에 authority가 한겹 더있는 상태라 권한정보를 인식을 못하는 것 아닌가라는 생각을 했는데 admin 같은경우에는 잘 됩니다.며칠째 붙잡고있는데 해결을 못하고 물어볼만한 곳이 없어 여기에 남겨봅니다...
- 미해결Spring Boot JWT Tutorial
/h2-console 403 에러
@Configuration public class SecurityConfig { @Bean public WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web.ignoring() .requestMatchers("/h2-console/**", "/favicon.ico"); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeRequests() .requestMatchers("/api/hello").permitAll() .anyRequest().authenticated() .and() .headers().frameOptions().disable() .and() .csrf().ignoringRequestMatchers("/h2-console/**").disable(); return http.build(); } } http://localhost:8080/h2-console 와 http://localhost:8080/favicon.ico 는 403 에러가 뜨고,http://localhost:8080/api/hello 는 200 이 떠요.무슨 문제인지 모르겠습니다 ㅠㅠ
- 미해결Spring Boot JWT Tutorial
설명란이 대체 어디에 있죠?
코드를 복사 붙여 넣기 할 수 있도록설명란에 추가한다고 하셨는데대체 설명란이 어디에 있죠?목차에도 따로 없고강의 페이지의 휠을 내려도 따로 자료가 없는데요??알려주시면 감사하겠습니다..현재 Security 설정, Data 설정하는 부분에서지금 코드를 일일이 꾸역꾸역 치고있네요..자료 알려주시면 감사하겠습니다.
- 미해결Spring Boot JWT Tutorial
BCrypt 암호화하는 과정이 궁금합니다.
에러 안나고 결과도 잘 나오는데LoginDto에 password가 어떻게 user 테이블에 있는 암호화된 비밀번호랑 비교돼서 동일한 결과값으로 판단돼서 토큰이 나오는지 궁금합니다. 제가 아직 부족해서인지 따라가면서 봐도 입력받은 password로 암호화하는 작업이 안보여서요
- 미해결Spring Boot JWT Tutorial
sql 에러
@Entity @Table(name = "users") @Getter @Setter @Builder @AllArgsConstructor @NoArgsConstructor public class User { @JsonIgnore @Id @Column(name = "userId") @GeneratedValue(strategy = GenerationType.IDENTITY) private Long userId; @Column(name="userName", length = 50, unique = true) private String userName; @JsonIgnore @Column(name = "userPw", length = 100) private String password; @Column(name = "nickName", length = 50) private String nickName; @JsonIgnore @Column(name = "activated") private boolean activated; // @ManyToMany와 @JoinTable은 User객체와 권한객체의 다대다 관계를 // 일대다, 다대일 관계의 조인 테이블로 정했다는 뜻입니다. @ManyToMany @JoinTable( name = "user_authority", joinColumns = {@JoinColumn(name = "userId", referencedColumnName = "userId")}, inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "authority_name")}) private Set<Authority> authorities;package com.example.jwt_security.entity; import lombok.*; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name = "authority") @Getter @Setter @Builder @AllArgsConstructor @NoArgsConstructor public class Authority { @Id @Column(name="authority_name", length = 50) private String authorityName; }Hibernate: drop table if exists authority CASCADE Hibernate: drop table if exists user_authority CASCADE Hibernate: drop table if exists users CASCADE Hibernate: create table authority ( authority_name varchar(50) not null, primary key (authority_name) )Hibernate: create table user_authority ( user_id bigint not null, authority_name varchar(50) not null, primary key (user_id, authority_name) )Hibernate: create table users ( user_id bigint generated by default as identity, activated boolean, nick_name varchar(50), user_pw varchar(100), user_name varchar(50), primary key (user_id) )Hibernate: alter table users add constraint UK_k8d0f2n7n88w1a16yhua64onx unique (user_name)Hibernate: alter table user_authority add constraint FK6ktglpl5mjosa283rvken2py5 foreign key (authority_name) references authorityHibernate: alter table user_authority add constraint FKhi46vu7680y1hwvmnnuh4cybx foreign key (user_id) references users insert into users (userName, userPw, nickName, activated) values ('admin', '$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi', 'admin', 1); insert into users (userName, userPw, nickName, activated) values ('user', '$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC', 'user', 1); insert into authority (authority_name) values ('ROLE_USER'); insert into authority (authority_name) values ('ROLE_ADMIN'); insert into user_authority (user_id, authority_name) values (1, 'ROLE_USER'); insert into user_authority (user_id, authority_name) values (1, 'ROLE_ADMIN'); insert into user_authority (user_id, authority_name) values (2, 'ROLE_USER');여기서 into 뒤에가 에러가 생기네요 테이블까지는 제대로 생성됐는데 왜그러는지 모르겠어요
- 미해결Spring Boot JWT Tutorial
SecurityContext 와 Authentication 관련 질문입니다.
안녕하세요 강의를 듣다가 궁금한 점이 생겨서 질문합니다. jwt의 특징으로는 Session 과는 다르게 Stateless 하다고 알고 있습니다. 하지만 Jwtfilter 클래스에 doFilter 메서드를 보시면 SecurityContext에 authentication을 set 하였습니다. 이렇게 되면 클라이언트와 서버는 Stateless가 아닌 Statefull 한 상태가 되는 것이 아닌가요?참고로 저는 authentication 안에 사용자의 인증 정보가 담겨 있다고 알고 있습니다.답변해 주시면 감사하겠습니다!!
- 미해결Spring Boot JWT Tutorial
POST http://localhost:8080/api/authenticate 를 실행할 때의 username과 password
POST http://localhost:8080/api/authenticate Content-Type: application/json { "username": "admin", "password": "admin" }의 내용은 data.sql에는 없는데 왜 해당 username과 password로 /api/authenticate 를 호출했을 때만 token값이 정상적으로 반환되는 걸까요?data.sql 내용은 아래와 같습니다. (https://github.com/SilverNine/spring-boot-jwt-tutorial/blob/master/src/main/resources/data.sql의 내용) INSERT INTO USER (username, password, nickname, activated) VALUES ('admin', '$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi', 'admin', 1); INSERT INTO USER (username, password, nickname, activated) VALUES ('user', '$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC', 'user', 1); INSERT INTO AUTHORITY (AUTHORITY_NAME) VALUES ('ROLE_USER'); INSERT INTO AUTHORITY (AUTHORITY_NAME) VALUES ('ROLE_ADMIN'); INSERT INTO USER_AUTHORITY (USER_ID, AUTHORITY_NAME) VALUES (1, 'ROLE_USER'); INSERT INTO USER_AUTHORITY (USER_ID, AUTHORITY_NAME) VALUES (1, 'ROLE_ADMIN'); INSERT INTO USER_AUTHORITY (user_id, authority_name) values (2, 'ROLE_USER'); <추가1>SecurityConfig.java에서 @Bean으로 설정해 놓은 PasswordEncoder와 관련이 있나 싶어서@Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); }https://bcrypt-generator.com/ 와 같은 사이트에서 이렇게 해당 문자열과 admin이 같다고 나왔습니다. 이것을 통해 UserService.java의 signup메서드를 보면 password 속성에 해당하는 문자열을 BCryptPasswordEncoder로 인코딩 한 값을 User entity의 password로 입력해 준다는 것을 알 수 있었습니다.@Transactional public UserDto signup(UserDto userDto) { if (userRepository.findOneWithAuthoritiesByUsername(userDto.getUsername()).orElse(null) != null) { throw new DuplicateMemberException("이미 가입되어 있는 유저입니다."); } Authority authority = Authority.builder() .authorityName("ROLE_USER") .build(); User user = User.builder() .username(userDto.getUsername()) .password(passwordEncoder.encode(userDto.getPassword())) .nickname(userDto.getNickname()) .authorities(Collections.singleton(authority)) .activated(true) .build(); return UserDto.from(userRepository.save(user)); }다만 아직도, /api/authenticate호출로 인해 AuthController의 authorize가 실행될 경우 message body에 담아 보낸 password 속성값의 평문을, DB의 User 테이블에 저장되어 있는 password 값과 동일한지 비교하기 위해 BCryptPasswordEncoder를 사용해 Bcrypt encrypted hash값으로 만드는 부분이 어디인지를 모르겠습니다. (data.sql에서 직접 Bcrypt encrypted hash로 입력한 문자열의 경우 signup메서드를 거치지 않고 DB의 User 테이블에 값을 바로 입력한 것인데 AuthController의 authorize가 호출될 경우 /POST http://localhost:8080/api/authenticate 에 실어서 보낸 password 평문 내용을 어디서 어떻게 Bcrypt encrypted hash로 변환해 User테이블의 Bcrypt encrypted hash 값과 비교하는 것인지 생각해 보다가 여기에 이르렀습니다.) <추가2>AuthController의 authorize메서드 동작 과정을 따라가다 보면 CustomUserDetailsService의 loadUserByUsername메서드를 실행하는데, @Override @Transactional public UserDetails loadUserByUsername(final String username) { return userRepository.findOneWithAuthoritiesByUsername(username) .map(user -> createUser(username, user)) .orElseThrow(() -> new UsernameNotFoundException(username + " -> 데이터베이스에서 레코드를 찾을 수 없습니다.")); }여기에서 createUser를 보면private org.springframework.security.core.userdetails.User createUser(String username, User user) { if (!user.isActivated()) { throw new RuntimeException(username + "해당 사용자가 활성화 되어 있지 않습니다."); } List<GrantedAuthority> grantedAuthorities = user.getAuthorities().stream() .map(authority -> new SimpleGrantedAuthority(authority.getAuthorityName())) .collect(Collectors.toList()); return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities); } spring security의 User클래스 객체를 생성해 반환하고 있고org.springframework.security.core.userdetails.User의 내용을 보면 public static final class UserBuilder 클래스 안에 private Function<String, String> passwordEncoder = (password) -> password;와 같은 내용이 있으며,public UserBuilder passwordEncoder(Function<String, String> encoder) { Assert.notNull(encoder, "encoder cannot be null"); this.passwordEncoder = encoder; return this; }와 같은 내용이 있는 것을 발견했습니다. 그렇다면 SecurityConfig에서 BCryptPasswordEncoder를 @Bean으로 설정해 놓았기 때문에 spring 컨테이너가 해당 PasswordEncoder를 프로젝트의 대표 PasswordEncoder??로 지정해 놓고 org.springframework.security.core.userdetails.User의private Function<String, String> passwordEncoder = (password) -> password;에서도 자동으로 주입받아 org.springframework.security.core.userdetails.User 객체를 만들 때 사용한 것이라고 보면 될까요? 사실 org.springframework.security.core.userdetails.User 내용을 계속 보고 있지만 아직도 CustomUserDetailsService의 createUser메서드와 같은 곳에서 아래와 같이 return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities);새로운 User객체 생성시 위와 같은 매개변수들을 전달해 줬을때 어떻게 PasswordEncoder가 적용되는 것인지 이해가 잘 되고 있지 않습니다. 영어 문해력의 문제인지, springframework내부의 클래스들은 동작 방식 이해가 좀 어렵더라고요.. 좀더 파고들어 본다한들 지금 수준에서 이해할 수 있을지 모르겠어서 도움을 구합니다.<추신>https://github.com/SilverNine/spring-boot-jwt-tutorial/blob/master/src/main/resources/data.sql 에서 INTO USER (username, password, nickname, activated) VALUES ('user', '$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC', 'user', 1);의 password 입력값이 수정되어야 할것 같습니다. 'user'문자열의 Bcrypt encrypted hash와 매치가 되지 않네요.
- 미해결Spring Boot JWT Tutorial
일반 스프링에서 진행
제가 사정이 있어서(학교 프로젝트) 스프링으로 진행을 해야 할 것 같은데, 스프링(메이븐,마이바티스)으로도 진행이 가능한가요?맨처음부터 포스트맨으로 진행시 바로 hello가 떠버려서 멘붕입니다 ㅜㅜ
- 미해결Spring Boot JWT Tutorial
TokenProvider의 의존성 주입이 끝난 이후 key변수를 할당하는 이유
3강 1:58에서 TokenProvider에 대해 설명해주시면서빈 생성 이후 의존성 주입까지 받은 뒤 Key를 생성하기 위해 InitializingBean을 implement한다고 설명해주셨는데,서치를 하다 보니 강사님처럼 인터페이스 구현 없이 생성자에서 바로 Key를 생성하는 코드도 간혹 보았습니다.이처럼 생성자 호출 시에 Key를 만들지 않고 그 이후에 Key를 생성하신 구체적인 이유가 궁금합니다.
- 미해결Spring Boot JWT Tutorial
authenticationManagerBuilder에서 getObject를 할때 에러가 뜹니다.
authenticationManagerBuilder.getObject();This object has not been built at org.springframework.security.config.annotation.AbstractSecurityBuilder.getObject(AbstractSecurityBuilder.java:51) ~[spring-security-config-5.7.4.jar:5.7.4] 왜그러는걸까요 추가로 DaoAuthenticationProvider에서 자동으로 additionalAuthenticationChecks해줘서 String presentedPassword = authentication.getCredentials().toString(); if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) { this.logger.debug("Failed to authenticate since password does not match stored value"); throw new BadCredentialsException(this.messages .getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); }패스워드를 체크할 수 있다는 글을 보았는데 확인해보니 deprecation이 되어있습니다.사용하지 않는게 좋나요?
- 미해결Spring Boot JWT Tutorial
WebSecurityConfigurerAdapter deprecated
WebSecurityConfigurerAdapter 가 이제 더 이상 지원을 하지 않는다고 하여 WebSecurityFilterChain으로 하래서 시도중인데 계속하여 401 에러가 뜹니다...@EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{ http .authorizeRequests() .antMatchers("api/hello").permitAll() .anyRequest().authenticated(); return http.build(); } }
- 미해결Spring Boot JWT Tutorial
!user.isActivated가 계속 오류납니다
eclipse로 따라하는중인데 계속 오류가나요..
- 미해결Spring Boot JWT Tutorial
그대로 따라했는데 오류가 납니다
Description:Parameter 0 of constructor in com.example.demo.service.CustomUserDetailsService required a bean of type 'com.example.demo.repository.UserRepository' that could not be found.Action:Consider defining a bean of type 'com.example.demo.repository.UserRepository' in your configuration. 구글링을 해봐도 전혀 해결되지 않아요 ㅠㅠ
- 미해결Spring Boot JWT Tutorial
실행이 안되는데 왜 그럴까요?
안녕하세요?프로그램을 다운 받아서 intellij로 실행시키는데 오류는 안나는데. 실행이 안되네요,,,,첨부 파일처럼 나타타는데요...뭐가 문제일까요?스프링부트가 실행되어야 하는데 그냥 죽어버린느 느낌입니다, . 제가. 너무. 몰라서요.답변을 좀 부탁드립니다,, .
- 미해결Spring Boot JWT Tutorial
java: package io.jsonwebtoken.io does not exist 에러 관련
강의 잘 듣고 있습니다 감사합니다!혹시스프링 2.7.3 사용, 자바 11 사용 중인데java: package io.jsonwebtoken.io does not existimport io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys;요 3개를 import 하지 못합니다...java: package io.jsonwebtoken.io does not exist오류가 뜹니다... 바쁘신데 죄송합니다.
- 미해결Spring Boot JWT Tutorial
3강 0:50~ Secret 키 관련 문의
3강 0:50에 나오는 시크릿 키는 특정 문자열을 Base 64로 인코딩한 것이라고 말씀해주셨습니다. 이 특정 문자열이라는 것은 총 길이 64바이트 이상의 문자열이면 되는 것일까요..?