- JSON을 이용하는 API 서버를 어떻게 만들어야 하는지 살펴본다.
- API 서버 구성 시 가장 주의해야 하는 부분이 보안과 인증에 대한 문제이므로 스프링 시큐리티를 사용해서 처리하고 인증 처리는 JWT를 사용하도록 한다.
🤍간단한 Note를 작성하고 이를 이용하는 API 서버 프로젝트 구현하기
1. Note 엔티티 작성
2. NoteRepository 인터페이스 작성
3. DTO 작성
3. 서비스 계층 작성
- 등록, 수정, 삭제, 조회 처리
public interface NoteService {
Long register(NoteDTO noteDTO);
NoteDTO get(Long num);
void modify(NoteDTO noteDTO);
void remove(Long num);
List<NoteDTO> getAllWithWriter(String writerEmail);
default Note dtoToEntity(NoteDTO noteDTO){
Note note = Note.builder()
.num(noteDTO.getNum())
.title(noteDTO.getTitle())
.content(noteDTO.getContent())
.writer(ClubMember.builder().email(noteDTO.getWriterEmail()).build())
.build();
return note;
}
default NoteDTO entityToDTO(Note note){
NoteDTO noteDTO = NoteDTO.builder()
.num(note.getNum())
.title(note.getTitle())
.content(note.getContent())
.writerEmail(note.getWriter().getEmail())
.regDate(note.getRegDate())
.modDate(note.getModDate())
.build();
return noteDTO;
}
4. 컨트롤러 작성
4-1. 등록 테스트
- 포스트맨으로 등록 테스트를 해보면 아래와 같이 글번호(Note num)가 출력된다.
4-2. 특정 번호 Note 확인하기
- 포스트맨으로 'notes/1'과 같이 요청하면 1번 글을 조회할 수 있다.
4-3. 특정 회원의 모든 Note 확인하기
- getList는 파라미터로 전달되는 이메일주소로 해당 회원이 작성한 모든 Note에 대한 정보를 JSON으로 반환함.
4-4. Note 삭제 테스트
- DELETE 방식으로 처리함.
4-5. Note 수정 테스트
- 수정할 때에는 등록과 달리 JSON 데이터에 num 속성을 포함해서 전송함.
🤍API 서버 필터
- 위에서는 /notes/라는 경로로 아무런 제약없이 접근이 가능했지만 이번에는 인증을 거친 사용자에 한해서만 서비스를 제공하고자함.
- 외부에서 API를 호출 시에는 주로 인증 정보나 인증 키(key)를 같이 전송해서 처리함.
- 이러한 키를 토큰(token)이라는 용어로 사용하기도 하는데 이번 프로젝트에서는 JWT(JSON Web Token)을 사용하도록 함.
- 특정 API를 호출할 때 반드시 인증에 사용할 토큰을 같이 전송하고 서버에서는 검증함 -> 필터(filter)
1. OncePerRequestFilter 사용해보기
ApiCheckFilter 클래스를 추가하고 SecurityConfig 클래스에 빈을 등록한다.
@Bean
public ApiCheckFilter apiCheckFilter(){
return new ApiCheckFilter();
}
적용 후 '/sample/all'과 같이 permitAll()을 적용한 URL 혹은 기존에 GET 방식으로 접근할 수 있는 URL을 호출하면 아래와 같은 로그가 기록되는 것을 확인할 수 있다.
2. 이번에는 '/notes'로 시작하는 경우에만 동작하도록 수정해 본다.
@Bean
public ApiCheckFilter apiCheckFilter(){
return new ApiCheckFilter("/notes/**/*");
}
public class ApiCheckFilter extends OncePerRequestFilter {
private AntPathMatcher antPathMatcher;
private String pattern;
public ApiCheckFilter(String pattern){
this.antPathMatcher = new AntPathMatcher();
this.pattern=pattern;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
log.info("REQUESTURI: "+request.getRequestURI());
log.info(antPathMatcher.match(pattern, request.getRequestURI()));
if(antPathMatcher.match(pattern, request.getRequestURI())){
log.info("ApiCheckFilter............................");
log.info("ApiCheckFilter............................");
log.info("ApiCheckFilter............................");
return;
}
filterChain.doFilter(request, response);
}
}
변경 후 프로젝트를 실행하면 '/notes/'로 시작하는 경로에만 로그가 출력되는 것을 확인할 수 있다.
🤍Authorization 헤더 처리
- 특정 API를 호출하는 클라이언트는 다른 서버 및 애플리케이션으로 실행되기 때문에 쿠키나 세션을 활용할 수 없음.
- 이때 Authorization 헤더를 사용해서 헤더 메시지에 특별한 값을 지정해서 전송할 수 있음.
1. ApiCheckFilter 작성
2. 커스텀 헤더를 Authorization, 12345678로 추가 후 요청하면 정상적인 결과가 나온다.
그런데 Authorization이 없어도 별다른 에러가 발생하지 않는다. 이때에는 AuthenticationManager를 이용해서 인증 처리를 해주면 된다.
3. ApiLoginFailHandler 작성 후
4. 잘못된 아이디, 로그인을 입력하면 아래와 같이 에러 메시지가 전송되는 것을 확인할 수 있다.
5. 인증 성공 처리
🤍JWT 토큰 생성 / 검증
- 인증 성공 후에는 적절한 데이터를 만들어 전송해 주어야 함 -> JWT(JSON Web Token) 이용
- 인증이 성공한 사용자에게 특수한 문자열(JWT)을 전송해 주고 API 호출 시에는 이 문자열을 읽어서 해당 Request가 정상적인 요청인지 확인하는 용도로 사용
1. JWT 라이브러리 추가
/jsonbtoken 라이브러리
implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
2. JWTUtil 클래스 작성
@Log4j2
public class JWTUtil {
private String secretKey = "zerock12345678";
//1month
private long expire = 60 *24 * 30;
public String generateToken(String content) throws Exception{
return Jwts.builder()
.setIssuedAt(new Date())
.setExpiration(Date.from(ZonedDateTime.now().plusMinutes(expire).toInstant()))
// .setExpiration(Date.from(ZonedDateTime.now().plusSeconds(1).toInstant()))
.claim("sub",content)
.signWith(SignatureAlgorithm.HS256,secretKey.getBytes("UTF-8"))
.compact();
}
public String validateAndExtract(String tokenStr) throws Exception{
String contentValue = null;
try {
DefaultJws defultJws = (DefaultJws) Jwts.parser()
.setSigningKey(secretKey.getBytes("UTF-8"))
.parseClaimsJws(tokenStr);
log.info(defultJws);
log.info(defultJws.getBody().getClass());
DefaultClaims claims = (DefaultClaims) defultJws.getBody();
log.info("-------------------------------");
contentValue = claims.getSubject();
}catch (Exception e){
e.printStackTrace();
log.error(e.getMessage());
contentValue = null;
}
return contentValue;
}
}
3. 테스트 코드 작성
public class JWTTests {
private JWTUtil jwtUtil;
@BeforeEach
public void testBefore(){
System.out.println("testBefore.............");
jwtUtil = new JWTUtil();
}
@Test
public void testEncode() throws Exception{
String email = "user95@zerock.org";
String str = jwtUtil.generateToken(email);
System.out.println(str);
}
@Test
public void testValidate() throws Exception{
String email = "user95@zerock.org";
String str = jwtUtil.generateToken(email);
Thread.sleep(5000);
String resultEmail = jwtUtil.validateAndExtract(str);
System.out.println(resultEmail);
}
}
4. 테스트 통과 후 만들어지는 JWT 문자열을 확인할 수 있음.
5. jwt.io 사이트에 접속 후 JWTUtil 클래스에 비밀키로 지정해놓은 'zerock12345678'을 입력하면 정상적인 JWT 문자열인지 검증할 수 있다.
6. Filter에 JWT를 적용하기 위해 먼저 ApiLoginFilter 클래스를 JWTUtil을 생성자로 주입받는 구조로 수정한다.
7. SecurityConfig 클래스도 수정한다.
8. 프로젝트를 실행하고 브라우저에 아래와 같이 입력하면 JWT가 발행된 것을 확인할 수 있다.
'💻 my code archive > 🏷️JAVA & Spring(Boot)' 카테고리의 다른 글
[스프링부트 블로그 만들기] 카카오 로그인 API 서비스 구현하기 (0) | 2022.03.25 |
---|---|
[스프링부트 블로그 만들기] 회원 정보 수정 구현하기 (0) | 2022.03.24 |
스프링부트 공부기록(31) - 스프링 시큐리티 구글 로그인 구현하기 (0) | 2022.03.21 |
스프링부트 공부기록(30) - 스프링 시큐리티(Spring Security) 연동, 인증, 인가, 커스터마이징 (0) | 2022.03.21 |
스프링부트 공부기록(29) - 영화 리뷰 프로젝트 :: 영화 목록 처리, 리뷰 등록, 영화 상세 정보 조회 구현하기 (0) | 2022.03.20 |