외부와의 통신에 사용할 DTO 클래스 생성
Repository 관련 코드 생성
로그인 API, 관련 로직 생성
로그인 시 사용할 LoginDto 클래스 생성
package com.example.jwttutorial.dto;
import lombok.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LoginDto {
@NotNull
@Size(min = 3, max = 50)
private String username;
@NotNull
@Size(min = 3, max = 100)
private String password;
}
Token 정보를 Response할 때 사용할 TokenDto 생성
package com.example.jwttutorial.dto;
import lombok.*;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TokenDto {
private String token;
}
회원 가입시에 사용할 UserDto 클래스 생성
package com.example.jwttutorial.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserDto {
@NotNull
@Size(min = 3, max = 50)
private String username;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@NotNull
@Size(min = 3, max = 100)
private String password;
@NotNull
@Size(min = 3, max = 50)
private String nickname;
}
UserRepository 생성
package com.example.jwttutorial.repository;
import com.example.jwttutorial.entity.JwtUser;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface JwtUserRepository extends JpaRepository<JwtUser, Long> {
@EntityGraph(attributePaths = "authorities")
Optional<JwtUser> findOneWithAuthoritiesByUsername(String username);
}
findOneWithAuthoritiesByUsername username을 기준으로 User정보를 가져올때 권한 정보도 같이 가져옵니다.
@EntityGraph는 쿼리가 수행이 될 때 Lazy조회가 아니고 Enger조회로 authorities정보를 같이 가져옵니다.
Spring Security에서 중요한 부분 중 하나인 UserDetailsService를 구현한 CutomUserDetailsSercie를 생성
package com.example.jwttutorial.service;
import com.example.jwttutorial.entity.JwtUser;
import com.example.jwttutorial.repository.JwtUserRepository;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Component("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
private final JwtUserRepository userRepository;
public CustomUserDetailsService(JwtUserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
@Transactional
public UserDetails loadUserByUsername(final String username) {
return userRepository.findOneWithAuthoritiesByUsername(username)
.map(user -> createUser(username, user))
.orElseThrow(() -> new UsernameNotFoundException(username + " -> 데이터베이스에서 찾을 수 없습니다."));
}
private org.springframework.security.core.userdetails.User createUser(String username, JwtUser 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);
}
}
UserDetailsService를 implements하고 UserRepository를 주입받습니다. loadUserByUsername 메소드를 오버라이드해서 로그인 시에 DB에서 유저정보와 구너한정보를 가져오게 됩니다. 해당 정보를 기반으로 userdetails.User 객체를 생성해서 리턴합니다.
로그인 API를 추가하기 위해 AuthController 클래스 생성
package com.example.jwttutorial.controller;
import com.example.jwttutorial.dto.LoginDto;
import com.example.jwttutorial.dto.TokenDto;
import com.example.jwttutorial.jwt.JwtFilter;
import com.example.jwttutorial.jwt.TokenProvider;
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.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@RestController
@RequestMapping("/api")
public class AuthController {
private final TokenProvider tokenProvider;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
public AuthController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
this.tokenProvider = tokenProvider;
this.authenticationManagerBuilder = authenticationManagerBuilder;
}
@PostMapping("/authenticate")
public ResponseEntity<TokenDto> authorize(@Valid @RequestBody LoginDto loginDto) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.createToken(authentication);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(JwtFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);
return new ResponseEntity<>(new TokenDto(jwt), httpHeaders, HttpStatus.OK);
}
}
AuthController는 TokenProvider, AuthenticationManagerBuilder를 주입받습니다.
로그인 API 경로는 /api/authenticate이고 POST요청을 받습니다.
LoginDto의 username, password를 파라미터로 받고 이를 이용해 AuthenticationToken 객체인 UserNamePasswordAuthenticationToken을 생성합니다.
AuthenticationToken을 이용해서 Authentication객체를 생성하려고 authentication메소드가 실행이 될때 CustomUserDetailsService의 LoadUserByUsername 메소드가 실행됩니다.
그 결과 값을 가지고 Authentication 객체를 생성하고 이를 SecurityContext에 저장하고 Authentication 객체(인증정보)를 기준으로 해서 createToken 메소드를 통해서 JWT Token을 생성합니다. JWT Token을 Response Header에도 넣어주고 TokenDto를 이용해서 Response Body에도 넣어서 리턴하게 됩니다.
postman에서 테스트 하면 잘 실행됩니다.
postman에서 테스트할 때 전역변수에 저장해서 다른 Request에서도 사용할 수 있습니다.
(Test탭에 들어가서 설정)
'Spring > JWT' 카테고리의 다른 글
회원가입, 권한검증 (0) | 2022.05.27 |
---|---|
JWT 코드, Security 설정 추가 (0) | 2022.05.19 |
Security 설정, Data 설정 (0) | 2022.05.18 |
JWT 소개 (0) | 2022.05.17 |