OAuthController.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. package com.icontrols.oauth.controller;
  2. import java.io.IOException;
  3. import java.io.UnsupportedEncodingException;
  4. import java.security.GeneralSecurityException;
  5. import java.security.NoSuchAlgorithmException;
  6. import java.time.Duration;
  7. import java.util.Base64;
  8. import java.util.Base64.Encoder;
  9. import java.util.List;
  10. import java.util.Random;
  11. import javax.servlet.http.HttpServletResponse;
  12. import javax.servlet.http.HttpSession;
  13. import org.slf4j.Logger;
  14. import org.slf4j.LoggerFactory;
  15. import org.springframework.beans.factory.annotation.Autowired;
  16. import org.springframework.data.redis.core.RedisTemplate;
  17. import org.springframework.data.redis.core.ValueOperations;
  18. import org.springframework.http.HttpEntity;
  19. import org.springframework.http.HttpHeaders;
  20. import org.springframework.http.HttpStatus;
  21. import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
  22. import org.springframework.ui.Model;
  23. import org.springframework.web.bind.annotation.RequestHeader;
  24. import org.springframework.web.bind.annotation.RequestMapping;
  25. import org.springframework.web.bind.annotation.RequestMethod;
  26. import org.springframework.web.bind.annotation.RequestParam;
  27. import org.springframework.web.bind.annotation.ResponseBody;
  28. import org.springframework.web.bind.annotation.RestController;
  29. import org.springframework.web.client.RestTemplate;
  30. import org.springframework.web.server.ResponseStatusException;
  31. import org.springframework.web.servlet.ModelAndView;
  32. import org.springframework.web.servlet.view.RedirectView;
  33. import com.icontrols.oauth.constants.ERROR;
  34. import com.icontrols.oauth.model.ClientInfo;
  35. import com.icontrols.oauth.model.ComplexInfo;
  36. import com.icontrols.oauth.model.JWTInfo;
  37. import com.icontrols.oauth.model.Token;
  38. import com.icontrols.oauth.model.WallpadCode;
  39. import com.icontrols.oauth.repo.ClientInfoRepository;
  40. import com.icontrols.oauth.repo.ComplexInfoRepository;
  41. import com.icontrols.oauth.utils.AES256Util;
  42. import com.icontrols.oauth.utils.JWTUtils;
  43. @RestController
  44. @RequestMapping(value = "/api/oauth2")
  45. public class OAuthController {
  46. private static final Logger logger = LoggerFactory.getLogger(OAuthController.class);
  47. @Autowired
  48. ClientInfoRepository clientInfoRepo;
  49. @Autowired
  50. ComplexInfoRepository complexInfoRepo;
  51. @Autowired
  52. RedisTemplate<String, Object> redisTemplate;
  53. // http://127.0.0.1:8080/api/oauth2/authorize?client_id=clientid&redirect_uri=http://127.0.0.1:8080/api/oauth2/redirect&scope=scope&response_type=code&state=state
  54. @RequestMapping(value = "/authorize", method = RequestMethod.GET)
  55. public ModelAndView Authorize(@RequestHeader HttpHeaders headers,
  56. @RequestParam(value = "response_type", required = true) String responseType,
  57. @RequestParam(value = "client_id", required = true) String clientId,
  58. @RequestParam(value = "state", required = true) String state, @RequestParam(value = "scope") String scope,
  59. @RequestParam(value = "redirect_uri", required = true) String redirectUri, HttpSession httpSession,
  60. Model model) {
  61. logger.info("**************************************************************");
  62. logger.info("**************************************************************");
  63. logger.info("[STEP #1] /api/oauth2/authorize");
  64. logger.info("**************************************************************");
  65. logger.info("**************************************************************");
  66. logger.info("*[parameters]");
  67. logger.info("*1. response_type: " + responseType);
  68. logger.info("*2. client_id: " + clientId);
  69. logger.info("*3. state: " + state);
  70. logger.info("*4. scope: " + scope);
  71. logger.info("*5. redirect_uri: " + redirectUri);
  72. ModelAndView mav = new ModelAndView();
  73. ClientInfo clientInfo = clientInfoRepo.findByClientId(clientId);
  74. if (clientInfo == null) {
  75. throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, ERROR.INVALID_CLIENT);
  76. } else {
  77. logger.info("*[clientinfo]");
  78. logger.info("*" + clientInfo.toString());
  79. if (!responseType.equals("code")) {
  80. throw new ResponseStatusException(HttpStatus.BAD_REQUEST, ERROR.UNSUPPORTED_GRANT_TYPE);
  81. } else {
  82. httpSession.setAttribute("redirectUri", redirectUri);
  83. httpSession.setAttribute("state", state);
  84. // TODO scope != null -> httpSession.setAttribute("scope", scope);
  85. List<ComplexInfo> complexInfos = complexInfoRepo.findAll();
  86. model.addAttribute("complexInfos", complexInfos);
  87. // \src\main\webapp\WEB-INF\jsp\InsertUserInfo.jsp
  88. mav.setViewName("InsertUserInfo");
  89. }
  90. }
  91. return mav;
  92. }
  93. @RequestMapping(value = "/user/info/submit", method = RequestMethod.GET)
  94. public ModelAndView sendCode(HttpSession httpSession, HttpServletResponse httpResponse,
  95. @RequestParam(value = "complex", required = true) String complex,
  96. @RequestParam(value = "dong", required = true) String dong,
  97. @RequestParam(value = "ho", required = true) String ho)
  98. throws IOException, NoSuchAlgorithmException, GeneralSecurityException {
  99. logger.info("**************************************************************");
  100. logger.info("**************************************************************");
  101. logger.info("[STEP #2] /api/oauth2/user/submit");
  102. logger.info("**************************************************************");
  103. logger.info("**************************************************************");
  104. logger.info("*[parameters]");
  105. logger.info("*1. complex: " + complex);
  106. logger.info("*2. dong: " + dong);
  107. logger.info("*3. ho: " + ho);
  108. // TODO 단지서버로 인증번호 전송해야함.
  109. // wallpad 코드 생성
  110. Random generator = new Random();
  111. String wallpadCode = "";
  112. for (int i = 0; i < 6; i++) {
  113. wallpadCode += Integer.toString(generator.nextInt(10));
  114. }
  115. logger.info("*[wallpadCode]");
  116. logger.info("*code: " + wallpadCode);
  117. // 세대정보 인코딩
  118. String homeInfo = complex + "/" + dong + "/" + ho;
  119. // db에 저장
  120. ValueOperations<String, Object> vop = redisTemplate.opsForValue();
  121. Duration d = Duration.ofSeconds(180);
  122. while (!vop.setIfAbsent(wallpadCode, homeInfo, d)) {
  123. wallpadCode = "";
  124. for (int i = 0; i < 6; i++) {
  125. wallpadCode += Integer.toString(generator.nextInt(10));
  126. }
  127. }
  128. logger.info("*[redis]");
  129. logger.info("*(key,value): (" + wallpadCode + "," + homeInfo + ")");
  130. // 단지서버로 월패드인증번호 전송
  131. WallpadCode body = new WallpadCode(dong, ho, wallpadCode);
  132. logger.info(body.toString());
  133. String url = "http://" + complex + ":8002/kakao/auth";
  134. logger.info(url);
  135. // TODO 예외처리 단지통신
  136. HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
  137. factory.setReadTimeout(3000);
  138. factory.setConnectTimeout(3000);
  139. RestTemplate template = new RestTemplate(factory);
  140. HttpEntity<Object> entity = new HttpEntity<Object>(body);
  141. try {
  142. String answer = template.postForObject(url, entity, String.class);
  143. logger.info(answer);
  144. } catch (Exception e) {
  145. new ResponseStatusException(HttpStatus.BAD_REQUEST, ERROR.INVALID_HOMEINFO);
  146. }
  147. ModelAndView mav = new ModelAndView();
  148. mav.setViewName("InsertCode");
  149. return mav;
  150. }
  151. // InsertCode.jsp에서 월패드 인증번호를 입력받아 ajax로 실행하는 부분,
  152. // 사용자가 입력한 인증번호의 유효성을 검증함.
  153. @RequestMapping(value = "/validate", method = RequestMethod.GET)
  154. public @ResponseBody Boolean validate(HttpSession httpSession,
  155. @RequestParam(value = "code", required = true) String wallpadCode) {
  156. logger.info("**************************************************************");
  157. logger.info("**************************************************************");
  158. logger.info("[STEP #3] /api/oauth2/auth");
  159. logger.info("**************************************************************");
  160. logger.info("**************************************************************");
  161. logger.info("*[parameters]");
  162. logger.info("*1. Input code: " + wallpadCode);
  163. ValueOperations<String, Object> vop = redisTemplate.opsForValue();
  164. if (vop.get(wallpadCode) == null) { // 없으면 null
  165. return false;
  166. }
  167. httpSession.setAttribute("homeId", vop.get(wallpadCode));
  168. return true;
  169. }
  170. // 월패드 인증이 실행된 경우, 인증 코드를 발급해 리다이렉트하는 부분
  171. @RequestMapping(value = "/code/generate", method = RequestMethod.GET)
  172. public RedirectView validateCode(HttpSession httpSession) {
  173. logger.info("**************************************************************");
  174. logger.info("**************************************************************");
  175. logger.info("[STEP #4] /code/generate");
  176. logger.info("**************************************************************");
  177. logger.info("**************************************************************");
  178. logger.info("*[parameters]");
  179. logger.info("*1. home id: " + httpSession.getAttribute("homeId"));
  180. String code = "";
  181. String homeInfo = "";
  182. ValueOperations<String, Object> vop = redisTemplate.opsForValue();
  183. // 코드 발급
  184. homeInfo = httpSession.getAttribute("homeId").toString();
  185. logger.info("*[home info]");
  186. logger.info("*1. homeInfo: " + homeInfo);
  187. code = "";
  188. Random rnd = new Random();
  189. for (int i = 0; i < 10; i++) {
  190. code += String.valueOf((char) ((int) (rnd.nextInt(26)) + 65));
  191. }
  192. logger.info("*[code]");
  193. logger.info("*1. code: " + code);
  194. Duration d = Duration.ofSeconds(180);
  195. while (!vop.setIfAbsent(code, homeInfo, d)) {
  196. code = "";
  197. for (int i = 0; i < 10; i++) {
  198. code += String.valueOf((char) ((int) (rnd.nextInt(26)) + 65));
  199. }
  200. }
  201. String redirectUri = (String) httpSession.getAttribute("redirectUri");
  202. redirectUri += "?state=" + httpSession.getAttribute("state");
  203. redirectUri += "&code=" + code;
  204. RedirectView redirectView = new RedirectView();
  205. redirectView.setUrl(redirectUri);
  206. return redirectView;
  207. }
  208. // 토큰 발급하는 부분
  209. @RequestMapping(value = "/token", method = RequestMethod.POST, produces = "application/json")
  210. public @ResponseBody Token Token(@RequestParam(value = "grant_type", required = true) String grantType,
  211. @RequestParam(value = "code", required = false) String code,
  212. @RequestParam(value = "refresh_token", required = false) String refreshToken, HttpSession httpSession,
  213. @RequestHeader HttpHeaders headers)
  214. throws NoSuchAlgorithmException, UnsupportedEncodingException, GeneralSecurityException {
  215. logger.info("[STEP #5] /api/oauth2/token");
  216. // TODO Code유효성 검사
  217. // TODO token 발급
  218. logger.info(code);
  219. logger.info(grantType);
  220. ValueOperations<String, Object> vop = redisTemplate.opsForValue();
  221. String newAccessToken = ""; // 생성해야 함.
  222. String newRefreshToken = ""; // 생성해야 함.
  223. Token token = new Token();
  224. if (grantType.equals("authorization_code")) {
  225. // TODO 코드 검사
  226. if (vop.get(code) == null) { // 인증코드가 없는 경우
  227. // 에러페이지 리턴
  228. throw new ResponseStatusException(HttpStatus.BAD_REQUEST, ERROR.INVALID_GRANT);
  229. } else {
  230. AES256Util aes = new AES256Util();
  231. String homeInfo = vop.get(code).toString();
  232. String jwt_homeInfo = homeInfo.split("/")[1] + "/" + homeInfo.split("/")[2];
  233. String encodedHomeInfo = aes.encrypt(jwt_homeInfo);
  234. logger.info("encodedHomeInfo =" + encodedHomeInfo);
  235. logger.info("encoded = " + encodedHomeInfo);
  236. String complexIp = homeInfo.split("/")[0];
  237. Encoder encoder = Base64.getEncoder();
  238. complexIp = new String(encoder.encode(complexIp.getBytes()));
  239. JWTInfo jwtinfo = new JWTInfo(complexIp, encodedHomeInfo);
  240. newAccessToken = JWTUtils.generateToken(jwtinfo, "accessToken");
  241. newRefreshToken = JWTUtils.generateToken(jwtinfo, "refreshToken");
  242. }
  243. } else if (grantType.equals("refresh_token")) {
  244. if (refreshToken != null) {
  245. Boolean bool = JWTUtils.validateToken(refreshToken, "refreshToken");
  246. if (bool) {
  247. // TODO token의 payload에서 compelxCd, homeId 가져오고 새로 토큰 생성한다
  248. if (headers.get("Authorization").get(0) == null)
  249. throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, ERROR.INVALID_CLIENT);
  250. if (!validateHeaderAuth(headers.get("Authorization").get(0)))
  251. throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, ERROR.INVALID_CLIENT);
  252. JWTInfo jwtinfo = JWTUtils.getJWTInfoFromToken(refreshToken);
  253. newAccessToken = JWTUtils.generateToken(jwtinfo, "accessToken");
  254. newRefreshToken = JWTUtils.generateToken(jwtinfo, "refreshToken");
  255. } else {
  256. throw new ResponseStatusException(HttpStatus.BAD_REQUEST, ERROR.INVALID_REFRESHTOKEN);
  257. }
  258. }
  259. } else {
  260. throw new ResponseStatusException(HttpStatus.BAD_REQUEST);
  261. }
  262. token.setAccess_token(newAccessToken);
  263. token.setRefresh_token(newRefreshToken);
  264. // class 만들어서 사용하는 것으로 변경해야함.
  265. logger.info(token.toString());
  266. return token;
  267. }
  268. public boolean validateHeaderAuth(String authorization) {
  269. String splitStr[] = authorization.split(" ");
  270. // splitStr length 가 2가 아니면 유효하지 않은 authorization 값
  271. if (splitStr.length != 2)
  272. return false;
  273. // splitStr이 Basic이 아니어도 에러
  274. if (!splitStr[0].equals("Basic"))
  275. return false;
  276. // Base64 디코드
  277. String decodeStr = new String(Base64.getDecoder().decode(splitStr[1]));
  278. splitStr = decodeStr.split(":");
  279. // splitStr length 가 2가 아니면 유효하지 않은 authorization 값
  280. if (splitStr.length != 2)
  281. return false;
  282. ClientInfo clientInfo = clientInfoRepo.findByClientId(splitStr[0]);
  283. if (clientInfo == null)
  284. return false;
  285. if (!clientInfo.getClientSecret().equals(splitStr[1]))
  286. return false;
  287. return true;
  288. }
  289. // ((Test)) Redirect Destination
  290. // 여기는 리다이렉트 받는 URl임 테스트용. Client를 대신함.
  291. @RequestMapping(value = "/redirect", method = RequestMethod.GET)
  292. public @ResponseBody String RedirectTester(HttpSession httpSession,
  293. @RequestParam(value = "code", required = true) String code) {
  294. logger.info("/api/oauth2/redirect");
  295. String redirectUri = "http://61.33.215.56:8003/api/oauth2/token?grant_type=authorization_code&code=" + code;
  296. RedirectView redirectView = new RedirectView();
  297. redirectView.setUrl(redirectUri);
  298. return code;
  299. }
  300. }