4. Clova extension 계정연동 서비스 목적
• 써드파티 회원 인증을 거친 사용자에게만 서비스를 제공하려 할 때
• 회원 가입 후 로그인한 사용자에게만 제공
• 써드파티 회원의 속성을 활용하는 서비스를 제공하려고 할 때
• 예) 나의 주문내역, 나의 예약 내역
5. Clova Extension - OAuth 예시 : 배달의 민족 회원 배달주소, 주문 메뉴설정
• https://blog.naver.com/clova_ai/221188646019
1
2
3
4
5
6. ‘짱구신사’ 익스텐션 데모 시나리오
• 일반 시나리오
• 사용자: 클로바, 짱구신사를 시작해줘
• 익스텐션: 안녕하세요. 짱구신사가 시작되었습니다. 어느 지역 날씨를 알려드릴까요?
• 사용자: 서울 날씨를 알려줘.
• 익스텐션: 서울의 날씨는 맑음입니다.
• 계정연동 시나리오
• 사용자: 클로바, 짱구신사를 시작해줘
• 익스텐션: 안녕하세요. 짱구신사가 시작되었습니다. 어느 지역 날씨를 알려드릴까요?
• 사용자: 우리 동네 날씨 어때? (지역정보 없는 발화가 입력되면 회원정보에 설정된 지역명 조회 )
• 익스텐션: ***의 날씨는 맑음입니다.
8. 1) Clova console 상에 익스텐션 tester로 등록되어 있으면 노출
2) 계정연동버튼
• Clova console에 계정연결여부가 ‘Y’이면 익스텐션 정보에 ’계정연동’버튼 노출
• 버튼을 누르면 Clova에서 OAuth2스펙에 따라 ‘로그인URL( Authorization URL ) ’로 설정된 웹페이지호출
3) 로그인 화면
• 써드파티 인증서버에서 제공하는 로그인 화면으로 회원 인증 진행
• 회원 인증에 성공하면, 2)단계에서 Clova가 넘겨준 OAuth 요청변수들을 ‘Access Token URI’ 로 요청전달
4) 접근권한 허용 화면
• 써드파티 인증서버에서 제공하는 접근권한 허용 화면
• 접근권한 허용하면 Clova가 넘겨준 redirect_uri로 이동 à 화면 5로 전환
5) 연동상태 허용 화면
• Clova에서는 익스텐션 서버로 요청을 보낼 때 accessToken 값을 같이 전송
10. Clova extension 계정연동 개발 과정
• Clova console에 익스텐션 등록
• 개발자콘솔에 기본정보, 서버정보, 계정연동 정보 등록
• 인터렉션 모델 구성
• 사용자 대화 시나리오 및 대화에 따른 처리할 Intent 및 slot 등록
• 익스텐션 API 서버 개발
• Clova로부터 Intent 요청을 받으면 이에 응답하는 API 개발
• 특정 API는 접근토큰이 있어야 응답가능하게 변경
• 인증 API 서버 개발
• Clova로부터 계정연동 페이지를 로딩하면 로그인페이지를 제공하고, 로그인 완료시 접근토큰을 발행
15. 익스텐션 API 서버 개발
• 익스텐션서버
• REST API 서버로서, Clova platform은 Clova console에 등록한 REST API 서버URL로 응답과 요청을 주고
받습니다.
• Clova platform은 1) 익스텐션 실행 2) 인텐트 실행 3) 익스텐션 종료라는 3가지 type의 요청을 보내고, 익스
텐션 서버는 여기에 맞게 응답을 하면 됩니다.
• 익스텐션 서버는 https로 외부망으로 통신할 수 있는 서버이어야 하며, 포트는 80 또는 443로만 통신합니다.
16. 서버가 처리해야할 작업
• Clova 요청 메시지의 request type에 따라 지정된 포맷의 응답 리
턴
• Request type
• LaunchRequest : 익스텐션의 시작 (“짱구박사 시작해줘” 했을 경우)
• SessionEndedRequest : 익스텐션의 종료 (“종료해줘” )
• IntentRequest
• Custom Intent à 개발자 콘솔에 입력한 Intent 와 Slot
• Built-in Intent à
https://developers.naver.com/console/clova/guide/CEK/References/CEK_API.md#cek-api-레퍼런스
17. OAuth 인증 API 서버 개발 – 주요 역할
• 회원 로그인
• 로그인 화면 제공 à Clova console에 등록한 ‘로그인 URL’에 해당
• 회원 인증 à 로그인 화면에서 입력한 id, pw에 대해 회원 인증 로직 구현
•
• 접근 토큰 발급
• 접근토큰 발행 대상 à Clova console에 등록한 ‘클라이언트 ID’와 ‘클라이언트
secret’이 대상
• Clova 는 Consumer, 써드파티 인증서버는 Provider 역할임
• 접근토큰 발행 : 회원 인증에 성공하면 Clova console에 등록한 ‘Access token
URI’로 요청을 보내고, 여기서 접근토큰을 발급 à 이후 Redirect URL로 이동
(https://prod-ni-cic.clova.ai/v1/al/token 로 고정되어 있음)
스펙: https://developers.naver.com/console/clova/guide/CEK/Guides/Link_User_Account.md
18. OAuth 인증 API 서버 개발 – 주요 작업
• 서버 구성
• 개발편의상 API 서버와 OAuth 서버는 하나의 서버로 제공
• 회원 정보 관리
• 회원 정보 및 인증 처리는 hard code로 처리
• 개발 환경
• SpringBoot 기반 : Web (API 서버) + Security (OAuth 서버)
19. OAuth 인증 API 서버 개발 – extension 처리 API
@RequestMapping(value = "/clova/extension", method= RequestMethod.POST, produces = "application/json" )
@ResponseBody
public ResponseEntity<MyExtensionMessage> weather (@RequestBody Map<String, Object> map) {
Map m = (HashMap)map.get("request");
String type = (String) m.get("type");
MyExtensionMessage mm = null;
if(type.equals("LaunchRequest")) { // extension 시작
mm= new MyExtensionMessage("안녕, 짱구 신사를 시작합니다. 어느 지역 날씨를 알려드릴까요 ?", false);
} else if (type.equals("IntentRequest")) { // extension의 인텐트 시작
…...........
} else if (type.equals(”SessionEndedRequest")) { // extension 종료
mm= new MyExtensionMessage("짱구 날씨를 종료합니다.", true);
}
return new ResponseEntity<MyExtensionMessage>(mm, HttpStatus.OK);
}
20. OAuth 인증 API 서버 개발 – extension 처리 API
if (intentName.equals("weatherIntent")) {
if (slots != null) {
Map myslot = (HashMap) slots.get("areaSlot");
if(myslot != null ) {
slotName = (String) myslot.get("name");
slotValue = (String) myslot.get("value");
System.out.println("slotName===" + slotName);
System.out.println("slotValue===" + slotValue);
} else {
// slot 이 없으면 사용자 정보에서
// 접근토큰을 이용해 사용자의 지역 정보 조회
slotValue = getUserArea(accessToken);
}
} else {
slotValue = getUserArea(accessToken);
}
mm = new MyExtensionMessage(intentName, slotValue + "의 날씨는 점점 따뜻해 지고 있어요.", false, "PlainText");
// Built-in Intent 처리
} else if (intentName.equals("Clova.YesIntent")) {
mm = new MyExtensionMessage(intentName, "예 라고 하셨나요?", true, "PlainText");
} else if (intentName.equals("Clova.NoIntent")) {
mm = new MyExtensionMessage(intentName, "노 라고 하셨나요?", true, "PlainText");
} else if (intentName.equals("Clova.GuideIntent")) {
mm = new MyExtensionMessage("hearTestIntent", "진주 날씨는 어때라고 해보세요", false, "PlainText");
} else if (intentName.equals("Clova.CancelIntent")) {
mm = new MyExtensionMessage("hearTestIntent", "짱구 신사 실행을 취소합니다. 안녕", true, "PlainText");
}
21. Extension 응답 message
처리 클래스
public class MyExtensionMessage {
public String version = "1.0";
public Map<String, Object> sessionAttributes = new HashMap();
public MyResponse response = null;
public MyExtensionMessage(String message, boolean session) {
MyResponseValue value = new MyResponseValue();
value.value = message;
if(!session) {
sessionAttributes.put("intent", "zzanguWeather");
}
MyResponse response = new MyResponse();
response.shouldEndSession = session;
response.outputSpeech.put("type", "SimpleSpeech");
response.outputSpeech.put("values", value);
this.response = response;
}
public static class MyResponse {
public Map<String, Object> outputSpeech = new
HashMap<String, Object>();
public Map<String, Map> card = new HashMap<String, Map>();
public ArrayList<String> directives = new ArrayList<String>();
public boolean shouldEndSession = false;
}
public static class MyResponseValue {
public String type = "PlainText";
public String lang = "ko";
public String value;
}
}
22. OAuth 인증 API 서버 개발 – 인증 처리 API
// 로그인 화면 html 로딩
@GetMapping("/clova/login")
@ResponseBody
public ModelAndView loginForm(HttpServletResponse httpServletResponse, @RequestParam("state") String state ) {
STATE_STRING = state;
return new ModelAndView("redirect:" + "/login.html");
}
@GetMapping("/clova/config")
@ResponseBody
public ModelAndView config( ) {
return new ModelAndView("redirect:" + "/config.html");
}
// login.html 에서 로그인 버튼 클릭시 접근토큰 발급 처리 (id, pw가 맞다고 가정 )
@PostMapping("/clova/login/check")
@ResponseBody
public void loginCheck(HttpServletResponse httpServletResponse, @RequestParam("userid") String userid, @RequestParam("passwd") String passwd,
@RequestParam("state") String state) throws IOException {
String redirect_uri = "https://prod-ni-cic.clova.ai/v1/al/token";
String url = "http://user:test@okgosu.net/oauth/authorize?response_type=code&client_id=clova&redirect_uri=" + redirect_uri +
"&scope=read&state="+STATE_STRING;
httpServletResponse.sendRedirect(url);
}
23. OAuth 인증 API 서버 개발 – 회원 API
@RequestMapping(value = "/clova/member", method= RequestMethod.POST,
produces = "application/json" )
@ResponseStatus(HttpStatus.CREATED)
Member create(@RequestBody Member member) {
return service.create(member);
}
@RequestMapping(value = "/clova/member", method=
RequestMethod.GET,produces = "application/json" )
Collection<Member> readList() {
return service.findAll();
}
@RequestMapping(value = "/clova/member/{id}", method= RequestMethod.GET,
produces = "application/json" )
ResponseEntity<Member> read(@PathVariable Integer id) {
Member b = service.findById(id);
return new ResponseEntity<Member>(b, HttpStatus.OK);
}
24. OAuth 인증 API 서버 개발 – 회원 관리 등록
@Service
public class MemberService {
private ConcurrentMap<Integer, Member> repo = new ConcurrentHashMap<>();
private AtomicInteger maxId = new AtomicInteger(0);
public MemberService() {
Member member = new Member("okgosu", "안드로메다");
Integer id = maxId.addAndGet(1);
member.setId(id);
repo.put(id, member);
}
public Member findById(Integer id) {
Member b = repo.get(id);
return b;
}
public boolean update(Member member) {
Member old = repo.put(member.getId(), member);
return old != null;
}
public Member create(Member member) {
Integer id = maxId.addAndGet(1);
member.setId(id);
repo.put(id, member);
return member;
}
public boolean delete(Integer id) {
Member old = repo.remove(id);
return old != null;
}
public Collection<Member> findAll() {
return repo.values();
}
}
25. Spring
Security
설정
@EnableResourceServer
@EnableAuthorizationServer
@SpringBootApplication
public class DemoApplication extends ResourceServerConfigurerAdapter {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/clova/member").authenticated();
}
}
# application.properties
security.user.name=user
security.user.password=test
# client id, secret
security.oauth2.client.client-id=clova
security.oauth2.client.client-secret=clova123
security.enable-csrf=false
security.oauth2.client.scope=read
server.port=80