[급함]로그인시 jwt 발급 문제
852
작성한 질문수 49
https://github.com/YuYoHan/project_study1 전체 코드
질문 1)
// 로그인
@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("문제가 있습니다");
}
} // 로그인
public ResponseEntity<TokenDTO> login(String userEmail, String userPw) throws Exception {
// Login ID/PW를 기반으로 UsernamePasswordAuthenticationToken 생성
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userEmail, userPw);
log.info("----------------------");
log.info("authenticationToken : " +authenticationToken);
log.info("----------------------");
// 실제 검증(사용자 비밀번호 체크)이 이루어지는 부분
// authenticateToken을 이용해서 Authentication 객체를 생성하고
// authentication 메서드가 실행될 때
// CustomUserDetailsService에서 만든 loadUserbyUsername 메서드가 실행
Authentication authentication = authenticationManagerBuilder
.getObject().authenticate(authenticationToken);
log.info("----------------------");
log.info("authentication : " + authentication);
log.info("----------------------");
// 해당 객체를 SecurityContextHolder에 저장
SecurityContextHolder.getContext().setAuthentication(authentication);
// authentication 객체를 createToken 메소드를 통해서 생성
// 인증 정보를 기반으로 생성
TokenDTO tokenDTO = jwtProvider.createToken(authentication);
log.info("----------------------");
log.info("tokenDTO : " + tokenDTO);
log.info("----------------------");
HttpHeaders headers = new HttpHeaders();
// response header에 jwt token을 넣어줌
headers.add(JwtAuthenticationFilter.HEADER_AUTHORIZATION, "Bearer " + tokenDTO);
log.info("----------------------");
log.info("headers : " + headers);
log.info("----------------------");
MemberEntity member = memberRepository.findByUserEmail(userEmail);
log.info("member : " + member);
TokenEntity tokenEntity = TokenEntity.builder()
.grantType(tokenDTO.getGrantType())
.accessToken(tokenDTO.getAccessToken())
.refreshToken(tokenDTO.getRefreshToken())
.userEmail(tokenDTO.getUserEmail())
.nickName(member.getNickName())
.userId(member.getUserId())
.build();
log.info("token : " + tokenEntity);
tokenRepository.save(tokenEntity);
return new ResponseEntity<>(tokenDTO, headers, HttpStatus.OK);
}package com.example.project1.config.auth;
import com.example.project1.entity.member.MemberEntity;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.security.core.GrantedAuthority;
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
public class PrincipalDetails implements UserDetails, OAuth2User {
private MemberEntity member;
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 GrantedAuthority() {
@Override
public String getAuthority() {
return "ROLE_" + member.getUserType().toString();
}
});
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
public String getName() {
return null;
}
}@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 : " + member);
return new PrincipalDetails(member);
}
}package com.example.project1.config.jwt;
import com.example.project1.domain.jwt.TokenDTO;
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.stereotype.Component;
import io.jsonwebtoken.security.Keys;
import javax.xml.bind.DatatypeConverter;
import java.security.Key;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
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) {
// 권한 가져오기
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
long now = (new Date()).getTime();
Date now2 = new Date();
// AccessToken 생성
Date accessTokenExpire = new Date(now + this.accessTokenTime);
String accessToken = Jwts.builder()
// 내용 sub : 유저의 이메일
// 토큰 제목
.setSubject(authentication.getName())
// 클레임 id : 유저 ID
.claim(AUTHORITIES_KEY, authorities)
// 내용 exp : 토큰 만료 시간, 시간은 NumericDate 형식(예: 1480849143370)으로 하며
// 항상 현재 시간 이후로 설정합니다.
.setExpiration(accessTokenExpire)
// 서명 : 비밀값과 함께 해시값을 ES256 방식으로 암호화
.signWith(key, SignatureAlgorithm.HS256)
.compact();
log.info("accessToken : " + accessToken);
// RefreshToken 생성
Date refreshTokenExpire = new Date(now + this.refreshTokenTime);
String refreshToken = Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.setExpiration(refreshTokenExpire)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
log.info("refreshToken : " + refreshToken);
return TokenDTO.builder()
.grantType("Bearer")
.accessToken(accessToken)
.refreshToken(refreshToken)
// principalDeatails에서 getUserName 메소드가 반환한 것을 담아준다.
// 이메일을 반환하도록 구성했으니 이메일이 반환됩니다.
.userEmail(authentication.getName())
.build();
}
// accessToken 생성
public TokenDTO createAccessToken(String userEmail) {
Long now = (new Date()).getTime();
Date now2 = new Date();
Date accessTokenExpire = new Date(now + this.accessTokenTime);
String accessToken = Jwts.builder()
.setIssuedAt(now2)
.setSubject(userEmail)
.setExpiration(accessTokenExpire)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
log.info("accessToken : " + accessToken);
return TokenDTO.builder()
.grantType("Bearer ")
.accessToken(accessToken)
.userEmail(userEmail)
.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) {
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;
}
}@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 토큰을 추출
String jwt = resolveToken(httpServletRequest);
String requestURI = httpServletRequest.getRequestURI();
if(StringUtils.hasText(jwt) && jwtProvider.validateToken(jwt)){
// 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext에 저장
Authentication authentication = jwtProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
log.info("Security Context에 '{}' 인증 정보를 저장했습니다., uri : {}",
authentication.getName(), requestURI);
} else {
log.debug("유효한 JWT 토큰이 없습니다. uri : {}", requestURI);
}
chain.doFilter(request, response);
}
// Request Header 에서 토큰 정보를 꺼내오기 위한 메소드
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;
}
}
}대략 적인 코드는 다음과 같습니다.
근데 컨트롤러에서 로그인 시 Exception에 걸려서 문제가 있다고 문구 찍은게 나오네요. log 돌려보니까 service에서 authenticationToken객체는 나오는데 authentication 여기서 부터 안나오는거 보니 여기서 문제가 있는거 같은데 400번 bad Request가 뜹니다 ㅠㅠ
질문2) 현재 방법이
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(userEmail, userPw);을 통해서 authentication으로 token을 생성하고 있는데 그냥 userEmail로만 받고
// accessToken 생성
public TokenDTO createAccessToken(String userEmail) {
Long now = (new Date()).getTime();
Date now2 = new Date();
Date accessTokenExpire = new Date(now + this.accessTokenTime);
String accessToken = Jwts.builder()
.setIssuedAt(now2)
.setSubject(userEmail)
.setExpiration(accessTokenExpire)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
log.info("accessToken : " + accessToken);
return TokenDTO.builder()
.grantType("Bearer ")
.accessToken(accessToken)
.userEmail(userEmail)
.build();
}이런식으로 토큰을 생성해도 괜찮나요?
질문3)
JwtAuthenticationFilter 클래스에서
@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 토큰을 추출
String jwt = resolveToken(httpServletRequest);
String requestURI = httpServletRequest.getRequestURI();
if(StringUtils.hasText(jwt) && jwtProvider.validateToken(jwt)){
// 토큰이 유효할 경우 토큰에서 Authentication 객체를 가지고 와서 SecurityContext에 저장
Authentication authentication = jwtProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
log.info("Security Context에 '{}' 인증 정보를 저장했습니다., uri : {}",
authentication.getName(), requestURI);
} else {
log.debug("유효한 JWT 토큰이 없습니다. uri : {}", requestURI);
}
chain.doFilter(request, response);
}
// Request Header 에서 토큰 정보를 꺼내오기 위한 메소드
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;
}
}
}이 처리를 해줬으니 만약 access token이 만료되서 refresh token을 보내서 access token을 발급받으려고 할 때 Bearer가 있는지 확인을 더 해줄 필요 없이 여기서 처리하니 바로 header에 담겨온 refresh token을 빼와서 유효성 검사를 해주고 access token을 발급해주면 되나요?
답변 1
JWT를 구현한 다음 이 API를 호출해서 사용하는 것은 프론트엔드 쪽에서 하는 역할인가요?
0
98
1
Jwt쓰면 스프링시큐리티는 필수적으로 사용해야하나요?
0
401
1
13:23 system.out 출력문이 다르게 나옵니다.
0
130
1
수료증 문의
0
228
2
9분대에 질문이 있습니다 !
0
115
1
password 비교를 하지 않았는데 어떻게 인증이 통과된 건가요?
0
322
1
이전 강의 참고하라는 말씀
0
253
1
강의 실습하다가 막히는 분들 참고(2024년8월 기준)
2
1116
2
구글 소셜 로그인 302
0
201
1
오류 문의 _ org.springframework.orm.jpa.JpaSystemException: could not deserialize
1
585
1
[자바] 시큐리티 Config 참고
13
953
1
이론강의
0
280
1
SpringSecurity JWT 로그인 URL 2개 설정하는 방법
0
488
1
2024.06기준) 최근 SecurityConfig 설정 문의
0
922
3
구글 로그인시 authentication이 null 값이라고 에러가 발생합니다.
0
681
2
특정 url필터 거는 방법 이슈
0
422
1
강사님께서 말씀하시는 시큐리티세션이 SecurityContext인가요?
0
279
1
25강 마지막 테스트에서 오류
1
1044
2
jwt를 저장하는 위치에 궁금한 점이 있습니다.
0
298
1
mustache를 사용하지 않고 thymeleaf를 사용하려고 하는데
0
698
1
세션 인증방식이 REST 원칙에 위배되는 건가요?
0
342
1
jwt와 실제데이터의 관계
1
246
1
jwt 와 세션ID의 관계
1
313
1
SecurityConfig에서 세션 설정, 인가 설정
0
422
1





