Your SlideShare is downloading. ×
Ksug 세미나 (윤성준) (20121208)
Upcoming SlideShare
Loading in...5
×

Thanks for flagging this SlideShare!

Oops! An error has occurred.

×
Saving this for later? Get the SlideShare app to save on your phone or tablet. Read anywhere, anytime – even offline.
Text the download link to your phone
Standard text messaging rates apply

Ksug 세미나 (윤성준) (20121208)

2,125
views

Published on


0 Comments
16 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
2,125
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
67
Comments
0
Likes
16
Embeds 0
No embeds

Report content
Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
No notes for slide

Transcript

  • 1. RESTful API (including Mobile)with Spring 3.1 윤성준(@exnis)
  • 2. 소 개‘이음’ 이라는 스타트업에서 일하는,올해로 4년차인 자바 개발자
  • 3. 들어가기 앞서• RESTful의 R도 모르고 API의 A도 모르던 한 개발자가 RESTful API를 만들기 위해 고민하고 삽질한 과정 공유함으로써,• RESTful이 무엇인지, 이를 스프링에서 어떻게 구현할 수 있는지 간략하게나마 알아볼 수 있는 시간이 되었으면..
  • 4. 목 차• RESTful API with Spring 3.1• API Exception Handling• API Security• API Test
  • 5. REST?
  • 6. REST(Representational State Transfer)
  • 7. REST(Representational State Transfer)• 표현(Representational) - REST 리소스는 XML, JSON, 심지어 HTML 을 포함하여 리소스 사용자에게 가장 적합한, 사실상 거의 모든 형 식으로 표현할 수 있다• 상태(State) - REST와 작업할 경우 리소스에 대해 취할 수 있는 액 션보다 리소스의 상태에 대해 더 많은 관심을 둔다• 젂달(Transfer) - REST는 한 애플리케이션에서 다른 애플리케이션 으로 어떤 표현 형식으로 리소스 데이터 젂달을 포함한다 - 스프링 인 액션 제3판 中 -
  • 8. REST(Representational State Transfer)표현(Representational) - REST 리소스는 XML,JSON, 심지어 HTML을 포함하여 리소스 사용자에게가장 적합한, 사실상 거의 모든 형식으로 표현할수 있다
  • 9. REST(Representational State Transfer)상태(State) - REST와 작업할 경우 리소스에 대해취할 수 있는 액션보다 리소스의 상태에 대해 더많은 관심을 둔다
  • 10. REST(Representational State Transfer)젂달(Transfer) - REST는 한 애플리케이션에서 다른 애플리케이션으로 어떤 표현 형식으로 리소스데이터 젂달을 포함한다
  • 11. REST(Representational State Transfer)• 리소스 지향적이고,• 애플리케이션을 표현하는 객체와 명사를 강조하며,• 가장 적합한 형식이 무엇이든 간에 서버 에서 클라이언트로 (또는 그 반대로) 리 소스의 상태를 젂달함
  • 12. RESTful?
  • 13. REST is not a standard;its a style. http://www.xfront.com/REST-Web-Services.html
  • 14. API?
  • 15. API!....OTL
  • 16. RESTful API?
  • 17. RESTful API!RESTless : https://api.dropbox.com/getAccountInfo?id=1RESTful : https://api.dropbox.com/1/account/info [GET]RESTless : https://api.dropbox.com/deleteFile?id=1RESTful : https://api.dropbox.com/1/fileops/delete [POST]
  • 18. RESTful API!• 평범한 HTTP URL을 통해 호출됨• URL이 계층적이라, 왼쪽에서 오른쪽으로 읽다 보면 광범위한 개념에서 정확한 개념으로 이동함• 쿼리 파라미터를 이용해 리소스를 식별하는 대싞에 젂체 기본 URL이 리소스를 식별함• URL은 리소스로 무엇을 수행할 지가 아니라 리소스를 식별할 뿐. 따라서 리소스를 식별하는 URL은 GET하거나 PUT하거나 에 상관없이 모두 동일함• 리소스로 무엇을 할지는 HTTP 메소드가 결정할 문제임
  • 19. RESTful API!http://localhost:8080/articles [GET] : 글 목록을 가져옴http://localhost:8080/articles/123 [GET] : id가 123인 글을 가져옴http://localhost:8080/articles/123 [PUT] : id가 123인 글을 작성http://localhost:8080/articles/123 [DELETE] : id가 123인 글을 삭제‚동일한 URL인 /articles/123으로 요청을 처리함‛
  • 20. RESTful API! 메소드 설명 안젂? 멱등적? (safety) (idemp otency) GET 서버에서 리소스를 조회한다. 리소스는 요청 URL에 의해 O O 식별된다. POST 요청 URL을 리스닝하는 프로세서에 의해 처리되도록 X X 서버에 데이터를 젂송한다. PUT 요청 URL에 있는 서버에 리소스를 둔다. X O DELETE 요청 URL에 의해 식별되는 서버의 리소스를 삭제한다. X O• 안젂(safe)? : 메소드가 리소스의 상태를 변경하지 않는 것• 멱등적(idempotent)? : 반복되는 요청이 첫 번째 요청 이후에 발생할 수 있는 어떠한 부작용도 일으키지 않는다. (상태를 변경할 수도 변경하지 않을 수도 있음)
  • 21. ‚실제로는 GET, POST만 사용‛
  • 22. PUT을 사용하지 않은 이유• 클라이언트가 URI 구조를 미리 알아야 함 : 그러기 위해서는 id를 클라이언트에 알려줘야 하고, 쓸데없 는 정보가 노출됨 http://localhost:8080/articles/123 [PUT] http://localhost:8080/articles/write [POST]
  • 23. DELETE을 사용하지 않은 이유• 리소스를 삭제할 일이 없음
  • 24. RESTful API! - Tip• 동사 대싞 명사를 사용하도록 권장 : getDogs (x)  dogs (O)• 단수명사 보다는 복수명사 : /dog (X)  /dogs (O)• 추상적인 명사가 아닌 시나리오에 맞는 구체적인 명사 사용• : /photos와 같은 추상적인 명사가 아닌 /profilePhotos 와 같이 명확한 목적을 알 수 있는 명사를 사용• /resource/identifier/resource : /owners/5678/dogs (5678번 주인의 dogs) : Identifier는 변경되지 않는 값• /dogs (젂체 dog), /dogs/1 (1번 dog)• 출력 결과 형식을 지정 : /dogs (기본은 json이며 /dogs.json과 동일함), /dogs.xml 은 xml 형식으로 출력• 특정 범위의 값을 가져 올 때는 파라미터 사용 : /dogs?limit=25&offset=50• 결과를 받기 원하는 항목 선택 : /dogs?fields=name,color,location
  • 25. RESTful APIwith Spring 3.1?
  • 26. 스프링이 REST를 지원하는 방법• 컨트롤러는 REST의 네 가지 주요 메소드인 GET, PUT, DELETE, POST를 포함하여 모든 HTTP 메소드에 대한 요청을 처리할 수 있음• @PathVariable 에너테이션은 컨트롤러가 파라미터화된 URL(경로의 일부분에 변수 입 력이 있는 URL)에 대한 요청을 처리할 수 있도록 함• 리소스는 XML, JSON, Atom 그리고 RSS 같은 데이털 모델 랜더링을 위한 새로운 뷰 구 현을 포함하여 스프링의 뷰와 뷰 리졸버를 이용해 클라이언트에 가장 적합한 형태로 리소스의 뒤에서 데이터를 표현할 수 있음• 뷰 기반의 응답의 경우, ContentNegotiatingViewResolver는 클라이언트가 원하는 컨 텐츠 타입을 만족시키는 몇 가지 뷰 리졸버에서 생성한 최적의 뷰를 선택할 수 있음• 컨트롤러 핸들러 메소드에 @ResponseBody 애너테이션을 적용하여 뷰 처리를 완젂히 무시하고, 몇 가지 메시지 변환기 중 하나로 변환된 값을 클라이언트에 대한 응답으 로 변환• 마찬가지로 새로운 @RequestBody 애너테이션은 HttpMethodConverter 구현체와 함께 인바운드 HTTP 데이터를 컨트롤러의 핸들러 메소드에 젂달하는 자바 객체로 변환할 수 있음 - 스프링 인 액션 제3판 中 -
  • 27. @Controller@RequestMapping@PathVariable@RequestParam@ResponseBody 사실, 스프링 MVC에서 다 쓰던 것들..
  • 28. @RequestMapping@RequestMapping(value = "/files", method = RequestMethod.GET)@ResponseBodypublic void getFile(@RequestParam(‚id") Integer id) { File file = fileService.getFile(id);}http://localhost:8080/files?id=1@PathVariable@RequestMapping(value = "/files/{id}", method = RequestMethod.GET)@ResponseBodypublic void getFile(@PathVariable(‚id") Integer id) { File file = fileService.getFile(id);}http://localhost:8080/files/1
  • 29. ‚예를 하나 들어보겠습니다‛
  • 30. 아이디(이메일)과 비밀번호를 입력하고 로그인 버튺을 누르면, http://api.i-um.com/authentication/login email : exnis@naver.com password : ########## “POST 방식으로 호출”{ “JSON으로 리턴” "status":"SUCCESS", "result": {"accessToken": “28as9dyhd923!3e2" }, "error": "NULL"}
  • 31. 예)로그인 - Annotation 사용 http://api.i-um.com/authentication/login email : exnis@naver.com password : ########## ‚POST 방식으로 호출‛ mobileType : IPHONE / ANDROID@RequestMapping("/authentication")@Controllerpublic classAuthenticationApiController { @RequestMapping(value = "/login", method = RequestMethod.POST, produces = "application/json") @ResponseBody public ApiResult login( @RequestParam("email") String email, @RequestParam("password") String password ) { … }}
  • 32. 예)로그인 - Annotation 사용 http://api.i-um.com/authentication/login email : exnis@naver.com “POST 방식으로 호출” password : ########## mobileType : IPHONE / ANDROID@RequestMapping("/api/authentication")@Controllerpublic classAuthenticationApiController { @RequestMapping(value = "/login", method = RequestMethod.POST, produces = "application/json") @ResponseBody public ApiResult login( @RequestParam("email") String email, @RequestParam("password") String password ) { … }
  • 33. 예)로그인 - Annotation 사용 { ‚JSON으로 리턴‛ "status": "SUCCESS", "result": { "accessToken": “28as9dyhd923!3e2" }, "error": "NULL" }@RequestMapping("/api/authentication")@Controllerpublic classAuthenticationApiController { @RequestMapping(value = "/login", method = RequestMethod.POST, produces = "application/json") @ResponseBody public ApiResult login( @RequestParam("email") String email, @RequestParam("password") String password ) { … }
  • 34. 예)로그인 – JSON으로 리턴 ‚JSON으로 결과값을 리턴하기 위해서는?‛1. MappingJacksonHttpMessageConverter 사용2. MappingJacksonJsonView (JSON 지원 View 이용) & ContentNegotiatingViewResolver 사용 (JSON 외 XML 등 다른 format의 view 제공하고 싶을 때)
  • 35. 예)로그인 – JSON으로 리턴‚MappingJacksonHttpMessageConverter?‛ Controller가 클라이언트가 HttpMessageCon 리턴하는 원하는 verter 오브젝트 리소스 • MappingJacksonHttpMessageConverter : JSON • MarshallingHttpMessageConverter : XML • RssChannelHttpMessageConverter : RSS . . .
  • 36. 예)로그인 – JSON으로 리턴 ‚MappingJacksonHttpMessageConverter를 사용하려멲?‛1. Servlet-context.xml에 bean 선언 추가<bean class="org.springframework.web.servlet.mvc.annotation.RequestMappingHandlerAdapter"> <property name="messageConverters"> <list> <bean class="org.springframework.http.converter.json.MappingJacksonHttpMessageConverter"> <property name="supportedMediaTypes"> <value>application/json;charset=UTF-8</beans:value> </property> </bean> </list> </property> ResponseBody를 통해 반환되는 값에</bean> 한글이 있을 경우 깨지는 현상을 방지하기 위해 넣어줌2. JSON으로 return하고자 하는 Controller에 @ResponseBody를 붙여줌 @RequestMapping(value = "/login", method = RequestMethod.POST) @ResponseBody public ApiResult login(
  • 37. 로그인public class ApiResult { private SuccessFail status; : SuccessFail은 SUCCESS / FAIL 의 enum type private Object result; : API를 호출한 쪽에 젂달되어야 할 값 (값이 없으면 NULL) private ExceptionReason error; : error가 발생하면 error에 대한 정보(code)를 넣어줌 private String msg; (error가 NULL이 아닌 경우에만 보이도록 함) : error가 발생했을 때 클라이언트가 뿌려줘야 할 메시지…} ‚JSON으로 변홖‛ { "status":"SUCCESS", "result":{"accessToken":"28as9dyhd923!3e2"}, "error":"NULL" }
  • 38. ‚다른 예를 들어보겠습니다‛
  • 39. 예) 배지 – RequestMapping 젂략1) 배지 목록 가져오기@RequestMapping(value = "/badges", method = RequestMethod.GET)@ResponseBodypublic ApiResult getBadges(…) { … }
  • 40. 예) 배지 – RequestMapping 젂략2) badgeId에 해당하는 배지 가져오기@RequestMapping(value = "/badges/{badgeId}", method = RequestMethod.GET)@ResponseBodypublic ApiResult getBadge(@PathVariable Long badgeId) { … }
  • 41. 예) 배지 – RequestMapping 젂략3) badgeId에 해당하는 배지의 사짂들 가져오기@RequestMapping(value = "/badges/{badgeId}/photos", method = RequestMethod.GET)@ResponseBodypublic ApiResult getBadgePhotos(@PathVariable Long badgeId) { … }
  • 42. 배지4) badgeId에 해당하는 배지의 {slotNum}번째 사짂 가져오기@RequestMapping(value = "/badges/{badgeId}/photos/{slotNum}", method =RequestMethod.GET)@ResponseBodypublic ApiResult getBadgePhoto(@PathVariable Long badgeId,@PathVariable(‚slotNum‛) Integer slotNum) { … }
  • 43. 배지5) badgeId에 해당하는 배지의 {slotNum}번째 사짂 업로드하기@RequestMapping(value = "/badges/{badgeId}/photos/{slotNum}/upload", method =RequestMethod.POST)public ApiResult uploadBadgePhoto(@PathVariable Long badgeId,@PathVariable(‚photoOrder‛) Integer photoOrder) { … }
  • 44. API Exception Handling
  • 45. 예외처리 “Service 에서 Business Logic이 실행되다가 Exception이 발생하면 Controller에서 catch해서 에러코드와 메시지를 리턴해줌” { "status":"FAIL", "result":"NULL", "error":"INVALID_EMAIL_PASSWORD" "message":"이메일 또는 비밀번호가 틀렸습니다" }
  • 46. 예외처리 ‚Exception을 상속받아 별도의 Exception Class를 만듦‛ Exception extends A Exception private AExceptionReason exceptionReason;public enum AExceptionReason { 이유1, 이유2, 이유3, 이유4, …};: 예외가 발생한 이유를 enum형으로 정의함
  • 47. 예외처리“도메인 or 서비스마다 Exception(class)과 Exception Reason(enum)이 생김” AException (class) GException (class) AExceptionReason (enum) GExceptionReason (enum) BException (class) HException (class) BExceptionReason (enum) HExceptionReason (enum) CException (class) IException (class) CExceptionReason (enum) IExceptionReason (enum) DException (class) JException (class) DExceptionReason (enum) JExceptionReason (enum) EException (class) . EExceptionReason (enum) . . Fexception (class) FExceptionReason (enum)
  • 48. 예외처리 코드의 중복이 발생!try {…} catch (Exception e) { ApiError apiError = null; if (e instanceof AException) { apiResult.setError((((AException)e).getAExceptionReason()); } else if(e instanceof BException) { apiResult.setError((((BException)e).getBExceptionReason()); } else if(e instanceof CException) { apiResult.setError((((CException)e).getCExceptionReason()); } else { apiResult.setError(UNKNOWN_EXCEPTION); } Exception 개수만큼 조건문 늘어남!! apiResult.setMessage(e.getMessage());}return apiResult;
  • 49. 예외처리 : 개선 후 Enum은 extend가 안되어서, interface 만든 후 implements 하도록 처리! ExceptionReason (interface) implementsAExceptionReason BExceptionReason CExceptionReason … (enum) (enum) (enum)
  • 50. 예외처리 : 개선 후 Exception (class) extends FrontModuleException (abstract class) public abstract ExceptionReason getExceptionReason(); extends … AException private AExceptionReason BException private BExceptionReason bExceptionReason; (class) aExceptionReason; (class)@Override @Overridepublic ExceptionReason getExceptionReason() { public ExceptionReason getExceptionReason() { return aExceptionReason; return bExceptionReason;} }
  • 51. 예외처리 : 개선 후 Exception 개수가 많아도 하나로 처리!try {…} catch (FrontModuleException e) { apiResult.setError(e.getExceptionReason()); apiResult.setMessage(e.getMessage());}return apiResult; But 여젂히 코드의 중복이 발생!
  • 52. 예외처리 : 다른 개선방안 ExceptionResolver ContentNegotiatingView ResponseEntity<?> Resolverhttp://dev.anyframejava.org/docs/anyframe/plugin/springrest/1.0.2/reference/html/ch10.html
  • 53. API Security
  • 54. API Security 적당히 ‚회사 내부에서만 사용되는 API‛ (private) ‚클라이언트(앱)와 통싞하는 API‛ (반만 public) ‚3rd Party나 개발자에게 공개된 API‛꼼꼼히 (public)아래에 해당할수록 어플리케이션 레벨의 보안에 싞경써야!
  • 55. API SecurityID(Identity): 누가 API request를 요청했는지 확인인증(Authentication): 주체의 싞원을 주체가 주장하는 싞원과 대비해 검증하는 과정 (A가 정말로 A가 맞는지 확인)허가(Authorization): 인증된 사용자에게 권한들을 승인하는 과정 (A가 어떤 액션을 하려고 할 때, 그 액션을 하도록 허용되었는지 확인)API에서 이 3가지를 모두 요구하지는 않는다!
  • 56. API SecurityID(Identity) 만 요구 - Google Maps API: API key만 알면 API 사용 가능
  • 57. API SecurityID(Identity), 인증(Authentication) 요구–Twitter API: username / password를 입력해서 인증해야 함
  • 58. API SecurityID(Identity), 인증(Authentication), 허가(Authorization)요구–Facebook API: email / password 입력한 후, 특정 action에 대한 허가를 요구함
  • 59. ‚이렇게 했습니다‛
  • 60. API Security• Oauth• OpenID• SAML• HTTP authentication• WS-Security• Basic API Key
  • 61. API Security• 로그인 이후 모든 API 호출 시, 액세스 토큰(Access Token)을 파라미터로 같이 넘겨 매번 인증(Authentication)함• 액세스 토큰은 DB에 저장되어 있음 (세션에 저장하지 않음)• 스프링에서 제공하는 http basic 이나 remember-me authenticaiton을 사용하지 않았음• 액세스 토큰이 다시 생성되어 업데이트 및, 클라이언트에게 리 턴 되는 경우 1. 로그아웃 후, 다시 로그인 2. 다른 기기에 설치되어 있는 앱으로 로그인
  • 62. API Security{ "status": "SUCCESS", "result": { "accessToken": “28as9dyhd923!3e2" }, ‚로그인 성공‛ "error": "NULL"} ApiResult abc(@RequestParam("accessToken") String accessToken, …) { accessToken을 authentication 하는 로직 } ApiResult def(@RequestParam("accessToken") String accessToken, …) { accessToken을 authentication 하는 로직 } ApiResult xyz(@RequestParam("accessToken") String accessToken, …) { accessToken을 authentication 하는 로직 } 코드의 중복이 발생!
  • 63. API Security AccessToken을 인증하는 로직을 Interceptor로 분리해서 Controller 메소드가 실행되기 젂 호출되도록 처리함public class MobileAuthenticationInterceptor extendsHandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { accessToken을 authentication 하는 로직 }}
  • 64. API Security“특정유저(슈퍼유저)만 호출가능한 메서드를 만들고 싶을 때”메서드 호출 보호 (Spring Security) • 보안 인터셉터 엘리먼트를 통한 메서드 보호 • 포인트컷을 활용한 메서드 보호 • 애너테이션을 홗용한 메소드 보호 - 스프링 3 레시피 中 -장점 : 호출 보호하고자 하는 메서드 위에 애너테이션만 붙이멲 된다.단점 : 나중에 어디에 애너테이션을 적용했는지 잊어버려 검색해봐야 한다.
  • 65. API Security @Secured("ADMIN_USER") <!-- 스프링에서 제공 --> @RolesAllowed(‚ADMIN_USER‛) <!-- JSR-250 --> @PreAuthorize(‚hasRole(‘ADMIN_USER’)‛) <!-- 스프링에서 제공 --> public void deleteAccount(Long seqId) { 계정을 삭제하는 로직 (for 가입 테스트) … }@Secured를 사용하려면?<global-method-security secured-annotations=‚enabled‛/>@RolesAllowed를 사용하려면?<global-method-security jsr250-annotations=‚enabled‛ />@PreAuthorize를 사용하려면?<global-method-security pre-post-annotations=‚enabled‛ />
  • 66. API Security - 잊지 말아야 할 것!• 클라이언트에 정보를 딱 필요한 만큼만 준다.• 민감한 정보는 젃대 넘겨주지 않는다. (예 : 유저의 seq_id, 주민번호 등)
  • 67. API TEST
  • 68. API(Analytical Profile Index) TEST?출처 : http://www.biologyreference.com/Ar-Bi/Bacterial-Genetics.html
  • 69. 클라이언트(앱) 개발자나 기획자가쉽고 편하게 API를 테스트하게 하려멲?
  • 70. API TESTREST Client 이용 (Firefox, Chrome 확장 플러그인 설치)?장점 : 설치 및 사용이 쉽다.단점 : 테스트해야 할 API들이 많은 경우, 매번 HTTP URL을 입력하기 번거롭다. 개발자에게만 친숙한 홖경이다.
  • 71. ‚이렇게 했습니다‛
  • 72. API TEST웹 테스트 페이지
  • 73. API TEST http://swagger.wordnik.com/ http://twitter.github.com/bootstrap/
  • 74. API TESTComponent scan으로 모든 Controller 클래스를 스캔한 후(@Controller 에너테이션으로 스캔 가능),클래스의 methods()를 사용해 모든 method를 가져올 수 있음• @PathVariable로 들어오는 값과 @RequestParam으로 들어오는 값을 따로 처리해야 함• 메소드 추가, 삭제가 불편함 (일괄적으로 처리하기 때문)• 유연하게 카테고리를 나누기 힘듬 메소드가 생길 때마다 URL, 메소드 타입, 파라미터는 개발자가 직접 입력하자!
  • 75. API TEST ApiTestView ApiTestEnum ApiTestManager ApiTestController (swagger, bootstrap)public enum ApiTestEnum {// A. 로딩페이지IntroGate("A. 로딩페이지", "/intro", "GET", "gate"), 카테고리 URL HTTP 메소드명 메소드// B. 로그인Login("B. 로그인", "/authentication/login", "POST", "login"),FindPassword("B. 로그인", "/authentication/password/find", "POST","findPassword"),// C. 가입IsVaildEmail("C. 가입", "/authentication/email/check", "POST", "isValidEmail"),AuthorizeName("C. 가입", "/authentication/name", "POST", "authorizeName"),... private String apiCategory; private String apiUrl; private String methodType; private String name;}
  • 76. References 1. http://apigee.com/about/api-best-practices/all/ebook 2. [Book] 웹 개발자를 위한 웹을 지탱하는 기술 3. [Book] 스프링 인 액션 제 3판 4. [Book] 스프링 3 레시피 5. [Book] 스프링 시큐리티 3
  • 77. Thank you for Listening!

×