일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- 북노트
- docker
- mysql
- FIRST원칙
- 도커
- mysql index
- Docker-compose
- MySQL 인덱스
- Docker기본개념
- elk
- Jenkins port 변경
- git
- F.I.R.S.T
- 도커예제
- Docker설치
- MongoDB
- 자바
- 알고리즘
- Springboot+Docker 초기설정
- Jenkins
- Nginx
- Expo #SpringBoot #네아로
- springboot
- 데이터볼륨
- Elasticsearch
- MongoDB설치 및 환경설정
- 프로그래머스
- Jenkins설치
- 도커실행
- CleanCode
- Today
- Total
MEMO
[Expo] Eject 없이 네이버 아이디 로그인(네아로) 적용하기 본문
Expo를 이용해서 애플리케이션 개발을 진행 중에 네이버 아이디로 로그인(네아로)을 적용해야 했습니다.
React Native App 개발을 처음 하는거라서 더 간편한 Expo로 개발을 시작하였는데,
Expo Eject 를 시킨다면 Expo로 시작한 장점이 사라질 것 같아서 Eject 없이 네아로 를 하는 방법을 생각해 보았습니다.
결론적으로, 저는 WebView 를 이용해서 Eject 없이 네아로를 구현하였으며, 일반적인 구현 방법보다는 프로세스가 어색할 수 있습니다.
0. 개발 환경
Expo (React Native) 를 이용한 App 개발 환경이며, 서버는 Spring Boot를 사용하고 있습니다.
1 . 네이버 개발자 센터에 App 설정
네아로를 구현하기 위해서는 네이버 개발자 센터에 App 등록을 하고 네아로 Redirect URL 등을 설정해야 합니다.
우선 '내 애플리케이션' 메뉴에 들어가서 Application을 등록하면 Client ID , Client Secret 값을 얻을 수 있습니다. 이제 API 설정 Tab에 들어가서 네이버 아이디로 로그인 Callback URL 을 설정합니다.
2. 프로 세스
A. App에서 네이버 아이디로 로그인 버튼을 클릭
B. 서버에서 네이버 로그인 URL 생성 후 App으로 전달
C. App 에서 사용자의 네이버 아이디 로그인 화면을 Web View로 사용자에게 노출
D. 네이버 개발자센터에서 설정한 Callback URL 로 Response 값 전달
E-1. 네이버 로그인 성공 시 회원 데이터 저장 또는 업데이트 후 상태 값 전달
E-2. 네이버 로그인 실패 시 실패 데이터 상태 값 전달
F. Web View 에서 Login 완료 버튼을 클릭하면, 가지고 있는 네이버 아이디 로그인 상태 값을 App으로 전달
3. 개발 소스
- 로그인 버튼 화면 소스(React login.js)
import React, {Component} from 'react';
import { View, Text, Platform, Image, ScrollView, WebView } from 'react-native'
import Button from '../../components/Button'
import Style from '../../assets/scss/myPage';
import { StackActions, NavigationActions } from 'react-navigation';
export default class SettingScreen extends Component{
static navigationOptions = {
title: 'login',
};
naverLogin = async() => {
const uri = await this.props.getNaverUri();
this.props.navigation.push('WebLogin',{
uri : uri,
login:this.insertLogin,
loginFail:this.loginFail,
})
}
insertLogin = () => {
console.log("SettingScreen insertLogin")
}
loginFail = () => {
console.log("SettingScreen loginFail")
this.props.navigation.goBack();
alert("로그인에 실패하였습니다. 잠시 후 다시 시도해주세요.");
}
render() {
const { isLogin } = this.props.login;
const { characterList, bookMarkList } = this.props.myPage;
return (
<View>
<Button title={"Facebook Login"} onPress={this.facebookLogin}/>
<Button title={"Naver Login"} onPress={this.naverLogin}/>
</View>
);
}
}
- Naver Login Url 생성(Spring Boot)
public ResponseEntity<String> getNaverURL(String auth_type) {
ResponseEntity<String> result = null;
try{
// CSRF 방지를 위한 상태 토큰 생성 코드
SecureRandom random = new SecureRandom();
// 상태 토큰으로 사용할 랜덤 문자열 생성
String state = new BigInteger(130, random).toString(32);
String callback = URLEncoder.encode(NAVER_CALLBACK_URL, "UTF-8");
String url = "https://nid.naver.com/oauth2.0/authorize?client_id="+NAVER_CLIENT_ID+"&response_type=code&redirect_uri="+callback+"&state="+state;
if(auth_type!=null){
url+= "&auth_type="+auth_type;
url+="&response_type=code";
}
result = new ResponseEntity<>(url, HttpStatus.OK);
}catch (Exception e){
e.printStackTrace();
result = new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
return result;
}
- Callback URL 로 들어온 네이버 아이디로 로그인 Response 값 확인 및 로그인 처리 URL(Spring Boot)
public ResponseEntity<Object> naverLogin(String code, String state, String grant_type) {
ResponseEntity<Object> entity = null;
String access_token = "Bearer ";
String refresh_token = "";
try{
Map<String,Object> getToken = getNaverToken(code, state, grant_type, null);
if((int)getToken.get("responseCode") ==200) {
//접근 토큰 발급 요청 성공
Map<String,String> token = new ObjectMapper().readValue(getToken.get("response").toString(), HashMap.class);
logger.info("token = "+token.get("access_token"));
access_token += token.get("access_token");
String profileURL = "https://openapi.naver.com/v1/nid/me";
URL profile = new URL(profileURL);
HttpURLConnection profile_con = (HttpURLConnection) profile.openConnection();
profile_con.setRequestMethod("POST");
profile_con.setRequestProperty("Authorization", access_token);
Map<String, Object> profileMap = apiService.br(profile_con);
Object profile_response = profileMap.get("response");
int profile_code = (int) profileMap.get("responseCode");
if(profile_code == 200){
//프로필 가져오기 성공
Map<String,Object> memberMap = new ObjectMapper().readValue(profile_response.toString(), HashMap.class);
Map<String,String> memberMapRes = (Map) memberMap.get("response");
if(memberMapRes.get("email") == null || memberMapRes.get("name") == null
|| memberMapRes.get("nickname")==null){
//email 은 필수 값이기 때문에 null 인경우 사용자가 네이버 프로필 권한에 제공하지 않음을 선택한것으로, 다시 동의를 구해야 함.
String url = getNaverURL("reprompt").getBody();
entity = new ResponseEntity<>(url, HttpStatus.NOT_ACCEPTABLE);
}else{
Member member = new Member();
member.setReg_type(NAVER);
member.setSns_id(memberMapRes.get("id"));
member.setEmail(memberMapRes.get("email"));
member.setNickName(memberMapRes.get("nickname"));
member.setName(memberMapRes.get("name"));
member.setProfileURL(memberMapRes.get("profile_image"));
member.setSns_accessToken(token.get("access_token"));
member.setSns_refreshToken(token.get("refresh_token"));
Member getMember = memberMapper.getMember(member);
if(getMember == null){
memberMapper.insertMember(member);
getMember = memberMapper.getMember(member);
}else{
member.setMember_no(getMember.getMember_no());
memberMapper.updateProfile(member);
memberMapper.updateSnsToken(member);
getMember = member;
}
HttpHeaders headers = updateToken(getMember.getMember_no());
entity = new ResponseEntity<>(getMember, headers, HttpStatus.OK);
}
}
}else{
entity = new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
}catch (Exception e){
e.printStackTrace();
entity = new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
return entity;
}
- 로그인 완료 화면(Spring Boot login.html)
<!DOCTYPE html>
<html xmlns:th="www.thymeleaf.org">
<head> <meta charset="UTF-8" />
<title></title>
</head>
<body style="flex:1;
justify-content: center;
vertical-align: middle;">
<div style="font-size: 50pt;justify-content: center;align-items: center;text-align: center;"
th:if="${result.statusCodeValue == 200}">로그인에 성공하였습니다.</div>
<div th:unless="${result.statusCodeValue == 406}">
<div style="font-size: 50pt;justify-content: center;align-items: center;text-align: center;" th:unless="${result.statusCodeValue == 200}">로그인에 실패하였습니다.</div>
<button style="margin: 10px auto;
display: flex;
flex-direction: row;
justify-content: space-between;
flex-wrap: wrap;
max-width: 320px;
font-size: 40pt;"
type="button" onclick="complete()">확인</button>
</div>
</body>
<script th:inline="javascript">
var result = [[${result}]];
window.onload = function(){
console.log("hi!!!!",result);
if(result.statusCodeValue === 406){
window.location.href=result.body;
}
};
function complete(){
console.log("complete result");
console.log(result);
window.postMessage(JSON.stringify(result),"*");
}
</script>
</html>
- WebView 화면 소스 (React Webview.js)
import React, {Component} from 'react';
import { View, Text, Platform, StyleSheet, Button, WebView } from 'react-native'
import { StackActions, NavigationActions } from 'react-navigation';
export default class WebLogin extends Component {
componentWillMount(){
this.setState({
url:this.props.navigation.getParam("uri")
});
}
webViewEnd = async(event) => {
const result = JSON.parse(event.nativeEvent.data);
console.log("result",result);
if(result.statusCodeValue === 200) {
//성공적 네이버 로그인/회원가입 완료
//login 정보 저장 하기!
const data = {
token:token,
nickName:nickName,
profile:profileURL
}
console.log("data = ",data);
await this.props.login(data);
const resetAction = StackActions.reset({
index: 0,
actions: [NavigationActions.navigate({ routeName: 'Setting' })],
});
this.props.navigation.dispatch(resetAction);
}else{
//실패..
this.props.navigation.goBack();
}
}
render(){
const uri = this.props.navigation.getParam("uri");
console.log("[WebLogin] uri = ",this.state.url);
return(
<View style={styles.container}>
<WebView
ref={ref=> (this.webview = ref)}
source={{uri:this.state.url}}
useWebKit={true}
onMessage={(event)=>this.webViewEnd(event)}
/>
</View>
)
}
}
const styles = StyleSheet.create({
container:{
flex:1,
}
})
'기타' 카테고리의 다른 글
[TDD] Test-Driven Development 테스트 주도 개발 (0) | 2020.06.23 |
---|---|
[Clean Code] 정리 (0) | 2020.06.01 |