Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
Tags
- @Value
- redis
- DTO
- 로그인
- 플라이웨이트
- @RequestParam
- 파라미터
- property
- 헬스체크
- actuator
- response
- JWT
- Docker
- RequestBody
- Request
- 디자인 패턴
- 반환
- Boot
- Spring
- enum
- 프로퍼티
- @RequestBody
- ResponseDto
- Codewars
- 코딩테스트
- RequestParam
- springboot
- 코드워즈
- yml
- Security
Archives
- Today
- Total
있을 유, 참 진
[JWT] 로그인 구현 1::로그인 기능 구현, JWT Provider 생성 본문
로그인 기능 구현
- 사용법 설명
JWT 관련 값 설정
loadUserByUsername(final String username)
- User createUser(String username, User user)
JWT Provider 생성
- String createToken(Authentication authentication)
- Authentication getAuthentication(String token)
- boolean validateToken(String token)
로그인 기능 구현
💡 로그인에 관련된 Controller 구현, `api/auth/authenticate` 을 호출하면 로그인이 된다. 순서는 아래와 같다.
- 유저의 아이디와 비밀번호 값을 이용해 authenticationToken 생성
- authenticationManager에서 토큰을 검증 후 검증된 토큰을 반환
- SecurityContextHolder에 해당 authentication 값을 저장
- JwtTokenProvider를 통해서 JWT 토큰을 생성한다
- 생성된 토큰을 헤더에 넣어 반환, 이후 해당 토큰 값은 추가된 JwtFilter에서 검증된다.
@RequiredArgsConstructor
@RequestMapping("/api/auth")
@RestController
public class AuthApi {
private final JwtTokenProvider jwtTokenProvider;
private final UserService userService;
private final AuthenticationManagerBuilder authenticationManager;
@PostMapping("/authenticate")
public ResponseEntity<TokenResponseDto> login(@Valid @RequestBody LoginRequestDto loginRequestDto) {
//1. AuthenticationManager를 통해 인증을 시도하고 인증이 성공하면 Authentication 객체를 리턴받는다.
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginRequestDto.getUsername(), loginRequestDto.getPassword());
// 해당 부분에 왔을 때 UserDetailsServiceImpl의 loadUserByUsername() 메소드가 실행된다.
Authentication authentication = authenticationManager.getObject().authenticate(authenticationToken);
//2. SecurityContextHolder에 위에서 생성한 Authentication 객체를 저장한다.
SecurityContextHolder.getContext().setAuthentication(authentication);
//3. JwtTokenProvider를 통해 JWT 토큰을 생성한다.
String jwtToken = jwtTokenProvider.createToken(authentication);
//4. 생성한 JWT 토큰을 Response Header에 담아서 리턴한다.
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(JwtFilter.AUTHORIZATION_HEADER, "Bearer " + jwtToken);
return ResponseEntity.status(HttpStatus.OK).headers(httpHeaders).body(new TokenResponseDto(jwtToken));
}
}
JWT 토큰
과 관련된 JwtTokenProvider
가 필요하고 authenticationManager
검증을 할 때 사용할 loadUserByUsername(final String username)
구현
JWT 관련 값 설정
💡 yml or properties 파일에 jwt 관련 값 세팅
jwt:
header: Authorization
# 특정값을 Base64로 인코딩한 값
secret: c2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQtc2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQK
# 토큰 만료 시간
token-validity-in-seconds: 1800000
loadUserByUsername(final String username)
💡 인터페이스인 UserDetailsService를 구현, 본인은 UserService에 해당 값을 구현받아서 loadUserByUsername(final String username)을 오버라이딩해 구현했다.
@RequiredArgsConstructor
@Service
public class UserService implements UserDetailsService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
/**
* 로그인 시에 DB에서 유저정보와 권한정보를 가져와 UserDetails 타입으로 리턴한다.
* @param username the username identifying the user whose data is required.
* @return 유저정보
* @throws UsernameNotFoundException
*/
@Override
@Transactional(readOnly = true)
public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException {
return userRepository.findByUsername(username)
.map(user -> createUser(username, user))
.orElseThrow(() -> new UsernameNotFoundException(username + " -> 데이터베이스에서 찾을 수 없습니다."));
}
}
User createUser(String username, User user)
💡 유저의 등급 권한 값을 받아 넣은 후, UserDetails의 구현체인 User값에 담아서 반환해 준다.
private org.springframework.security.core.userdetails.User createUser(String username, User user) {
if(Objects.equals(user.isActivated(), false)) throw new RuntimeException(username + " -> 활성화되지 않은 사용자입니다.");
List<GrantedAuthority> authorities = user.getAuthorities().stream().map(auth ->
new SimpleGrantedAuthority(auth.getName()))
.collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), authorities);
}
JWT Provider 생성
💡 JWT에 관련된 클래스. 토큰 생성, 토큰 → Authentication, 토큰 검증 등 JWT 토큰과 관련된 모든 것을 다 해준다.
//...생략
/**
* JWT 토큰 생성, 추출 그리고 검증에 관한 클래스
*/
@Slf4j
@Component
public class JwtTokenProvider implements InitializingBean {
private static final String AUTHORITIES_KEY = "auth";
private final String secretKey;
private final long tokenValidityInMilliseconds;
private Key key;
// 1. Bean 생성 후 주입 받은 후에
public JwtTokenProvider(@Value("${jwt.secret}") String secretKey,
@Value("${jwt.token-validity-in-seconds}") Long tokenValidityInSeconds) {
this.secretKey = secretKey;
this.tokenValidityInMilliseconds = tokenValidityInSeconds;
}
// 2. secret 값을 Base64로 디코딩해 Key변수에 할당
@Override
public void afterPropertiesSet() throws Exception {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
}
/**
* Authentication 객체의 권한 정보를 이용해 토큰 생성
* @param authentication - Authentication 객체
* @return - 토큰
*/
public String createToken(Authentication authentication) {
//권한 값을 받아 하나의 문자열로 합침
String authorities = authentication.getAuthorities().stream()
.map(auth -> auth.getAuthority())
.collect(Collectors.joining(","));
long now = new Date().getTime();
Date validity = new Date(now + this.tokenValidityInMilliseconds);
return Jwts.builder()
.setSubject(authentication.getName()) // 페이로드 주제 정보
.claim(AUTHORITIES_KEY, authorities)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validity)
.compact();
}
/**
* 토큰에서 인증 정보 조회 후 Authentication 객체 리턴
* @param token
* @return
*/
//토큰 -> 클레임 추출 -> 유저 객체 제작 -> Authentication 객체 리턴
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
List<? extends SimpleGrantedAuthority> authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
/**
* 필터에서 사용할 토큰 검증
* @param token 필터 정보
* @return 토큰이 유효 여부
*/
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;
}
}
String createToken(Authentication authentication)
💡 Login에 성공했을 때 JWT 토큰을 만들어 헤더에 넣어 반환하는 부분에 사용할 메서드
public String createToken(Authentication authentication) {
//권한 값을 받아 하나의 문자열로 합침
String authorities = authentication.getAuthorities().stream()
.map(auth -> auth.getAuthority())
.collect(Collectors.joining(","));
long now = new Date().getTime();
Date validity = new Date(now + this.tokenValidityInMilliseconds);
return Jwts.builder()
.setSubject(authentication.getName()) // 페이로드 주제 정보
.claim(AUTHORITIES_KEY, authorities)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validity)
.compact();
}
Authentication getAuthentication(String token)
💡 토큰값을 받아서 Authentication 객체로 반환해 주는 메서드, 앞으로 구현할 JWTFilter에서 사용한다.
public Authentication getAuthentication(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
List<? extends SimpleGrantedAuthority> authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
boolean validateToken(String token)
💡 JWT 토큰을 검증하는 메서드, 앞으로 구현할 JWTFilter에서 사용한다.
/**
* 필터에서 사용할 토큰 검증
* @param token 필터 정보
* @return 토큰이 유효 여부
*/
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 > JWT' 카테고리의 다른 글
[JWT] 로그인 구현 2::로그인 필터 구현, Spring Security에 적용하기 (2) | 2023.04.16 |
---|---|
[JWT] JWT 회원가입 구현하기 (0) | 2023.04.07 |
[JWT] Json Web Token 인증 (2) | 2023.04.05 |
Comments