Compare commits

...

17 Commits

Author SHA1 Message Date
8fe6a32ede Merge branch 'main' of https://git.byeori.cloud/admin/ocr-java
pull merge
2026-03-25 23:20:05 +09:00
root
fee9b12395 로그인 구조 변경 2026-02-27 18:16:26 +09:00
root
5fd0c03560 userid 타입 변경 2026-02-27 11:14:31 +09:00
89d1acf5c9 구조 변경 2026-02-25 17:53:07 +09:00
6a2405a782 ds_store제거 2026-02-25 17:51:55 +09:00
root
d9211cc12b 일부 수정 2026-02-23 19:57:09 +09:00
d8af2f71c6 Merge branch 'main' of https://git.byeori.cloud/admin/ocr-java
merge
2026-02-18 15:19:59 +09:00
root
074d4c8d63 api서버로 이미지 전송 2026-02-04 16:12:52 +09:00
a92d31e371 보안 검토 2026-02-03 00:15:27 +09:00
root
e54c219b76 이미지 수신 2026-01-28 19:15:49 +09:00
8d9d75e372 사용자로그인여부 확인하는 서비스 2025-12-29 18:02:32 +09:00
root
692c2392fd 메인 보내기 2025-12-26 20:19:38 +09:00
root
7e40126da4 jwt start 2025-12-24 18:00:24 +09:00
cb7f468d95 사용자정보 가져오기 성공 2025-12-22 23:21:00 +09:00
root
b504ac0f93 사용자정보조회2 2025-12-22 12:58:51 +09:00
root
6a0bc5e38d 토큰받기 성공, 사용자정보조회 시작 2025-12-22 12:44:10 +09:00
root
d59df7a223 토큰받기까지 성공 2025-12-17 16:58:54 +09:00
16 changed files with 327 additions and 58 deletions

2
prd/.gitignore vendored
View File

@@ -34,4 +34,4 @@ out/
/.nb-gradle/
### VS Code ###
.vscode/
.vscode/*

0
prd/gradlew vendored Normal file → Executable file
View File

View File

@@ -0,0 +1,37 @@
package site.ocr.prd.components;
import java.security.Key;
import java.util.Date;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
@Component
public class JwtProvider {
// jwt용 secret key
private final Key key;
// jwt토큰 유지 시간 = 1시간
private final long expireMillies = 1000 * 60 * 60;
public JwtProvider(@Value("${jwt.secret}")String secrest) {
byte[] ketByte = Decoders.BASE64.decode(secrest);
this.key = Keys.hmacShaKeyFor(ketByte);
}
public String createJwtToken(String id) {
String result = Jwts.builder()
.setSubject(id)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expireMillies))
.signWith(key, SignatureAlgorithm.HS256)
.compact();
return result;
}
}

View File

@@ -1,4 +1,4 @@
package site.ocr.prd;
package site.ocr.prd.config;
import java.util.List;
@@ -22,12 +22,15 @@ public class SecurityConfig {
configuration.setAllowedOrigins(List.of("http://localhost:3000"));
configuration.setAllowedMethods(List.of("GET","POST", "PUT","DELETE","OPTION"));
configuration.setAllowedHeaders(List.of("*"));
configuration.setAllowCredentials(true);
return configuration;
};
cors.configurationSource(configurationSource);
})
.csrf(AbstractHttpConfigurer::disable); //대체 언제 이런 문법이 생겼냐;
.csrf(AbstractHttpConfigurer::disable) //대체 언제 이런 문법이 생겼냐;
.authorizeHttpRequests(auth -> auth
.anyRequest().permitAll()); //외부 요청은 필터가 거르는 형태였기때문에 허용 해줘야함
return http.build();
}
}
}

View File

@@ -1,7 +1,8 @@
package site.ocr.prd;
package site.ocr.prd.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.reactive.function.client.WebClient;
@Configuration
@@ -13,4 +14,8 @@ public class WebClientConfig {
.build();
}
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

View File

@@ -1,10 +1,12 @@
package site.ocr.prd;
package site.ocr.prd.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import site.ocr.prd.SessionLoggingInterceptor;
@Configuration
public class WebMVCConfig implements WebMvcConfigurer {

View File

@@ -1,18 +1,50 @@
package site.ocr.prd.contorllers;
import org.springframework.web.bind.annotation.RestController;
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.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
@RestController
public class ImgController {
private final RestTemplate restTemplate;
@Autowired
public ImgController(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
@PostMapping("img/get-ocr")
public String postGetImgOcr(@RequestBody String entity) {
//TODO: process POST request
@PostMapping("img/get-img")
public ResponseEntity<String> postGetImg(@RequestPart("image") MultipartFile image) {
System.out.println("image :: " + image.getResource().getFilename());
return entity;
//헤더 설정(이미지)
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
//바디에 이미지 파일 추가
MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();
body.add("image", image.getResource());
//요청 엔티티 생성
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);
//요청 전송
ResponseEntity<String> response =
restTemplate.postForEntity("http://127.0.0.1:9002/ocr",
requestEntity, String.class);
return ResponseEntity.ok(response.getBody());
}
}

View File

@@ -1,47 +1,124 @@
package site.ocr.prd.contorllers;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import jakarta.servlet.http.HttpServletRequest;
import site.ocr.prd.dto.LoginRequestDto;
import site.ocr.prd.dto.LoginResponseDto;
import site.ocr.prd.services.LoginService;
import java.util.Map;
import org.slf4j.Logger;
import java.util.HashMap;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseCookie;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import jakarta.servlet.http.HttpServletRequest;
import site.ocr.prd.dto.LoginReqDTO;
import site.ocr.prd.dto.LoginResDTO;
import site.ocr.prd.dto.UserInfoInqyReqDTO;
import site.ocr.prd.dto.UserInfoInqyResDTO;
import site.ocr.prd.dto.UserInfoResDTO;
import site.ocr.prd.services.LoginService;
import org.springframework.web.bind.annotation.RequestParam;
@RestController
@Controller
public class LoginController {
//service 선언
private LoginService loginService = new LoginService(WebClient.builder());
@GetMapping("login/oauth-kakao-authorize") //kakao에서 get으로 요청 보냄
public void kakaoLoginRequestDto(HttpServletRequest request) {
String accessToken = request.getParameter("code");
System.out.println(accessToken);
LoginRequestDto requestDTO = new LoginRequestDto();
requestDTO.setGrant_type("authorization_code");
requestDTO.setClient_id("a1d6afef2d4508a10a498b7069f67496");
requestDTO.setRedirect_uri("http://localhost:9001/login/oauth-kakao-authorize");
requestDTO.setCode(accessToken);
//Logger 선언
private static final Logger logger = LoggerFactory.getLogger(LoginController.class);
LoginResponseDto result = loginService.getToken(requestDTO);
System.out.println(result.toString());
//service 선언
private final LoginService loginService;
public LoginController(LoginService loginService) {
this.loginService = loginService;
}
/**
* 프론트에서 카카오로 로그인 요청 후 카카오에서 리다이렉트 해준 인가코드로 토큰 발급 및 사용자정보 조회
* @param redirectRespn 카카오에서 리다이렉트해준 인가코드
*/
@GetMapping("/oauth/oauth-kakao-authorize") //kakao에서 get으로 리다이렉트 해줌
public ResponseEntity<Map<String, String>> kakaoLoginRequest(HttpServletRequest redirectRespn) {
String code = redirectRespn.getParameter("code");
logger.info("인가코드 :: " + code);
/**
* 카카오에 토큰값 요청
* requestDTO
* @param grant_type
* @param client_id 카카오콘솔의 app key
* @param code 인가코드
*/
LoginReqDTO loginRequest = new LoginReqDTO();
loginRequest.setGrant_type("authorization_code");
loginRequest.setClient_id("a1d6afef2d4508a10a498b7069f67496");
loginRequest.setRedirect_uri("http://localhost:9001/oauth/oauth-kakao-authorize");
loginRequest.setCode(code);
/**
* 카카오로부터 받은 토큰값
* @param access_token 사용자정보 조회용 토큰
* @param expires_in 토큰 유효시간(초)
* @param refresh_token 사용자 리프레시 토큰
*/
LoginResDTO loginResult = loginService.getToken(loginRequest);
logger.info("토큰발급 결과 :: " + loginResult.toString());
UserInfoInqyReqDTO userInfoInqyRequest = new UserInfoInqyReqDTO();
userInfoInqyRequest.setAccess_token(loginResult.getAccess_token());
/**
* 사용자정보 조회
* @param access_token 사용자정보 조회용 토큰
*/
UserInfoInqyResDTO userInfoInqyResponse = loginService.getUserInfo(userInfoInqyRequest);
logger.info("사용자정보 :: " + userInfoInqyResponse.toString());
ResponseCookie cookie = loginService.createJwtCookie(userInfoInqyResponse.getId());
Map<String, String> response = new HashMap<>();
response.put("success", "true");
response.put("userId", userInfoInqyResponse.getId());
response.put("message", "Login successful");
response.put("userInfo", userInfoInqyResponse.getName());
return ResponseEntity.status(302)
.header(HttpHeaders.SET_COOKIE, cookie.toString())
.header(HttpHeaders.LOCATION, "http://localhost:3000/pages/oauth/callback")
.body(response);
}
@PostMapping("login/oauth-kakao-token")
public String kakaoLoginResponseDto(@RequestBody LoginResponseDto response) {
/**
* JWT 토큰 조회 (프론트에서 로그인 상태 확인용)
* @param userId 사용자 ID
* @return 로그인 결과 및 JWT 토큰
*/
@GetMapping("/oauth/get-jwt-token")
public ResponseEntity<Map<String, String>> getJwtToken(@RequestParam String userId) {
ResponseCookie cookie = loginService.createJwtCookie(userId);
Map<String, String> response = new java.util.HashMap<>();
response.put("token", cookie.toString());
return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, cookie.toString())
.body(response);
}
@GetMapping("login/oauth-kakao-token")
public String kakaoLoginResponseDto(@RequestBody LoginResDTO response) {
//TODO: process POST request
System.out.println("response :: ");
System.out.println(response.getAccess_token());
return response.getAccess_token();
}
@GetMapping("login/get-user-info")
public ResponseEntity<UserInfoResDTO> getUserInfo(@RequestParam HttpServletRequest request) {
UserInfoResDTO result = new UserInfoResDTO();
return ResponseEntity.status(200).build();
}
}

View File

@@ -9,13 +9,17 @@ import lombok.ToString;
@ToString
@Getter
@Setter
public class LoginRequestDto {
public class LoginReqDTO {
@JsonProperty("grant_type")
String grant_type; //authorization_code
@JsonProperty("client_id")
String client_id; // REST API
@JsonProperty("redirect_uri")
String redirect_uri; //인가코드가 리다이렉트된 uri
@JsonProperty("code")
String code; //인가코드 요청으로 얻은 인가코드
}

View File

@@ -9,19 +9,25 @@ import lombok.ToString;
@ToString
@Getter
@Setter
public class LoginResponseDto {
public class LoginResDTO {
@JsonProperty("token_type")
String token_type; //토큰타입, bearer 고정
@JsonProperty("access_token")
String access_token; //사용자 엑세스 토큰
@JsonProperty("id_token")
String id_token; //ID 토큰값, openID connect가 활성화된 경우만 발급
@JsonProperty("expires_in")
Integer expires_in; //엑세스토큰과 id토큰의 만료시간())
@JsonProperty("refresh_token")
String refresh_token; //사용자 리프레시 토큰
@JsonProperty("refresh_token_expires_in")
Integer refresh_token_expires_in; //리프레시 토큰 만료 시간()
String scope; //인증된 사용자의 정보조회 범위, 여러개일 경우 공백으로 구분
}

View File

@@ -0,0 +1,16 @@
package site.ocr.prd.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Getter
@Setter
public class UserInfoInqyReqDTO {
@JsonProperty("access_token")
String access_token;
}

View File

@@ -0,0 +1,17 @@
package site.ocr.prd.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Getter
@Setter
public class UserInfoInqyResDTO {
String id;
String name;
String email;
String age_range;
String birthday;
String gender;
}

View File

@@ -0,0 +1,13 @@
package site.ocr.prd.dto;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@ToString
@Getter
@Setter
public class UserInfoResDTO {
String userId;
String userName;
}

View File

@@ -1,39 +1,93 @@
package site.ocr.prd.services;
import java.time.Duration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseCookie;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.http.HttpHeaders;
import site.ocr.prd.dto.LoginRequestDto;
import site.ocr.prd.dto.LoginResponseDto;
import com.fasterxml.jackson.databind.JsonNode;
import site.ocr.prd.components.JwtProvider;
import site.ocr.prd.dto.LoginReqDTO;
import site.ocr.prd.dto.LoginResDTO;
import site.ocr.prd.dto.UserInfoInqyReqDTO;
import site.ocr.prd.dto.UserInfoInqyResDTO;
@Service
public class LoginService {
private final WebClient webClient;
//logger 선언
private static final Logger logger = LoggerFactory.getLogger(LoginService.class);
public LoginService(WebClient.Builder builder) {
//webclient builder
private final WebClient webClient;
//JWT provider
private final JwtProvider jwtProvider;
public LoginService(WebClient.Builder builder, JwtProvider jwtProvider) {
this.webClient = builder.build();
this.jwtProvider = jwtProvider;
}
public LoginResponseDto getToken(LoginRequestDto request) {
LoginResponseDto result = webClient.post()
public LoginResDTO getToken(LoginReqDTO request) {
logger.info("kakao auth code :: " + request.getCode());
LoginResDTO result = webClient.post()
.uri("https://kauth.kakao.com/oauth/token")
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=utf-8")
.body(BodyInserters.fromFormData("grant_type", "authorization_code")
.with("client_id", "a1d6afef2d4508a10a498b7069f67496")
.with("redirect_uri", "http://localhost:9001/login/oauth-kakao-authorize")
.with("redirect_uri", "http://localhost:9001/oauth/oauth-kakao-authorize")
.with("code", request.getCode()))
.retrieve()
.bodyToMono(LoginResponseDto.class)
//.bodyToMono(String.class)
.bodyToMono(LoginResDTO.class)
.block();
//System.out.println("요청 ::: " + request.toString());
//System.out.println("성공여부 ::: " + "result");
return result;
}
public UserInfoInqyResDTO getUserInfo(UserInfoInqyReqDTO request) {
// 응답 DTO
UserInfoInqyResDTO result = new UserInfoInqyResDTO();
JsonNode root = webClient.get()
.uri("https://kapi.kakao.com/v2/user/me")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + request.getAccess_token())
.retrieve()
.bodyToMono(JsonNode.class)
.block();
//사용자ID
//jsonnode :: {"id":4438121341,"connected_at":"2025-09-09T03:53:23Z"}
logger.info("jsonnode :: " + root.toString());
String id = root.path("id").asText();
String name = root.path("name").asText();
String email = root.path("email").asText();
result.setId(id);
result.setName(name);
result.setEmail(email);
return result;
}
/**
* JWT 토큰을 생성하고 ResponseCookie로 변환
* @param userId 사용자 ID
* @return ResponseCookie JWT 토큰이 포함된 쿠키
*/
public ResponseCookie createJwtCookie(String userId) {
String jwt = jwtProvider.createJwtToken(userId);
return ResponseCookie.from("accessToken", jwt)
.httpOnly(true)
.secure(true)
.sameSite("Lax")
.maxAge(Duration.ofHours(1))
.build();
}
}

View File

@@ -7,4 +7,5 @@ server:
port: 9001
logging:
level:
root: debug
root: debug
org.springframework.security: DEBUG

View File

@@ -2,4 +2,6 @@
spring:
profiles:
active:
- DEV
- DEV
jwt:
secret: tOrtq6iI5i2Zjs83qRVzdhyd3D4WXjmcsNZ9Gljhr+dz7cUtLWlkD9shYdpgALCdXplEGJDJoyBeTCfY5Fwb3Q==