[완료된 포스트입니다.]
1. jwt 토큰 발급
2. 요청에 대한 jwt 인가
제가 나름 공부하고 정리하면서 그린 클래스 다이어 그램인데요
먼저 천천히 보시고 마지막까지 보신 이후 다시 보시면 이해가 더 잘될거에요!
주황선은 로그인 부분, 파란선은 인가 부분으로 구분하였습니다
한 챕터가 끝날 때 마다 다시 순서대로 정리해보겠습니다.
@Builder
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class LoginDto {
private String email;
private String password;
}
@CrossOrigin(origins = "http://localhost:3000")
@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
public class AuthController {
private final LoginService loginService;
@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginDto loginDto) {
TokenInfo token = loginService.login(loginDto.getEmail(), loginDto.getPassword());
return new ResponseEntity<>(token,HttpStatus.OK);
}
}
(저는 email을 id역할로 하기 때문에 각자 상황에 맞게 바꿔주시면 됩니다! security 기본설정은 username입니다)
@Service
@Transactional
@RequiredArgsConstructor
public class LoginService {
private final AuthenticationManagerBuilder authenticationManagerBuilder;
private final JwtTokenProvider jwtTokenProvider;
public TokenInfo login(String email, String password) {
//전달 받은 정보들을 통해 토큰 생성
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(email, password);
//email 기반으로 인증 후 Authentication 객체 반환
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
TokenInfo token = jwtTokenProvider.generateToken(authentication);
return token;
}
}
login 함수에서는 email과 password를 받아 UsernamePasswordAuthenticationToken 형태의 token을 생성하고 이를
AuthenticationManagerBuilder 클래스의 authenticate함수로 넘겨줍니다.이때, authenticate함수가 실행될 때 CustomUserDetailsService의 loadUserByUsername 가 호출되어 인증을 수행합니다.
이후 인증에 성공하면 generateToken의 반환값인 token을 반환합니다.
@Service
@RequiredArgsConstructor
public class UserDetailService implements UserDetailsService {
private final UserService userService;
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
//userRepository.findByEmail로 대체 가능
return userService.findUser(email);
}
}
UserDetailService에서는 단순히 userRepository에 email로 등록된 user가 있는지 조회하는 코드입니다.
저는 userService를 분리했지만 userRepository에서 바로 찾으셔도 됩니다.
@Component
public class JwtTokenProvider {
private final Key key;
//application.yml에 설정한 secretKey를 주입받음
public JwtTokenProvider(@Value("${jwt.secret}") String secretKey) {
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
this.key = Keys.hmacShaKeyFor(keyBytes);
}
//JWT 토큰 생성
public TokenInfo generateToken(Authentication authentication) {
//권한 얻어오기
String authorities = authentication.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
long now = (new Date()).getTime();
//Access Token 생성
Date accessTokenExpiresIn = new Date(now + 864000000);
String accessToken = Jwts.builder()
.setSubject(authentication.getName())
.claim("auth", authorities)
.setExpiration(accessTokenExpiresIn)
.signWith(key, SignatureAlgorithm.HS256)
.compact();
//Refresh Token 생성
String refreshToken = Jwts.builder()
.setExpiration(new Date(now + 86400000))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
return TokenInfo.builder()
.grantType("Bearer")
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}
...
}
@Value("${jwt.secret}") String secretKey 부분에서는 application.yml에
jwt:
secret: VlwEyVBsYt9V4zq57TejMnVByzblYcfPQye08f7MGVA9XkKa
같이 적었습니다!
@SpringBootTest
@Transactional
@AutoConfigureMockMvc
class AuthControllerTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private LoginService loginService;
@DisplayName("토큰발급")
@Test
void login() throws Exception {
//given
LoginDto loginDto = LoginDto.builder()
.email("TestEmail@email.com")
.password("testpassword")
.build();
String reqJSON = new Gson().toJson(loginDto);
//when
MvcResult mvcResult = mockMvc.perform(
post("/auth/login")
.contentType("application/json")
.content(reqJSON)
).andReturn();
//Gson으로 이쁘게 json 출력
Gson gson = new GsonBuilder().setPrettyPrinting().create();
TokenInfo tokenInfo = gson.fromJson(mvcResult.getResponse().getContentAsString(), TokenInfo.class);
String resJSON = gson.toJson(tokenInfo);
//then
System.out.println("resJSON = " + resJSON);
}
}
MockMVC를 이용해 post요청을 보내고 응답값을 Gson을 통해 이쁘게 출력해줍니다!
(결과값만 볼려면 주석부분에서 mvcResult.getResponse().getContentAsString()을 그냥 출력하면 됩니다)
postman에서 값들을 담아 요청을 보내보겠습니다.