- JSON을 이용하는 API 서버를 어떻게 만들어야 하는지 살펴본다.
- API 서버 구성 시 가장 주의해야 하는 부분이 보안과 인증에 대한 문제이므로 스프링 시큐리티를 사용해서 처리하고 인증 처리는 JWT를 사용하도록 한다.
1. 🤍간단한 Note를 작성하고 이를 이용하는 API 서버 프로젝트 구현하기
1. Note 엔티티 작성

2. NoteRepository 인터페이스 작성

3. DTO 작성

3. 서비스 계층 작성
- 등록, 수정, 삭제, 조회 처리
<java />
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 속성을 포함해서 전송함.

2. 🤍API 서버 필터
- 위에서는 /notes/라는 경로로 아무런 제약없이 접근이 가능했지만 이번에는 인증을 거친 사용자에 한해서만 서비스를 제공하고자함.
- 외부에서 API를 호출 시에는 주로 인증 정보나 인증 키(key)를 같이 전송해서 처리함.
- 이러한 키를 토큰(token)이라는 용어로 사용하기도 하는데 이번 프로젝트에서는 JWT(JSON Web Token)을 사용하도록 함.
- 특정 API를 호출할 때 반드시 인증에 사용할 토큰을 같이 전송하고 서버에서는 검증함 -> 필터(filter)
1. OncePerRequestFilter 사용해보기
ApiCheckFilter 클래스를 추가하고 SecurityConfig 클래스에 빈을 등록한다.

<java />
@Bean public ApiCheckFilter apiCheckFilter(){ return new ApiCheckFilter(); }
적용 후 '/sample/all'과 같이 permitAll()을 적용한 URL 혹은 기존에 GET 방식으로 접근할 수 있는 URL을 호출하면 아래와 같은 로그가 기록되는 것을 확인할 수 있다.

2. 이번에는 '/notes'로 시작하는 경우에만 동작하도록 수정해 본다.
<java />
@Bean public ApiCheckFilter apiCheckFilter(){ return new ApiCheckFilter("/notes/**/*"); }
<java />
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/'로 시작하는 경로에만 로그가 출력되는 것을 확인할 수 있다.

3. 🤍Authorization 헤더 처리
- 특정 API를 호출하는 클라이언트는 다른 서버 및 애플리케이션으로 실행되기 때문에 쿠키나 세션을 활용할 수 없음.
- 이때 Authorization 헤더를 사용해서 헤더 메시지에 특별한 값을 지정해서 전송할 수 있음.
1. ApiCheckFilter 작성

2. 커스텀 헤더를 Authorization, 12345678로 추가 후 요청하면 정상적인 결과가 나온다.

그런데 Authorization이 없어도 별다른 에러가 발생하지 않는다. 이때에는 AuthenticationManager를 이용해서 인증 처리를 해주면 된다.

3. ApiLoginFailHandler 작성 후

4. 잘못된 아이디, 로그인을 입력하면 아래와 같이 에러 메시지가 전송되는 것을 확인할 수 있다.

5. 인증 성공 처리


4. 🤍JWT 토큰 생성 / 검증
- 인증 성공 후에는 적절한 데이터를 만들어 전송해 주어야 함 -> JWT(JSON Web Token) 이용
- 인증이 성공한 사용자에게 특수한 문자열(JWT)을 전송해 주고 API 호출 시에는 이 문자열을 읽어서 해당 Request가 정상적인 요청인지 확인하는 용도로 사용
1. JWT 라이브러리 추가
<java />
/jsonbtoken 라이브러리 implementation group: 'io.jsonwebtoken', name: 'jjwt', version: '0.9.1'
2. JWTUtil 클래스 작성
<java />
@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. 테스트 코드 작성
<java />
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 |