diff --git a/pom.xml b/pom.xml index bdf6c4f5..c877eb56 100644 --- a/pom.xml +++ b/pom.xml @@ -125,22 +125,22 @@ egovframework.rte.fdl.crypto ${egovframework.rte.version} - - - org.apache.poi - poi - 5.0.0 - - - org.apache.poi - poi-ooxml - 3.9 - - - commons-httpclient - commons-httpclient - 3.1 - + + + org.apache.poi + poi + 5.0.0 + + + org.apache.poi + poi-ooxml + 3.9 + + + commons-httpclient + commons-httpclient + 3.1 + @@ -191,7 +191,29 @@ jackson-dataformat-xml 2.9.0 - + + + com.github.scribejava + scribejava-core + 2.8.1 + + + + com.googlecode.json-simple + json-simple + 1.1.1 + + + + org.apache.httpcomponents + httpcore + 4.4.6 + + + org.apache.httpcomponents + httpclient + 4.5.3 + com.google.zxing @@ -266,29 +288,29 @@ commons-dbcp 1.4 - - org.lazyluke - log4jdbc-remix - 0.2.7 - - - org.projectlombok - lombok - 1.18.20 - provided - - - - - com.google.code.gson - gson - 2.8.7 - - - org.json - json - 20201115 - + + org.lazyluke + log4jdbc-remix + 0.2.7 + + + org.projectlombok + lombok + 1.18.20 + provided + + + + + com.google.code.gson + gson + 2.8.7 + + + org.json + json + 20201115 + diff --git a/src/main/java/nlib/cmm/snslogin/GoogleOAuthRequest.java b/src/main/java/nlib/cmm/snslogin/GoogleOAuthRequest.java new file mode 100644 index 00000000..9e7d6eb2 --- /dev/null +++ b/src/main/java/nlib/cmm/snslogin/GoogleOAuthRequest.java @@ -0,0 +1,90 @@ +package nlib.cmm.snslogin; + +public class GoogleOAuthRequest { + private String redirectUri; + private String clientId; + private String clientSecret; + private String code; + private String responseType; + private String scope; + private String accessType; + private String grantType; + private String state; + private String includeGrantedScopes; + private String loginHint; + private String prompt; + + public String getRedirectUri() { + return redirectUri; + } + public void setRedirectUri(String redirectUri) { + this.redirectUri = redirectUri; + } + public String getClientId() { + return clientId; + } + public void setClientId(String clientId) { + this.clientId = clientId; + } + public String getClientSecret() { + return clientSecret; + } + public void setClientSecret(String clientSecret) { + this.clientSecret = clientSecret; + } + public String getCode() { + return code; + } + public void setCode(String code) { + this.code = code; + } + public String getResponseType() { + return responseType; + } + public void setResponseType(String responseType) { + this.responseType = responseType; + } + public String getScope() { + return scope; + } + public void setScope(String scope) { + this.scope = scope; + } + public String getAccessType() { + return accessType; + } + public void setAccessType(String accessType) { + this.accessType = accessType; + } + public String getGrantType() { + return grantType; + } + public void setGrantType(String grantType) { + this.grantType = grantType; + } + public String getState() { + return state; + } + public void setState(String state) { + this.state = state; + } + public String getIncludeGrantedScopes() { + return includeGrantedScopes; + } + public void setIncludeGrantedScopes(String includeGrantedScopes) { + this.includeGrantedScopes = includeGrantedScopes; + } + public String getLoginHint() { + return loginHint; + } + public void setLoginHint(String loginHint) { + this.loginHint = loginHint; + } + public String getPrompt() { + return prompt; + } + public void setPrompt(String prompt) { + this.prompt = prompt; + } + +} \ No newline at end of file diff --git a/src/main/java/nlib/cmm/snslogin/GoogleOAuthResponse.java b/src/main/java/nlib/cmm/snslogin/GoogleOAuthResponse.java new file mode 100644 index 00000000..6adb25b0 --- /dev/null +++ b/src/main/java/nlib/cmm/snslogin/GoogleOAuthResponse.java @@ -0,0 +1,49 @@ +package nlib.cmm.snslogin; + + +public class GoogleOAuthResponse { + private String accessToken; + private String expiresIn; + private String refreshToken; + private String scope; + private String tokenType; + private String idToken; + + public String getAccessToken() { + return accessToken; + } + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + public String getExpiresIn() { + return expiresIn; + } + public void setExpiresIn(String expiresIn) { + this.expiresIn = expiresIn; + } + public String getRefreshToken() { + return refreshToken; + } + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + public String getScope() { + return scope; + } + public void setScope(String scope) { + this.scope = scope; + } + public String getTokenType() { + return tokenType; + } + public void setTokenType(String tokenType) { + this.tokenType = tokenType; + } + public String getIdToken() { + return idToken; + } + public void setIdToken(String idToken) { + this.idToken = idToken; + } + +} \ No newline at end of file diff --git a/src/main/java/nlib/cmm/snslogin/KakaoController.java b/src/main/java/nlib/cmm/snslogin/KakaoController.java new file mode 100644 index 00000000..55732d1b --- /dev/null +++ b/src/main/java/nlib/cmm/snslogin/KakaoController.java @@ -0,0 +1,81 @@ +package nlib.cmm.snslogin; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.util.ArrayList; +import java.util.List; + +import javax.servlet.http.HttpSession; + +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.message.BasicNameValuePair; +import org.springframework.stereotype.Controller; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +@Controller +public class KakaoController { + + private final static String K_CLIENT_ID = "e74d25210853313dd1fead4c6c1f06ec"; + + public static String getAuthorizationUrl(HttpSession session,String K_REDIRECT_URI) { + String kakaoUrl = "https://kauth.kakao.com/oauth/authorize?" + "client_id=" + K_CLIENT_ID + "&redirect_uri=" + + K_REDIRECT_URI + "&response_type=code"; + return kakaoUrl; + } + + public static JsonNode getAccessToken(String autorize_code,String K_REDIRECT_URI) { + final String RequestUrl = "https://kauth.kakao.com/oauth/token"; + final List postParams = new ArrayList(); + postParams.add(new BasicNameValuePair("grant_type", "authorization_code")); + postParams.add(new BasicNameValuePair("client_id", K_CLIENT_ID)); // REST API KEY + postParams.add(new BasicNameValuePair("redirect_uri",K_REDIRECT_URI)); + // 리다이렉트 URI + postParams.add(new BasicNameValuePair("code",autorize_code)); // 로그인 과정중 얻은 code 값 + final HttpClient client = HttpClientBuilder.create().build(); + final HttpPost post = new HttpPost(RequestUrl); + JsonNode returnNode = null; + try { + post.setEntity(new UrlEncodedFormEntity(postParams)); + final HttpResponse response = client.execute(post); + // JSON 형태 반환값 처리 + ObjectMapper mapper = new ObjectMapper(); + returnNode = mapper.readTree(response.getEntity().getContent()); + } catch (UnsupportedEncodingException e){ + e.printStackTrace(); + } catch (ClientProtocolException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); } + finally { + // clear resources + } return returnNode; + } + + public static JsonNode getKakaoUserInfo(JsonNode accessToken) { + final String RequestUrl = "https://kapi.kakao.com/v2/user/me"; + final HttpClient client = HttpClientBuilder.create().build(); + final HttpPost post = new HttpPost(RequestUrl); + // add header + post.addHeader("Authorization", "Bearer " + accessToken); + JsonNode returnNode = null; + try { + final HttpResponse response = client.execute(post); + // JSON 형태 반환값 처리 + ObjectMapper mapper = new ObjectMapper(); + returnNode = mapper.readTree(response.getEntity().getContent()); + } catch (ClientProtocolException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } finally { + // clear resources + } return returnNode; + } +} \ No newline at end of file diff --git a/src/main/java/nlib/cmm/snslogin/NaverLoginApi.java b/src/main/java/nlib/cmm/snslogin/NaverLoginApi.java new file mode 100644 index 00000000..992e3d8e --- /dev/null +++ b/src/main/java/nlib/cmm/snslogin/NaverLoginApi.java @@ -0,0 +1,29 @@ +package nlib.cmm.snslogin; + +import com.github.scribejava.core.builder.api.DefaultApi20; + +public class NaverLoginApi extends DefaultApi20{ + + protected NaverLoginApi(){ + } + + private static class InstanceHolder{ + private static final NaverLoginApi INSTANCE = new NaverLoginApi(); + } + + + public static NaverLoginApi instance(){ + return InstanceHolder.INSTANCE; + } + + @Override + public String getAccessTokenEndpoint() { + return "https://nid.naver.com/oauth2.0/token?grant_type=authorization_code"; + } + + @Override + protected String getAuthorizationBaseUrl() { + return "https://nid.naver.com/oauth2.0/authorize"; + } + +} diff --git a/src/main/java/nlib/cmm/snslogin/NaverLoginBO.java b/src/main/java/nlib/cmm/snslogin/NaverLoginBO.java new file mode 100644 index 00000000..126ab0d9 --- /dev/null +++ b/src/main/java/nlib/cmm/snslogin/NaverLoginBO.java @@ -0,0 +1,102 @@ +package nlib.cmm.snslogin; + +import java.io.IOException; +import java.util.UUID; + +import javax.servlet.http.HttpSession; + +import org.springframework.util.StringUtils; + +import com.github.scribejava.core.builder.ServiceBuilder; +import com.github.scribejava.core.model.OAuth2AccessToken; +import com.github.scribejava.core.model.OAuthRequest; +import com.github.scribejava.core.model.Response; +import com.github.scribejava.core.model.Verb; +import com.github.scribejava.core.oauth.OAuth20Service; + +public class NaverLoginBO { + + /* 인증 요청문을 구성하는 파라미터 */ + //client_id: 애플리케이션 등록 후 발급받은 클라이언트 아이디 + //response_type: 인증 과정에 대한 구분값. code로 값이 고정돼 있습니다. + //redirect_uri: 네이버 로그인 인증의 결과를 전달받을 콜백 URL(URL 인코딩). 애플리케이션을 등록할 때 Callback URL에 설정한 정보입니다. + //state: 애플리케이션이 생성한 상태 토큰 + private final static String CLIENT_ID = "jefkoYhSfrQ3TtZz5mTp"; + private final static String CLIENT_SECRET = "E2qAGaLEaG"; + private static String REDIRECT_URI = ""; + private final static String SESSION_STATE = "oauth_state"; + /* 프로필 조회 API URL */ + private final static String PROFILE_API_URL = "https://openapi.naver.com/v1/nid/me"; + + public void setRedirect_url(String REDIRECT_URI) { + this.REDIRECT_URI = REDIRECT_URI; + } + /* 네이버 아이디로 인증 URL 생성 Method */ + public String getAuthorizationUrl(HttpSession session) { + + /* 세션 유효성 검증을 위하여 난수를 생성 */ + String state = generateRandomString(); + /* 생성한 난수 값을 session에 저장 */ + setSession(session,state); + System.out.println(getSession(session)); + + /* Scribe에서 제공하는 인증 URL 생성 기능을 이용하여 네아로 인증 URL 생성 */ + OAuth20Service oauthService = new ServiceBuilder() + .apiKey(CLIENT_ID) + .apiSecret(CLIENT_SECRET) + .callback(REDIRECT_URI) + .state(state) //앞서 생성한 난수값을 인증 URL생성시 사용함 + .build(NaverLoginApi.instance()); + + return oauthService.getAuthorizationUrl(); + } + /* 네이버아이디로 Callback 처리 및 AccessToken 획득 Method */ + public OAuth2AccessToken getAccessToken(HttpSession session, String code, String state) throws IOException{ + + /* Callback으로 전달받은 세선검증용 난수값과 세션에 저장되어있는 값이 일치하는지 확인 */ + String sessionState = getSession(session); + if(StringUtils.pathEquals(sessionState, state)){ + + OAuth20Service oauthService = new ServiceBuilder() + .apiKey(CLIENT_ID) + .apiSecret(CLIENT_SECRET) + .callback(REDIRECT_URI) + .state(state) + .build(NaverLoginApi.instance()); + + /* Scribe에서 제공하는 AccessToken 획득 기능으로 네아로 Access Token을 획득 */ + OAuth2AccessToken accessToken = oauthService.getAccessToken(code); + return accessToken; + } + return null; + } + + /* 세션 유효성 검증을 위한 난수 생성기 */ + private String generateRandomString() { + return UUID.randomUUID().toString(); + } + + /* http session에 데이터 저장 */ + private void setSession(HttpSession session,String state){ + session.setAttribute(SESSION_STATE, state); + } + + /* http session에서 데이터 가져오기 */ + private String getSession(HttpSession session){ + return (String) session.getAttribute(SESSION_STATE); + } + /* Access Token을 이용하여 네이버 사용자 프로필 API를 호출 */ + public String getUserProfile(OAuth2AccessToken oauthToken) throws IOException{ + + OAuth20Service oauthService =new ServiceBuilder() + .apiKey(CLIENT_ID) + .apiSecret(CLIENT_SECRET) + .callback(REDIRECT_URI).build(NaverLoginApi.instance()); + + OAuthRequest request = new OAuthRequest(Verb.GET, PROFILE_API_URL, oauthService); + oauthService.signRequest(oauthToken, request); + Response response = request.send(); + return response.getBody(); + } + +} diff --git a/src/main/java/nlib/security/SpringSecurityConfig.java b/src/main/java/nlib/security/SpringSecurityConfig.java index 9591c3e3..731b2acb 100644 --- a/src/main/java/nlib/security/SpringSecurityConfig.java +++ b/src/main/java/nlib/security/SpringSecurityConfig.java @@ -58,8 +58,6 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { .antMatchers("/inform/**") .antMatchers("/alert/**") .antMatchers("/code/**") - .antMatchers("/test/**") - .antMatchers("/login/**") ; } @@ -69,6 +67,8 @@ public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { .antMatchers("/login/login*.do").permitAll() .antMatchers("/login/logout*.do").permitAll() .antMatchers("/login/naver*.do").permitAll() + .antMatchers("/login/google*.do").permitAll() + .antMatchers("/login/kakao*.do").permitAll() .antMatchers("/member/*.do").permitAll() .antMatchers("/member/*.ajax").permitAll() .antMatchers("/userInfo/*.do").permitAll() diff --git a/src/main/java/nlib/user/web/LoginController.java b/src/main/java/nlib/user/web/LoginController.java index b4114df6..19dfa406 100644 --- a/src/main/java/nlib/user/web/LoginController.java +++ b/src/main/java/nlib/user/web/LoginController.java @@ -1,39 +1,66 @@ package nlib.user.web; +import java.io.IOException; +import java.net.URI; +import java.util.Map; + import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.security.web.savedrequest.HttpSessionRequestCache; import org.springframework.security.web.savedrequest.RequestCache; import org.springframework.security.web.savedrequest.SavedRequest; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.ui.ModelMap; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.util.UriComponentsBuilder; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.github.scribejava.core.model.OAuth2AccessToken; + +import nlib.cmm.snslogin.GoogleOAuthResponse; +import nlib.cmm.snslogin.KakaoController; +import nlib.cmm.snslogin.NaverLoginBO; import nlib.user.service.LoginService; import nlib.util.StringUtil; /** *
- * @Class Name : LoginController.java
+ * @Class Name : LoginController.java
  * 
- * @Description : 스프링 시큐리티를 이용한 로그인 처리 컨트롤러 
+ * @Description : 스프링 시큐리티를 이용한 로그인 처리 컨트롤러 
  * 
  * 
- * @프로젝트명: 지방문화원 통합자료관리시스템 구축사업 (2021)
+ * @프로젝트명: 지방문화원 통합자료관리시스템 구축사업 (2021)
  *
  * 
* - * @ ------------ -------- --------------------------- - * @ 수정일 수정자 수정내용 - * @ ------------ -------- --------------------------- - * @ 2021. 7. 9. KNKIM 최초 생성 + * @ ------------ -------- --------------------------- @ 수정일 수정자 수정내용 @ + * ------------ -------- --------------------------- @ 2021. 7. 9. KNKIM 최초 생성 * * * @author 이씨플라자 * DIGITALSHIP KNKIM @@ -42,16 +69,24 @@ import nlib.util.StringUtil; * */ @Controller -public class LoginController -{ +public class LoginController { private static final Logger log = LoggerFactory.getLogger(LoginController.class); - - + + /* NaverLoginBO */ + private NaverLoginBO naverLoginBO; + private String apiResult = null; + + @Autowired + private void setNaverLoginBO(NaverLoginBO naverLoginBO) { + this.naverLoginBO = naverLoginBO; + } + @Resource(name = "loginService") private LoginService loginService; - + + /** - * 로그인 정보 입력 화면을 출력한다. + * 로그인 정보 입력 화면을 출력한다. * * @param req * @param error @@ -60,25 +95,141 @@ public class LoginController * @return */ @RequestMapping("/login/loginForm.do") - public String loginForm( - HttpServletRequest req - , @RequestParam(required=false) String error - , @RequestParam(required=false) String logout - , ModelMap model) { - - if(error != null) { - model.addAttribute("error" , String.format("ID와 비밀번호를 확인하여 주시기 바랍니다. %s", error)); + public String loginForm(HttpServletRequest req, @RequestParam(required = false) String error, + @RequestParam(required = false) String logout, Model model, HttpSession session) { + if (error != null) { + model.addAttribute("error", String.format("ID와 비밀번호를 확인하여 주시기 바랍니다. %s", error)); } - if(logout != null) { - model.addAttribute("logout" , String.format("로그아웃하였습니다 %s", logout)); + if (logout != null) { + model.addAttribute("logout", String.format("로그아웃하였습니다 %s", logout)); } + // 네이버 로그인 URL 생성 + /* 네이버아이디로 인증 URL을 생성하기 위하여 naverLoginBO클래스의 getAuthorizationUrl메소드 호출 */ + naverLoginBO.setRedirect_url("http://nlib.nculture.org/nlib/login/naverCallback.do"); + String naverAuthUrl = naverLoginBO.getAuthorizationUrl(session); + // 구글 로그인 URL 생성 + String googleUrl = "https://accounts.google.com/o/oauth2/v2/auth?" + + "client_id=879126511006-jro7bld7b2epl3n5mkksp0p24k2inbpu.apps.googleusercontent.com" + + "&redirect_uri=http://nlib.nculture.org/nlib/login/googleCallback.do" + + "&response_type=code" + + "&scope=email%20profile%20openid" + + "&access_type=offline"; + + // 카카오 로그인 URL 생성 + String k_redirect_url="http://nlib.nculture.org/nlib/login/kakaoCallback.do"; + String kakaoUrl = KakaoController.getAuthorizationUrl(session,k_redirect_url); + + model.addAttribute("naverUrl", naverAuthUrl); + model.addAttribute("googleUrl", googleUrl); + model.addAttribute("kakaoUrl", kakaoUrl); return "nlib/login/loginForm"; } + // 네이버 로그인 성공시 callback호출 메소드 + @RequestMapping(value = "/login/naverCallback.do", method = { RequestMethod.GET, RequestMethod.POST }) + public String naverCallback(Model model, @RequestParam String code, @RequestParam String state, HttpSession session) + throws IOException, ParseException { + OAuth2AccessToken oauthToken; + oauthToken = naverLoginBO.getAccessToken(session, code, state); + // 로그인 사용자 정보를 읽어온다. + apiResult = naverLoginBO.getUserProfile(oauthToken); + JSONParser parser = new JSONParser(); + Object obj = parser.parse(apiResult); + JSONObject jsonObj = (JSONObject) obj; + JSONObject response_obj = (JSONObject) jsonObj.get("response"); + String email = (String) response_obj.get("email"); + model.addAttribute("result", apiResult); + + /* 네이버 로그인 성공 페이지 View 호출 */ + return "naverSuccess"; + } + + // 구글 로그인 콜백 + @RequestMapping(value = "/login/googleCallback.do") + public String googleCallback(@RequestParam(value = "code") String authCode, HttpSession session, Model model, + HttpServletRequest request) throws Exception { + + String code = request.getParameter("code"); + HttpHeaders headers = new HttpHeaders(); + RestTemplate restTemplate = new RestTemplate(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap parameters = new LinkedMultiValueMap<>(); + parameters.add("code", code); + parameters.add("client_id", "879126511006-jro7bld7b2epl3n5mkksp0p24k2inbpu.apps.googleusercontent.com"); + parameters.add("client_secret", "F6T-ZV9jibLhKtcugxPUtBlN"); + parameters.add("redirect_uri", "http://nlib.nculture.org/nlib/login/googleCallback.do"); + parameters.add("grant_type", "authorization_code"); + + HttpEntity> rest_request = new HttpEntity<>(parameters,headers); + + URI uri = URI.create("https://www.googleapis.com/oauth2/v4/token"); + + ResponseEntity resultEntity; + resultEntity = restTemplate.postForEntity(uri, rest_request, String.class); + // JSON 파싱을 위한 기본값 세팅 + // 요청시 파라미터는 스네이크 케이스로 세팅되므로 Object mapper에 미리 설정해준다. + ObjectMapper mapper = new ObjectMapper(); + mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + mapper.setSerializationInclusion(Include.NON_NULL); + // Token Request + GoogleOAuthResponse result = mapper.readValue(resultEntity.getBody(), new TypeReference() { + }); + + // ID Token만 추출 (사용자의 정보는 jwt로 인코딩 되어있다) + String jwtToken = result.getIdToken(); + String requestUrl = UriComponentsBuilder.fromHttpUrl("https://oauth2.googleapis.com/tokeninfo") + .queryParam("id_token", jwtToken).toUriString(); + + String resultJson = restTemplate.getForObject(requestUrl, String.class); + + Map userInfo = mapper.readValue(resultJson, new TypeReference>() { + }); + System.out.println(userInfo.get("email")); + model.addAttribute("token", result.getAccessToken()); + + return "redirect:/"; + } + @RequestMapping(value = "/login/kakaoCallback.do") + public ModelAndView kakaoCallback(@RequestParam("code") String code, HttpServletRequest request, HttpServletResponse response, HttpSession session) + throws Exception { + ModelAndView mav = new ModelAndView(); + // 결과값을 node에 담아줌 + String k_redirect_url="http://nlib.nculture.org/nlib/login/kakaoCallback.do"; + JsonNode node = KakaoController.getAccessToken(code,k_redirect_url); + // accessToken에 사용자의 로그인한 모든 정보가 들어있음 + JsonNode accessToken = node.get("access_token"); + // 사용자의 정보 + JsonNode userInfo = KakaoController.getKakaoUserInfo(accessToken); + String kemail = null; + String kname = null; + String kgender = null; + String kbirthday = null; + String kage = null; + String kimage = null; + // 유저정보 카카오에서 가져오기Get properties + JsonNode properties = userInfo.path("properties"); + JsonNode kakao_account = userInfo.path("kakao_account"); + kemail = kakao_account.path("email").asText(); + kname = properties.path("nickname").asText(); + kimage = properties.path("profile_image").asText(); + kgender = kakao_account.path("gender").asText(); + kbirthday = kakao_account.path("birthday").asText(); + kage = kakao_account.path("age_range").asText(); + session.setAttribute("kemail", kemail); + session.setAttribute("kname", kname); + session.setAttribute("kimage", kimage); + session.setAttribute("kgender", kgender); + session.setAttribute("kbirthday", kbirthday); + session.setAttribute("kage", kage); + mav.setViewName("main"); + return mav; + }// end kakaoLogin() + + /** - * 로그인을 처리한다. - * (스프링 시큐리티 로그인 수행) + * 로그인을 처리한다. (스프링 시큐리티 로그인 수행) * * @param req * @param res @@ -88,51 +239,35 @@ public class LoginController * @return */ @RequestMapping("/login/login.do") - public String login( - HttpServletRequest req - , HttpServletResponse res - , @RequestParam(required=false) String username - , @RequestParam(required=false) String password - , ModelMap model) { + public String login(HttpServletRequest req, HttpServletResponse res, + @RequestParam(required = false) String username, @RequestParam(required = false) String password, + ModelMap model) { String redirectUrl = loginService.login(req, username, password); - - // 정상적으로 로그인된 경우 - if(StringUtil.isEmpty(redirectUrl)) { - RequestCache cache = new HttpSessionRequestCache(); - SavedRequest savedRequest = cache.getRequest(req, res); - if(savedRequest == null) redirectUrl = "/"; - else redirectUrl = savedRequest.getRedirectUrl(); + + // 정상적으로 로그인된 경우 + if (StringUtil.isEmpty(redirectUrl)) { + RequestCache cache = new HttpSessionRequestCache(); + SavedRequest savedRequest = cache.getRequest(req, res); + if (savedRequest == null) + redirectUrl = "/"; + else + redirectUrl = savedRequest.getRedirectUrl(); } log.debug("login.do > RequestCache : url=" + redirectUrl); - - return "redirect:" + redirectUrl; + + return "redirect:" + redirectUrl; } - - /** - * 로그아웃 후의 처리를 담당한다. - * 스프링 시큐리티에서 로그아웃이 수행된 후, 호출된다. + * 로그아웃 후의 처리를 담당한다. 스프링 시큐리티에서 로그아웃이 수행된 후, 호출된다. * * @param req * @return */ @RequestMapping("/login/logout.do") - public String logout(HttpServletRequest req) { + public String logout(HttpServletRequest req) { return null; } - - /** - * 네이버 로그인 - * @param session - * @return - * @throws Exception - */ - @RequestMapping(value="/login/naverLoginResultPopup.do") - public String naverLoginResult(HttpSession session) throws Exception{ - - return "nlib/popup/naverLoginResultPopup"; - } } \ No newline at end of file diff --git a/src/main/java/nlib/user/web/MemberController.java b/src/main/java/nlib/user/web/MemberController.java index 05d68f1b..32498a1f 100644 --- a/src/main/java/nlib/user/web/MemberController.java +++ b/src/main/java/nlib/user/web/MemberController.java @@ -2,6 +2,7 @@ package nlib.user.web; import java.io.IOException; +import java.net.URI; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; @@ -10,6 +11,7 @@ import java.nio.file.Paths; import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Random; import javax.annotation.Resource; @@ -17,14 +19,40 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; import org.springframework.ui.ModelMap; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.servlet.ModelAndView; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; +import org.springframework.web.servlet.support.RequestContextUtils; +import org.springframework.web.util.UriComponentsBuilder; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.PropertyNamingStrategy; +import com.github.scribejava.core.model.OAuth2AccessToken; + +import nlib.cmm.snslogin.GoogleOAuthResponse; +import nlib.cmm.snslogin.KakaoController; +import nlib.cmm.snslogin.NaverLoginBO; import nlib.info.service.InformService; import nlib.restful.service.DataApiReqVO; import nlib.restful.service.DataApiResVO; @@ -53,6 +81,7 @@ import nlib.user.service.NlibLoginVO; * @version 1.0 * */ + @Controller public class MemberController { @Resource(name="memberService") @@ -61,6 +90,15 @@ public class MemberController { @Resource(name="informService") private InformService informService; + /* NaverLoginBO */ + private NaverLoginBO naverLoginBO; + private String apiResult = null; + + @Autowired + private void setNaverLoginBO(NaverLoginBO naverLoginBO) { + this.naverLoginBO = naverLoginBO; + } + /* 이메일 메시지 및 템플릿 정보 */ //템플릿 파일경로 @Value("#{properties['mailing.sender.membership.template']}") @@ -74,34 +112,12 @@ public class MemberController { @Value("#{properties['mailing.sender.membership.name']}") private String senderName; - public ModelMap selectMemberJoiningInfo(HttpServletRequest req) { - return null; - } - public ModelMap certificateMember(HttpServletRequest req) { return null; } - - public ModelMap insertMemberInfo(HttpServletRequest req) { - return null; - } - public ModelMap sendEmailForMemberJoining(HttpServletRequest req) { return null; } - - public ModelMap searchIdForm(HttpServletRequest req) { - return null; - } - - public ModelMap searchId(HttpServletRequest req) { - return null; - } - - public ModelMap initPassword(HttpServletRequest req) { - return null; - } - public ModelMap changePassword(HttpServletRequest req) { return null; } @@ -122,28 +138,46 @@ public class MemberController { return null; } - @RequestMapping(value="/member/naverSignResultPopup.do") - public String naverSignResult(HttpSession session) throws Exception{ - return "nlib/popup/naverSignResultPopup"; - } - @RequestMapping(value="/member/selectMemberJoiningInfo.do") public String selectMemberJoiningInfo(HttpServletRequest req,ModelMap model) { - DataApiReqVO reqVO = new DataApiReqVO(); - DataApiResVO resVO = new DataApiResVO(); - resVO = informService.selectTermsInfo(reqVO); - System.out.println(resVO.getInfo().get("terms")); - model.addAttribute("result",resVO); return "/nlib/member/selectMemberJoiningInfo"; } @RequestMapping(value="/member/snsCertForm.do") - public String snsCertForm(HttpSession session) throws Exception{ + public String snsCertForm(HttpSession session, Model model) throws Exception{ + // 네이버 로그인 URL 생성 + /* 네이버아이디로 인증 URL을 생성하기 위하여 naverLoginBO클래스의 getAuthorizationUrl메소드 호출 */ + naverLoginBO.setRedirect_url("http://nlib.nculture.org/nlib/member/naverCallback.do"); + String naverAuthUrl = naverLoginBO.getAuthorizationUrl(session); + + // 구글 로그인 URL 생성 + String googleUrl = "https://accounts.google.com/o/oauth2/v2/auth?" + + "client_id=879126511006-jro7bld7b2epl3n5mkksp0p24k2inbpu.apps.googleusercontent.com" + + "&redirect_uri=http://nlib.nculture.org/nlib/member/googleCallback.do" + + "&response_type=code" + + "&scope=email%20profile%20openid" + + "&access_type=offline"; + + // 카카오 로그인 URL 생성 + String k_redirect_url="http://nlib.nculture.org/nlib/member/kakaoCallback.do"; + String kakaoUrl = KakaoController.getAuthorizationUrl(session,k_redirect_url); + + model.addAttribute("naverUrl", naverAuthUrl); + model.addAttribute("googleUrl", googleUrl); + model.addAttribute("kakaoUrl", kakaoUrl); return "nlib/member/snsCertForm"; } @RequestMapping(value="/member/insertMemberInfoForm.do") - public String insertMemberInfoForm(NlibLoginVO vo,HttpSession session,ModelMap model ) throws Exception{ + public String insertMemberInfoForm(HttpServletRequest request,NlibLoginVO vo,HttpSession session,ModelMap model ) throws Exception{ + Map flashMap =RequestContextUtils.getInputFlashMap(request); + + if(flashMap != null) + { + vo.setId(String.valueOf(flashMap.get("email"))); + }else { + return "forward:/member/selectMemberJoiningInfo.do"; + } model.addAttribute("loginVO",vo); return "nlib/member/insertMemberInfoForm"; } @@ -286,4 +320,127 @@ public class MemberController { System.out.println(request.getAttribute("initPasswordPhoneNum")); return "nlib/member/initPassword"; } + + /** + * 회원가입 SNS 네이버인증 + * @param model + * @param code + * @param state + * @param session + * @return + * @throws IOException + * @throws ParseException + */ + @RequestMapping(value = "/member/naverCallback.do", method = { RequestMethod.GET, RequestMethod.POST }) + public String naverCallback(Model model, @RequestParam String code, @RequestParam String state, HttpSession session,RedirectAttributes rttr) + throws IOException, ParseException { + OAuth2AccessToken oauthToken; + oauthToken = naverLoginBO.getAccessToken(session, code, state); + // 로그인 사용자 정보를 읽어온다. + apiResult = naverLoginBO.getUserProfile(oauthToken); + JSONParser parser = new JSONParser(); + Object obj = parser.parse(apiResult); + JSONObject jsonObj = (JSONObject) obj; + JSONObject response_obj = (JSONObject) jsonObj.get("response"); + String email = (String) response_obj.get("email"); + rttr.addFlashAttribute("email",email); + /* 네이버 로그인 성공 페이지 View 호출 */ + return "redirect:/member/insertMemberInfoForm.do"; + } + + /** + * 회원가입 SNS 구글인증 + * @exception Exception + */ + @RequestMapping(value = "/member/googleCallback.do") + public String googleCallback(@RequestParam(value = "code") String authCode, HttpSession session, Model model, + HttpServletRequest request,RedirectAttributes rttr) throws Exception { + + String code = request.getParameter("code"); + HttpHeaders headers = new HttpHeaders(); + RestTemplate restTemplate = new RestTemplate(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + MultiValueMap parameters = new LinkedMultiValueMap<>(); + parameters.add("code", code); + parameters.add("client_id", "879126511006-jro7bld7b2epl3n5mkksp0p24k2inbpu.apps.googleusercontent.com"); + parameters.add("client_secret", "F6T-ZV9jibLhKtcugxPUtBlN"); + parameters.add("redirect_uri", "http://nlib.nculture.org/nlib/member/googleCallback.do"); + parameters.add("grant_type", "authorization_code"); + + HttpEntity> rest_request = new HttpEntity<>(parameters,headers); + + URI uri = URI.create("https://www.googleapis.com/oauth2/v4/token"); + + ResponseEntity resultEntity; + resultEntity = restTemplate.postForEntity(uri, rest_request, String.class); + // JSON 파싱을 위한 기본값 세팅 + // 요청시 파라미터는 스네이크 케이스로 세팅되므로 Object mapper에 미리 설정해준다. + ObjectMapper mapper = new ObjectMapper(); + mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE); + mapper.setSerializationInclusion(Include.NON_NULL); + // Token Request + GoogleOAuthResponse result = mapper.readValue(resultEntity.getBody(), new TypeReference() { + }); + + // ID Token만 추출 (사용자의 정보는 jwt로 인코딩 되어있다) + String jwtToken = result.getIdToken(); + String requestUrl = UriComponentsBuilder.fromHttpUrl("https://oauth2.googleapis.com/tokeninfo") + .queryParam("id_token", jwtToken).toUriString(); + + String resultJson = restTemplate.getForObject(requestUrl, String.class); + + Map userInfo = mapper.readValue(resultJson, new TypeReference>() { + }); + + rttr.addFlashAttribute("email",userInfo.get("email")); + return "redirect:/member/insertMemberInfoForm.do"; + } + + /** + * 회원가입 SNS 카카오인증 + * @param code + * @param request + * @param response + * @param session + * @return + * @throws Exception + */ + @RequestMapping(value = "/member/kakaoCallback.do") + public String kakaoCallback(@RequestParam("code") String code, HttpServletRequest request, HttpServletResponse response, HttpSession session + ,RedirectAttributes rttr) + throws Exception { + ModelAndView mav = new ModelAndView(); + // 결과값을 node에 담아줌 + String k_redirect_url="http://nlib.nculture.org/nlib/member/kakaoCallback.do"; + JsonNode node = KakaoController.getAccessToken(code,k_redirect_url); + // accessToken에 사용자의 로그인한 모든 정보가 들어있음 + JsonNode accessToken = node.get("access_token"); + // 사용자의 정보 + JsonNode userInfo = KakaoController.getKakaoUserInfo(accessToken); + String kemail = null; + String kname = null; + String kgender = null; + String kbirthday = null; + String kage = null; + String kimage = null; + // 유저정보 카카오에서 가져오기Get properties + JsonNode properties = userInfo.path("properties"); + JsonNode kakao_account = userInfo.path("kakao_account"); + kemail = kakao_account.path("email").asText(); + kname = properties.path("nickname").asText(); + kimage = properties.path("profile_image").asText(); + kgender = kakao_account.path("gender").asText(); + kbirthday = kakao_account.path("birthday").asText(); + kage = kakao_account.path("age_range").asText(); + session.setAttribute("kemail", kemail); + session.setAttribute("kname", kname); + session.setAttribute("kimage", kimage); + session.setAttribute("kgender", kgender); + session.setAttribute("kbirthday", kbirthday); + session.setAttribute("kage", kage); + mav.setViewName("main"); + rttr.addFlashAttribute("email",kemail); + return "redirect:/member/insertMemberInfoForm.do"; + }// end kakaoLogin() } \ No newline at end of file diff --git a/src/main/resources/egovframework/spring/context-common.xml b/src/main/resources/egovframework/spring/context-common.xml index f954b8e3..a20eb93d 100644 --- a/src/main/resources/egovframework/spring/context-common.xml +++ b/src/main/resources/egovframework/spring/context-common.xml @@ -65,7 +65,8 @@ - + + diff --git a/src/main/webapp/WEB-INF/jsp/nlib/login/loginForm.jsp b/src/main/webapp/WEB-INF/jsp/nlib/login/loginForm.jsp index 0c7222d9..5cfc396e 100644 --- a/src/main/webapp/WEB-INF/jsp/nlib/login/loginForm.jsp +++ b/src/main/webapp/WEB-INF/jsp/nlib/login/loginForm.jsp @@ -34,20 +34,11 @@ LoginTest - - - + - -
- - - - - - - - - - -
-
-
- -
- - - - - +
+ + + +
+ +
+
+ + +
\ No newline at end of file diff --git a/src/main/webapp/WEB-INF/jsp/nlib/member/snsCertForm.jsp b/src/main/webapp/WEB-INF/jsp/nlib/member/snsCertForm.jsp index b1d6a349..365d29b2 100644 --- a/src/main/webapp/WEB-INF/jsp/nlib/member/snsCertForm.jsp +++ b/src/main/webapp/WEB-INF/jsp/nlib/member/snsCertForm.jsp @@ -56,166 +56,17 @@ -LoginTest - - - - -SNS 인증
-
-
- - - - - -
- - - - - - -
- - - - - +SNS 인증 +
+ +
+ +
diff --git a/src/main/webapp/WEB-INF/jsp/nlib/popup/naverLoginResultPopup.jsp b/src/main/webapp/WEB-INF/jsp/nlib/popup/naverLoginResultPopup.jsp deleted file mode 100644 index cc7807ad..00000000 --- a/src/main/webapp/WEB-INF/jsp/nlib/popup/naverLoginResultPopup.jsp +++ /dev/null @@ -1,50 +0,0 @@ -<% -/** - *
- * @Class Name : naverLoginResultPopup.jsp
- * 
- * @Description : 로그인시 naver로그인 페이지
- * 
- * 
- * @프로젝트명: 지방문화원 통합자료관리시스템 구축사업 (2021)
- *
- * 
- * - * @ ------------ -------- --------------------------- - * @ 수정일 수정자 수정내용 - * @ ------------ -------- --------------------------- - * @ 2021. 7. 12. JSYOO 최초 생성 - * - * - * @author 이씨플라자 * DIGITALSHIP JSYOO - * @since 2021. 7. 12. - * @version 1.0 - * - */ - %> -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> - - - - - - - - diff --git a/src/main/webapp/WEB-INF/jsp/nlib/popup/naverSignResultPopup.jsp b/src/main/webapp/WEB-INF/jsp/nlib/popup/naverSignResultPopup.jsp deleted file mode 100644 index 9f009a7c..00000000 --- a/src/main/webapp/WEB-INF/jsp/nlib/popup/naverSignResultPopup.jsp +++ /dev/null @@ -1,56 +0,0 @@ -<% -/** - *
- * @Class Name : naverSignResultPopup.jsp
- * 
- * @Description : 회원가입시 naver로그인 페이지
- * 
- * 
- * @프로젝트명: 지방문화원 통합자료관리시스템 구축사업 (2021)
- *
- * 
- * - * @ ------------ -------- --------------------------- - * @ 수정일 수정자 수정내용 - * @ ------------ -------- --------------------------- - * @ 2021. 7. 12. JSYOO 최초 생성 - * - * - * @author 이씨플라자 * DIGITALSHIP JSYOO - * @since 2021. 7. 12. - * @version 1.0 - * - */ - %> -<%@ page language="java" contentType="text/html; charset=UTF-8" - pageEncoding="UTF-8"%> -<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> -<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt"%> - - - - - - - - diff --git a/src/main/webapp/images/nlib/sign-in/g-normal.png b/src/main/webapp/images/nlib/sign-in/g-normal.png deleted file mode 100644 index 79e972c7..00000000 Binary files a/src/main/webapp/images/nlib/sign-in/g-normal.png and /dev/null differ diff --git a/src/main/webapp/images/nlib/sns/google_login_btn.png b/src/main/webapp/images/nlib/sns/google_login_btn.png new file mode 100644 index 00000000..8db4cd57 Binary files /dev/null and b/src/main/webapp/images/nlib/sns/google_login_btn.png differ diff --git a/src/main/webapp/images/nlib/sns/kakao_login_btn.png b/src/main/webapp/images/nlib/sns/kakao_login_btn.png new file mode 100644 index 00000000..e2c7b035 Binary files /dev/null and b/src/main/webapp/images/nlib/sns/kakao_login_btn.png differ diff --git a/src/main/webapp/images/nlib/sns/naver_login_btn.png b/src/main/webapp/images/nlib/sns/naver_login_btn.png new file mode 100644 index 00000000..bcdfe7e9 Binary files /dev/null and b/src/main/webapp/images/nlib/sns/naver_login_btn.png differ