Skip to content
Snippets Groups Projects
Commit 0b638ed2 authored by 천 진강's avatar 천 진강
Browse files

Merge branch 'feat/auth' into 'dev'

Feat/auth : 사용자 토큰 발행 및 참여 프로젝트 반환

See merge request !2
parents 53843083 aa3f93d9
No related branches found
No related tags found
3 merge requests!15Feat/certificate,!6Feat/forwarding 포트포워딩 CRUD,!2Feat/auth : 사용자 토큰 발행 및 참여 프로젝트 반환
Showing
with 349 additions and 0 deletions
......@@ -28,6 +28,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'org.pacesys:openstack4j:3.1.0'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
......
package com.aolda.itda.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) { // 스프링단에서 cors 설정
registry.addMapping("/**")
.allowedOriginPatterns("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH", "FETCH")
.allowedHeaders("*")
.allowCredentials(true)
.exposedHeaders("Authorization", "X-Refresh-Token", "Access-Control-Allow-Origin")
;
}
}
package com.aolda.itda.controller;
import com.aolda.itda.dto.auth.LoginRequestDTO;
import com.aolda.itda.service.AuthService;
import com.fasterxml.jackson.core.JsonProcessingException;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api/auth")
@RequiredArgsConstructor
public class AuthController {
private final AuthService authService;
@PostMapping("/login")
public ResponseEntity<Object> login(HttpServletResponse response,
@RequestBody LoginRequestDTO loginRequestDTO) throws JsonProcessingException {
return ResponseEntity.ok(authService.userLogin(response, loginRequestDTO));
}
}
package com.aolda.itda.dto.auth;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class LoginRequestDTO {
private String id;
private String password;
}
package com.aolda.itda.dto.auth;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.List;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class LoginResponseDTO {
private Boolean isAdmin;
private List<ProjectIdAndNameDTO> lists;
}
package com.aolda.itda.dto.auth;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ProjectIdAndNameDTO {
private String id;
private String name;
}
package com.aolda.itda.dto.auth;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.List;
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class ProjectRoleDTO {
private String projectName;
private String roleName;
}
package com.aolda.itda.exception;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@Slf4j
@RestControllerAdvice
public class ApiExceptionHandler {
@ExceptionHandler(value = CustomException.class)
public ResponseEntity<ErrorResponse> handleCustomException(CustomException e) {
log.error("[handleCustomException] {} : {}", e.getErrorCode().name(), e.getErrorCode().getMessage());
return ErrorResponse.fromException(e);
}
}
package com.aolda.itda.exception;
import lombok.Getter;
@Getter
public class CustomException extends RuntimeException{
private ErrorCode errorCode;
private String info;
public CustomException(ErrorCode errorCode) {
this.errorCode = errorCode;
}
public CustomException(ErrorCode errorCode, String info){
this.errorCode = errorCode;
this.info = info;
}
}
package com.aolda.itda.exception;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
@Getter
@RequiredArgsConstructor
public enum ErrorCode {
// User
INVALID_USER_INFO(HttpStatus.BAD_REQUEST, "잘못된 회원 정보입니다");
private final HttpStatus status;
private final String message;
}
package com.aolda.itda.exception;
import lombok.Builder;
import lombok.Getter;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
@Getter
@Builder
public class ErrorResponse {
private final HttpStatus status; // HTTP 상태 코드
private final String code; // 에러 코드
private final String message; // 에러 메시지
public static ResponseEntity<ErrorResponse> fromException(CustomException e) {
String message = e.getErrorCode().getMessage();
if (e.getInfo() != null) {
message += " " + e.getInfo(); // 추가 정보가 있는 경우 결합
}
return ResponseEntity
.status(e.getErrorCode().getStatus())
.body(ErrorResponse.builder()
.status(e.getErrorCode().getStatus())
.code(e.getErrorCode().name())
.message(message)
.build());
}
}
package com.aolda.itda.service;
import com.aolda.itda.dto.auth.LoginRequestDTO;
import com.aolda.itda.dto.auth.LoginResponseDTO;
import com.aolda.itda.dto.auth.ProjectIdAndNameDTO;
import com.aolda.itda.dto.auth.ProjectRoleDTO;
import com.aolda.itda.exception.CustomException;
import com.aolda.itda.exception.ErrorCode;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.*;
@Service
@RequiredArgsConstructor
public class AuthService {
@Value("${spring.server.keystone}")
private String keystone;
@Value("${spring.server.admin-id}")
private String adminId;
@Value("${spring.server.admin-password}")
private String adminPassword;
private final RestTemplate restTemplate = new RestTemplate();
private final ObjectMapper objectMapper = new ObjectMapper();
// 사용자 로그인 후 토큰 발행 및 Role 반환
public LoginResponseDTO userLogin(HttpServletResponse response, LoginRequestDTO loginRequestDTO) throws JsonProcessingException {
Map<String, String> user = getToken(loginRequestDTO.getId(), loginRequestDTO.getPassword());
String userId = user.get("id");
String token = user.get("token");
if (userId == null || token == null) {
throw new CustomException(ErrorCode.INVALID_USER_INFO);
}
response.addHeader("X-Subject-Token", token);
return LoginResponseDTO.builder()
.isAdmin(false)
.lists(getProjectsWithUser(user))
.build();
}
// 특정 사용자의 토큰 발행
private Map<String, String> getToken(String id, String password) {
String url = keystone + "/auth/tokens";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
String requestBody = "{\n" +
" \"auth\": {\n" +
" \"identity\": {\n" +
" \"methods\": [\n" +
" \"password\"\n" +
" ],\n" +
" \"password\": {\n" +
" \"user\": {\n" +
" \"name\": \""+ id + "\",\n" +
" \"domain\": {\n" +
" \"name\": \"Default\"\n" +
" },\n" +
" \"password\": \"" + password + "\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}";
HttpEntity<String> requestEntity = new HttpEntity<>(requestBody, headers);
ResponseEntity<Map> res = restTemplate.postForEntity(url, requestEntity, Map.class);
Map<String, Object> resToken = (Map<String, Object>) res.getBody().get("token");
Map<String, Object> resUser = (Map<String, Object>) resToken.get("user");
String userId = (String) resUser.get("id");
String token = res.getHeaders().getFirst("X-Subject-Token");
return Map.of("id", userId,
"token", token);
}
// 특정 사용자의 프로젝트별 Role 반환
private List<ProjectRoleDTO> getRolesWithProjects(Map<String, String> user) throws JsonProcessingException {
String userId = user.get("id");
String token = user.get("token");
if (userId == null || token == null) {
throw new CustomException(ErrorCode.INVALID_USER_INFO);
}
String url = keystone + "/role_assignments?user.id=" + userId + "&effective&include_names=true";
HttpHeaders headers = new HttpHeaders();
headers.set("X-Auth-Token", getAdminToken());
HttpEntity<String> requestEntity = new HttpEntity<>(headers);
ResponseEntity<String> res = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
JsonNode node = objectMapper.readTree(res.getBody());
ArrayNode arrayNode = (ArrayNode) node.get("role_assignments");
List<ProjectRoleDTO> lists = new ArrayList<>();
for (JsonNode assignment : arrayNode) {
String projectName = assignment.path("scope").path("project").path("name").asText();
String roleName = assignment.path("role").path("name").asText();
ProjectRoleDTO projectRoleDTO = new ProjectRoleDTO(projectName, roleName);
lists.add(projectRoleDTO);
}
return lists;
}
// 관리자용 토큰 발행
public String getAdminToken() {
Map<String, String> user = getToken(adminId, adminPassword);
return user.get("token");
}
private List<ProjectIdAndNameDTO> getProjectsWithUser(Map<String, String> user) throws JsonProcessingException {
String userId = user.get("id");
String token = user.get("token");
if (userId == null || token == null) {
throw new CustomException(ErrorCode.INVALID_USER_INFO);
}
String url = keystone + "/users/" + userId + "/projects";
HttpHeaders headers = new HttpHeaders();
headers.set("X-Auth-Token", getAdminToken());
HttpEntity<String> requestEntity = new HttpEntity<>(headers);
ResponseEntity<String> res = restTemplate.exchange(url, HttpMethod.GET, requestEntity, String.class);
JsonNode node = objectMapper.readTree(res.getBody());
ArrayNode arrayNode = (ArrayNode) node.get("projects");
List<ProjectIdAndNameDTO> lists = new ArrayList<>();
for (JsonNode assignment : arrayNode) {
String projectId = assignment.path("id").asText();
String projectName = assignment.path("name").asText();
lists.add(new ProjectIdAndNameDTO(projectId, projectName));
}
return lists;
}
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment