diff --git a/.gitignore b/.gitignore index c2065bc..bad8fef 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,10 @@ out/ ### VS Code ### .vscode/ + +src/main/resources/application-secret.properties +src/main/resources/keystore.p12 +docker-compose.yml +src/test/resources/application-secret.properties +init.sql +Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..04728f5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,11 @@ +FROM openjdk:21-jdk-slim + +WORKDIR /app + +ENV TZ=Asia/Seoul + +# JAR 복사 +COPY ./build/libs/devlog-0.0.1-SNAPSHOT.jar app.jar + +# 실행 +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/app.sh b/app.sh new file mode 100644 index 0000000..cc8dab7 --- /dev/null +++ b/app.sh @@ -0,0 +1,3 @@ +docker logs devlog-app + +docker exec -it devlog-app bash diff --git a/build.gradle b/build.gradle index eeeb325..bc61aad 100644 --- a/build.gradle +++ b/build.gradle @@ -1,38 +1,76 @@ plugins { - id 'java' - id 'org.springframework.boot' version '3.4.4' - id 'io.spring.dependency-management' version '1.1.7' + id 'java' + id 'org.springframework.boot' version '3.4.4' + id 'io.spring.dependency-management' version '1.1.7' } group = 'apptive' version = '0.0.1-SNAPSHOT' java { - toolchain { - languageVersion = JavaLanguageVersion.of(21) - } + toolchain { + languageVersion = JavaLanguageVersion.of(21) + } } configurations { - compileOnly { - extendsFrom annotationProcessor - } + compileOnly { + extendsFrom annotationProcessor + } } repositories { - mavenCentral() + mavenCentral() } dependencies { - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-web' - compileOnly 'org.projectlombok:lombok' - runtimeOnly 'com.mysql:mysql-connector-j' - annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-test' - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + // Spring Boot Starters + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-webflux' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-resource-server' + implementation 'org.springframework.boot:spring-boot-starter-mail' + + // JWT (JSON Web Token) + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + // Redis Lettuce + // implementation 'io.lettuce:lettuce-core:6.2.5.RELEASE' + + implementation ('org.springframework.boot:spring-boot-starter-data-redis') { exclude group: 'io.lettuce', module: 'lettuce-core' } + implementation 'redis.clients:jedis' + + // SpringDoc OpenAPI (for Swagger UI) + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0' + + // ULID Generators + implementation 'de.huxhorn.sulky:de.huxhorn.sulky.ulid:8.2.0' + implementation 'com.github.f4b6a3:ulid-creator:5.2.0' + + // Lombok (Compile-time annotations) + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + + // MySQL Driver (runtime only) + runtimeOnly 'com.mysql:mysql-connector-j' + + // Development tools (for hot-reloading) + developmentOnly 'org.springframework.boot:spring-boot-devtools' + + // Test dependencies + testImplementation 'org.springframework.boot:spring-boot-starter-test' + testImplementation 'org.springframework.security:spring-security-test' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + // Annotation processor for Spring Boot configuration + annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' } tasks.named('test') { - useJUnitPlatform() + useJUnitPlatform() } diff --git a/certs/app_certs/ca.pem b/certs/app_certs/ca.pem new file mode 100644 index 0000000..bfcc81c --- /dev/null +++ b/certs/app_certs/ca.pem @@ -0,0 +1,40 @@ +-----BEGIN CERTIFICATE----- +MIIDHTCCAgUCFFw5T+2r/PXbAFYdJUeCO9NurTMhMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDE0NzIwWhcNMzUw +NDE0MDE0NzIwWjBLMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMQ0wCwYDVQQDDARXVURDMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntyNuLTcMjWIgfrSPi+pvf3hNsHLzlV1 +Ze+hXvqs4fE0p36qVbWRUkwv+Vp4drKrlXrY5Em7JCNZ0IT9PuZ3PwFbnG5FwV+O +H3B7WrRx4JHhLZU3w+WHFGfWFDZS2eYWrf1f1tDOlyq92xx0QEqDNE+gUMJ+z1N7 +5yoDForShgTmxD+KzYp4e7BvKKKn/U6YosBC8GyiMWdcka0IUMoTwpQRQ3Vlx/KX ++YGJlwWA9wvNWOqgcYCxeCIa7X+8K9i92Py01T82zqLMcr52Ri2JEdMzHz4pp/aZ +d56pfUhGuN6ET18fTTGeKuB1PrPu80OgfOYvJL9u1/twuO/xi/naBQIDAQABMA0G +CSqGSIb3DQEBCwUAA4IBAQAudZAxVxMh/aUeTj6WK5mpVpUINmAwt938FVSQ3nUC +mTafNjSNfu/Me+PvjCkmf9RNk5BSdHVwxseOBaU0WZnvLM056ayyQnSwWT53WwtZ +CSb3VHBbwaJqAiAnhQxCHHhWDK4rtIPrWZ/SCek2E5l29ccZHTuVnaFF3pG8YRs1 ++UXVWeCB0fIJwQdugtGkzVzgO1HW7tKyG1BqCP9q9FnfODia9d6jKAA5dScBPanG +yPNI8eq4Fpk4R6P1jM2jw9pCJ4CW4lpgGQD0KATJve+ooHJqL3Ri6xkljNymGfbP +UD8eyRxgUaKUloQY2UbafzO+cLn2uRiB4sfn/T8hybeW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIUJQjfesPiPpsGhsWZH4oTggUWVD8wDQYJKoZIhvcNAQEL +BQAwSzELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYDVQQHDAVQdXNh +bjENMAsGA1UECgwEV1VEQzENMAsGA1UEAwwEV1VEQzAeFw0yNTA0MTYwMTQzMDda +Fw0zNTA0MTQwMTQzMDdaMEsxCzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEO +MAwGA1UEBwwFUHVzYW4xDTALBgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDczXj3rCSwS+cv2FXTRWxcLxuE +m0DY9RqQfUZ6XM24IuBpxeicqIcGs8lYSWmx9cRFnxDG5beV5BSBOt3MoR9Q5Caf +Ld4JWHEIXicDk78vQjTE7QB1xPkieU0s4wqh9W0rPKEYsJ8SEhFgMUq5g5EiaVqQ +PRaaEeK1C0Zn0e5NdwcMgJ6E6uwij9E1U95QAw6AFyvqoS+s/1TsEcAkN3oIXdlD +kYy7lO4uLZI4Dfh77kfAf3LxKN/qE6WE20wyzipwb7yXFVzcTm5WoLSwV4axWrYF +Bc67x3X5t6K985Y1f5UMfnKq+JRAl0wFC0SOhS8NXHFVNQCbXvwldfcUSwc/AgMB +AAGjUzBRMB0GA1UdDgQWBBRa5Q7LNg15ZZYZSMQoowHLNtJtezAfBgNVHSMEGDAW +gBRa5Q7LNg15ZZYZSMQoowHLNtJtezAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQDXLVSeVyhCuw4oJoRw/wPwQEeBScxmpkSQvMzl19QhFXkxZxUT +5vhkCJc3lp1Ifa405n5I1+WFVhWyF1cSTXYddxz/eLjW1p54VNPXm5nxKzwwyftE +Ps36G32D0kdkk9pRqnsQ2Dt5j8hf2RVXzyswu97mTkRXTQMDwKUtwopr6HlIZiOY +BzUOEJf4NQr3VeGkXeWfqjWMU7ch7FWf7SsyqNFinAgiDLaIdEYSRBi8xiRJpOn4 +BUbhAlS2L4uGPWGa7TZ8sf8/s4Mdh784NSFn+4D6pVa7Quq5e6OmBVK4J851zAic +7mQsPhJLXactGWdksrjBSMQS7E5R8LG+k0j3 +-----END CERTIFICATE----- diff --git a/certs/app_certs/client-ca.pem b/certs/app_certs/client-ca.pem new file mode 100644 index 0000000..bfcc81c --- /dev/null +++ b/certs/app_certs/client-ca.pem @@ -0,0 +1,40 @@ +-----BEGIN CERTIFICATE----- +MIIDHTCCAgUCFFw5T+2r/PXbAFYdJUeCO9NurTMhMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDE0NzIwWhcNMzUw +NDE0MDE0NzIwWjBLMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMQ0wCwYDVQQDDARXVURDMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntyNuLTcMjWIgfrSPi+pvf3hNsHLzlV1 +Ze+hXvqs4fE0p36qVbWRUkwv+Vp4drKrlXrY5Em7JCNZ0IT9PuZ3PwFbnG5FwV+O +H3B7WrRx4JHhLZU3w+WHFGfWFDZS2eYWrf1f1tDOlyq92xx0QEqDNE+gUMJ+z1N7 +5yoDForShgTmxD+KzYp4e7BvKKKn/U6YosBC8GyiMWdcka0IUMoTwpQRQ3Vlx/KX ++YGJlwWA9wvNWOqgcYCxeCIa7X+8K9i92Py01T82zqLMcr52Ri2JEdMzHz4pp/aZ +d56pfUhGuN6ET18fTTGeKuB1PrPu80OgfOYvJL9u1/twuO/xi/naBQIDAQABMA0G +CSqGSIb3DQEBCwUAA4IBAQAudZAxVxMh/aUeTj6WK5mpVpUINmAwt938FVSQ3nUC +mTafNjSNfu/Me+PvjCkmf9RNk5BSdHVwxseOBaU0WZnvLM056ayyQnSwWT53WwtZ +CSb3VHBbwaJqAiAnhQxCHHhWDK4rtIPrWZ/SCek2E5l29ccZHTuVnaFF3pG8YRs1 ++UXVWeCB0fIJwQdugtGkzVzgO1HW7tKyG1BqCP9q9FnfODia9d6jKAA5dScBPanG +yPNI8eq4Fpk4R6P1jM2jw9pCJ4CW4lpgGQD0KATJve+ooHJqL3Ri6xkljNymGfbP +UD8eyRxgUaKUloQY2UbafzO+cLn2uRiB4sfn/T8hybeW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIUJQjfesPiPpsGhsWZH4oTggUWVD8wDQYJKoZIhvcNAQEL +BQAwSzELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYDVQQHDAVQdXNh +bjENMAsGA1UECgwEV1VEQzENMAsGA1UEAwwEV1VEQzAeFw0yNTA0MTYwMTQzMDda +Fw0zNTA0MTQwMTQzMDdaMEsxCzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEO +MAwGA1UEBwwFUHVzYW4xDTALBgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDczXj3rCSwS+cv2FXTRWxcLxuE +m0DY9RqQfUZ6XM24IuBpxeicqIcGs8lYSWmx9cRFnxDG5beV5BSBOt3MoR9Q5Caf +Ld4JWHEIXicDk78vQjTE7QB1xPkieU0s4wqh9W0rPKEYsJ8SEhFgMUq5g5EiaVqQ +PRaaEeK1C0Zn0e5NdwcMgJ6E6uwij9E1U95QAw6AFyvqoS+s/1TsEcAkN3oIXdlD +kYy7lO4uLZI4Dfh77kfAf3LxKN/qE6WE20wyzipwb7yXFVzcTm5WoLSwV4axWrYF +Bc67x3X5t6K985Y1f5UMfnKq+JRAl0wFC0SOhS8NXHFVNQCbXvwldfcUSwc/AgMB +AAGjUzBRMB0GA1UdDgQWBBRa5Q7LNg15ZZYZSMQoowHLNtJtezAfBgNVHSMEGDAW +gBRa5Q7LNg15ZZYZSMQoowHLNtJtezAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQDXLVSeVyhCuw4oJoRw/wPwQEeBScxmpkSQvMzl19QhFXkxZxUT +5vhkCJc3lp1Ifa405n5I1+WFVhWyF1cSTXYddxz/eLjW1p54VNPXm5nxKzwwyftE +Ps36G32D0kdkk9pRqnsQ2Dt5j8hf2RVXzyswu97mTkRXTQMDwKUtwopr6HlIZiOY +BzUOEJf4NQr3VeGkXeWfqjWMU7ch7FWf7SsyqNFinAgiDLaIdEYSRBi8xiRJpOn4 +BUbhAlS2L4uGPWGa7TZ8sf8/s4Mdh784NSFn+4D6pVa7Quq5e6OmBVK4J851zAic +7mQsPhJLXactGWdksrjBSMQS7E5R8LG+k0j3 +-----END CERTIFICATE----- diff --git a/certs/app_certs/client-cert.pem b/certs/app_certs/client-cert.pem new file mode 100644 index 0000000..0efca3f --- /dev/null +++ b/certs/app_certs/client-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJjCCAg4CFFw5T+2r/PXbAFYdJUeCO9NurTMjMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDU1OTAyWhcNMjYw +NDE2MDU1OTAyWjBUMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMRYwFAYDVQQDDA1kZXZsb2ctY2xpZW50 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3FizfydyA4/N8rtlV4D3 +YNkeWNs1afvPP5ylut7LgVXHqQvp3UuCMlmG4fHkdH7QAm5bxTgQnAWRCYFQ3/M2 +/eizc44LphuKkHZ8+uNKOZpjNjFXLzPe+m67hd+qVJJq0afscp+/UQkF348mxmoR +92nfaf1YWSWmdEU15iVahWAXyN8QUWfd6LanwXS2hd9u3ZzwIwGpYmf0JjnbLpvl +FrDkBEX3n56xe1SBoYnBC8tz9Ljelg3WHfTBaAj23wxEuJpTrfKnc4CGT+A0KFoi +12uRyNN0Qe1VroCyFKN75DjRc7KvZd5WjXf6WTXo83GoAVH89hGCW0EEyRgfpteK +7wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQALrx8OeaBJOvH/wTIA9UxCECd3pLfF +ojnitMDHs45RDzrO8adxqTGuU1yyHtCpVtJOI7OLN+De3uVgSWb5fU9SRO9tgE4m +0WAgQXkxDVXrV2AThUnzyDNkWynxad4ledhVJ8hJN4OAMGA8dg9jovP3OMVjScZP +nQ+JIqhai1Ony6k7BTPwSXoByOouRG5M1c6HsKW0JL+mziz5qKh6UQE1O2Wu2BBR +aoFnAogK5WWBhYxntdX5WfIfGz2Pq/NjDyXwbSnLliOmr1HoXLpDtOoPTmnjbqD3 +m2ytsApd7YRYiM88w7VRtt5VzYXyrqPkWCNBn9xyeNKnQwprrRxfMKj1 +-----END CERTIFICATE----- diff --git a/certs/app_certs/client-key-nopass.pem b/certs/app_certs/client-key-nopass.pem new file mode 100644 index 0000000..65df333 --- /dev/null +++ b/certs/app_certs/client-key-nopass.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDcWLN/J3IDj83y +u2VXgPdg2R5Y2zVp+88/nKW63suBVcepC+ndS4IyWYbh8eR0ftACblvFOBCcBZEJ +gVDf8zb96LNzjgumG4qQdnz640o5mmM2MVcvM976bruF36pUkmrRp+xyn79RCQXf +jybGahH3ad9p/VhZJaZ0RTXmJVqFYBfI3xBRZ93otqfBdLaF327dnPAjAaliZ/Qm +Odsum+UWsOQERfefnrF7VIGhicELy3P0uN6WDdYd9MFoCPbfDES4mlOt8qdzgIZP +4DQoWiLXa5HI03RB7VWugLIUo3vkONFzsq9l3laNd/pZNejzcagBUfz2EYJbQQTJ +GB+m14rvAgMBAAECggEAAXr+7K6PKkhEaXqk7aAcceAtXqH++6xfZ5w2pMPB9WDo +xwBatvWsYuvZWltHLIcUH67AbCgv3i15HHv49nss/1R3nZTbJgmUw0idasMYly9i +2u8XpblhC90NDFm1LQ+OIKp8aW9GZWLdPEyZG89vNF3IGtKbtn7tsSxBTLKOp1hg +IhMTK0VToA5lCbGn0JPSJlZTxSlKpz1HK/WxVWnGWGeSvIdBt33o0Ea9IHTT1esg +j7zr8jVz9FWcteiyIy1eRptKa+zLTtwLdRL0C1l20Sg2flml/k4UzwuexpOlg7VL +qsp7drzOfq/wPew88W3edhbpe52agsS29yoOtD6XgQKBgQDv8RAZnNAKWvcrqYAe +M9NBpMp/7+zeoK5u3A7LwUr9AOTYH9WdD53Y+3pn+ER7Dakn1o8TEDhwC0K623yX +uwHjMGeDHMifDGIe+FIVvJVVYf0I5qMGojUUbDDswQf3e5axrV1jJnk5++QaYHba +L9Ap+FweQfkmoPGiq3h74atIqQKBgQDrF+m4Wvwib27g2O285T5SmY2hPQwqysg1 +N30IBV/m6brwKf7mcXdp1UF1od6jIG8FutI5kPrqfeYZShfgo20biv1YqgPAUoSF +EOjILQ0bMXadv0VZJfDqXnGAFh1c0otTpVUsZ5Qv/UYX46HdVovRuzqmp59JAIba +TsRhFEt91wKBgQCEd9xhp5eb//iyHFRlWEtr1GUQGQ/3IVLsVYW9rCuQXuv4/ipb +GgIVh1FfEUwNe89F9UjsR2pBQZZHv2GcC1zRZyne0wdX9+g8HPCEm6b+iqi+P0cG +JIuViN3B+BhD4/Ggiowib11CS/T1MwirEPamFT4WXmoFj5mYK37LNh3wcQKBgQDj +2dNeKGDChznxlo4kTBLxP33zThWiy9LrMRJvWbYvOU1DQ5CXjFVuL5A4EGCVvfOc +nArwXEG0T71ZuWQXBo3S6gzNiEoGdnOV/GOAz5kqR/Bsx1rRImKy5EIhIE3pDu6W +bWF1nhYTxOfQc4EH4r+00D/yEffhay9IGptec6sPFQKBgGY+RuvEja4LmRmxqFqf +0jybFry3ct+iM30Bp+ythjT15S9JFUvtGYuRH4NwFP3PzBKrmWPpkSdewFxPhHon +ZpSL9zWjH7W5l9HUBbuiYM5PF5nbz8uzZXQuL2zHl4dp1PYSdgHnZO4q6/oVt2iv +Y56fzbNtDQOSzNcS2KrKNBNx +-----END PRIVATE KEY----- diff --git a/certs/app_certs/client-key.pem b/certs/app_certs/client-key.pem new file mode 100644 index 0000000..949ac8d --- /dev/null +++ b/certs/app_certs/client-key.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQID1XMsFyUjIQCAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBxxVDXKG4mZOsVmGBYEITbBIIE +0HwgneRxrrWTxCUNrmpOcDZ7Sl9Xr1DhZYOwa7W5aqwzDhToxB2Q4PMTzPQJtJYx +wzUgP5bEK8t4tIKoEulu9yXbMMhlYWhHBR0dB7VReyvLispZdduOHeJ5kf1mCwPa +l6q6ZdGkjG7+P9CGx3Sfv995vXMtbuDjgtpoXqQ1A7frplmfMjh0BUcG22UwLbUB +XhjJ35iYgXIbeR54F09Pg0bg8VkYcS05+DsSdWS4r3IYDVauBZg+cXCb0aRl+GXO +jnmYYDymkJEzi1Fw4f7wg7X8QKuyUadYGxhnK0q0znSkOMXB7SVISWYajWsUCB+p +yBWU7KdafsntAqaDNmpph24PJEWGyimPmVjU7KgHxqyrYb/taXrsiN22sFEPX3Ta +5ye1QUCu5p6EFPxoKZt0WU4/75fwAsGypmxpHeWgf3qQmewpVbQFzkqmbs1sD6NI +1Q9dMLoG5CopS7MhOsfRRITEhM9es4CjlTo1EJZsBkS5ZBsXoiMhTRqmazDA+bhd +R+cPQRxQ0S62hRX85BQAAy6ONxbLyHiEo4XVhVOD/OdoJxmFpVQCVteErqTDSWJa +PHkx7ZjAOgUR2r1/EX6pVIky1omFyiRD4AGX/dguDIEJxTwCTiayNRvedUSxIfg+ +XGJWfs7Nj7V9I8WF9sGRuBYkOS8GAjaYJpU5Fw1CThFU0VtlcpF8WioQhoTR3Soi +dqLuq6TyYEeADLRNL37YxSI1GcbK4Wf3RjJ6uoGyosFS8JLSlPE2Ib7Rrj6LL8sm +1KNUY+Kgmn/5mirLRTM+MxRXUXdk6wzOknLdNkeQwrWJHCJm0qZtyjRPPK2vwNta +p2HkvyxmlTqQx3b3+OcAis+Bjf2H/jzH1kAUV4MWgT3bHhUaQWcS/baBC9QiBaMc +qM6ejP9/GZaljkUMOupAoOz8pnY5ewVMbSl5+IPZBa7K24wgKiHZFzjMVCGzDmzr +WgiTbOubOOb9OaTR/iDZG+3sAtkPi5WsgVHrYyV01SUwS4uetxdJJA6T0nsw2SjN +PJ3WkSMRoUiTt5Ij6XsH2pzuc/osxEAtm0xNflW5FSPtg/ADK2wd9MT0PZCmo+ba +OYvMoO9aJS25PP3XK+nXHaQh7vxeroP/KGEtYWWCiRvZeZ0cX+3jhevHHQJ4mjbn +MIbD0/qsVBpFksqPKWlVKjjaFVr/yTLqSb69zVM15Ah3al7sKiUbgqbGLqM92zIt +jeyGNw5o8tRhmsCeUEvpXP4GVGBanio30ikqMb1z5OrG6XB7fy/JjSLksCRBB9uV +Vq8HCe4OdKDO/vDN8mp2DxZL+OGd/hEqdUZ0CyKrmBO3lU3h4yLNh2QFdNXimCjK +6nyDu6vhdAF6j2iUWC/sOOuEt7QELYGBCdBbn0wfH/XaWneDZ0IYwvSdbTEw9qoQ +wvboCFX49g3pkbIj10yp5gDo6mE9bPzPyjeud/Ag8y3LlS/MO+uhKv5XupD2+3sl +3iwTgSy+iQjHDII0AVb+QaavVthHkRHNL/rl0n/fmyZHzFDALNXIJ/yNPxL85D78 +MiMYQJ9E+rO+ZYWwmRXW+FxfZ1JyaDQsPV1ZWqJdmpiA3vnL3ZL4xrXK25w6tZy3 +FlfN+bnP1r2LNQFKBrERky0RBCU8iv6xQUbkQyPFThh0 +-----END ENCRYPTED PRIVATE KEY----- diff --git a/certs/app_certs/client.csr b/certs/app_certs/client.csr new file mode 100644 index 0000000..de9ebc0 --- /dev/null +++ b/certs/app_certs/client.csr @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICmTCCAYECAQAwVDELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYD +VQQHDAVQdXNhbjENMAsGA1UECgwEV1VEQzEWMBQGA1UEAwwNZGV2bG9nLWNsaWVu +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANxYs38ncgOPzfK7ZVeA +92DZHljbNWn7zz+cpbrey4FVx6kL6d1LgjJZhuHx5HR+0AJuW8U4EJwFkQmBUN/z +Nv3os3OOC6YbipB2fPrjSjmaYzYxVy8z3vpuu4XfqlSSatGn7HKfv1EJBd+PJsZq +Efdp32n9WFklpnRFNeYlWoVgF8jfEFFn3ei2p8F0toXfbt2c8CMBqWJn9CY52y6b +5Raw5ARF95+esXtUgaGJwQvLc/S43pYN1h30wWgI9t8MRLiaU63yp3OAhk/gNCha +ItdrkcjTdEHtVa6AshSje+Q40XOyr2XeVo13+lk16PNxqAFR/PYRgltBBMkYH6bX +iu8CAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQC4Vzu4fyhewiMXIFYptfr5kaDr +4BFKBqDQHZERgSoKlM8bIA5+TCTVGQ38mUGiDbuu+34XFseJC9dP9m8OED6Hmgm9 +yCWN+64GFiC1jOlkFw2VmE6rHH6N2sbwOgTPy91YJh8kPnjr92i/2qHXX89T7LcW +IvQE6e0TlIXan43udpXKH97z8DVUdfwakd3gH0K2wsAmUtSfYjeZty+EdB/LYpFw +jvDnw7jbpHDtkWAf/w9lpgibrYzC7FNl9JaHq6ng24J3ude4kAHQ4QKU8Z8jkiJn +6Koe4topx8xbkBieAdNzLMgi0nv+tY4/UMMI1njk7gVbsyQ7wY5Ir6qvm4hZ +-----END CERTIFICATE REQUEST----- diff --git a/certs/app_certs/google-fullchain.pem b/certs/app_certs/google-fullchain.pem new file mode 100644 index 0000000..8ac0960 --- /dev/null +++ b/certs/app_certs/google-fullchain.pem @@ -0,0 +1,89 @@ +-----BEGIN CERTIFICATE----- +MIIEeDCCA2CgAwIBAgIRAIIOTeFcOkvUEAhZaRElMkYwDQYJKoZIhvcNAQELBQAw +OzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczEM +MAoGA1UEAxMDV1IyMB4XDTI1MDMyMDExMjAzNFoXDTI1MDYxMjExMjAzM1owHjEc +MBoGA1UEAxMTYWNjb3VudHMuZ29vZ2xlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49 +AwEHA0IABLxsHfzEUtktVB6CycZH6AaINi5Bhsw07rZEuJhOE3X/gATjzeZNf+1j +pl/fApfNdgTlNWCFrrP36YTzx+82aOOjggJdMIICWTAOBgNVHQ8BAf8EBAMCB4Aw +EwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUliDn +8rqSai7lTNzAcR/TSF65DKQwHwYDVR0jBBgwFoAU3hse7XkV1D43JMMhu+w0OW1C +sjAwWAYIKwYBBQUHAQEETDBKMCEGCCsGAQUFBzABhhVodHRwOi8vby5wa2kuZ29v +Zy93cjIwJQYIKwYBBQUHMAKGGWh0dHA6Ly9pLnBraS5nb29nL3dyMi5jcnQwNQYD +VR0RBC4wLIITYWNjb3VudHMuZ29vZ2xlLmNvbYIVKi5wYXJ0bmVyLmFuZHJvaWQu +Y29tMBMGA1UdIAQMMAowCAYGZ4EMAQIBMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6 +Ly9jLnBraS5nb29nL3dyMi83NXI0WnlBM3ZBMC5jcmwwggEEBgorBgEEAdZ5AgQC +BIH1BIHyAPAAdwDPEVbu1S58r/OHW9lpLpvpGnFnSrAX7KwB0lt3zsw7CAAAAZWz +f4ySAAAEAwBIMEYCIQCynwunRTr3Zl0HlSl791D5pedOwAhFDr9T750YJAGPzAIh +AP3zIGEjXeps4Ft1FUwfl3Yco+L1fkyybt4YT77pfBZ+AHUAouMK5EXvva2bfjjt +R2d3U9eCW4SU1yteGyzEuVCkR+cAAAGVs3+MdQAABAMARjBEAiA+h5fkWNb51WXn +kDYKXu+FW43PCjAhzfskOo5d6W6mQwIgT3ky97SykxawRlhso0Z75lCh/2qqLdPb +Ro8LyQDOxLswDQYJKoZIhvcNAQELBQADggEBAHFdiq+W1/SaIFfURWnomlywdd31 +1o8b/YH/K9tUbzB90FrtXDc8Y9+GbZ6naNAYeoQVUrRmlaZuPlTJbpjVDLoJu+1i +WrzpT6On00Z8RJJmBV/gtYJ6s3GxMpFlDmXZDS0W2yNvs9O2YoRQdjNYJ9lnjQEv +NgoI9oOJ5s37xqtijV7qyALHzD2+dZlP77jzaa+18U0j1cV3V4vmBED/cNxlaX1i +bAO53XukfH5ylvOxB5K09mIA2OYYU5N8U9XBFGRVBKLfzo0rdmnt9XqvZsH1CyKJ +tUS0x6P17D4f3mOEqVjQMJG7EVy4smqEUnRfOIrleHIFXMfv8o6MPVlgc9I= +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIFCzCCAvOgAwIBAgIQf/AFoHxM3tEArZ1mpRB7mDANBgkqhkiG9w0BAQsFADBH +MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM +QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMjMxMjEzMDkwMDAwWhcNMjkwMjIw +MTQwMDAwWjA7MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNl +cnZpY2VzMQwwCgYDVQQDEwNXUjIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCp/5x/RR5wqFOfytnlDd5GV1d9vI+aWqxG8YSau5HbyfsvAfuSCQAWXqAc ++MGr+XgvSszYhaLYWTwO0xj7sfUkDSbutltkdnwUxy96zqhMt/TZCPzfhyM1IKji +aeKMTj+xWfpgoh6zySBTGYLKNlNtYE3pAJH8do1cCA8Kwtzxc2vFE24KT3rC8gIc +LrRjg9ox9i11MLL7q8Ju26nADrn5Z9TDJVd06wW06Y613ijNzHoU5HEDy01hLmFX +xRmpC5iEGuh5KdmyjS//V2pm4M6rlagplmNwEmceOuHbsCFx13ye/aoXbv4r+zgX +FNFmp6+atXDMyGOBOozAKql2N87jAgMBAAGjgf4wgfswDgYDVR0PAQH/BAQDAgGG +MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/ +AgEAMB0GA1UdDgQWBBTeGx7teRXUPjckwyG77DQ5bUKyMDAfBgNVHSMEGDAWgBTk +rysmcRorSCeFL1JmLO/wiRNxPjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAKG +GGh0dHA6Ly9pLnBraS5nb29nL3IxLmNydDArBgNVHR8EJDAiMCCgHqAchhpodHRw +Oi8vYy5wa2kuZ29vZy9yL3IxLmNybDATBgNVHSAEDDAKMAgGBmeBDAECATANBgkq +hkiG9w0BAQsFAAOCAgEARXWL5R87RBOWGqtY8TXJbz3S0DNKhjO6V1FP7sQ02hYS +TL8Tnw3UVOlIecAwPJQl8hr0ujKUtjNyC4XuCRElNJThb0Lbgpt7fyqaqf9/qdLe +SiDLs/sDA7j4BwXaWZIvGEaYzq9yviQmsR4ATb0IrZNBRAq7x9UBhb+TV+PfdBJT +DhEl05vc3ssnbrPCuTNiOcLgNeFbpwkuGcuRKnZc8d/KI4RApW//mkHgte8y0YWu +ryUJ8GLFbsLIbjL9uNrizkqRSvOFVU6xddZIMy9vhNkSXJ/UcZhjJY1pXAprffJB +vei7j+Qi151lRehMCofa6WBmiA4fx+FOVsV2/7R6V2nyAiIJJkEd2nSi5SnzxJrl +Xdaqev3htytmOPvoKWa676ATL/hzfvDaQBEcXd2Ppvy+275W+DKcH0FBbX62xevG +iza3F4ydzxl6NJ8hk8R+dDXSqv1MbRT1ybB5W0k8878XSOjvmiYTDIfyc9acxVJr +Y/cykHipa+te1pOhv7wYPYtZ9orGBV5SGOJm4NrB3K1aJar0RfzxC3ikr7Dyc6Qw +qDTBU39CluVIQeuQRgwG3MuSxl7zRERDRilGoKb8uY45JzmxWuKxrfwT/478JuHU +/oTxUFqOl2stKnn7QGTq8z29W+GgBLCXSBxC9epaHM0myFH/FJlniXJfHeytWt0= +-----END CERTIFICATE----- + +-----BEGIN CERTIFICATE----- +MIIFYjCCBEqgAwIBAgIQd70NbNs2+RrqIQ/E8FjTDTANBgkqhkiG9w0BAQsFADBX +MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEQMA4GA1UE +CxMHUm9vdCBDQTEbMBkGA1UEAxMSR2xvYmFsU2lnbiBSb290IENBMB4XDTIwMDYx +OTAwMDA0MloXDTI4MDEyODAwMDA0MlowRzELMAkGA1UEBhMCVVMxIjAgBgNVBAoT +GUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBMTEMxFDASBgNVBAMTC0dUUyBSb290IFIx +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAthECix7joXebO9y/lD63 +ladAPKH9gvl9MgaCcfb2jH/76Nu8ai6Xl6OMS/kr9rH5zoQdsfnFl97vufKj6bwS +iV6nqlKr+CMny6SxnGPb15l+8Ape62im9MZaRw1NEDPjTrETo8gYbEvs/AmQ351k +KSUjB6G00j0uYODP0gmHu81I8E3CwnqIiru6z1kZ1q+PsAewnjHxgsHA3y6mbWwZ +DrXYfiYaRQM9sHmklCitD38m5agI/pboPGiUU+6DOogrFZYJsuB6jC511pzrp1Zk +j5ZPaK49l8KEj8C8QMALXL32h7M1bKwYUH+E4EzNktMg6TO8UpmvMrUpsyUqtEj5 +cuHKZPfmghCN6J3Cioj6OGaK/GP5Afl4/Xtcd/p2h/rs37EOeZVXtL0m79YB0esW +CruOC7XFxYpVq9Os6pFLKcwZpDIlTirxZUTQAs6qzkm06p98g7BAe+dDq6dso499 +iYH6TKX/1Y7DzkvgtdizjkXPdsDtQCv9Uw+wp9U7DbGKogPeMa3Md+pvez7W35Ei +Eua++tgy/BBjFFFy3l3WFpO9KWgz7zpm7AeKJt8T11dleCfeXkkUAKIAf5qoIbap +sZWwpbkNFhHax2xIPEDgfg1azVY80ZcFuctL7TlLnMQ/0lUTbiSw1nH69MG6zO0b +9f6BQdgAmD06yK56mDcYBZUCAwEAAaOCATgwggE0MA4GA1UdDwEB/wQEAwIBhjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTkrysmcRorSCeFL1JmLO/wiRNxPjAf +BgNVHSMEGDAWgBRge2YaRQ2XyolQL30EzTSo//z9SzBgBggrBgEFBQcBAQRUMFIw +JQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnBraS5nb29nL2dzcjEwKQYIKwYBBQUH +MAKGHWh0dHA6Ly9wa2kuZ29vZy9nc3IxL2dzcjEuY3J0MDIGA1UdHwQrMCkwJ6Al +oCOGIWh0dHA6Ly9jcmwucGtpLmdvb2cvZ3NyMS9nc3IxLmNybDA7BgNVHSAENDAy +MAgGBmeBDAECATAIBgZngQwBAgIwDQYLKwYBBAHWeQIFAwIwDQYLKwYBBAHWeQIF +AwMwDQYJKoZIhvcNAQELBQADggEBADSkHrEoo9C0dhemMXoh6dFSPsjbdBZBiLg9 +NR3t5P+T4Vxfq7vqfM/b5A3Ri1fyJm9bvhdGaJQ3b2t6yMAYN/olUazsaL+yyEn9 +WprKASOshIArAoyZl+tJaox118fessmXn1hIVw41oeQa1v1vg4Fv74zPl6/AhSrw +9U5pCZEt4Wi4wStz6dTZ/CLANx8LZh1J7QJVj2fhMtfTJr9w4z30Z209fOU0iOMy ++qduBmpvvYuR7hZL6Dupszfnw0Skfths18dG9ZKb59UhvmaSGZRVbNQpsg3BZlvi +d0lIKO2d1xozclOzgjXPYovJJIultzkMu34qQb9Sz/yilrbCgj8= +-----END CERTIFICATE----- + diff --git a/certs/app_certs/google-intermediate.pem b/certs/app_certs/google-intermediate.pem new file mode 100644 index 0000000..82eee6d --- /dev/null +++ b/certs/app_certs/google-intermediate.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFCzCCAvOgAwIBAgIQf/AFoHxM3tEArZ1mpRB7mDANBgkqhkiG9w0BAQsFADBH +MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM +QzEUMBIGA1UEAxMLR1RTIFJvb3QgUjEwHhcNMjMxMjEzMDkwMDAwWhcNMjkwMjIw +MTQwMDAwWjA7MQswCQYDVQQGEwJVUzEeMBwGA1UEChMVR29vZ2xlIFRydXN0IFNl +cnZpY2VzMQwwCgYDVQQDEwNXUjIwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK +AoIBAQCp/5x/RR5wqFOfytnlDd5GV1d9vI+aWqxG8YSau5HbyfsvAfuSCQAWXqAc ++MGr+XgvSszYhaLYWTwO0xj7sfUkDSbutltkdnwUxy96zqhMt/TZCPzfhyM1IKji +aeKMTj+xWfpgoh6zySBTGYLKNlNtYE3pAJH8do1cCA8Kwtzxc2vFE24KT3rC8gIc +LrRjg9ox9i11MLL7q8Ju26nADrn5Z9TDJVd06wW06Y613ijNzHoU5HEDy01hLmFX +xRmpC5iEGuh5KdmyjS//V2pm4M6rlagplmNwEmceOuHbsCFx13ye/aoXbv4r+zgX +FNFmp6+atXDMyGOBOozAKql2N87jAgMBAAGjgf4wgfswDgYDVR0PAQH/BAQDAgGG +MB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEFBQcDAjASBgNVHRMBAf8ECDAGAQH/ +AgEAMB0GA1UdDgQWBBTeGx7teRXUPjckwyG77DQ5bUKyMDAfBgNVHSMEGDAWgBTk +rysmcRorSCeFL1JmLO/wiRNxPjA0BggrBgEFBQcBAQQoMCYwJAYIKwYBBQUHMAKG +GGh0dHA6Ly9pLnBraS5nb29nL3IxLmNydDArBgNVHR8EJDAiMCCgHqAchhpodHRw +Oi8vYy5wa2kuZ29vZy9yL3IxLmNybDATBgNVHSAEDDAKMAgGBmeBDAECATANBgkq +hkiG9w0BAQsFAAOCAgEARXWL5R87RBOWGqtY8TXJbz3S0DNKhjO6V1FP7sQ02hYS +TL8Tnw3UVOlIecAwPJQl8hr0ujKUtjNyC4XuCRElNJThb0Lbgpt7fyqaqf9/qdLe +SiDLs/sDA7j4BwXaWZIvGEaYzq9yviQmsR4ATb0IrZNBRAq7x9UBhb+TV+PfdBJT +DhEl05vc3ssnbrPCuTNiOcLgNeFbpwkuGcuRKnZc8d/KI4RApW//mkHgte8y0YWu +ryUJ8GLFbsLIbjL9uNrizkqRSvOFVU6xddZIMy9vhNkSXJ/UcZhjJY1pXAprffJB +vei7j+Qi151lRehMCofa6WBmiA4fx+FOVsV2/7R6V2nyAiIJJkEd2nSi5SnzxJrl +Xdaqev3htytmOPvoKWa676ATL/hzfvDaQBEcXd2Ppvy+275W+DKcH0FBbX62xevG +iza3F4ydzxl6NJ8hk8R+dDXSqv1MbRT1ybB5W0k8878XSOjvmiYTDIfyc9acxVJr +Y/cykHipa+te1pOhv7wYPYtZ9orGBV5SGOJm4NrB3K1aJar0RfzxC3ikr7Dyc6Qw +qDTBU39CluVIQeuQRgwG3MuSxl7zRERDRilGoKb8uY45JzmxWuKxrfwT/478JuHU +/oTxUFqOl2stKnn7QGTq8z29W+GgBLCXSBxC9epaHM0myFH/FJlniXJfHeytWt0= +-----END CERTIFICATE----- + diff --git a/certs/app_certs/google-root.pem b/certs/app_certs/google-root.pem new file mode 100644 index 0000000..455abc9 --- /dev/null +++ b/certs/app_certs/google-root.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFYjCCBEqgAwIBAgIQd70NbNs2+RrqIQ/E8FjTDTANBgkqhkiG9w0BAQsFADBX +MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEQMA4GA1UE +CxMHUm9vdCBDQTEbMBkGA1UEAxMSR2xvYmFsU2lnbiBSb290IENBMB4XDTIwMDYx +OTAwMDA0MloXDTI4MDEyODAwMDA0MlowRzELMAkGA1UEBhMCVVMxIjAgBgNVBAoT +GUdvb2dsZSBUcnVzdCBTZXJ2aWNlcyBMTEMxFDASBgNVBAMTC0dUUyBSb290IFIx +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAthECix7joXebO9y/lD63 +ladAPKH9gvl9MgaCcfb2jH/76Nu8ai6Xl6OMS/kr9rH5zoQdsfnFl97vufKj6bwS +iV6nqlKr+CMny6SxnGPb15l+8Ape62im9MZaRw1NEDPjTrETo8gYbEvs/AmQ351k +KSUjB6G00j0uYODP0gmHu81I8E3CwnqIiru6z1kZ1q+PsAewnjHxgsHA3y6mbWwZ +DrXYfiYaRQM9sHmklCitD38m5agI/pboPGiUU+6DOogrFZYJsuB6jC511pzrp1Zk +j5ZPaK49l8KEj8C8QMALXL32h7M1bKwYUH+E4EzNktMg6TO8UpmvMrUpsyUqtEj5 +cuHKZPfmghCN6J3Cioj6OGaK/GP5Afl4/Xtcd/p2h/rs37EOeZVXtL0m79YB0esW +CruOC7XFxYpVq9Os6pFLKcwZpDIlTirxZUTQAs6qzkm06p98g7BAe+dDq6dso499 +iYH6TKX/1Y7DzkvgtdizjkXPdsDtQCv9Uw+wp9U7DbGKogPeMa3Md+pvez7W35Ei +Eua++tgy/BBjFFFy3l3WFpO9KWgz7zpm7AeKJt8T11dleCfeXkkUAKIAf5qoIbap +sZWwpbkNFhHax2xIPEDgfg1azVY80ZcFuctL7TlLnMQ/0lUTbiSw1nH69MG6zO0b +9f6BQdgAmD06yK56mDcYBZUCAwEAAaOCATgwggE0MA4GA1UdDwEB/wQEAwIBhjAP +BgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTkrysmcRorSCeFL1JmLO/wiRNxPjAf +BgNVHSMEGDAWgBRge2YaRQ2XyolQL30EzTSo//z9SzBgBggrBgEFBQcBAQRUMFIw +JQYIKwYBBQUHMAGGGWh0dHA6Ly9vY3NwLnBraS5nb29nL2dzcjEwKQYIKwYBBQUH +MAKGHWh0dHA6Ly9wa2kuZ29vZy9nc3IxL2dzcjEuY3J0MDIGA1UdHwQrMCkwJ6Al +oCOGIWh0dHA6Ly9jcmwucGtpLmdvb2cvZ3NyMS9nc3IxLmNybDA7BgNVHSAENDAy +MAgGBmeBDAECATAIBgZngQwBAgIwDQYLKwYBBAHWeQIFAwIwDQYLKwYBBAHWeQIF +AwMwDQYJKoZIhvcNAQELBQADggEBADSkHrEoo9C0dhemMXoh6dFSPsjbdBZBiLg9 +NR3t5P+T4Vxfq7vqfM/b5A3Ri1fyJm9bvhdGaJQ3b2t6yMAYN/olUazsaL+yyEn9 +WprKASOshIArAoyZl+tJaox118fessmXn1hIVw41oeQa1v1vg4Fv74zPl6/AhSrw +9U5pCZEt4Wi4wStz6dTZ/CLANx8LZh1J7QJVj2fhMtfTJr9w4z30Z209fOU0iOMy ++qduBmpvvYuR7hZL6Dupszfnw0Skfths18dG9ZKb59UhvmaSGZRVbNQpsg3BZlvi +d0lIKO2d1xozclOzgjXPYovJJIultzkMu34qQb9Sz/yilrbCgj8= +-----END CERTIFICATE----- + diff --git a/certs/app_certs/google-server.pem b/certs/app_certs/google-server.pem new file mode 100644 index 0000000..9ef5df6 --- /dev/null +++ b/certs/app_certs/google-server.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEeDCCA2CgAwIBAgIRAIIOTeFcOkvUEAhZaRElMkYwDQYJKoZIhvcNAQELBQAw +OzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczEM +MAoGA1UEAxMDV1IyMB4XDTI1MDMyMDExMjAzNFoXDTI1MDYxMjExMjAzM1owHjEc +MBoGA1UEAxMTYWNjb3VudHMuZ29vZ2xlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49 +AwEHA0IABLxsHfzEUtktVB6CycZH6AaINi5Bhsw07rZEuJhOE3X/gATjzeZNf+1j +pl/fApfNdgTlNWCFrrP36YTzx+82aOOjggJdMIICWTAOBgNVHQ8BAf8EBAMCB4Aw +EwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUliDn +8rqSai7lTNzAcR/TSF65DKQwHwYDVR0jBBgwFoAU3hse7XkV1D43JMMhu+w0OW1C +sjAwWAYIKwYBBQUHAQEETDBKMCEGCCsGAQUFBzABhhVodHRwOi8vby5wa2kuZ29v +Zy93cjIwJQYIKwYBBQUHMAKGGWh0dHA6Ly9pLnBraS5nb29nL3dyMi5jcnQwNQYD +VR0RBC4wLIITYWNjb3VudHMuZ29vZ2xlLmNvbYIVKi5wYXJ0bmVyLmFuZHJvaWQu +Y29tMBMGA1UdIAQMMAowCAYGZ4EMAQIBMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6 +Ly9jLnBraS5nb29nL3dyMi83NXI0WnlBM3ZBMC5jcmwwggEEBgorBgEEAdZ5AgQC +BIH1BIHyAPAAdwDPEVbu1S58r/OHW9lpLpvpGnFnSrAX7KwB0lt3zsw7CAAAAZWz +f4ySAAAEAwBIMEYCIQCynwunRTr3Zl0HlSl791D5pedOwAhFDr9T750YJAGPzAIh +AP3zIGEjXeps4Ft1FUwfl3Yco+L1fkyybt4YT77pfBZ+AHUAouMK5EXvva2bfjjt +R2d3U9eCW4SU1yteGyzEuVCkR+cAAAGVs3+MdQAABAMARjBEAiA+h5fkWNb51WXn +kDYKXu+FW43PCjAhzfskOo5d6W6mQwIgT3ky97SykxawRlhso0Z75lCh/2qqLdPb +Ro8LyQDOxLswDQYJKoZIhvcNAQELBQADggEBAHFdiq+W1/SaIFfURWnomlywdd31 +1o8b/YH/K9tUbzB90FrtXDc8Y9+GbZ6naNAYeoQVUrRmlaZuPlTJbpjVDLoJu+1i +WrzpT6On00Z8RJJmBV/gtYJ6s3GxMpFlDmXZDS0W2yNvs9O2YoRQdjNYJ9lnjQEv +NgoI9oOJ5s37xqtijV7qyALHzD2+dZlP77jzaa+18U0j1cV3V4vmBED/cNxlaX1i +bAO53XukfH5ylvOxB5K09mIA2OYYU5N8U9XBFGRVBKLfzo0rdmnt9XqvZsH1CyKJ +tUS0x6P17D4f3mOEqVjQMJG7EVy4smqEUnRfOIrleHIFXMfv8o6MPVlgc9I= +-----END CERTIFICATE----- + diff --git a/certs/app_certs/google.crt b/certs/app_certs/google.crt new file mode 100644 index 0000000..0b18e24 --- /dev/null +++ b/certs/app_certs/google.crt @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEeDCCA2CgAwIBAgIRAIIOTeFcOkvUEAhZaRElMkYwDQYJKoZIhvcNAQELBQAw +OzELMAkGA1UEBhMCVVMxHjAcBgNVBAoTFUdvb2dsZSBUcnVzdCBTZXJ2aWNlczEM +MAoGA1UEAxMDV1IyMB4XDTI1MDMyMDExMjAzNFoXDTI1MDYxMjExMjAzM1owHjEc +MBoGA1UEAxMTYWNjb3VudHMuZ29vZ2xlLmNvbTBZMBMGByqGSM49AgEGCCqGSM49 +AwEHA0IABLxsHfzEUtktVB6CycZH6AaINi5Bhsw07rZEuJhOE3X/gATjzeZNf+1j +pl/fApfNdgTlNWCFrrP36YTzx+82aOOjggJdMIICWTAOBgNVHQ8BAf8EBAMCB4Aw +EwYDVR0lBAwwCgYIKwYBBQUHAwEwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQUliDn +8rqSai7lTNzAcR/TSF65DKQwHwYDVR0jBBgwFoAU3hse7XkV1D43JMMhu+w0OW1C +sjAwWAYIKwYBBQUHAQEETDBKMCEGCCsGAQUFBzABhhVodHRwOi8vby5wa2kuZ29v +Zy93cjIwJQYIKwYBBQUHMAKGGWh0dHA6Ly9pLnBraS5nb29nL3dyMi5jcnQwNQYD +VR0RBC4wLIITYWNjb3VudHMuZ29vZ2xlLmNvbYIVKi5wYXJ0bmVyLmFuZHJvaWQu +Y29tMBMGA1UdIAQMMAowCAYGZ4EMAQIBMDYGA1UdHwQvMC0wK6ApoCeGJWh0dHA6 +Ly9jLnBraS5nb29nL3dyMi83NXI0WnlBM3ZBMC5jcmwwggEEBgorBgEEAdZ5AgQC +BIH1BIHyAPAAdwDPEVbu1S58r/OHW9lpLpvpGnFnSrAX7KwB0lt3zsw7CAAAAZWz +f4ySAAAEAwBIMEYCIQCynwunRTr3Zl0HlSl791D5pedOwAhFDr9T750YJAGPzAIh +AP3zIGEjXeps4Ft1FUwfl3Yco+L1fkyybt4YT77pfBZ+AHUAouMK5EXvva2bfjjt +R2d3U9eCW4SU1yteGyzEuVCkR+cAAAGVs3+MdQAABAMARjBEAiA+h5fkWNb51WXn +kDYKXu+FW43PCjAhzfskOo5d6W6mQwIgT3ky97SykxawRlhso0Z75lCh/2qqLdPb +Ro8LyQDOxLswDQYJKoZIhvcNAQELBQADggEBAHFdiq+W1/SaIFfURWnomlywdd31 +1o8b/YH/K9tUbzB90FrtXDc8Y9+GbZ6naNAYeoQVUrRmlaZuPlTJbpjVDLoJu+1i +WrzpT6On00Z8RJJmBV/gtYJ6s3GxMpFlDmXZDS0W2yNvs9O2YoRQdjNYJ9lnjQEv +NgoI9oOJ5s37xqtijV7qyALHzD2+dZlP77jzaa+18U0j1cV3V4vmBED/cNxlaX1i +bAO53XukfH5ylvOxB5K09mIA2OYYU5N8U9XBFGRVBKLfzo0rdmnt9XqvZsH1CyKJ +tUS0x6P17D4f3mOEqVjQMJG7EVy4smqEUnRfOIrleHIFXMfv8o6MPVlgc9I= +-----END CERTIFICATE----- diff --git a/certs/app_certs/kakao.crt b/certs/app_certs/kakao.crt new file mode 100644 index 0000000..fcf3943 --- /dev/null +++ b/certs/app_certs/kakao.crt @@ -0,0 +1,36 @@ +-----BEGIN CERTIFICATE----- +MIIGXDCCBUSgAwIBAgIQCcI+65G/+xGGjgArGV0WwTANBgkqhkiG9w0BAQsFADBe +MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 +d3cuZGlnaWNlcnQuY29tMR0wGwYDVQQDExRUaGF3dGUgVExTIFJTQSBDQSBHMTAe +Fw0yNDA5MDIwMDAwMDBaFw0yNTA5MjkyMzU5NTlaMF0xCzAJBgNVBAYTAktSMRAw +DgYDVQQIEwdKZWp1LWRvMRAwDgYDVQQHEwdKZWp1LXNpMRQwEgYDVQQKEwtLYWth +byBDb3JwLjEUMBIGA1UEAwwLKi5rYWthby5jb20wggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCUYrQCyt9RoTxsI9UGMPcKn2P6nhSrPDZgoWAeHMaxGL5Z +egeej1jqMfmVHJofj2gVddzo0IgiAQNX8zciSfbRoZ0yswLpBah83FzAtUumVv+j +Uw3lNgJYiZ8JZD57mxAhr7sBjTbsNnWQwSQHHU4RwkaOUkIIpptgxly2RLQZfQGX +szadKsQt6JQIN3SKWsYdwL7rvOOjTsuFkrkL8VD+O+BZ+Vygi8WoFA03FXqB3/Zo +zBPZ63GsCtMX8R/fIVHauYN8uon7NgdFv03bkyMEQRhTN4x4V9bZBI4tHuJaQQKq +o0aXL19ajFcZfU31sg0VUhUlBdPxU4tymQGoxUulAgMBAAGjggMVMIIDETAfBgNV +HSMEGDAWgBSljP4yzOsPLNQZxgi4ACSIXcPFtzAdBgNVHQ4EFgQULYHFPZeJYSS2 +GU5r3cP6GZKDFDAwIQYDVR0RBBowGIILKi5rYWthby5jb22CCWtha2FvLmNvbTA+ +BgNVHSAENzA1MDMGBmeBDAECAjApMCcGCCsGAQUFBwIBFhtodHRwOi8vd3d3LmRp +Z2ljZXJ0LmNvbS9DUFMwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUF +BwMBBggrBgEFBQcDAjA7BgNVHR8ENDAyMDCgLqAshipodHRwOi8vY2RwLnRoYXd0 +ZS5jb20vVGhhd3RlVExTUlNBQ0FHMS5jcmwwcAYIKwYBBQUHAQEEZDBiMCQGCCsG +AQUFBzABhhhodHRwOi8vc3RhdHVzLnRoYXd0ZS5jb20wOgYIKwYBBQUHMAKGLmh0 +dHA6Ly9jYWNlcnRzLnRoYXd0ZS5jb20vVGhhd3RlVExTUlNBQ0FHMS5jcnQwDAYD +VR0TAQH/BAIwADCCAX4GCisGAQQB1nkCBAIEggFuBIIBagFoAHUA3dzKNJXX4RYF +55Uy+sef+D0cUN/bADoUEnYKLKy7yCoAAAGRsBHz1wAABAMARjBEAiBf+r1MYrtG +yKMdKL5Ki70uyLixh08hsKB84Z3GtGOnrgIgPvrKvoKO5lrHvPDuh6JSJj0hfGvC +h/aAmlDHBtp+gs4AdgB9WR4S4XgqexxhZ3xe/fjQh1wUoE6VnrkDL9kOjC55uAAA +AZGwEfPRAAAEAwBHMEUCIH72goc+J5aqWtdkIZgdkXQROOcfkLCUXEI0pe4NgsbE +AiEAlOrZgTfQqloanUY+og7pQXmQ0visIswhNRUgRMR5Qy8AdwDm0jFjQHeMwRBB +Btdxuc7B0kD2loSG+7qHMh39HjeOUAAAAZGwEfPyAAAEAwBIMEYCIQCVOTmU9r/G +C6tYNZnjQvWbHIHIMbOtctHYCp861ZOKdAIhAI3rYocpxAuyPdUsTVRV4nUM4YT8 +Iirf/8YpFJwNdONZMA0GCSqGSIb3DQEBCwUAA4IBAQAGhp+PD+OOujkJlR9qn9l0 +N5L3+zCcIyjtgLz1SwIaSjeE7GkHs3YVEt7ZoRU/iBZltidxRUSqsCqYCJw7bArK +g3QyOUIcbD7Xrlm+k6ZX2XDXs5wAyB7xP+Ax4ycMrtPFUgLqEkPdHl43uhp3nEcu +vlUCeaD9gMuJ/h9VFE6qydR0T+1cOr0Mvwc4lTrj+7oO3lRutTUrTiD1CmqTuMYZ +119MR/68H45KRDR1tZIxwTbC+qZy1kHXRkeA4SoctbFIXSmg9vdl1qL2+TGLEebz +B26KJbUTV0GBj2ASYNBXUr4nY31ZSOpGd6UKTANVYwsQVuSE2qvwVABWtLwMYIsD +-----END CERTIFICATE----- diff --git a/certs/app_certs/keystore.p12 b/certs/app_certs/keystore.p12 new file mode 100644 index 0000000..49a7bb0 Binary files /dev/null and b/certs/app_certs/keystore.p12 differ diff --git a/certs/app_certs/keystore.pem b/certs/app_certs/keystore.pem new file mode 100644 index 0000000..8b403af --- /dev/null +++ b/certs/app_certs/keystore.pem @@ -0,0 +1,78 @@ +Bag Attributes + friendlyName: devlog-app + localKeyID: 7A 75 ED 96 03 AC E0 78 BB DB F3 B9 DD 89 90 6D 4C 76 C7 8C +subject=C = KR, ST = Pusan, L = Pusan, O = WUDC, CN = devlog-app +issuer=C = KR, ST = Pusan, L = Pusan, O = WUDC, CN = WUDC +-----BEGIN CERTIFICATE----- +MIIDIzCCAgsCFFhWSQG3H1J81Xt7M+sUWjv4RQLAMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDIzODQ4WhcNMjYw +NDE2MDIzODQ4WjBRMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMRMwEQYDVQQDDApkZXZsb2ctYXBwMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAksMadi9QiZ6rkVmfeqGRjJ2D +NzkAROiCe+FiziwN+q82Vl8LVlt8z3DCGilMOkCwNDp8QSlp153QMy0l6yVzmxKD +X5QakyNHlQJGO7Afm7dbCMzIIxbY5LrLtGzbO9Hrc+hupVPnV/nf4/xPuOcqxwBT +T/cOSaaya5KHajXkBjDXRPNrPj50tDf0gQP+f1YiCd/wVaTv8Xuu6yLa7A5dnAaH +WalsAmhIxVHegt0bAvJ7CzMqFcSarGJEBOEjKMHXdX2s56Nr56SGsxAbkOroyItW +GgrvlfxOkkV7Nhg6+8vJc11BPW6yZ6s+6mkKvB4DKjdaYXSsGsec/98c6CYFAQID +AQABMA0GCSqGSIb3DQEBCwUAA4IBAQCNlBmDRL3e1ryjlOEgQSa49YCjDHGSrWli +PKYvdL4pJa9XXJFXssSvKRXfGlSfva+VVGkouqSbYLdwNEF1ixp3G6RGdwq14yZw ++I6bi/76Yops/JTRsekLcV3E/oqKuaIMrdo8RgB6kRmxmMNnB1+Zb9amNEc+mduM +1L7r8nPyUWsxLAUjCoW/W5KMU1JjVxm7PRJmnrBvRzA8eXnxXJBesZMJ2fUAMJCt +RvGTRy5f/M1Xb8iJw6O2zu3FAszn+eZBHsXsy1z45vNNoWH+DTFf+kCuTJqiPwNA +/4tYRkZIUbVg6NlrCSqFyxRYRY3R/hujpekvtE3W9cFBCsiDHLbp +-----END CERTIFICATE----- +Bag Attributes: +subject=C = KR, ST = Pusan, L = Pusan, O = WUDC, CN = WUDC +issuer=C = KR, ST = Pusan, L = Pusan, O = WUDC, CN = WUDC +-----BEGIN CERTIFICATE----- +MIIDHTCCAgUCFFw5T+2r/PXbAFYdJUeCO9NurTMhMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDE0NzIwWhcNMzUw +NDE0MDE0NzIwWjBLMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMQ0wCwYDVQQDDARXVURDMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntyNuLTcMjWIgfrSPi+pvf3hNsHLzlV1 +Ze+hXvqs4fE0p36qVbWRUkwv+Vp4drKrlXrY5Em7JCNZ0IT9PuZ3PwFbnG5FwV+O +H3B7WrRx4JHhLZU3w+WHFGfWFDZS2eYWrf1f1tDOlyq92xx0QEqDNE+gUMJ+z1N7 +5yoDForShgTmxD+KzYp4e7BvKKKn/U6YosBC8GyiMWdcka0IUMoTwpQRQ3Vlx/KX ++YGJlwWA9wvNWOqgcYCxeCIa7X+8K9i92Py01T82zqLMcr52Ri2JEdMzHz4pp/aZ +d56pfUhGuN6ET18fTTGeKuB1PrPu80OgfOYvJL9u1/twuO/xi/naBQIDAQABMA0G +CSqGSIb3DQEBCwUAA4IBAQAudZAxVxMh/aUeTj6WK5mpVpUINmAwt938FVSQ3nUC +mTafNjSNfu/Me+PvjCkmf9RNk5BSdHVwxseOBaU0WZnvLM056ayyQnSwWT53WwtZ +CSb3VHBbwaJqAiAnhQxCHHhWDK4rtIPrWZ/SCek2E5l29ccZHTuVnaFF3pG8YRs1 ++UXVWeCB0fIJwQdugtGkzVzgO1HW7tKyG1BqCP9q9FnfODia9d6jKAA5dScBPanG +yPNI8eq4Fpk4R6P1jM2jw9pCJ4CW4lpgGQD0KATJve+ooHJqL3Ri6xkljNymGfbP +UD8eyRxgUaKUloQY2UbafzO+cLn2uRiB4sfn/T8hybeW +-----END CERTIFICATE----- +Bag Attributes + friendlyName: devlog-app + localKeyID: 7A 75 ED 96 03 AC E0 78 BB DB F3 B9 DD 89 90 6D 4C 76 C7 8C +Key Attributes: +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCSwxp2L1CJnquR +WZ96oZGMnYM3OQBE6IJ74WLOLA36rzZWXwtWW3zPcMIaKUw6QLA0OnxBKWnXndAz +LSXrJXObEoNflBqTI0eVAkY7sB+bt1sIzMgjFtjkusu0bNs70etz6G6lU+dX+d/j +/E+45yrHAFNP9w5JprJrkodqNeQGMNdE82s+PnS0N/SBA/5/ViIJ3/BVpO/xe67r +ItrsDl2cBodZqWwCaEjFUd6C3RsC8nsLMyoVxJqsYkQE4SMowdd1fazno2vnpIaz +EBuQ6ujIi1YaCu+V/E6SRXs2GDr7y8lzXUE9brJnqz7qaQq8HgMqN1phdKwax5z/ +3xzoJgUBAgMBAAECggEAB0rjjzjVpyj3vH64EndhzJttEDroXQQyq6Ys6zK8NRcs +u4j4fr+ICaTAOF2R+JkLSGUZlIFSzZB9bnWRW0heoLeASKkK0wHfRjO5OrELOQkY +4GyQi1HQ0DjJ83qvQB8ztGw5x0ROjAwSCHmamoT+FqpY+XG8x4MdfYPn76qi3H3Q +iRs5SqzksnYL5irsdOLh84FwJuXhQzZVaBFnK38P2xWNRS2uiJYQoDjEavYcRoLr +ZmoZgtyLn7cMXRD177y2KhixhLReJqiIP3fX2SZCD8sGOzvLxlcUQh/cusIyw+4M +t5fAb3G3kpxey6IpeWwriXBVhPlv/t0h1rB3m+3jSwKBgQDC8fXRAm6GVIoTAjiY +ipxFu88u2KDhfN+HRzLKQwy3lcl9V4qFybI1oR2HxllMyB03+gtcxT2gFe4AI6az +KUZSjjYtzGqSxeus4cFc4Ec11LJbPk5fb2CnRLhQdV748YvWX4P/ZXkcI1mu34k0 +namLtM/jFvJQ+7uvGBkihK580wKBgQDAuf3EiOqDFG2qczaTcqzS4VqPYtXmCqw4 +vwjZNRMmvXNzNly2AuTA7ham3QTKDcXmeaF/zmFxWZhNdiIbW0oolraQ9ZNrqZap +eKBHrAhEOTdF6ZwEXjK88c0/TOUA8HC3enqlO6qrHNIPtUTsR+lTmlyo/SiLSWiO +wnKqSoICWwKBgCt5+vCaMjwTLpf+rtCWWTPUJuizt22Sg+ePoWwqd/OZnE4v79zW +lsAPJp7ZRaEyIBIT2eTeuFezjFjLmqnqUpymyr58EGiba2wrDQzBmCARR5XB14jB +NjUXxmNrSbsLY7xzoOScpN35pE6z2824O8/Ei3iB7ZjSC5GJNlHUdXWxAoGAJO0+ +F0MYk+b9IDSVF2lYfctZ+7E3RK101CaePmfx9HFGRqP63ZDuXZ0A0BX3DfPXoFJb +xE4502sUSHtDC7TRH7fI4Tt8dJt4153aMAFhUBkaYxXgo+GcnSFDb0Z/dk+beTxJ +dZFaIRETmpjjzNX2eeNQr7xZ4V4+X2QYblJ6WJMCgYEAnz7BSZgwauHJaQ2zUuRC +nwsrQ8x2Lb6oUtl517F5L9KdDCuZDhDT/fR5i78GRQUBRJAeA1TeKeA1oSCn7uI8 +Sf+cnkc3LrfgesTE5cg10qsNZ9WJV0uZto0Fi0Ku6JlLFIGn64rMedyyTbXoDlCY +gH32hd6WfH01of4nr/ShfD8= +-----END PRIVATE KEY----- diff --git a/certs/app_certs/naver.crt b/certs/app_certs/naver.crt new file mode 100644 index 0000000..9bcae38 --- /dev/null +++ b/certs/app_certs/naver.crt @@ -0,0 +1,42 @@ +-----BEGIN CERTIFICATE----- +MIIHVjCCBt2gAwIBAgIQBi5XjUxQQ8sxGGoah/DeHzAKBggqhkjOPQQDAzBWMQsw +CQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMTAwLgYDVQQDEydEaWdp +Q2VydCBUTFMgSHlicmlkIEVDQyBTSEEzODQgMjAyMCBDQTEwHhcNMjUwMzA1MDAw +MDAwWhcNMjYwMzE3MjM1OTU5WjBlMQswCQYDVQQGEwJLUjEUMBIGA1UECBMLR3ll +b25nZ2ktZG8xFDASBgNVBAcTC1Nlb25nbmFtLXNpMRQwEgYDVQQKEwtOQVZFUiBD +b3JwLjEUMBIGA1UEAwwLKi5uYXZlci5jb20wWTATBgcqhkjOPQIBBggqhkjOPQMB +BwNCAARwbkPreVaks3onKHkhrKVnU2j1DaZfqIXviUhkLoZZSWBUSyhROzT17eyv +Of5BLlim5cY6hR2n37rbHm9c2kMPo4IFfDCCBXgwHwYDVR0jBBgwFoAUCrwIKReM +pTlteg7OM8cus+37w3owHQYDVR0OBBYEFIyPahhzclG4UZZ7U2d8VizKld9BMIIC +DgYDVR0RBIICBTCCAgGCCyoubmF2ZXIuY29tggsqLm5hdmVyLm5ldIISKi5zZWFy +Y2gubmF2ZXIuY29tghAqLnZldGEubmF2ZXIuY29tgg8qLm5pZC5uYXZlci5jb22C +ESoudGVybXMubmF2ZXIuY29tghMqLnN3aW5kb3cubmF2ZXIuY29tghEqLnN0b3Jl +Lm5hdmVyLmNvbYIRKi5zdG9jay5uYXZlci5jb22CEiouc3BvcnRzLm5hdmVyLmNv +bYIUKi5zaG9wcGluZy5uYXZlci5jb22CECoubmV3cy5uYXZlci5jb22CECoucG9z +dC5uYXZlci5jb22CECouYmxvZy5uYXZlci5jb22CDyoua2luLm5hdmVyLmNvbYIT +Ki5maW5hbmNlLm5hdmVyLmNvbYIVKi5lbnRlcnRhaW4ubmF2ZXIuY29tghAqLmRp +Y3QubmF2ZXIuY29tghYqLmNvbW1lbnRib3gubmF2ZXIuY29tghMqLmNvbW1lbnQu +bmF2ZXIuY29tghEqLmNvbWljLm5hdmVyLmNvbYIQKi5jYWZlLm5hdmVyLmNvbYIO +Ki5hZC5uYXZlci5jb22CEiouZXhwZXJ0Lm5hdmVyLmNvbYINKi5tLm5hdmVyLmNv +bYIQKi5saWtlLm5hdmVyLmNvbYIaKi5pbXByZXNzaW9uLW5lby5uYXZlci5jb20w +PgYDVR0gBDcwNTAzBgZngQwBAgIwKTAnBggrBgEFBQcCARYbaHR0cDovL3d3dy5k +aWdpY2VydC5jb20vQ1BTMA4GA1UdDwEB/wQEAwIDiDAdBgNVHSUEFjAUBggrBgEF +BQcDAQYIKwYBBQUHAwIwgZsGA1UdHwSBkzCBkDBGoESgQoZAaHR0cDovL2NybDMu +ZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VExTSHlicmlkRUNDU0hBMzg0MjAyMENBMS0x +LmNybDBGoESgQoZAaHR0cDovL2NybDQuZGlnaWNlcnQuY29tL0RpZ2lDZXJ0VExT +SHlicmlkRUNDU0hBMzg0MjAyMENBMS0xLmNybDCBhQYIKwYBBQUHAQEEeTB3MCQG +CCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wTwYIKwYBBQUHMAKG +Q2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRMU0h5YnJpZEVD +Q1NIQTM4NDIwMjBDQTEtMS5jcnQwDAYDVR0TAQH/BAIwADCCAX8GCisGAQQB1nkC +BAIEggFvBIIBawFpAHYADleUvPOuqT4zGyyZB7P3kN+bwj1xMiXdIaklrGHFTiEA +AAGVZImplQAABAMARzBFAiEAuYk/yx3umm8H+XrXNcgP1if28lxiHNYLfG8kdf5t +ytUCIHCJRMX2+NLEBLc33zsSLhCD6uim96ZzeAsGMbxfI8izAHYAZBHEbKQS7KeJ +HKICLgC8q08oB9QeNSer6v7VA8l9zfAAAAGVZImp1QAABAMARzBFAiEAtjwIDf5+ +E4Tbiu8zvK0U3xKGjKTd2fQ7F5nhs1N7zcQCIBG4fBRor6GqfkYz8LDtHi9r93dv +loatNSKac1+je1GxAHcASZybad4dfOz8Nt7Nh2SmuFuvCoeAGdFVUvvp6ynd+MMA +AAGVZImp4wAABAMASDBGAiEAs/xntzku3+3gRoAYmj5tTFVWikLu13W+2ZjHzdp2 +jCsCIQDsaHK22U1wY+ZbqEVK1foz9++oKsDuZDlOqd4hZZn0OzAKBggqhkjOPQQD +AwNnADBkAjAclZurq/yDjWZHjlmNDckGmXrcUlidhqJ8RofrAR/SE5i4yyWhM35Y +n14mcDi/6GkCMBwbBr+AwCHY1k67nYGIsxuQAt7VWON2q73jl7kqQ8q79WR2g89a +J7WShHkL3R2VCw== +-----END CERTIFICATE----- diff --git a/certs/app_certs/server-cert.pem b/certs/app_certs/server-cert.pem new file mode 100644 index 0000000..e9c6e0f --- /dev/null +++ b/certs/app_certs/server-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDIzCCAgsCFFhWSQG3H1J81Xt7M+sUWjv4RQLAMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDIzODQ4WhcNMjYw +NDE2MDIzODQ4WjBRMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMRMwEQYDVQQDDApkZXZsb2ctYXBwMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAksMadi9QiZ6rkVmfeqGRjJ2D +NzkAROiCe+FiziwN+q82Vl8LVlt8z3DCGilMOkCwNDp8QSlp153QMy0l6yVzmxKD +X5QakyNHlQJGO7Afm7dbCMzIIxbY5LrLtGzbO9Hrc+hupVPnV/nf4/xPuOcqxwBT +T/cOSaaya5KHajXkBjDXRPNrPj50tDf0gQP+f1YiCd/wVaTv8Xuu6yLa7A5dnAaH +WalsAmhIxVHegt0bAvJ7CzMqFcSarGJEBOEjKMHXdX2s56Nr56SGsxAbkOroyItW +GgrvlfxOkkV7Nhg6+8vJc11BPW6yZ6s+6mkKvB4DKjdaYXSsGsec/98c6CYFAQID +AQABMA0GCSqGSIb3DQEBCwUAA4IBAQCNlBmDRL3e1ryjlOEgQSa49YCjDHGSrWli +PKYvdL4pJa9XXJFXssSvKRXfGlSfva+VVGkouqSbYLdwNEF1ixp3G6RGdwq14yZw ++I6bi/76Yops/JTRsekLcV3E/oqKuaIMrdo8RgB6kRmxmMNnB1+Zb9amNEc+mduM +1L7r8nPyUWsxLAUjCoW/W5KMU1JjVxm7PRJmnrBvRzA8eXnxXJBesZMJ2fUAMJCt +RvGTRy5f/M1Xb8iJw6O2zu3FAszn+eZBHsXsy1z45vNNoWH+DTFf+kCuTJqiPwNA +/4tYRkZIUbVg6NlrCSqFyxRYRY3R/hujpekvtE3W9cFBCsiDHLbp +-----END CERTIFICATE----- diff --git a/certs/app_certs/server-full-chain.pem b/certs/app_certs/server-full-chain.pem new file mode 100644 index 0000000..aab39ab --- /dev/null +++ b/certs/app_certs/server-full-chain.pem @@ -0,0 +1,38 @@ +-----BEGIN CERTIFICATE----- +MIIDIzCCAgsCFFhWSQG3H1J81Xt7M+sUWjv4RQLAMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDIzODQ4WhcNMjYw +NDE2MDIzODQ4WjBRMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMRMwEQYDVQQDDApkZXZsb2ctYXBwMIIB +IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAksMadi9QiZ6rkVmfeqGRjJ2D +NzkAROiCe+FiziwN+q82Vl8LVlt8z3DCGilMOkCwNDp8QSlp153QMy0l6yVzmxKD +X5QakyNHlQJGO7Afm7dbCMzIIxbY5LrLtGzbO9Hrc+hupVPnV/nf4/xPuOcqxwBT +T/cOSaaya5KHajXkBjDXRPNrPj50tDf0gQP+f1YiCd/wVaTv8Xuu6yLa7A5dnAaH +WalsAmhIxVHegt0bAvJ7CzMqFcSarGJEBOEjKMHXdX2s56Nr56SGsxAbkOroyItW +GgrvlfxOkkV7Nhg6+8vJc11BPW6yZ6s+6mkKvB4DKjdaYXSsGsec/98c6CYFAQID +AQABMA0GCSqGSIb3DQEBCwUAA4IBAQCNlBmDRL3e1ryjlOEgQSa49YCjDHGSrWli +PKYvdL4pJa9XXJFXssSvKRXfGlSfva+VVGkouqSbYLdwNEF1ixp3G6RGdwq14yZw ++I6bi/76Yops/JTRsekLcV3E/oqKuaIMrdo8RgB6kRmxmMNnB1+Zb9amNEc+mduM +1L7r8nPyUWsxLAUjCoW/W5KMU1JjVxm7PRJmnrBvRzA8eXnxXJBesZMJ2fUAMJCt +RvGTRy5f/M1Xb8iJw6O2zu3FAszn+eZBHsXsy1z45vNNoWH+DTFf+kCuTJqiPwNA +/4tYRkZIUbVg6NlrCSqFyxRYRY3R/hujpekvtE3W9cFBCsiDHLbp +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDHTCCAgUCFFw5T+2r/PXbAFYdJUeCO9NurTMhMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDE0NzIwWhcNMzUw +NDE0MDE0NzIwWjBLMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMQ0wCwYDVQQDDARXVURDMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntyNuLTcMjWIgfrSPi+pvf3hNsHLzlV1 +Ze+hXvqs4fE0p36qVbWRUkwv+Vp4drKrlXrY5Em7JCNZ0IT9PuZ3PwFbnG5FwV+O +H3B7WrRx4JHhLZU3w+WHFGfWFDZS2eYWrf1f1tDOlyq92xx0QEqDNE+gUMJ+z1N7 +5yoDForShgTmxD+KzYp4e7BvKKKn/U6YosBC8GyiMWdcka0IUMoTwpQRQ3Vlx/KX ++YGJlwWA9wvNWOqgcYCxeCIa7X+8K9i92Py01T82zqLMcr52Ri2JEdMzHz4pp/aZ +d56pfUhGuN6ET18fTTGeKuB1PrPu80OgfOYvJL9u1/twuO/xi/naBQIDAQABMA0G +CSqGSIb3DQEBCwUAA4IBAQAudZAxVxMh/aUeTj6WK5mpVpUINmAwt938FVSQ3nUC +mTafNjSNfu/Me+PvjCkmf9RNk5BSdHVwxseOBaU0WZnvLM056ayyQnSwWT53WwtZ +CSb3VHBbwaJqAiAnhQxCHHhWDK4rtIPrWZ/SCek2E5l29ccZHTuVnaFF3pG8YRs1 ++UXVWeCB0fIJwQdugtGkzVzgO1HW7tKyG1BqCP9q9FnfODia9d6jKAA5dScBPanG +yPNI8eq4Fpk4R6P1jM2jw9pCJ4CW4lpgGQD0KATJve+ooHJqL3Ri6xkljNymGfbP +UD8eyRxgUaKUloQY2UbafzO+cLn2uRiB4sfn/T8hybeW +-----END CERTIFICATE----- diff --git a/certs/app_certs/server-key.pem b/certs/app_certs/server-key.pem new file mode 100644 index 0000000..c0b3f31 --- /dev/null +++ b/certs/app_certs/server-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCSwxp2L1CJnquR +WZ96oZGMnYM3OQBE6IJ74WLOLA36rzZWXwtWW3zPcMIaKUw6QLA0OnxBKWnXndAz +LSXrJXObEoNflBqTI0eVAkY7sB+bt1sIzMgjFtjkusu0bNs70etz6G6lU+dX+d/j +/E+45yrHAFNP9w5JprJrkodqNeQGMNdE82s+PnS0N/SBA/5/ViIJ3/BVpO/xe67r +ItrsDl2cBodZqWwCaEjFUd6C3RsC8nsLMyoVxJqsYkQE4SMowdd1fazno2vnpIaz +EBuQ6ujIi1YaCu+V/E6SRXs2GDr7y8lzXUE9brJnqz7qaQq8HgMqN1phdKwax5z/ +3xzoJgUBAgMBAAECggEAB0rjjzjVpyj3vH64EndhzJttEDroXQQyq6Ys6zK8NRcs +u4j4fr+ICaTAOF2R+JkLSGUZlIFSzZB9bnWRW0heoLeASKkK0wHfRjO5OrELOQkY +4GyQi1HQ0DjJ83qvQB8ztGw5x0ROjAwSCHmamoT+FqpY+XG8x4MdfYPn76qi3H3Q +iRs5SqzksnYL5irsdOLh84FwJuXhQzZVaBFnK38P2xWNRS2uiJYQoDjEavYcRoLr +ZmoZgtyLn7cMXRD177y2KhixhLReJqiIP3fX2SZCD8sGOzvLxlcUQh/cusIyw+4M +t5fAb3G3kpxey6IpeWwriXBVhPlv/t0h1rB3m+3jSwKBgQDC8fXRAm6GVIoTAjiY +ipxFu88u2KDhfN+HRzLKQwy3lcl9V4qFybI1oR2HxllMyB03+gtcxT2gFe4AI6az +KUZSjjYtzGqSxeus4cFc4Ec11LJbPk5fb2CnRLhQdV748YvWX4P/ZXkcI1mu34k0 +namLtM/jFvJQ+7uvGBkihK580wKBgQDAuf3EiOqDFG2qczaTcqzS4VqPYtXmCqw4 +vwjZNRMmvXNzNly2AuTA7ham3QTKDcXmeaF/zmFxWZhNdiIbW0oolraQ9ZNrqZap +eKBHrAhEOTdF6ZwEXjK88c0/TOUA8HC3enqlO6qrHNIPtUTsR+lTmlyo/SiLSWiO +wnKqSoICWwKBgCt5+vCaMjwTLpf+rtCWWTPUJuizt22Sg+ePoWwqd/OZnE4v79zW +lsAPJp7ZRaEyIBIT2eTeuFezjFjLmqnqUpymyr58EGiba2wrDQzBmCARR5XB14jB +NjUXxmNrSbsLY7xzoOScpN35pE6z2824O8/Ei3iB7ZjSC5GJNlHUdXWxAoGAJO0+ +F0MYk+b9IDSVF2lYfctZ+7E3RK101CaePmfx9HFGRqP63ZDuXZ0A0BX3DfPXoFJb +xE4502sUSHtDC7TRH7fI4Tt8dJt4153aMAFhUBkaYxXgo+GcnSFDb0Z/dk+beTxJ +dZFaIRETmpjjzNX2eeNQr7xZ4V4+X2QYblJ6WJMCgYEAnz7BSZgwauHJaQ2zUuRC +nwsrQ8x2Lb6oUtl517F5L9KdDCuZDhDT/fR5i78GRQUBRJAeA1TeKeA1oSCn7uI8 +Sf+cnkc3LrfgesTE5cg10qsNZ9WJV0uZto0Fi0Ku6JlLFIGn64rMedyyTbXoDlCY +gH32hd6WfH01of4nr/ShfD8= +-----END PRIVATE KEY----- diff --git a/certs/app_certs/server.csr b/certs/app_certs/server.csr new file mode 100644 index 0000000..287ffa6 --- /dev/null +++ b/certs/app_certs/server.csr @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICljCCAX4CAQAwUTELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYD +VQQHDAVQdXNhbjENMAsGA1UECgwEV1VEQzETMBEGA1UEAwwKZGV2bG9nLWFwcDCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAJLDGnYvUImeq5FZn3qhkYyd +gzc5AETognvhYs4sDfqvNlZfC1ZbfM9wwhopTDpAsDQ6fEEpaded0DMtJeslc5sS +g1+UGpMjR5UCRjuwH5u3WwjMyCMW2OS6y7Rs2zvR63PobqVT51f53+P8T7jnKscA +U0/3DkmmsmuSh2o15AYw10Tzaz4+dLQ39IED/n9WIgnf8FWk7/F7rusi2uwOXZwG +h1mpbAJoSMVR3oLdGwLyewszKhXEmqxiRAThIyjB13V9rOeja+ekhrMQG5Dq6MiL +VhoK75X8TpJFezYYOvvLyXNdQT1usmerPuppCrweAyo3WmF0rBrHnP/fHOgmBQEC +AwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQBQgaCS/3NRrDwYmhYSInyDgNiqrz+V +txsvjJGLi+eWsG8538qvDYAjmjlAUZkP48Dhc4n9Wx0nkc8i+n5HjJ60nqBHu6eQ +mKcNmudEzGewF7XTyPY11AT3GNJqOqtek6kbhauxFCby2Iuz8B8obe8EeDhJ4TZ7 +qs6Y96l/ocv3AFDrRNk6kVkaTPyMhjHGvg5BZuJRZfdnGNXWWaY1m3kumGj/4zTR +smiqcc0uQSnqAPY1MO/9qIxrh/1wF0+LQL2HTQAtz3DWu5EUZFRiktnyWOGdibbh ++3ACKB7ixq+7nKQEgzEg49DTFSgwByAt1VWaqiumj4A7x+h64PZFNc8S +-----END CERTIFICATE REQUEST----- diff --git a/certs/app_certs/truststore.p12 b/certs/app_certs/truststore.p12 new file mode 100644 index 0000000..b70d1cd Binary files /dev/null and b/certs/app_certs/truststore.p12 differ diff --git a/certs/app_certs/truststore.pem b/certs/app_certs/truststore.pem new file mode 100644 index 0000000..b992733 --- /dev/null +++ b/certs/app_certs/truststore.pem @@ -0,0 +1,147 @@ +Bag Attributes + friendlyName: devlog-ca + 2.16.840.1.113894.746875.1.1: +subject=C = KR, ST = Pusan, L = Pusan, O = WUDC, CN = WUDC +issuer=C = KR, ST = Pusan, L = Pusan, O = WUDC, CN = WUDC +-----BEGIN CERTIFICATE----- +MIIDHTCCAgUCFFw5T+2r/PXbAFYdJUeCO9NurTMhMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDE0NzIwWhcNMzUw +NDE0MDE0NzIwWjBLMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMQ0wCwYDVQQDDARXVURDMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntyNuLTcMjWIgfrSPi+pvf3hNsHLzlV1 +Ze+hXvqs4fE0p36qVbWRUkwv+Vp4drKrlXrY5Em7JCNZ0IT9PuZ3PwFbnG5FwV+O +H3B7WrRx4JHhLZU3w+WHFGfWFDZS2eYWrf1f1tDOlyq92xx0QEqDNE+gUMJ+z1N7 +5yoDForShgTmxD+KzYp4e7BvKKKn/U6YosBC8GyiMWdcka0IUMoTwpQRQ3Vlx/KX ++YGJlwWA9wvNWOqgcYCxeCIa7X+8K9i92Py01T82zqLMcr52Ri2JEdMzHz4pp/aZ +d56pfUhGuN6ET18fTTGeKuB1PrPu80OgfOYvJL9u1/twuO/xi/naBQIDAQABMA0G +CSqGSIb3DQEBCwUAA4IBAQAudZAxVxMh/aUeTj6WK5mpVpUINmAwt938FVSQ3nUC +mTafNjSNfu/Me+PvjCkmf9RNk5BSdHVwxseOBaU0WZnvLM056ayyQnSwWT53WwtZ +CSb3VHBbwaJqAiAnhQxCHHhWDK4rtIPrWZ/SCek2E5l29ccZHTuVnaFF3pG8YRs1 ++UXVWeCB0fIJwQdugtGkzVzgO1HW7tKyG1BqCP9q9FnfODia9d6jKAA5dScBPanG +yPNI8eq4Fpk4R6P1jM2jw9pCJ4CW4lpgGQD0KATJve+ooHJqL3Ri6xkljNymGfbP +UD8eyRxgUaKUloQY2UbafzO+cLn2uRiB4sfn/T8hybeW +-----END CERTIFICATE----- +Bag Attributes + friendlyName: mysql-server + 2.16.840.1.113894.746875.1.1: +subject=C = KR, ST = Pusan, L = Pusan, O = WUDC, CN = devlog-mysql +issuer=C = KR, ST = Pusan, L = Pusan, O = WUDC, CN = WUDC +-----BEGIN CERTIFICATE----- +MIIDJTCCAg0CFFhWSQG3H1J81Xt7M+sUWjv4RQK+MA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDIwMTA4WhcNMzUw +NDE0MDIwMTA4WjBTMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMRUwEwYDVQQDDAxkZXZsb2ctbXlzcWww +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDoOhJ5apnT8jlYjtVh7rgh +F2DKtabV+H1gEq6NxsrzLlfzfn1BqC4OCznCZArYcTPjLLwfNGVsRzsVtm/mWSCY +GKffPURUHW/TqnRHhFw0GXB63NK8dEdvzQib7S5esvLmT+u9VSNgfNQCvpw3fyQD +akaGk2GP+pKU1Snmp7stmjUDv8G3UIVePfhizXvMqI2LnfDgAUnQioonAoyksq5s +6GMrUoJZvjVeJutt6Kec8sguAdOb/yvMQ7BBbweAZqwkgGkVz0gQUzJH79BLPbsq +FbHzpBs5pBajk1yrg9bRJe0hPDcLwWXqczr5tSxLHv966PhK98j1XA+Acq77ljod +AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJIUK9L5sCg9U09p7FbwQbLdVB7p45xT +8NKkOpDxEN+kFacNkbbkL3pD64T/3dQ4LWNiWV2WlBdqJoMIl1Rafcd+Oj6OqiTW +l9ICl/dhul+Z3MGXGdvgfc49vHO4SPc/ijMzowA7+3oiopOVIsid7wVCxZDeueVQ +HJQiqyVMC6vTZBGfoMzbUcOZn+MJV8Q9Fnh3kcx+clhAYiKsh52RKrDGhZrRzvoF +cLuOJlL5IcHYMBRxfoeYdoD200qG2TZb7GnIxb/vvzeLIGYwb80amuXkHaNuAqNY +rfFOMk4EAg3UChDquFDkD6nhP3zgPTiLULHSVbPz54sV7RHtzW1xUHo= +-----END CERTIFICATE----- +Bag Attributes + friendlyName: intermediate + 2.16.840.1.113894.746875.1.1: +subject=C = KR, ST = Pusan, L = Pusan, O = WUDC, CN = WUDC +issuer=C = KR, ST = Pusan, L = Pusan, O = WUDC, CN = WUDC +-----BEGIN CERTIFICATE----- +MIIDHTCCAgUCFFw5T+2r/PXbAFYdJUeCO9NurTMhMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDE0NzIwWhcNMzUw +NDE0MDE0NzIwWjBLMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMQ0wCwYDVQQDDARXVURDMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntyNuLTcMjWIgfrSPi+pvf3hNsHLzlV1 +Ze+hXvqs4fE0p36qVbWRUkwv+Vp4drKrlXrY5Em7JCNZ0IT9PuZ3PwFbnG5FwV+O +H3B7WrRx4JHhLZU3w+WHFGfWFDZS2eYWrf1f1tDOlyq92xx0QEqDNE+gUMJ+z1N7 +5yoDForShgTmxD+KzYp4e7BvKKKn/U6YosBC8GyiMWdcka0IUMoTwpQRQ3Vlx/KX ++YGJlwWA9wvNWOqgcYCxeCIa7X+8K9i92Py01T82zqLMcr52Ri2JEdMzHz4pp/aZ +d56pfUhGuN6ET18fTTGeKuB1PrPu80OgfOYvJL9u1/twuO/xi/naBQIDAQABMA0G +CSqGSIb3DQEBCwUAA4IBAQAudZAxVxMh/aUeTj6WK5mpVpUINmAwt938FVSQ3nUC +mTafNjSNfu/Me+PvjCkmf9RNk5BSdHVwxseOBaU0WZnvLM056ayyQnSwWT53WwtZ +CSb3VHBbwaJqAiAnhQxCHHhWDK4rtIPrWZ/SCek2E5l29ccZHTuVnaFF3pG8YRs1 ++UXVWeCB0fIJwQdugtGkzVzgO1HW7tKyG1BqCP9q9FnfODia9d6jKAA5dScBPanG +yPNI8eq4Fpk4R6P1jM2jw9pCJ4CW4lpgGQD0KATJve+ooHJqL3Ri6xkljNymGfbP +UD8eyRxgUaKUloQY2UbafzO+cLn2uRiB4sfn/T8hybeW +-----END CERTIFICATE----- +Bag Attributes + friendlyName: root + 2.16.840.1.113894.746875.1.1: +subject=C = KR, ST = Pusan, L = Pusan, O = WUDC, CN = WUDC +issuer=C = KR, ST = Pusan, L = Pusan, O = WUDC, CN = WUDC +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIUJQjfesPiPpsGhsWZH4oTggUWVD8wDQYJKoZIhvcNAQEL +BQAwSzELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYDVQQHDAVQdXNh +bjENMAsGA1UECgwEV1VEQzENMAsGA1UEAwwEV1VEQzAeFw0yNTA0MTYwMTQzMDda +Fw0zNTA0MTQwMTQzMDdaMEsxCzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEO +MAwGA1UEBwwFUHVzYW4xDTALBgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDczXj3rCSwS+cv2FXTRWxcLxuE +m0DY9RqQfUZ6XM24IuBpxeicqIcGs8lYSWmx9cRFnxDG5beV5BSBOt3MoR9Q5Caf +Ld4JWHEIXicDk78vQjTE7QB1xPkieU0s4wqh9W0rPKEYsJ8SEhFgMUq5g5EiaVqQ +PRaaEeK1C0Zn0e5NdwcMgJ6E6uwij9E1U95QAw6AFyvqoS+s/1TsEcAkN3oIXdlD +kYy7lO4uLZI4Dfh77kfAf3LxKN/qE6WE20wyzipwb7yXFVzcTm5WoLSwV4axWrYF +Bc67x3X5t6K985Y1f5UMfnKq+JRAl0wFC0SOhS8NXHFVNQCbXvwldfcUSwc/AgMB +AAGjUzBRMB0GA1UdDgQWBBRa5Q7LNg15ZZYZSMQoowHLNtJtezAfBgNVHSMEGDAW +gBRa5Q7LNg15ZZYZSMQoowHLNtJtezAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQDXLVSeVyhCuw4oJoRw/wPwQEeBScxmpkSQvMzl19QhFXkxZxUT +5vhkCJc3lp1Ifa405n5I1+WFVhWyF1cSTXYddxz/eLjW1p54VNPXm5nxKzwwyftE +Ps36G32D0kdkk9pRqnsQ2Dt5j8hf2RVXzyswu97mTkRXTQMDwKUtwopr6HlIZiOY +BzUOEJf4NQr3VeGkXeWfqjWMU7ch7FWf7SsyqNFinAgiDLaIdEYSRBi8xiRJpOn4 +BUbhAlS2L4uGPWGa7TZ8sf8/s4Mdh784NSFn+4D6pVa7Quq5e6OmBVK4J851zAic +7mQsPhJLXactGWdksrjBSMQS7E5R8LG+k0j3 +-----END CERTIFICATE----- +Bag Attributes + friendlyName: redis-server + 2.16.840.1.113894.746875.1.1: +subject=C = KR, ST = Pusan, L = Pusan, O = WUDC, CN = devlog-redis +issuer=C = KR, ST = Pusan, L = Pusan, O = WUDC, CN = WUDC +-----BEGIN CERTIFICATE----- +MIIDJTCCAg0CFFhWSQG3H1J81Xt7M+sUWjv4RQK/MA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDIyMzUzWhcNMjYw +NDE2MDIyMzUzWjBTMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMRUwEwYDVQQDDAxkZXZsb2ctcmVkaXMw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD6WlIHMwItBuZD2HLs0sJb +OwQTvZmeCvcqCkTCc19GSWTAO0yuw8aZlsaNFKlOamGknDXVzUOKOojNNAzOeW4Y +/sbG+AtgFiFLblRPGIbKs4fNJa9TMJkt7u69YNVgRLnXRHIcoaQz/Ah5+398Puyw +HFSZOYpAmcxVCalYTT3+NnX4+Umnq/a7YFZFiiHBKWvmhCc+zp6CFQ1JjLKb/bdN +YiNxQoQ4RQf5/FquuQT5ZigsLGRMnnKA1IB3nmLR4PklGGEyYYakh+mFBnmWGWny +uxOj/6Q9ZsCs1jsrwl/W1EtWqjFZpWCJmkJbnNXMCgtriz1sE3P/RqbAF/1H9exF +AgMBAAEwDQYJKoZIhvcNAQELBQADggEBACR9DxcobWtjqiiR3r7/jQYMdnUVwGkv +euMJXRCzzPVV1PkgEMotGTksoZ0GElProZo5TK/DiGmc5zi3Ql7rADk6blDk1kj3 +W0i9/rDwBkgC5r7NFHoxS/73yumAZsrIjz0sYlRgzUHc33jv7pb2RPmkFZ4Qj5By +R3W7/rN+9+GflTvMgVeTljPfyS5/Tt5svnp1oE97vd/TMPZgMF1KHYG4yaGQfztX +YnPYb2knshGeGWmCzNoPrQI3Ug3LKyXwN2f2QYqeCahGztBkZPp3yy+CNSqMtI0H +iSwkDA0dGd2Fk6w4ZI/o+QUvOwSgsxx96LXaffCGbSpdeJXflyFRopM= +-----END CERTIFICATE----- +Bag Attributes + friendlyName: nginx-server + 2.16.840.1.113894.746875.1.1: +subject=C = KR, ST = Pusan, L = Pusan, O = WUDC, OU = WUDC, CN = devlog-nginx, emailAddress = qqwwee771441@gmail.com +issuer=C = KR, ST = Pusan, L = Pusan, O = WUDC, CN = WUDC +-----BEGIN CERTIFICATE----- +MIIDXDCCAkQCFFw5T+2r/PXbAFYdJUeCO9NurTMiMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDMwMjQzWhcNMjYw +NDE2MDMwMjQzWjCBiTELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYD +VQQHDAVQdXNhbjENMAsGA1UECgwEV1VEQzENMAsGA1UECwwEV1VEQzEVMBMGA1UE +AwwMZGV2bG9nLW5naW54MSUwIwYJKoZIhvcNAQkBFhZxcXd3ZWU3NzE0NDFAZ21h +aWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8B8rbUMBDAUe +47l7uKtGTgwTAStVBueE69flhTnd4f2cOigIueXYqHPL8vukLHFkCvHYS2DSsTsd +ja/QhaqWQJK0f5H3by4KzbFgCfRbjWnw2LYTfk4zZo69gkCPbsaDPAZ2Q6XyLZ7Z +SBu+uTTHftVELieUu1HKjJCyfH7M9++cufM1V61EgODI5YEVcziyGuTZaQLeKi10 +5wMe57FcDq4ABNDjvmGAsJKzR5+z8W02f5OIacjcOVStlsyzvkrCEvJKcacgeH+s +RBA8tG8hUup8c13TjLQ0CM8GF7PigXRKYFfiLRqJju7ThNKKzozLJWknK69SMW61 +1tR3ohb90wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCb5BMY3Woz1Kxeog+7oHnv +wVSVemf+XbtRfkOQrFSlEFY10OKlIf4mT2PEkgmx8lfNHx+RGmv0w1g9N/VAw7zh +X1wr39GOrawzgWZLo7kLtfNQ2Q2IfLttTkiR2IlYPdGU0+c1LS1EnuVHgZbjwnyf +e4SAfA/zc2CKY3H23H7rm1Xr8aKKzLB/13Xc1kGEzQVucnoJyMPtwRDzWnFO5qoA +7d4ClDoYg3Wy6I02ZKKMkktKSfbp+AQBzvT1s+iPWookpdkco/DZCkO6tmyh6CdR +FaYUymw5MVElSCjRkzDQ0DhO5fZhu3w0VgaSfusRIfeXSGxUBhzrV7Z0Vjh2cFgQ +-----END CERTIFICATE----- diff --git a/certs/ca-key.pem b/certs/ca-key.pem new file mode 100644 index 0000000..d1ed693 --- /dev/null +++ b/certs/ca-key.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQItBgWYtKAT6gCAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBBP12tS4G9Uy8kzR1+xDHQQBIIE +0HqE3pxgavsUTSgWIKdVDHgaP/IEjon3Wau5OgAmy01oWH9OTxeFiKSXfhFlsIqt +jWbGzC8MqlZ11kQPVn4HdezaEVQ4xY3m35DJQf2CyesFXYd7rXTqtiwqsa0lmyps +IrL77eB9RzOMNnvLi2NvfCbRJ/YtqVeeh60d5uCzlIUF4pGJfaKPqq17q9by9S3m +a7+rpm4gH0RooxuRMinyQwG6haPF7cJv/LxkPc1nVAtlVFdWPctdoYDbEnOAZ6zD +Ajam/YwduVOmDaUlX7oQDkU/B0Nki9rGDr6yuPc1OVdFqFjDSeqVr+LWnvLRRwZX +Qx2K4rG361yFcWhOcydIJmtbrDLZM+QwII/UqiFwptyTj007ioQqEU0kBhrnyFeW +VRqiDrn6m/bCZQbyF3UlGCI1HVWgQnPMBgboOMQsqXeDjVwXwTYmSag/jJDIXs4d +yZjyf1RF5Y5Fa15zkHb04ANbvsHW+QDMBCtir8/R64AtjXXI52oBG62KNGlZ814r +VP519ml69ABFTHJbfzrSzc8er783heibe6NsKc4TeLZ3Eqz6es0kwc7Z9hUFUOy4 +zkow1zpAIleWtALrwUXFQO6EOmxQ+SfM7WIXmtRHwfEHuD3R0h0D2vvvYvInNGjU +RVjaLx1FQXcoA8AK9qXq0rHBgJx0TY+jsE9ltQtDx8f+aD7NO5Fb9ImTsPUN4sZ9 +R/ivOwF1q+I2hQ53D80uBHwNrBaZkmvDO/N/7cRww6sbB3Ur5iGIp1DR0a3hvtpE +mQ2LEJIE5G4RpXDfdvpBzYtLQQsNI3Op16GN6ggugjq+1qlN43APseSpiUSh+evu +9ZTduUVr4y4ypT8jJD1CUbspP9MQShCkrQ6XGGh1cDX8kvKMKrPTdpApdv8daUZY +GGpeNgKw1yGQB/up/okaFrDvD3c5MpH9HyvGOLERV8dFNgspkpsZFC3bMHoIE9qJ +83PB7rleLyMHmQFfvwL2FtklaWqw98H+Fjh/AzXDwvj6Wfcvn7DhhdESvF1elG/W +QdBIhsVcoY9iDxcl9qsjIY21onsq5hy11iya7p/frUnFpmLbr7b9BJ3ZbAKh51C4 +6TB6gKQxOzjQUMAsgIvIn/H4spdvAZ3bopcED0G8MjC0hRFmfbs2d5pf2x07P1H4 +WG78zHTuEZRTBYi+th7K2xBhwpDs6Lh2KBhJAc21NN6AWhaGQBA3kqxrIkGNTlVX +SjV9hXrqG/x86SUpzcxY2yG4Y0FzBhHaHYNxmE0VvfrOgdBmAc9sYkYlZGo4X9wC +Zg8amOAQgjyLgmr+zIVJ4oUAemEAl/qqkKvoI/Z7LnO4QYqI0VaQkEMLX9m7NBaZ +XKVWbwauqJ8VBK0qBLqIC8a+rb8qZGwHnuBBtTTUmRmkPz+uCvbJacfi2lQ21Mcq +Mh32Gqy8HnGd+j1FX8QiFjknDRTfsAOtwOBzIlVUiwJn0RuEGImYOyKjqjiptXNR +0XdGlkVLM8Y3KsO6RL5842sYjE63/kxAkczfq8MQ0D7YMrZa7hZ42Syr7siYC/EL +lDxqg+yen80CeZ6V1aD7MVmCb7kQmphge2PCAmr8dHlfNytcub2TgrI2Lmc80jEn +Pw2s2YKpLIjUrykKp91XAqswb6V3Kmqt7JaAPtKqdv5u +-----END ENCRYPTED PRIVATE KEY----- diff --git a/certs/ca.pem b/certs/ca.pem new file mode 100644 index 0000000..0f8f29c --- /dev/null +++ b/certs/ca.pem @@ -0,0 +1,21 @@ +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIUJQjfesPiPpsGhsWZH4oTggUWVD8wDQYJKoZIhvcNAQEL +BQAwSzELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYDVQQHDAVQdXNh +bjENMAsGA1UECgwEV1VEQzENMAsGA1UEAwwEV1VEQzAeFw0yNTA0MTYwMTQzMDda +Fw0zNTA0MTQwMTQzMDdaMEsxCzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEO +MAwGA1UEBwwFUHVzYW4xDTALBgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDczXj3rCSwS+cv2FXTRWxcLxuE +m0DY9RqQfUZ6XM24IuBpxeicqIcGs8lYSWmx9cRFnxDG5beV5BSBOt3MoR9Q5Caf +Ld4JWHEIXicDk78vQjTE7QB1xPkieU0s4wqh9W0rPKEYsJ8SEhFgMUq5g5EiaVqQ +PRaaEeK1C0Zn0e5NdwcMgJ6E6uwij9E1U95QAw6AFyvqoS+s/1TsEcAkN3oIXdlD +kYy7lO4uLZI4Dfh77kfAf3LxKN/qE6WE20wyzipwb7yXFVzcTm5WoLSwV4axWrYF +Bc67x3X5t6K985Y1f5UMfnKq+JRAl0wFC0SOhS8NXHFVNQCbXvwldfcUSwc/AgMB +AAGjUzBRMB0GA1UdDgQWBBRa5Q7LNg15ZZYZSMQoowHLNtJtezAfBgNVHSMEGDAW +gBRa5Q7LNg15ZZYZSMQoowHLNtJtezAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQDXLVSeVyhCuw4oJoRw/wPwQEeBScxmpkSQvMzl19QhFXkxZxUT +5vhkCJc3lp1Ifa405n5I1+WFVhWyF1cSTXYddxz/eLjW1p54VNPXm5nxKzwwyftE +Ps36G32D0kdkk9pRqnsQ2Dt5j8hf2RVXzyswu97mTkRXTQMDwKUtwopr6HlIZiOY +BzUOEJf4NQr3VeGkXeWfqjWMU7ch7FWf7SsyqNFinAgiDLaIdEYSRBi8xiRJpOn4 +BUbhAlS2L4uGPWGa7TZ8sf8/s4Mdh784NSFn+4D6pVa7Quq5e6OmBVK4J851zAic +7mQsPhJLXactGWdksrjBSMQS7E5R8LG+k0j3 +-----END CERTIFICATE----- diff --git a/certs/ca.srl b/certs/ca.srl new file mode 100644 index 0000000..301625b --- /dev/null +++ b/certs/ca.srl @@ -0,0 +1 @@ +5C394FEDABFCF5DB00561D2547823BD36EAD3324 diff --git a/certs/intermediate-key.pem b/certs/intermediate-key.pem new file mode 100644 index 0000000..09be3f5 --- /dev/null +++ b/certs/intermediate-key.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI34aCVMAjVvsCAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBCGo3xYFwkXwd4iDaH5F2laBIIE +0JhoFyQ3IqFBMptzo9psmvEnhkrogK/UMnMxU7t3ttgxM1G/OhRO3P0pzbKSrpLC +sSys0TnXiz0Lr4Mfj/kLMme9XUdWqO+blM+bJx0XcXql38o2tFD5OkUfYFUrlz8d +MD1RWqVmou+dEe2evSl3VH1cjl1gN3zaCakGJKzRCdrQtLbt/9cnPOaN+sRvsx3i +rC1P9D9xtlLhCSoMi30LG7TXx64S2kQ2ErcUeT/9OQHDtQ/jSUHSgNBXzMola9Ds +BShTeozDcKp9bz85Xoa0OcJ2QhEJd8xZfTHRcKvPZ/kqDqI5NdfutlrIPnZv4c32 +o5trQ7IEBAv48a9QGEsYQDSG4mgzjje4vsGp9Y28kMjG39R3y2tvWcFCTeHQYf9X +6EJ4fyJqF7Ul4/kZUUQzj0mD/lmTh9kKKpNwOylfllowS3GzzytEZ1gbYY16fqcE +64zY3CvMJCnIBriq+UtUEg2hySJYyHyEFpIfs/HZ1+aEC7RsCwrEjKfRz82Ye6VB +RWDxL9pKF3QUnzMEqErsWBbRymb+jwlwoRJW9w9+H/4P61SYkL96R7SuTTdUIyW7 +RBpKZrwlNH7yfuXaLJbfESNpoq0nEKgzKZHfeX0wSf2dLsPp4CqttRF976J0lWb9 +Nk6F5k6khyp1zOpo2Te4luOjjtQxsIagOWvaPXerOqVvYXf3JCeSFjYJYqoklvNH +uE6ujzoHj7br2CIgf+ukW3EZqSqYuwvBOqAHwe0W7XabpRzcXzQJZW1y9B8gMhqW +XT623dkcANEEy7fjbOQT060sexTXM56T/GnE8pVZukSkPppaAAU83iMmY1O4f+GW +Y1nPenhwSVmlbqWTnD/9hbuubkBFxrv9/MTy8EvohlH+O5m7OvCGibMhtZYMArDc +hhfBNIHmTx06j41beFGZEAd7Eg6c1ckF+7eEKuWRxRIYRhL9wQpPkkO9O+JwFQE1 +Ut/0MW7l3b2z2DnfG/gtVtE8h7sn/lgW77suuhdkOzthFLDBjak2qtal0agGDRo7 +n5aSVBYxy3DCyX+Lk38NBr5OB3/x1oczztxs+sQ7rubTnRoXq8rk3incP3QJDZBW +Fi7uCPeeNSAhYMJcta5LxxSxUNGYaxPTlIXvJ1U5Dnxixi/W9nzprcevMJpxRCLH +WJLUjtjNKFdunVkfKWOPcUYRIlVppkN/wG3dU1UbNFChS7i1jTngEK4hiL9fzsch +992C5WLWHiWqXZi5xzP6wUCImQRS5omk8Ks9Br1ICp8Gk+2tRpHTbYZj535sa/Wi +nbbNMYNQKPWleEJG6T2auFLtNfDlsBs8O9gN3OoK4xXiQNoFrYrilNVGOz4Vg8bC +X3ERzYDMo1tW5bBJUCeeIzoW3sUIMr/rK/7UFx6HseSNfc5BJ1g66b+gmyXsqwu+ +1rjAbZTaq7OSpfumpsfGwUNWdYXNrRHIo63LoOoeJyeuBRfYpYLWuGNnUKJyFilj +dlBTscAghl5EE8LrYD9HUWnUzbu4G+sreJMUNDjE+iQO0m4oxJUI0J9PxJBoqwwd +8XS0lteOKVj9QuFuGPMMY3QEvGYRlDEklVM8Ud54WzYFt1YdOmEe+pynQ1AJC/Nm +5fGOHPTe/6f9wVR0n6IBWH/xCFIRWo4pihzYYsZCp7EP +-----END ENCRYPTED PRIVATE KEY----- diff --git a/certs/intermediate.csr b/certs/intermediate.csr new file mode 100644 index 0000000..c871cba --- /dev/null +++ b/certs/intermediate.csr @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICkDCCAXgCAQAwSzELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYD +VQQHDAVQdXNhbjENMAsGA1UECgwEV1VEQzENMAsGA1UEAwwEV1VEQzCCASIwDQYJ +KoZIhvcNAQEBBQADggEPADCCAQoCggEBAJ7cjbi03DI1iIH60j4vqb394TbBy85V +dWXvoV76rOHxNKd+qlW1kVJML/laeHayq5V62ORJuyQjWdCE/T7mdz8BW5xuRcFf +jh9we1q0ceCR4S2VN8PlhxRn1hQ2UtnmFq39X9bQzpcqvdscdEBKgzRPoFDCfs9T +e+cqAxaK0oYE5sQ/is2KeHuwbyiip/1OmKLAQvBsojFnXJGtCFDKE8KUEUN1Zcfy +l/mBiZcFgPcLzVjqoHGAsXgiGu1/vCvYvdj8tNU/Ns6izHK+dkYtiRHTMx8+Kaf2 +mXeeqX1IRrjehE9fH00xnirgdT6z7vNDoHzmLyS/btf7cLjv8Yv52gUCAwEAAaAA +MA0GCSqGSIb3DQEBCwUAA4IBAQAmh5NaHCxpjzYBuTediHEJdJk6nQnLi4dPpRvD +JrGmVHdxpaoKRyj2tIW5DGnfshs57oDgbXDPw8yE2tYG64BKPD0/xhhv8ztdJgfE +gjlKZMvFWpwdNDcXmgYQDg4EkQl4el5druIIVYPIgg+mgHOsOE3FZVM8liDWlr7N +dCUQHDR+vvtE9rJf8RIMHH1DufrObQYffE0YwMjg6GGJeQvOYn7VSkYILzYxIA/S +neY/y9YBogE034LFXlnhbo2/p5bY2qtZkNdKsC80xc8BjPdsnFcEdldgSypAfFRE +r/9kCe7ur9RO5HHr7nlm1oiLya+IMmqIVSAdjfFBBq4h8q5z +-----END CERTIFICATE REQUEST----- diff --git a/certs/intermediate.pem b/certs/intermediate.pem new file mode 100644 index 0000000..3c3bdae --- /dev/null +++ b/certs/intermediate.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDHTCCAgUCFFw5T+2r/PXbAFYdJUeCO9NurTMhMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDE0NzIwWhcNMzUw +NDE0MDE0NzIwWjBLMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMQ0wCwYDVQQDDARXVURDMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntyNuLTcMjWIgfrSPi+pvf3hNsHLzlV1 +Ze+hXvqs4fE0p36qVbWRUkwv+Vp4drKrlXrY5Em7JCNZ0IT9PuZ3PwFbnG5FwV+O +H3B7WrRx4JHhLZU3w+WHFGfWFDZS2eYWrf1f1tDOlyq92xx0QEqDNE+gUMJ+z1N7 +5yoDForShgTmxD+KzYp4e7BvKKKn/U6YosBC8GyiMWdcka0IUMoTwpQRQ3Vlx/KX ++YGJlwWA9wvNWOqgcYCxeCIa7X+8K9i92Py01T82zqLMcr52Ri2JEdMzHz4pp/aZ +d56pfUhGuN6ET18fTTGeKuB1PrPu80OgfOYvJL9u1/twuO/xi/naBQIDAQABMA0G +CSqGSIb3DQEBCwUAA4IBAQAudZAxVxMh/aUeTj6WK5mpVpUINmAwt938FVSQ3nUC +mTafNjSNfu/Me+PvjCkmf9RNk5BSdHVwxseOBaU0WZnvLM056ayyQnSwWT53WwtZ +CSb3VHBbwaJqAiAnhQxCHHhWDK4rtIPrWZ/SCek2E5l29ccZHTuVnaFF3pG8YRs1 ++UXVWeCB0fIJwQdugtGkzVzgO1HW7tKyG1BqCP9q9FnfODia9d6jKAA5dScBPanG +yPNI8eq4Fpk4R6P1jM2jw9pCJ4CW4lpgGQD0KATJve+ooHJqL3Ri6xkljNymGfbP +UD8eyRxgUaKUloQY2UbafzO+cLn2uRiB4sfn/T8hybeW +-----END CERTIFICATE----- diff --git a/certs/intermediate.srl b/certs/intermediate.srl new file mode 100644 index 0000000..464bd5c --- /dev/null +++ b/certs/intermediate.srl @@ -0,0 +1 @@ +58564901B71F527CD57B7B33EB145A3BF84502C0 diff --git a/certs/mysql_certs/ca.pem b/certs/mysql_certs/ca.pem new file mode 100644 index 0000000..bfcc81c --- /dev/null +++ b/certs/mysql_certs/ca.pem @@ -0,0 +1,40 @@ +-----BEGIN CERTIFICATE----- +MIIDHTCCAgUCFFw5T+2r/PXbAFYdJUeCO9NurTMhMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDE0NzIwWhcNMzUw +NDE0MDE0NzIwWjBLMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMQ0wCwYDVQQDDARXVURDMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntyNuLTcMjWIgfrSPi+pvf3hNsHLzlV1 +Ze+hXvqs4fE0p36qVbWRUkwv+Vp4drKrlXrY5Em7JCNZ0IT9PuZ3PwFbnG5FwV+O +H3B7WrRx4JHhLZU3w+WHFGfWFDZS2eYWrf1f1tDOlyq92xx0QEqDNE+gUMJ+z1N7 +5yoDForShgTmxD+KzYp4e7BvKKKn/U6YosBC8GyiMWdcka0IUMoTwpQRQ3Vlx/KX ++YGJlwWA9wvNWOqgcYCxeCIa7X+8K9i92Py01T82zqLMcr52Ri2JEdMzHz4pp/aZ +d56pfUhGuN6ET18fTTGeKuB1PrPu80OgfOYvJL9u1/twuO/xi/naBQIDAQABMA0G +CSqGSIb3DQEBCwUAA4IBAQAudZAxVxMh/aUeTj6WK5mpVpUINmAwt938FVSQ3nUC +mTafNjSNfu/Me+PvjCkmf9RNk5BSdHVwxseOBaU0WZnvLM056ayyQnSwWT53WwtZ +CSb3VHBbwaJqAiAnhQxCHHhWDK4rtIPrWZ/SCek2E5l29ccZHTuVnaFF3pG8YRs1 ++UXVWeCB0fIJwQdugtGkzVzgO1HW7tKyG1BqCP9q9FnfODia9d6jKAA5dScBPanG +yPNI8eq4Fpk4R6P1jM2jw9pCJ4CW4lpgGQD0KATJve+ooHJqL3Ri6xkljNymGfbP +UD8eyRxgUaKUloQY2UbafzO+cLn2uRiB4sfn/T8hybeW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIUJQjfesPiPpsGhsWZH4oTggUWVD8wDQYJKoZIhvcNAQEL +BQAwSzELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYDVQQHDAVQdXNh +bjENMAsGA1UECgwEV1VEQzENMAsGA1UEAwwEV1VEQzAeFw0yNTA0MTYwMTQzMDda +Fw0zNTA0MTQwMTQzMDdaMEsxCzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEO +MAwGA1UEBwwFUHVzYW4xDTALBgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDczXj3rCSwS+cv2FXTRWxcLxuE +m0DY9RqQfUZ6XM24IuBpxeicqIcGs8lYSWmx9cRFnxDG5beV5BSBOt3MoR9Q5Caf +Ld4JWHEIXicDk78vQjTE7QB1xPkieU0s4wqh9W0rPKEYsJ8SEhFgMUq5g5EiaVqQ +PRaaEeK1C0Zn0e5NdwcMgJ6E6uwij9E1U95QAw6AFyvqoS+s/1TsEcAkN3oIXdlD +kYy7lO4uLZI4Dfh77kfAf3LxKN/qE6WE20wyzipwb7yXFVzcTm5WoLSwV4axWrYF +Bc67x3X5t6K985Y1f5UMfnKq+JRAl0wFC0SOhS8NXHFVNQCbXvwldfcUSwc/AgMB +AAGjUzBRMB0GA1UdDgQWBBRa5Q7LNg15ZZYZSMQoowHLNtJtezAfBgNVHSMEGDAW +gBRa5Q7LNg15ZZYZSMQoowHLNtJtezAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQDXLVSeVyhCuw4oJoRw/wPwQEeBScxmpkSQvMzl19QhFXkxZxUT +5vhkCJc3lp1Ifa405n5I1+WFVhWyF1cSTXYddxz/eLjW1p54VNPXm5nxKzwwyftE +Ps36G32D0kdkk9pRqnsQ2Dt5j8hf2RVXzyswu97mTkRXTQMDwKUtwopr6HlIZiOY +BzUOEJf4NQr3VeGkXeWfqjWMU7ch7FWf7SsyqNFinAgiDLaIdEYSRBi8xiRJpOn4 +BUbhAlS2L4uGPWGa7TZ8sf8/s4Mdh784NSFn+4D6pVa7Quq5e6OmBVK4J851zAic +7mQsPhJLXactGWdksrjBSMQS7E5R8LG+k0j3 +-----END CERTIFICATE----- diff --git a/certs/mysql_certs/server-cert.pem b/certs/mysql_certs/server-cert.pem new file mode 100644 index 0000000..efbe92d --- /dev/null +++ b/certs/mysql_certs/server-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJTCCAg0CFFhWSQG3H1J81Xt7M+sUWjv4RQK+MA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDIwMTA4WhcNMzUw +NDE0MDIwMTA4WjBTMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMRUwEwYDVQQDDAxkZXZsb2ctbXlzcWww +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDoOhJ5apnT8jlYjtVh7rgh +F2DKtabV+H1gEq6NxsrzLlfzfn1BqC4OCznCZArYcTPjLLwfNGVsRzsVtm/mWSCY +GKffPURUHW/TqnRHhFw0GXB63NK8dEdvzQib7S5esvLmT+u9VSNgfNQCvpw3fyQD +akaGk2GP+pKU1Snmp7stmjUDv8G3UIVePfhizXvMqI2LnfDgAUnQioonAoyksq5s +6GMrUoJZvjVeJutt6Kec8sguAdOb/yvMQ7BBbweAZqwkgGkVz0gQUzJH79BLPbsq +FbHzpBs5pBajk1yrg9bRJe0hPDcLwWXqczr5tSxLHv966PhK98j1XA+Acq77ljod +AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJIUK9L5sCg9U09p7FbwQbLdVB7p45xT +8NKkOpDxEN+kFacNkbbkL3pD64T/3dQ4LWNiWV2WlBdqJoMIl1Rafcd+Oj6OqiTW +l9ICl/dhul+Z3MGXGdvgfc49vHO4SPc/ijMzowA7+3oiopOVIsid7wVCxZDeueVQ +HJQiqyVMC6vTZBGfoMzbUcOZn+MJV8Q9Fnh3kcx+clhAYiKsh52RKrDGhZrRzvoF +cLuOJlL5IcHYMBRxfoeYdoD200qG2TZb7GnIxb/vvzeLIGYwb80amuXkHaNuAqNY +rfFOMk4EAg3UChDquFDkD6nhP3zgPTiLULHSVbPz54sV7RHtzW1xUHo= +-----END CERTIFICATE----- diff --git a/certs/mysql_certs/server-full-chain.pem b/certs/mysql_certs/server-full-chain.pem new file mode 100644 index 0000000..2265fda --- /dev/null +++ b/certs/mysql_certs/server-full-chain.pem @@ -0,0 +1,38 @@ +-----BEGIN CERTIFICATE----- +MIIDJTCCAg0CFFhWSQG3H1J81Xt7M+sUWjv4RQK+MA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDIwMTA4WhcNMzUw +NDE0MDIwMTA4WjBTMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMRUwEwYDVQQDDAxkZXZsb2ctbXlzcWww +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDoOhJ5apnT8jlYjtVh7rgh +F2DKtabV+H1gEq6NxsrzLlfzfn1BqC4OCznCZArYcTPjLLwfNGVsRzsVtm/mWSCY +GKffPURUHW/TqnRHhFw0GXB63NK8dEdvzQib7S5esvLmT+u9VSNgfNQCvpw3fyQD +akaGk2GP+pKU1Snmp7stmjUDv8G3UIVePfhizXvMqI2LnfDgAUnQioonAoyksq5s +6GMrUoJZvjVeJutt6Kec8sguAdOb/yvMQ7BBbweAZqwkgGkVz0gQUzJH79BLPbsq +FbHzpBs5pBajk1yrg9bRJe0hPDcLwWXqczr5tSxLHv966PhK98j1XA+Acq77ljod +AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJIUK9L5sCg9U09p7FbwQbLdVB7p45xT +8NKkOpDxEN+kFacNkbbkL3pD64T/3dQ4LWNiWV2WlBdqJoMIl1Rafcd+Oj6OqiTW +l9ICl/dhul+Z3MGXGdvgfc49vHO4SPc/ijMzowA7+3oiopOVIsid7wVCxZDeueVQ +HJQiqyVMC6vTZBGfoMzbUcOZn+MJV8Q9Fnh3kcx+clhAYiKsh52RKrDGhZrRzvoF +cLuOJlL5IcHYMBRxfoeYdoD200qG2TZb7GnIxb/vvzeLIGYwb80amuXkHaNuAqNY +rfFOMk4EAg3UChDquFDkD6nhP3zgPTiLULHSVbPz54sV7RHtzW1xUHo= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDHTCCAgUCFFw5T+2r/PXbAFYdJUeCO9NurTMhMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDE0NzIwWhcNMzUw +NDE0MDE0NzIwWjBLMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMQ0wCwYDVQQDDARXVURDMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntyNuLTcMjWIgfrSPi+pvf3hNsHLzlV1 +Ze+hXvqs4fE0p36qVbWRUkwv+Vp4drKrlXrY5Em7JCNZ0IT9PuZ3PwFbnG5FwV+O +H3B7WrRx4JHhLZU3w+WHFGfWFDZS2eYWrf1f1tDOlyq92xx0QEqDNE+gUMJ+z1N7 +5yoDForShgTmxD+KzYp4e7BvKKKn/U6YosBC8GyiMWdcka0IUMoTwpQRQ3Vlx/KX ++YGJlwWA9wvNWOqgcYCxeCIa7X+8K9i92Py01T82zqLMcr52Ri2JEdMzHz4pp/aZ +d56pfUhGuN6ET18fTTGeKuB1PrPu80OgfOYvJL9u1/twuO/xi/naBQIDAQABMA0G +CSqGSIb3DQEBCwUAA4IBAQAudZAxVxMh/aUeTj6WK5mpVpUINmAwt938FVSQ3nUC +mTafNjSNfu/Me+PvjCkmf9RNk5BSdHVwxseOBaU0WZnvLM056ayyQnSwWT53WwtZ +CSb3VHBbwaJqAiAnhQxCHHhWDK4rtIPrWZ/SCek2E5l29ccZHTuVnaFF3pG8YRs1 ++UXVWeCB0fIJwQdugtGkzVzgO1HW7tKyG1BqCP9q9FnfODia9d6jKAA5dScBPanG +yPNI8eq4Fpk4R6P1jM2jw9pCJ4CW4lpgGQD0KATJve+ooHJqL3Ri6xkljNymGfbP +UD8eyRxgUaKUloQY2UbafzO+cLn2uRiB4sfn/T8hybeW +-----END CERTIFICATE----- diff --git a/certs/mysql_certs/server-key-nopass.pem b/certs/mysql_certs/server-key-nopass.pem new file mode 100644 index 0000000..a9a8c8f --- /dev/null +++ b/certs/mysql_certs/server-key-nopass.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDoOhJ5apnT8jlY +jtVh7rghF2DKtabV+H1gEq6NxsrzLlfzfn1BqC4OCznCZArYcTPjLLwfNGVsRzsV +tm/mWSCYGKffPURUHW/TqnRHhFw0GXB63NK8dEdvzQib7S5esvLmT+u9VSNgfNQC +vpw3fyQDakaGk2GP+pKU1Snmp7stmjUDv8G3UIVePfhizXvMqI2LnfDgAUnQioon +Aoyksq5s6GMrUoJZvjVeJutt6Kec8sguAdOb/yvMQ7BBbweAZqwkgGkVz0gQUzJH +79BLPbsqFbHzpBs5pBajk1yrg9bRJe0hPDcLwWXqczr5tSxLHv966PhK98j1XA+A +cq77ljodAgMBAAECggEAED6zuSOgZCevJEdFnQaugb4ZasS/SXuLBbP42vkbjFxj +Eaj5CSVDJ39YiIUoDxEIMK2Z8VLgf20SMIglFseIVKtw3thx1bKDdpqXbMNREeHv +sOCOq1k1ABinMAUs97nr7PmNQ57XjTHeQNzln6o4sjZ2fYaEziYYp+mrdzOnj952 +D0QJkoDbTjh3gVqvHAQ0rA+eej1Ffw2SXZZEZrvdgTrkVsF3WwWqx0OcPpyFSm2n +ZEYKHlNSltEXHifcGYrUc5o17cJMzshMDePn7MVC21bxDazS8tUpLaRkqt/Lpv1e +JuAVH1WPlNfG9dGDTqdwVI9VMT2by/fRFrHziHC2UQKBgQD+VSIXHabG1NNUZyfy +WG58nQXIkMIBO/eXa/bdOv4z1iA7J1w9z/zoi0pn+dec4oiAyCutn44VMTf8/LPq +vrsJAjzkRNWMkUo0aLj5gxEPQ82veCgtzbKRe/YaYi+64//UQFOZpsoXxiKwiUnA +QOYLUcBQFQsCAb7Wk9dBSPerSQKBgQDpv9ZTUczvMoOIYUP1PhmVnyUuGNEgE/hN +i8wclKjEWUeNW4CpahB+KZZu8pYA0cJu0ztIABpC6DwlOZBoe0J9gFFWaikjGnX5 +JJkZslvEBFWrK2NSpsPeu89/ePZhLix7pYJmM2zo4XtMK/0hX0DpcIN+xeR0cXjP +sUZJhO2kNQKBgQCU2pTSPKuA0c1CKAHsSC+aRXi+E2NIv6VAfZMFlmJzSk6g8H9/ +Of0GyYdp5YN5Mei8nutZefn5k032hpxytuDW+/VRkKv/0oVAuU4R0tEoQwHeQhAa +BrsNhSTb+j1/P7RasK99TW4YjgF9m0yL9i/tzhIljLtdmFHuWqbwcdlq6QKBgQDp +KS5E1iexwZVqiHsdOeCTWrffj2mqscDQuU3UhIUDtnqlCk0AsIfbEOi5qsjt8E4d +9h3/5/pKGxVDnHPrhGgCf+iiZiq6lT5wUo1VEJBwqlI594GPhEGE/5ou8R3yOfit +LZ8xCsLsWV5/0LEihL1fHZhM8GC9tiJoKdCOrUXOsQKBgQDuaA2ZsV22PSNGdxRc +G/ofW1DUtW8gYHvykbC6RIWpYnRcXoFigSuSyZQQoAstQn4J1m/sZPgUFjqOZicx +QHgWKv4LAbGjI0GMNNEQdixI1XYubBUUIrv4vGFUsxo6OJxNQ1dzdzcoLjgrakvl +xZsIgVhJNixol+849LXUX1XF5Q== +-----END PRIVATE KEY----- diff --git a/certs/mysql_certs/server-key.pem b/certs/mysql_certs/server-key.pem new file mode 100644 index 0000000..c0589fc --- /dev/null +++ b/certs/mysql_certs/server-key.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQITSnU481rBeoCAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBDxAJ8qnGwZyOjPHlycB5o2BIIE +0CyZYQ7m1WPMbJ+Pmq0C/wEC6VGJ+UghhzSArJIshRRB7iNmIat9GwKqyaA9tAP0 +eiSVv2YYLO8k3r7zqyFUAgY8HjX+PLbpWoy7OuR7kR3mzrbXNa4fyP9KgL/WU2mc +JrWlOzFSt0y283qADeHsEVPJNHwIPKrhpSaNLbDO9mfRJHSaNF5kXlxJWcN5iMEn +RaDc2vF5FIkshvql6Sad4HRjP74ThVzN7yqiQeVHZSZOUGs4hMkzZmJ9Uwq6zMHl +NxSkrz0Ig+47bpAtsmir1NGtRm3YuXS1wkrfmFvek18kLmmVPklxH1cW/2JbWrJB +SeohqITG2xECKKySQpTnbZCtTuTgVbg3nkn4qDf3xarUjs+0CPf17oiXPPImsec0 +opukyA/AVQzL/ujtoAxgEcpJvAoOSMwuwDfWcZCfPWSeOkSqZce1vFjBReTiNvE+ +yctQ8R8fbrXxLzPfKSZqfRWPYTrhNkPzPVdO7AzrWs2yr9p1x8qLdJdxc/VdBvJe ++Ywzb6qq6Ds36dCPVisWfieHyZ5jPC7x6h4WRbQi2Rxp3c0CU8bolLKB4IggdSn1 +nRTHQhzWpBpIpex+ARJjW56KxBOk/qDEsK+Pde7cayokyK99s9ri/Sovh9178koO +AVoVJa0XemihtYEca4JuQNEj9t27g3/ekBS6RVrnQeJhPluLwhtA0vRz36OJGPzY +yXbKfb0l63p9bJw4PD7yCIfUE6/4yW9aOBWUsYQU8ETgUXlH8WaGaSCQg1oGAOCH +M7mqLWme3YxR2NQNhbMefc+YBfp4VJcidp3wSqVPN7/VleZ/7TasCNtDoxZEklqZ +BkbK8coMK6sj+mnCODDlG8iae2Yg+UhuZrzDJZ8NTx6mMObzXuKatJQ/fn2tay09 +VIUp907gqM5eROdArfvHEAojQRW3+9759UwjM0fTN+1VRXRD9tV2jofx4Q0aK43G +L8Fh5gyN+A/c8exA4aoJmq/4eoMrEjrgnybKby9DS86B8PBC1MW26gjM1yHE82j1 +UoGeNH6a/qRg+XmJOQJ+JXte4o3JwPb0PB7M9KxF5uquuG6oN0gGDPTNej5imdIV +ZPPWX+ZxdGiwy2xsWbegf2VMTlQL7mHcHeW+k2fYNgs0zhPyZE/bkV9PWta/ZQ3s +Y+A1DxskICE+sPXIxQXugxdTgyOfA8Xuq5EJZszQ+AFoyOo27BA3wCIotXlGXfEy ++8xlvHcobKSa7+lQJH8MfExk7wQEMsPWx/j2P39aPXTEim7XGNPNTKYbFTI+2yuY +TjqkrCeLuG9vz9vm1JjPNmK/DM0lKc5QAmr7p+CPEezWkhyriMSpUOc6eLK77I44 +DxgDFss1tPeMaU/Nd1nxntmkWDYm91Lz69TYlk9r99cFvovYdihq/3lULzsJ5aw/ +13UFtm0/HLMeKYdvhfBQ/jx4XUdeQBUOY09xk9g82zVPOG7vaxKqj0VHW0EEF+K2 ++2okHVIKFVcbxhKOjYLs4cdtKhRWG1MO7BBZw6vyuOCmHk2/03v/j3+dH2IJ1pzV +r8qCzlslNKpHKHAaGQ7weKSpCRQydvXnu4w82lt7PQ59ErEXHpaFtulvPtMs77aR +5eTksWcbH7YdFnJ2bB5AEw13yEEVW56PSPNjYRAiuWCe +-----END ENCRYPTED PRIVATE KEY----- diff --git a/certs/mysql_certs/server.csr b/certs/mysql_certs/server.csr new file mode 100644 index 0000000..467e846 --- /dev/null +++ b/certs/mysql_certs/server.csr @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICmDCCAYACAQAwUzELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYD +VQQHDAVQdXNhbjENMAsGA1UECgwEV1VEQzEVMBMGA1UEAwwMZGV2bG9nLW15c3Fs +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA6DoSeWqZ0/I5WI7VYe64 +IRdgyrWm1fh9YBKujcbK8y5X8359QaguDgs5wmQK2HEz4yy8HzRlbEc7FbZv5lkg +mBin3z1EVB1v06p0R4RcNBlwetzSvHRHb80Im+0uXrLy5k/rvVUjYHzUAr6cN38k +A2pGhpNhj/qSlNUp5qe7LZo1A7/Bt1CFXj34Ys17zKiNi53w4AFJ0IqKJwKMpLKu +bOhjK1KCWb41XibrbeinnPLILgHTm/8rzEOwQW8HgGasJIBpFc9IEFMyR+/QSz27 +KhWx86QbOaQWo5Ncq4PW0SXtITw3C8Fl6nM6+bUsSx7/euj4SvfI9VwPgHKu+5Y6 +HQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAAHo704+gQMIjN69TdqxuP4iXRxo +HEoIUj/zkTMPgxTVW33vtQmoSlwRia0o8q8+Y26CQzjs1V8ne47I7446MSAcG453 +qyIyPjmeqrvPSoWCRyzA43BuGpkAcV/C8MxnR6nG7js9cISzYXObABmuas0Qy7FN +1J1MjlMGqwymiS0rQoGQzqMkY2mqmAvk/DyCNv1GPUyZOBH1IwddwDLWq9fMvorT +7GKF0rwniW4ItrDPBEkLSdoJST1OTR69EMHpBQ4szo0nMlSwAx7k7SHm6je5m1Al +PEPuXMFRkI/ueMdsu+VPS6Cy/kWEhbvZEDq6hLxrVwI7j85WeEri49+p6sE= +-----END CERTIFICATE REQUEST----- diff --git a/certs/nginx_certs/ca.pem b/certs/nginx_certs/ca.pem new file mode 100644 index 0000000..bfcc81c --- /dev/null +++ b/certs/nginx_certs/ca.pem @@ -0,0 +1,40 @@ +-----BEGIN CERTIFICATE----- +MIIDHTCCAgUCFFw5T+2r/PXbAFYdJUeCO9NurTMhMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDE0NzIwWhcNMzUw +NDE0MDE0NzIwWjBLMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMQ0wCwYDVQQDDARXVURDMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntyNuLTcMjWIgfrSPi+pvf3hNsHLzlV1 +Ze+hXvqs4fE0p36qVbWRUkwv+Vp4drKrlXrY5Em7JCNZ0IT9PuZ3PwFbnG5FwV+O +H3B7WrRx4JHhLZU3w+WHFGfWFDZS2eYWrf1f1tDOlyq92xx0QEqDNE+gUMJ+z1N7 +5yoDForShgTmxD+KzYp4e7BvKKKn/U6YosBC8GyiMWdcka0IUMoTwpQRQ3Vlx/KX ++YGJlwWA9wvNWOqgcYCxeCIa7X+8K9i92Py01T82zqLMcr52Ri2JEdMzHz4pp/aZ +d56pfUhGuN6ET18fTTGeKuB1PrPu80OgfOYvJL9u1/twuO/xi/naBQIDAQABMA0G +CSqGSIb3DQEBCwUAA4IBAQAudZAxVxMh/aUeTj6WK5mpVpUINmAwt938FVSQ3nUC +mTafNjSNfu/Me+PvjCkmf9RNk5BSdHVwxseOBaU0WZnvLM056ayyQnSwWT53WwtZ +CSb3VHBbwaJqAiAnhQxCHHhWDK4rtIPrWZ/SCek2E5l29ccZHTuVnaFF3pG8YRs1 ++UXVWeCB0fIJwQdugtGkzVzgO1HW7tKyG1BqCP9q9FnfODia9d6jKAA5dScBPanG +yPNI8eq4Fpk4R6P1jM2jw9pCJ4CW4lpgGQD0KATJve+ooHJqL3Ri6xkljNymGfbP +UD8eyRxgUaKUloQY2UbafzO+cLn2uRiB4sfn/T8hybeW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIUJQjfesPiPpsGhsWZH4oTggUWVD8wDQYJKoZIhvcNAQEL +BQAwSzELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYDVQQHDAVQdXNh +bjENMAsGA1UECgwEV1VEQzENMAsGA1UEAwwEV1VEQzAeFw0yNTA0MTYwMTQzMDda +Fw0zNTA0MTQwMTQzMDdaMEsxCzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEO +MAwGA1UEBwwFUHVzYW4xDTALBgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDczXj3rCSwS+cv2FXTRWxcLxuE +m0DY9RqQfUZ6XM24IuBpxeicqIcGs8lYSWmx9cRFnxDG5beV5BSBOt3MoR9Q5Caf +Ld4JWHEIXicDk78vQjTE7QB1xPkieU0s4wqh9W0rPKEYsJ8SEhFgMUq5g5EiaVqQ +PRaaEeK1C0Zn0e5NdwcMgJ6E6uwij9E1U95QAw6AFyvqoS+s/1TsEcAkN3oIXdlD +kYy7lO4uLZI4Dfh77kfAf3LxKN/qE6WE20wyzipwb7yXFVzcTm5WoLSwV4axWrYF +Bc67x3X5t6K985Y1f5UMfnKq+JRAl0wFC0SOhS8NXHFVNQCbXvwldfcUSwc/AgMB +AAGjUzBRMB0GA1UdDgQWBBRa5Q7LNg15ZZYZSMQoowHLNtJtezAfBgNVHSMEGDAW +gBRa5Q7LNg15ZZYZSMQoowHLNtJtezAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQDXLVSeVyhCuw4oJoRw/wPwQEeBScxmpkSQvMzl19QhFXkxZxUT +5vhkCJc3lp1Ifa405n5I1+WFVhWyF1cSTXYddxz/eLjW1p54VNPXm5nxKzwwyftE +Ps36G32D0kdkk9pRqnsQ2Dt5j8hf2RVXzyswu97mTkRXTQMDwKUtwopr6HlIZiOY +BzUOEJf4NQr3VeGkXeWfqjWMU7ch7FWf7SsyqNFinAgiDLaIdEYSRBi8xiRJpOn4 +BUbhAlS2L4uGPWGa7TZ8sf8/s4Mdh784NSFn+4D6pVa7Quq5e6OmBVK4J851zAic +7mQsPhJLXactGWdksrjBSMQS7E5R8LG+k0j3 +-----END CERTIFICATE----- diff --git a/certs/nginx_certs/client-ca.pem b/certs/nginx_certs/client-ca.pem new file mode 100644 index 0000000..bfcc81c --- /dev/null +++ b/certs/nginx_certs/client-ca.pem @@ -0,0 +1,40 @@ +-----BEGIN CERTIFICATE----- +MIIDHTCCAgUCFFw5T+2r/PXbAFYdJUeCO9NurTMhMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDE0NzIwWhcNMzUw +NDE0MDE0NzIwWjBLMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMQ0wCwYDVQQDDARXVURDMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntyNuLTcMjWIgfrSPi+pvf3hNsHLzlV1 +Ze+hXvqs4fE0p36qVbWRUkwv+Vp4drKrlXrY5Em7JCNZ0IT9PuZ3PwFbnG5FwV+O +H3B7WrRx4JHhLZU3w+WHFGfWFDZS2eYWrf1f1tDOlyq92xx0QEqDNE+gUMJ+z1N7 +5yoDForShgTmxD+KzYp4e7BvKKKn/U6YosBC8GyiMWdcka0IUMoTwpQRQ3Vlx/KX ++YGJlwWA9wvNWOqgcYCxeCIa7X+8K9i92Py01T82zqLMcr52Ri2JEdMzHz4pp/aZ +d56pfUhGuN6ET18fTTGeKuB1PrPu80OgfOYvJL9u1/twuO/xi/naBQIDAQABMA0G +CSqGSIb3DQEBCwUAA4IBAQAudZAxVxMh/aUeTj6WK5mpVpUINmAwt938FVSQ3nUC +mTafNjSNfu/Me+PvjCkmf9RNk5BSdHVwxseOBaU0WZnvLM056ayyQnSwWT53WwtZ +CSb3VHBbwaJqAiAnhQxCHHhWDK4rtIPrWZ/SCek2E5l29ccZHTuVnaFF3pG8YRs1 ++UXVWeCB0fIJwQdugtGkzVzgO1HW7tKyG1BqCP9q9FnfODia9d6jKAA5dScBPanG +yPNI8eq4Fpk4R6P1jM2jw9pCJ4CW4lpgGQD0KATJve+ooHJqL3Ri6xkljNymGfbP +UD8eyRxgUaKUloQY2UbafzO+cLn2uRiB4sfn/T8hybeW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIUJQjfesPiPpsGhsWZH4oTggUWVD8wDQYJKoZIhvcNAQEL +BQAwSzELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYDVQQHDAVQdXNh +bjENMAsGA1UECgwEV1VEQzENMAsGA1UEAwwEV1VEQzAeFw0yNTA0MTYwMTQzMDda +Fw0zNTA0MTQwMTQzMDdaMEsxCzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEO +MAwGA1UEBwwFUHVzYW4xDTALBgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDczXj3rCSwS+cv2FXTRWxcLxuE +m0DY9RqQfUZ6XM24IuBpxeicqIcGs8lYSWmx9cRFnxDG5beV5BSBOt3MoR9Q5Caf +Ld4JWHEIXicDk78vQjTE7QB1xPkieU0s4wqh9W0rPKEYsJ8SEhFgMUq5g5EiaVqQ +PRaaEeK1C0Zn0e5NdwcMgJ6E6uwij9E1U95QAw6AFyvqoS+s/1TsEcAkN3oIXdlD +kYy7lO4uLZI4Dfh77kfAf3LxKN/qE6WE20wyzipwb7yXFVzcTm5WoLSwV4axWrYF +Bc67x3X5t6K985Y1f5UMfnKq+JRAl0wFC0SOhS8NXHFVNQCbXvwldfcUSwc/AgMB +AAGjUzBRMB0GA1UdDgQWBBRa5Q7LNg15ZZYZSMQoowHLNtJtezAfBgNVHSMEGDAW +gBRa5Q7LNg15ZZYZSMQoowHLNtJtezAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQDXLVSeVyhCuw4oJoRw/wPwQEeBScxmpkSQvMzl19QhFXkxZxUT +5vhkCJc3lp1Ifa405n5I1+WFVhWyF1cSTXYddxz/eLjW1p54VNPXm5nxKzwwyftE +Ps36G32D0kdkk9pRqnsQ2Dt5j8hf2RVXzyswu97mTkRXTQMDwKUtwopr6HlIZiOY +BzUOEJf4NQr3VeGkXeWfqjWMU7ch7FWf7SsyqNFinAgiDLaIdEYSRBi8xiRJpOn4 +BUbhAlS2L4uGPWGa7TZ8sf8/s4Mdh784NSFn+4D6pVa7Quq5e6OmBVK4J851zAic +7mQsPhJLXactGWdksrjBSMQS7E5R8LG+k0j3 +-----END CERTIFICATE----- diff --git a/certs/nginx_certs/client-cert.pem b/certs/nginx_certs/client-cert.pem new file mode 100644 index 0000000..d0732d1 --- /dev/null +++ b/certs/nginx_certs/client-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJjCCAg4CFFw5T+2r/PXbAFYdJUeCO9NurTMkMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDYwMDM0WhcNMjYw +NDE2MDYwMDM0WjBUMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMRYwFAYDVQQDDA1kZXZsb2ctY2xpZW50 +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3SbWErxx/vBb5icHdsGV +6H6UZ2yKHEth4QGaamc+xL56o15kW2rWE5WBltyvJloA1UcpEXmAcsqmAWj2YNPl +lZMDdq36PXX/cnDZON4+tIWseN1PZCS5ycxSayYrcmCnj2LDZzp7gLQKxlVsisqJ +GfaKrzjI7ysq/qWX3NaQa/OvayezUwyH68OLer4w1k7juh6bR3oWzbRBNFcnOzO2 +j9T5FITWN6fhjUPz9YXw71F7Mp0jdtDPxRZJTFIx3w+P/ikAeWTgLv6T7mEr8o3D +kXLKGY7ZFgv7BfvgqxDhoYbwGR4IfwljTME6hew51Gwp+vDUZ+frvikn8Q8ehUHV +OQIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQBx7hoHN/g6mKXxiZx4FBLvFt0BN1LO +YgP4s6bV3p979ar5FSHAFcOgyNa2zKMSNPCSkbEZUL+fzW9MrE+q2CcbaWG4nTGn +M6xWiuWXeTVASNpw6n+pq+fP4OlGjS7MrSjkCXyjhZu1MvhU4L3oJmH+bYEjsZxP +Jhgaq+BqySScFjkzh7xKqVVNwkNQD1bo0LuuE8Tpo6RFEblPsoa3KjQdby3HENhh +LqYzuxmcIevPLsW8UB8D2eIQ7mvHrViKrF5nIo7ioTvw8bp7bf7SrjGaHiQ/A8Ly +9iBkCGBx4cs0IrLvlcLvehbSwLKe4u+tH3BMOOljORS43xecI+RRsx9I +-----END CERTIFICATE----- diff --git a/certs/nginx_certs/client-key-nopass.pem b/certs/nginx_certs/client-key-nopass.pem new file mode 100644 index 0000000..7e614d7 --- /dev/null +++ b/certs/nginx_certs/client-key-nopass.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDdJtYSvHH+8Fvm +Jwd2wZXofpRnbIocS2HhAZpqZz7EvnqjXmRbatYTlYGW3K8mWgDVRykReYByyqYB +aPZg0+WVkwN2rfo9df9ycNk43j60hax43U9kJLnJzFJrJityYKePYsNnOnuAtArG +VWyKyokZ9oqvOMjvKyr+pZfc1pBr869rJ7NTDIfrw4t6vjDWTuO6HptHehbNtEE0 +Vyc7M7aP1PkUhNY3p+GNQ/P1hfDvUXsynSN20M/FFklMUjHfD4/+KQB5ZOAu/pPu +YSvyjcORcsoZjtkWC/sF++CrEOGhhvAZHgh/CWNMwTqF7DnUbCn68NRn5+u+KSfx +Dx6FQdU5AgMBAAECggEAGo4mx4IsWBYJmvXVzhtJwmysqkycuvAMVUXpglxaa6qJ +tGNJvrZx9VEPCgv+1iaZkgKk+k2yMFaIH4Q4jYD0QQUxtccHVOj93wKQ+uSo9+lT +QAInHdnRG1u3C9m9/tJ/XFbaKuOZX+d+obkxOus+EkmJ9qdlbV6sH37H4QM8vVGA +8u8weVznVbguZsjbGPOT/vttc3vcbx0SVnVMrP1yBHM+ZQ0MSCsr8AdFhUv8rb7i +0lQuBhTRzSGYclfBdDRxOh1qVrR8qfZitdYj6feJzpIDMJA2LCmDu6nae6PtY/4H +bjJtXc3NvvED3QGoZQ7bapPxlANXB4Pw4zCCDt4yXwKBgQD14XjCMi5byCI+Qfae +N6bszeuDkZLcmX4b5btiWzbJPlStAQsL8bPrz6Fx5ZLYeEPuyys3EQUDuWRmY4Kr +2CkZaLb4itL85beZvR+/aXh5G8T2fNqZJkS8YxYVtYILPSh4wBB711hW5KsDclzT +iLyi0FHqJB1jdssP6GgoRFueZwKBgQDmQNOTDEvTqwWryh5BJypRDAAzWSXu0KKO +RCljC7zfuVSdEVz1BxrxyCKpNfH1MbpdiWQFeGR3nSzh2qtjew3oqrtr8Yksg683 +V8KtJ3XREWi9QtdkNmFH3oYEjhCJQ7X55v0iukspLAbVoU/SVtXh6bGyTBbQurCP +8AN8wWtrXwKBgHUBxeiL3rm4hGsiEsz56MqZt3CVztCBjpyR91j31RtxOPRXIb2e +WKNn3AkKWZX/rTwunLMIu10pVRjQU/eY1v4Lcb7WuU61tmhHsprxAu6HA3TUt2XX +6y/G61SLWoYkpWTI6U81jAlVqffq7TeQw0urXL/STdXuSvWYADDhTsQTAoGBAKO8 +Pbg36kQfPe0n0dPrEgCIVCwvnPXyj2YzumqgkjNWC4GWM1BbOSHufBdwMRt3vVt+ +tA3fyzH0J1KEuZQIkZ9+qcDdBfsNua/VTK7tfK6rfpv3yEuPECaXax4aGFBEQkfv +ptrnN0OT91g7WhPthDMeiCqOSTstRxlUSGaS9NxpAoGBAJ/UKTyFFR4Svke7ZkK+ +NuxYYkb/HImZcAmK05QvaWe2uEgseWqstiwtH2vviFaj+jcfZnoQLAPqbnxXzh5m +w0tnOiV7HGj1iOWCi/JmXVnp15tZbWDx7OJJHysLPMgDjcA5KRMjCm59h2Rv5s0g +c6G9Es1YnTU8RnJkWaIeQu0k +-----END PRIVATE KEY----- diff --git a/certs/nginx_certs/client-key.pem b/certs/nginx_certs/client-key.pem new file mode 100644 index 0000000..af92128 --- /dev/null +++ b/certs/nginx_certs/client-key.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQINAHMTU39tfkCAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBCzcw1fhM2NPxhjIR3HH22QBIIE +0M5TzHexIK8By+Riks9D+4sRSKCjCOKswrv4N23XbMUXGRKBIYVWV8hHYms15eh8 +NQApiTpsH1raa3ffsuO5tO/GPWqVpHXrPkQiDaq8liLb+TVBmm/nnLuXiogcwS5W +A6g7D0M9kwhxqHtxmQujKATIXm6eL2LZCI98y2wIexTtP9YJJqz8L4hOsH1bBIFQ +92HfvEHSkhMkjQq1YtALqL4fHdoOmJzyBEJeJIa22dfcNrS32sQpauK6m4+B4yaR +otTxJmLetrmb++lF8q6FwlevPsihod72aiN04tqLdEhPX2klm97NPFvVnMZ8Kx4d +hwgPwybGtwZBPc/bf8uehVhz8s8iqVbfhbbyBpuLaFbw0qFEuU+DtU7E4jfi+/qm +zLZd8919eNdicuOhQhv4ISvM47h9yzGcZaTp2P8dyvdp6BPbLWa22AuN9xDIlzWE +qmlpW847KEyy0YRksqx6HKlAinJ7xPz+ZIYsVu/ztMAK3UP6rwJ8G8lxObQQTdMf +Tpfg/SObjEvmEtB8z2daB0VFswrMf7i0lpIM4Ngksb4SkC3cMaLRUzOQzirOMQaw +XrHLzv3OBUAc+8i9WW2OQjHE5YDPDvQYIBStNKoE4XIr2B6jDlXy/TpzCpKXZkeD +6WnEAUFxgx5ZxpktTaL9NCfbKQralMXYdUannEsznE/R3Q/Vou0FqEJ/FVSpc79L +bGPZwIAlOtsASlmyxHTp58TpDRnaTjLx2ti4zArdOJjsErii/z+U52nAQnxy72Pw +RrcxrDwBmfAnRZ55vlTjTkygtOxUMd6rGPSfx84+M7ocXrJyn/ZhZC527/SDYDsF +vO8anqU5QHvg+GEhAlJk9yroXee4jpN3unBtt7NPQ4yDJfXgk95HvminNMJ9bUNw +MQ7r8suqXYVl8dS0raZ9zALOEFwtf2v/qFmWJrW9QM5AxvHoHgV0HzII2YHGMqek +OpZ9SzpexDxJcJgLKz0cohOxCg2AV8LFxV5m95pcc4/4SXXfNpbF2zcM/BuwRqDS +ZEvtOXcBwSCFMNA3mlnDtf5Bpdpazb5mdgUuQ8WuLGOJ+/bPRsL94oDTCzSPs1Ia +7HBCAJp7gK+/aj4iUVftLimr88I3uWmBcQrf8UIyKWocj0cIZGoAmbKRmgnriIRw +WRu6qXGWrEDsGiS3cAJjtCH3NgE2sIbomXGjqLigwl33n9Tws9bKC5M5JSb4OiS9 +l6dFZNFnMV86m+c3v3UVn0+xOvShT14eMradfuSUNHKVqeZC1q/nntuWrb1B6zQJ +fHCoPaSgQoafi7I9q7W6oZ42sN/fiwWvZTc8DJu0gAYrIUPnzCpmXxzoeB+RIeaz +kzTLAxiDql1jmZun9sthKPy15+hBJAfieol4XmC1jmfN1juJyGWCKpRLnifo6/Jl +k5HsQMn3CMadAwLY0uG+5pUH/6BR93pgAA5O7l2ee0gpcOjk9WEPmHhMzpGdX6j7 +MG4xh71g3rxDvYbSMp10df7OxtCnPmOFjR0SDAk+kzD/YUn4XzfOfd7Bxhe8/fhV +18mUbYMpNgGHaQ+K2Dl2Mu8KCQzIqUJ5pPjcBDW2BgbAS4noLcjgtu+eYaqZvx97 +BTYoQDaSwblPgoJDtQIL+hOtFPVVqSrRTGhxMne7Kh/Q +-----END ENCRYPTED PRIVATE KEY----- diff --git a/certs/nginx_certs/client.csr b/certs/nginx_certs/client.csr new file mode 100644 index 0000000..4774ba8 --- /dev/null +++ b/certs/nginx_certs/client.csr @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICmTCCAYECAQAwVDELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYD +VQQHDAVQdXNhbjENMAsGA1UECgwEV1VEQzEWMBQGA1UEAwwNZGV2bG9nLWNsaWVu +dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAN0m1hK8cf7wW+YnB3bB +leh+lGdsihxLYeEBmmpnPsS+eqNeZFtq1hOVgZbcryZaANVHKRF5gHLKpgFo9mDT +5ZWTA3at+j11/3Jw2TjePrSFrHjdT2QkucnMUmsmK3Jgp49iw2c6e4C0CsZVbIrK +iRn2iq84yO8rKv6ll9zWkGvzr2sns1MMh+vDi3q+MNZO47oem0d6Fs20QTRXJzsz +to/U+RSE1jen4Y1D8/WF8O9RezKdI3bQz8UWSUxSMd8Pj/4pAHlk4C7+k+5hK/KN +w5FyyhmO2RYL+wX74KsQ4aGG8BkeCH8JY0zBOoXsOdRsKfrw1Gfn674pJ/EPHoVB +1TkCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4IBAQC1I9QFhPVVSKIRK/a/6sGQ/OPC +9aL+Ucv0XB93eZij8EnxVKuX+JU0LRcgZCw1J+AecBbl/kcmLtJcC3HbAxNxV9JV +lcKmki2tuaHpkiJ3AHGI1jpr6sAMyd9lhTtpDe+jq5jKreDauDH+vyBMuSNmpg70 +vHd7/O2ogBDwh6VkR+8JH8L7lztWFgddFLNqtjZUv6LErKREz9YiMCDJ/wA6WSj0 +2stXGIBx9QT4EKh7E4cTc/5BDLjrynFg2Xk//13iNnB3c4afmLEnCBChLsib+Q7F +LEmv79AR6ErBS0IYO/EMGH7g4eNkGosgNL6loNds4tbqslWaPGTkOOVat1+U +-----END CERTIFICATE REQUEST----- diff --git a/certs/nginx_certs/dhparam.pem b/certs/nginx_certs/dhparam.pem new file mode 100644 index 0000000..d7c1ead --- /dev/null +++ b/certs/nginx_certs/dhparam.pem @@ -0,0 +1,8 @@ +-----BEGIN DH PARAMETERS----- +MIIBCAKCAQEAj5Q42ch3w5QnqSRs0Lwx0qvYNwRHmDrxb8yAxkmFoPGYz4L2ykBz +q5iiJsskr0HWdWfav/zcgc8taEh8HRlWcYQqxZwx+X43J0Vxd8IaUBFJq8k8e6gs +5aa6NNhG2WJ28+xTyzMmgyxuadxU+CUmPJdQ6cZ2aJ1Pz46xpYyiip+UcIjlqve7 +Ok2FWlf6FendLL8MqKmaYatFbfwmuOYZ12rJiCPeFie41BTa1rZe/2LoM20+18zC +heS1stQjHLvUsNzIYIx/ecsLUf0MhMXPd/H4m4rPLkdkfyWeHZI56dt7/A5EQV2l +mf/jxmhGAW5cVi5Rq/YQ2yhXpN2xiGlXRwIBAg== +-----END DH PARAMETERS----- diff --git a/certs/nginx_certs/fullchain.pem b/certs/nginx_certs/fullchain.pem new file mode 100644 index 0000000..2962fab --- /dev/null +++ b/certs/nginx_certs/fullchain.pem @@ -0,0 +1,48 @@ +-----BEGIN CERTIFICATE----- +MIIDojCCAyigAwIBAgISBtX8xdMcA7U76qJ3ggfM6qwAMAoGCCqGSM49BAMDMDIx +CzAJBgNVBAYTAlVTMRYwFAYDVQQKEw1MZXQncyBFbmNyeXB0MQswCQYDVQQDEwJF +NjAeFw0yNTA0MTQxODA0MDNaFw0yNTA3MTMxODA0MDJaMBQxEjAQBgNVBAMTCXd1 +ZGMubGluazBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABP5fTZQrB2xvspcZw6OE +E8BQRV10PVc+ES6LJyqV2Lm5vBDTW7p5Jrkux/v7rHlfRlLdA4+weAiyOiw9t1yw +W+qjggI6MIICNjAOBgNVHQ8BAf8EBAMCB4AwHQYDVR0lBBYwFAYIKwYBBQUHAwEG +CCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFNXtO0IsxQYmeK5lWd7E +VstPzIYBMB8GA1UdIwQYMBaAFJMnRpgDqVFojpjWxEJI2yO/WJTSMFUGCCsGAQUF +BwEBBEkwRzAhBggrBgEFBQcwAYYVaHR0cDovL2U2Lm8ubGVuY3Iub3JnMCIGCCsG +AQUFBzAChhZodHRwOi8vZTYuaS5sZW5jci5vcmcvMBQGA1UdEQQNMAuCCXd1ZGMu +bGluazATBgNVHSAEDDAKMAgGBmeBDAECATAsBgNVHR8EJTAjMCGgH6AdhhtodHRw +Oi8vZTYuYy5sZW5jci5vcmcvMS5jcmwwggEFBgorBgEEAdZ5AgQCBIH2BIHzAPEA +dgDd3Mo0ldfhFgXnlTL6x5/4PRxQ39sAOhQSdgosrLvIKgAAAZY1ro31AAAEAwBH +MEUCIQCLqDa0QSg4SbMqvWqeJgMFQde83vn+5FymNcGzv/skwgIgPnLCaA+l0NXj +HKY6tZwxJsafvKwsX0IJkfKnM3iWZ6UAdwB9WR4S4XgqexxhZ3xe/fjQh1wUoE6V +nrkDL9kOjC55uAAAAZY1rpWPAAAEAwBIMEYCIQDSGl8VvseVkuOXdEa8vXARvtup +TiLy2X9i0vprMF2w0gIhAJc0nY/t5jyZ6l12tpOeWxnoaS2y6QlYR1NQxllMwheH +MAoGCCqGSM49BAMDA2gAMGUCME5DUISzz+IaJ7gGVl7ArksQ2U107kmcNCrAke5i +BlhD65cfHGB+iQR6JroepHvmnQIxAMv78mix461p4WzHrm6c6JSpqqzL0d9TTeyi +VOTSvJXRjK1SA1tMim9rpXRFobBsIg== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEVzCCAj+gAwIBAgIRALBXPpFzlydw27SHyzpFKzgwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMjQwMzEzMDAwMDAw +WhcNMjcwMzEyMjM1OTU5WjAyMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg +RW5jcnlwdDELMAkGA1UEAxMCRTYwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATZ8Z5G +h/ghcWCoJuuj+rnq2h25EqfUJtlRFLFhfHWWvyILOR/VvtEKRqotPEoJhC6+QJVV +6RlAN2Z17TJOdwRJ+HB7wxjnzvdxEP6sdNgA1O1tHHMWMxCcOrLqbGL0vbijgfgw +gfUwDgYDVR0PAQH/BAQDAgGGMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcD +ATASBgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBSTJ0aYA6lRaI6Y1sRCSNsj +v1iU0jAfBgNVHSMEGDAWgBR5tFnme7bl5AFzgAiIyBpY9umbbjAyBggrBgEFBQcB +AQQmMCQwIgYIKwYBBQUHMAKGFmh0dHA6Ly94MS5pLmxlbmNyLm9yZy8wEwYDVR0g +BAwwCjAIBgZngQwBAgEwJwYDVR0fBCAwHjAcoBqgGIYWaHR0cDovL3gxLmMubGVu +Y3Iub3JnLzANBgkqhkiG9w0BAQsFAAOCAgEAfYt7SiA1sgWGCIpunk46r4AExIRc +MxkKgUhNlrrv1B21hOaXN/5miE+LOTbrcmU/M9yvC6MVY730GNFoL8IhJ8j8vrOL +pMY22OP6baS1k9YMrtDTlwJHoGby04ThTUeBDksS9RiuHvicZqBedQdIF65pZuhp +eDcGBcLiYasQr/EO5gxxtLyTmgsHSOVSBcFOn9lgv7LECPq9i7mfH3mpxgrRKSxH +pOoZ0KXMcB+hHuvlklHntvcI0mMMQ0mhYj6qtMFStkF1RpCG3IPdIwpVCQqu8GV7 +s8ubknRzs+3C/Bm19RFOoiPpDkwvyNfvmQ14XkyqqKK5oZ8zhD32kFRQkxa8uZSu +h4aTImFxknu39waBxIRXE4jKxlAmQc4QjFZoq1KmQqQg0J/1JF8RlFvJas1VcjLv +YlvUB2t6npO6oQjB3l+PNf0DpQH7iUx3Wz5AjQCi6L25FjyE06q6BZ/QlmtYdl/8 +ZYao4SRqPEs/6cAiF+Qf5zg2UkaWtDphl1LKMuTNLotvsX99HP69V2faNyegodQ0 +LyTApr/vT01YPE46vNsDLgK+4cL6TrzC/a4WcmF5SRJ938zrv/duJHLXQIku5v0+ +EwOy59Hdm0PT/Er/84dDV0CSjdR/2XuZM3kpysSKLgD1cKiDA+IRguODCxfO9cyY +Ig46v9mFmBvyH04= +-----END CERTIFICATE----- diff --git a/certs/nginx_certs/keystore.p12 b/certs/nginx_certs/keystore.p12 new file mode 100644 index 0000000..15bf2f2 Binary files /dev/null and b/certs/nginx_certs/keystore.p12 differ diff --git a/certs/nginx_certs/privkey.pem b/certs/nginx_certs/privkey.pem new file mode 100644 index 0000000..69752b7 --- /dev/null +++ b/certs/nginx_certs/privkey.pem @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg7aVdUdXHPQbdYclU +idagaVxyeOvQk+tCGxtYxt/CPWKhRANCAAT+X02UKwdsb7KXGcOjhBPAUEVddD1X +PhEuiycqldi5ubwQ01u6eSa5Lsf7+6x5X0ZS3QOPsHgIsjosPbdcsFvq +-----END PRIVATE KEY----- diff --git a/certs/nginx_certs/server-cert.pem b/certs/nginx_certs/server-cert.pem new file mode 100644 index 0000000..ffee905 --- /dev/null +++ b/certs/nginx_certs/server-cert.pem @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDXDCCAkQCFFw5T+2r/PXbAFYdJUeCO9NurTMiMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDMwMjQzWhcNMjYw +NDE2MDMwMjQzWjCBiTELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYD +VQQHDAVQdXNhbjENMAsGA1UECgwEV1VEQzENMAsGA1UECwwEV1VEQzEVMBMGA1UE +AwwMZGV2bG9nLW5naW54MSUwIwYJKoZIhvcNAQkBFhZxcXd3ZWU3NzE0NDFAZ21h +aWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8B8rbUMBDAUe +47l7uKtGTgwTAStVBueE69flhTnd4f2cOigIueXYqHPL8vukLHFkCvHYS2DSsTsd +ja/QhaqWQJK0f5H3by4KzbFgCfRbjWnw2LYTfk4zZo69gkCPbsaDPAZ2Q6XyLZ7Z +SBu+uTTHftVELieUu1HKjJCyfH7M9++cufM1V61EgODI5YEVcziyGuTZaQLeKi10 +5wMe57FcDq4ABNDjvmGAsJKzR5+z8W02f5OIacjcOVStlsyzvkrCEvJKcacgeH+s +RBA8tG8hUup8c13TjLQ0CM8GF7PigXRKYFfiLRqJju7ThNKKzozLJWknK69SMW61 +1tR3ohb90wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCb5BMY3Woz1Kxeog+7oHnv +wVSVemf+XbtRfkOQrFSlEFY10OKlIf4mT2PEkgmx8lfNHx+RGmv0w1g9N/VAw7zh +X1wr39GOrawzgWZLo7kLtfNQ2Q2IfLttTkiR2IlYPdGU0+c1LS1EnuVHgZbjwnyf +e4SAfA/zc2CKY3H23H7rm1Xr8aKKzLB/13Xc1kGEzQVucnoJyMPtwRDzWnFO5qoA +7d4ClDoYg3Wy6I02ZKKMkktKSfbp+AQBzvT1s+iPWookpdkco/DZCkO6tmyh6CdR +FaYUymw5MVElSCjRkzDQ0DhO5fZhu3w0VgaSfusRIfeXSGxUBhzrV7Z0Vjh2cFgQ +-----END CERTIFICATE----- diff --git a/certs/nginx_certs/server-full-chain.pem b/certs/nginx_certs/server-full-chain.pem new file mode 100644 index 0000000..23ccba5 --- /dev/null +++ b/certs/nginx_certs/server-full-chain.pem @@ -0,0 +1,39 @@ +-----BEGIN CERTIFICATE----- +MIIDXDCCAkQCFFw5T+2r/PXbAFYdJUeCO9NurTMiMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDMwMjQzWhcNMjYw +NDE2MDMwMjQzWjCBiTELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYD +VQQHDAVQdXNhbjENMAsGA1UECgwEV1VEQzENMAsGA1UECwwEV1VEQzEVMBMGA1UE +AwwMZGV2bG9nLW5naW54MSUwIwYJKoZIhvcNAQkBFhZxcXd3ZWU3NzE0NDFAZ21h +aWwuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA8B8rbUMBDAUe +47l7uKtGTgwTAStVBueE69flhTnd4f2cOigIueXYqHPL8vukLHFkCvHYS2DSsTsd +ja/QhaqWQJK0f5H3by4KzbFgCfRbjWnw2LYTfk4zZo69gkCPbsaDPAZ2Q6XyLZ7Z +SBu+uTTHftVELieUu1HKjJCyfH7M9++cufM1V61EgODI5YEVcziyGuTZaQLeKi10 +5wMe57FcDq4ABNDjvmGAsJKzR5+z8W02f5OIacjcOVStlsyzvkrCEvJKcacgeH+s +RBA8tG8hUup8c13TjLQ0CM8GF7PigXRKYFfiLRqJju7ThNKKzozLJWknK69SMW61 +1tR3ohb90wIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCb5BMY3Woz1Kxeog+7oHnv +wVSVemf+XbtRfkOQrFSlEFY10OKlIf4mT2PEkgmx8lfNHx+RGmv0w1g9N/VAw7zh +X1wr39GOrawzgWZLo7kLtfNQ2Q2IfLttTkiR2IlYPdGU0+c1LS1EnuVHgZbjwnyf +e4SAfA/zc2CKY3H23H7rm1Xr8aKKzLB/13Xc1kGEzQVucnoJyMPtwRDzWnFO5qoA +7d4ClDoYg3Wy6I02ZKKMkktKSfbp+AQBzvT1s+iPWookpdkco/DZCkO6tmyh6CdR +FaYUymw5MVElSCjRkzDQ0DhO5fZhu3w0VgaSfusRIfeXSGxUBhzrV7Z0Vjh2cFgQ +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDHTCCAgUCFFw5T+2r/PXbAFYdJUeCO9NurTMhMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDE0NzIwWhcNMzUw +NDE0MDE0NzIwWjBLMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMQ0wCwYDVQQDDARXVURDMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntyNuLTcMjWIgfrSPi+pvf3hNsHLzlV1 +Ze+hXvqs4fE0p36qVbWRUkwv+Vp4drKrlXrY5Em7JCNZ0IT9PuZ3PwFbnG5FwV+O +H3B7WrRx4JHhLZU3w+WHFGfWFDZS2eYWrf1f1tDOlyq92xx0QEqDNE+gUMJ+z1N7 +5yoDForShgTmxD+KzYp4e7BvKKKn/U6YosBC8GyiMWdcka0IUMoTwpQRQ3Vlx/KX ++YGJlwWA9wvNWOqgcYCxeCIa7X+8K9i92Py01T82zqLMcr52Ri2JEdMzHz4pp/aZ +d56pfUhGuN6ET18fTTGeKuB1PrPu80OgfOYvJL9u1/twuO/xi/naBQIDAQABMA0G +CSqGSIb3DQEBCwUAA4IBAQAudZAxVxMh/aUeTj6WK5mpVpUINmAwt938FVSQ3nUC +mTafNjSNfu/Me+PvjCkmf9RNk5BSdHVwxseOBaU0WZnvLM056ayyQnSwWT53WwtZ +CSb3VHBbwaJqAiAnhQxCHHhWDK4rtIPrWZ/SCek2E5l29ccZHTuVnaFF3pG8YRs1 ++UXVWeCB0fIJwQdugtGkzVzgO1HW7tKyG1BqCP9q9FnfODia9d6jKAA5dScBPanG +yPNI8eq4Fpk4R6P1jM2jw9pCJ4CW4lpgGQD0KATJve+ooHJqL3Ri6xkljNymGfbP +UD8eyRxgUaKUloQY2UbafzO+cLn2uRiB4sfn/T8hybeW +-----END CERTIFICATE----- diff --git a/certs/nginx_certs/server-key-nopass.pem b/certs/nginx_certs/server-key-nopass.pem new file mode 100644 index 0000000..9ad3b34 --- /dev/null +++ b/certs/nginx_certs/server-key-nopass.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDwHyttQwEMBR7j +uXu4q0ZODBMBK1UG54Tr1+WFOd3h/Zw6KAi55dioc8vy+6QscWQK8dhLYNKxOx2N +r9CFqpZAkrR/kfdvLgrNsWAJ9FuNafDYthN+TjNmjr2CQI9uxoM8BnZDpfItntlI +G765NMd+1UQuJ5S7UcqMkLJ8fsz375y58zVXrUSA4MjlgRVzOLIa5NlpAt4qLXTn +Ax7nsVwOrgAE0OO+YYCwkrNHn7PxbTZ/k4hpyNw5VK2WzLO+SsIS8kpxpyB4f6xE +EDy0byFS6nxzXdOMtDQIzwYXs+KBdEpgV+ItGomO7tOE0orOjMslaScrr1IxbrXW +1HeiFv3TAgMBAAECggEAG8GGnA0kOFb5MJqWrgyCRnBH3Bk9rVwitb5Rhhpfxwkx +P1m0VIS4jBRCjXZADnEW/trgxSnOgPUT3/ZkNKc40gMDQUHGp7/bONaZgt0PniOS +VwKI808nyuGSD1XWHphswAoBLqWmflZ/yEGIxNDQhJ0zUeB6ZUK5lpD9SL3BhKEh +GOzC9n/3R11rv9e1sOpPbS2G/46VXcvrk2rODtTbAhRjVK7qNNU1atSENFY7i+ro +ZpOaecxaGKa53v8G2DSWwrhmwiKUaEYl4/1r/Nt7m+0DuHJ+N9rluLMzZBJ2FSCj +fL/KwCgpS/wgmTyaBO5aroAZ9Kn1rJg9ypLqpeO8SQKBgQD8sDl7y6YqnbV0UIvT +qFfA5A+eHLY6WPE6hKITvZtaNlV5oXFHoZnxafSNmGMMUD2xX+Ho9Bbod64T70S8 +AtFkYWa0aCGKrOXFp4yQ1YhoDvwNrkrfC2/21B6TcnNlXtnJt4N75BqmHpCAsMr3 +Y4gh39TD/E9OoxZlqjvM83W82QKBgQDzRMil9HwZuWCVpESI2vKAzTscrbjmG0zX +eixpYAXTYbftEPXomEeK8vK69C1VY5C7bg9dAkN4EstMf0UhVzqc/prlJU4lYyld +NCrdHWLDOHRVRjDBS+uQMtD80QTfWhnuwuiXSXVwgOVXXHId87LpaoXbnWiNWIPt +VeJyUlyUiwKBgQC03nbm86gnDOfbL+RGRlM50ISVSjvtm8hfeJyCwM84TXf1Lefl +u/hy4sKXr6IGKFFgax9LHZxgHXpO7o8mfGXhEg7vxzFrD8MQLZPh/9Y5lbA5Iq6Y +5Jjp9n9ET0HmJ3SAfIjy3x1sFqicVO+TEH1asdqbxjgGM5pOfwF6/DEkUQKBgQDB +cJOgqz03c1ojA+FfdcLZuthjYJyjlVfQDB1emXVodc68Wv0/vD8/dRqgoOOff7Gp +6MWcrt0ZkNBJ/vXooiVAi5B93+E5B5XvwDjJZMGQ/MY8vLiuLAGfw1i9gDPAqzPt +0N0E1+vjzwsPIbq4qH0+8G6KiKuHobgeNGoBgA6QKwKBgB70EBBkOMCVjlgbHicz +wMsVGgi4mlkC7SNtHusi4zWV5+kh87KYflWk8LoWzc8KYmVN0qDzzvs53JUhAGkD +kGS2MuuIAqw1rjXPBKifsnJyfS2+pyGF6c7z9kCJss90ePtm1sonMG56L1LmZcV/ +hCTqqysgo3mBWRUNNoJzlprr +-----END PRIVATE KEY----- diff --git a/certs/nginx_certs/server-key.pem b/certs/nginx_certs/server-key.pem new file mode 100644 index 0000000..7401c33 --- /dev/null +++ b/certs/nginx_certs/server-key.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIOBcFegCBRRoCAggA +MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBCicQp3l4+1A0XvQzcsRXyyBIIE +0Il3EU0OxxKrqgzOSE01dwi3+nvu/FWvhxsR/HPbW1aIlk48pQX3t60CKYOHp/6s +6tWukOlBekuM7JComIDeZxExoZiCLmv3YAq+aOoeuqqX2Nl2dJB/+g23gpL4BRqe +jGo0WVqi6Pa30x1llYuddraDgcyQ9He11Wj2NfwjnihjX+gxX79YSpB3Mb41JIZ2 +NKbrtHEHEW09A9CeLn9V89h2+ixWFuvU7wemnK1qIOTwAJKG4YSLEh4QIQEHPyHe +Ep8lGqaWrjP2u/sQYaJr/9hThge/cipgXW+AJDerGHG+3rFqhz4d+fLJsRO/Ue3U +6qsYu9ESnUEUyJn65IxXFcdiST8qJxfrkYxeIfud/A7xJ7op/cOwO/bR1vEMlRdr +z3GuSQZausLhUy1LOo80RSgI548DN5cKuIWMCYUuaI7tMEUA4J3uEtRUz1D3N4F7 +iB+JAcNhG4kTn+jWy5lDlBO0Ug2fZJKh1nAocc+eYrhU+cImRK+CIAP8kKSUKlbu +TFyLR6v4SKjEgetYinzI2nm3AoLQ/7qFJxHXYwByRc+/CCJhPTzLnZ26f0iRTImz +LygfhDenWP8tTmL/uAKf3TtrFj1SNK0hEPpkpdPESVjmK9Ml0q5m3EUT2Qw3PtAB +Fd0vITaUibhbUpkftPxJxz0EnSJwpwPJupF1zLAUc0Q3dDekXajd3qzsnk1WPfit +wIMXBWc8yq+kEjnM/+gXTgKAYCOT0ByC8fyI0f/39F6ATNsLWRugji+z18e5DMg1 +rTBE6ITmNURlVS2YoGZSb+YXxNLkcigiPWpIJqAFIU4xo9lLltIaipzcpay4cGMx +17KK/QChi6+376tCBynSS6TDhcWqxTUcPMH0PBdw8u9tvjGh1yBOVbMCWUkHq9O3 +G2bNdyAkbwNe1EHIP2UR0YM3ulzlvJmx5VzD13wM3GOldyLUOjM9IeDdzrtmZ7dy +KCsIEGp1GSWux624vHZZzdA3M6rXBrtOfI8hGgc/JZOXviwGeBwXK7TbA7BkMQ+R +k46TP2r3NTvxXfoWNV40XcxDRY7I8aoFvwI31S8ewAqok2Jfa9lzlYQLtt5ZBcqA +c4P3LlPrGy92A+d0smGBsUxkq4hyiehgxJk2nibn0FSA1U9Vxvl6HQcFJoWB7ETp +azSgEFiEdH0Pjy7rO5looAdMNn8ZBWlffLUClBBim/JzzDXbS5biRjeEDcdcIxw0 +GlnUrgr9fVGLMiQmSzF1wWHrzEgX31ilboYQkxhh26AMiVjdCefk53uWk7iwhPNU +5Bat3Ag6jTh2lpEwGMVjo6M8vL0gK4EMyTEQMbS/kx7tntCOSQbhEdXbDjif5xwT +jb1mq10BUsArgDZo1wicUEpEMFFpr2ehy1g5WKkNYAdYdiQzXggj5DwdFJ6l3SA1 +5AQ1mq6Z2YB22dvpfiHyLY26lwBXXE3I7apJ2FPHmuM8WsU+Y5l3AmbcMBCaVjwZ +xNlW1hp3Z1JgoecwTawnFuZzzucYfOSvTEVn/uo1UMfjwpcnBXHWnNcRVEpYx0Xf +WE/ZI+taiqLY65cAQOLiz4mixX1lUB9frU1fBvZOVCo3tXBOBPBvTcq+QqPOXnLH +fj1y6e30iAawKf9JkPXWpG8Lon9Oq4Oe1SjDWu5BIqZd +-----END ENCRYPTED PRIVATE KEY----- diff --git a/certs/nginx_certs/server.csr b/certs/nginx_certs/server.csr new file mode 100644 index 0000000..36e8c8b --- /dev/null +++ b/certs/nginx_certs/server.csr @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIC/TCCAeUCAQAwgYkxCzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwG +A1UEBwwFUHVzYW4xDTALBgNVBAoMBFdVREMxDTALBgNVBAsMBFdVREMxFTATBgNV +BAMMDGRldmxvZy1uZ2lueDElMCMGCSqGSIb3DQEJARYWcXF3d2VlNzcxNDQxQGdt +YWlsLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAPAfK21DAQwF +HuO5e7irRk4MEwErVQbnhOvX5YU53eH9nDooCLnl2Khzy/L7pCxxZArx2Etg0rE7 +HY2v0IWqlkCStH+R928uCs2xYAn0W41p8Ni2E35OM2aOvYJAj27GgzwGdkOl8i2e +2Ugbvrk0x37VRC4nlLtRyoyQsnx+zPfvnLnzNVetRIDgyOWBFXM4shrk2WkC3iot +dOcDHuexXA6uAATQ475hgLCSs0efs/FtNn+TiGnI3DlUrZbMs75KwhLySnGnIHh/ +rEQQPLRvIVLqfHNd04y0NAjPBhez4oF0SmBX4i0aiY7u04TSis6MyyVpJyuvUjFu +tdbUd6IW/dMCAwEAAaAuMBMGCSqGSIb3DQEJAjEGDARXVURDMBcGCSqGSIb3DQEJ +BzEKDAhhYXNzZGQ0MTANBgkqhkiG9w0BAQsFAAOCAQEAR6QlE6QrwhSEJjzUQAr4 +Ld6K22ea09EBxa6BTjIiOTepokhS5yoP799+cR6200/fXA1TQ9W250O+uMBm2/D7 +ln9xcdIE5zfoyXJj/VdTwWKWyS7rRtcFlws/d6Iesj+LH34mrFG/mKSfenC2STuF +pP96SP/3geuA30633y/8agZSAFE04FHh69Oe41C07JvKRG7rVshi8s7A4b++Lgit +9tn9EReWWC3k3jzH1ZVGJuRUQNCayTvCc3JB+w/ZEBAFGQvxynalM/dytRmM36GL +paERO4SMUecLVY+FxsFzyjyNI5LqGSVRllEYVjYlIg0OyN8T4APHKh9+aiEtAyHo +Sw== +-----END CERTIFICATE REQUEST----- diff --git a/certs/nginx_certs/truststore.p12 b/certs/nginx_certs/truststore.p12 new file mode 100644 index 0000000..8191db8 Binary files /dev/null and b/certs/nginx_certs/truststore.p12 differ diff --git a/certs/redis_certs/ca.pem b/certs/redis_certs/ca.pem new file mode 100644 index 0000000..bfcc81c --- /dev/null +++ b/certs/redis_certs/ca.pem @@ -0,0 +1,40 @@ +-----BEGIN CERTIFICATE----- +MIIDHTCCAgUCFFw5T+2r/PXbAFYdJUeCO9NurTMhMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDE0NzIwWhcNMzUw +NDE0MDE0NzIwWjBLMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMQ0wCwYDVQQDDARXVURDMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntyNuLTcMjWIgfrSPi+pvf3hNsHLzlV1 +Ze+hXvqs4fE0p36qVbWRUkwv+Vp4drKrlXrY5Em7JCNZ0IT9PuZ3PwFbnG5FwV+O +H3B7WrRx4JHhLZU3w+WHFGfWFDZS2eYWrf1f1tDOlyq92xx0QEqDNE+gUMJ+z1N7 +5yoDForShgTmxD+KzYp4e7BvKKKn/U6YosBC8GyiMWdcka0IUMoTwpQRQ3Vlx/KX ++YGJlwWA9wvNWOqgcYCxeCIa7X+8K9i92Py01T82zqLMcr52Ri2JEdMzHz4pp/aZ +d56pfUhGuN6ET18fTTGeKuB1PrPu80OgfOYvJL9u1/twuO/xi/naBQIDAQABMA0G +CSqGSIb3DQEBCwUAA4IBAQAudZAxVxMh/aUeTj6WK5mpVpUINmAwt938FVSQ3nUC +mTafNjSNfu/Me+PvjCkmf9RNk5BSdHVwxseOBaU0WZnvLM056ayyQnSwWT53WwtZ +CSb3VHBbwaJqAiAnhQxCHHhWDK4rtIPrWZ/SCek2E5l29ccZHTuVnaFF3pG8YRs1 ++UXVWeCB0fIJwQdugtGkzVzgO1HW7tKyG1BqCP9q9FnfODia9d6jKAA5dScBPanG +yPNI8eq4Fpk4R6P1jM2jw9pCJ4CW4lpgGQD0KATJve+ooHJqL3Ri6xkljNymGfbP +UD8eyRxgUaKUloQY2UbafzO+cLn2uRiB4sfn/T8hybeW +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDdzCCAl+gAwIBAgIUJQjfesPiPpsGhsWZH4oTggUWVD8wDQYJKoZIhvcNAQEL +BQAwSzELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYDVQQHDAVQdXNh +bjENMAsGA1UECgwEV1VEQzENMAsGA1UEAwwEV1VEQzAeFw0yNTA0MTYwMTQzMDda +Fw0zNTA0MTQwMTQzMDdaMEsxCzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEO +MAwGA1UEBwwFUHVzYW4xDTALBgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDczXj3rCSwS+cv2FXTRWxcLxuE +m0DY9RqQfUZ6XM24IuBpxeicqIcGs8lYSWmx9cRFnxDG5beV5BSBOt3MoR9Q5Caf +Ld4JWHEIXicDk78vQjTE7QB1xPkieU0s4wqh9W0rPKEYsJ8SEhFgMUq5g5EiaVqQ +PRaaEeK1C0Zn0e5NdwcMgJ6E6uwij9E1U95QAw6AFyvqoS+s/1TsEcAkN3oIXdlD +kYy7lO4uLZI4Dfh77kfAf3LxKN/qE6WE20wyzipwb7yXFVzcTm5WoLSwV4axWrYF +Bc67x3X5t6K985Y1f5UMfnKq+JRAl0wFC0SOhS8NXHFVNQCbXvwldfcUSwc/AgMB +AAGjUzBRMB0GA1UdDgQWBBRa5Q7LNg15ZZYZSMQoowHLNtJtezAfBgNVHSMEGDAW +gBRa5Q7LNg15ZZYZSMQoowHLNtJtezAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4IBAQDXLVSeVyhCuw4oJoRw/wPwQEeBScxmpkSQvMzl19QhFXkxZxUT +5vhkCJc3lp1Ifa405n5I1+WFVhWyF1cSTXYddxz/eLjW1p54VNPXm5nxKzwwyftE +Ps36G32D0kdkk9pRqnsQ2Dt5j8hf2RVXzyswu97mTkRXTQMDwKUtwopr6HlIZiOY +BzUOEJf4NQr3VeGkXeWfqjWMU7ch7FWf7SsyqNFinAgiDLaIdEYSRBi8xiRJpOn4 +BUbhAlS2L4uGPWGa7TZ8sf8/s4Mdh784NSFn+4D6pVa7Quq5e6OmBVK4J851zAic +7mQsPhJLXactGWdksrjBSMQS7E5R8LG+k0j3 +-----END CERTIFICATE----- diff --git a/certs/redis_certs/server-cert.pem b/certs/redis_certs/server-cert.pem new file mode 100644 index 0000000..cdfbd82 --- /dev/null +++ b/certs/redis_certs/server-cert.pem @@ -0,0 +1,19 @@ +-----BEGIN CERTIFICATE----- +MIIDJTCCAg0CFFhWSQG3H1J81Xt7M+sUWjv4RQK/MA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDIyMzUzWhcNMjYw +NDE2MDIyMzUzWjBTMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMRUwEwYDVQQDDAxkZXZsb2ctcmVkaXMw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD6WlIHMwItBuZD2HLs0sJb +OwQTvZmeCvcqCkTCc19GSWTAO0yuw8aZlsaNFKlOamGknDXVzUOKOojNNAzOeW4Y +/sbG+AtgFiFLblRPGIbKs4fNJa9TMJkt7u69YNVgRLnXRHIcoaQz/Ah5+398Puyw +HFSZOYpAmcxVCalYTT3+NnX4+Umnq/a7YFZFiiHBKWvmhCc+zp6CFQ1JjLKb/bdN +YiNxQoQ4RQf5/FquuQT5ZigsLGRMnnKA1IB3nmLR4PklGGEyYYakh+mFBnmWGWny +uxOj/6Q9ZsCs1jsrwl/W1EtWqjFZpWCJmkJbnNXMCgtriz1sE3P/RqbAF/1H9exF +AgMBAAEwDQYJKoZIhvcNAQELBQADggEBACR9DxcobWtjqiiR3r7/jQYMdnUVwGkv +euMJXRCzzPVV1PkgEMotGTksoZ0GElProZo5TK/DiGmc5zi3Ql7rADk6blDk1kj3 +W0i9/rDwBkgC5r7NFHoxS/73yumAZsrIjz0sYlRgzUHc33jv7pb2RPmkFZ4Qj5By +R3W7/rN+9+GflTvMgVeTljPfyS5/Tt5svnp1oE97vd/TMPZgMF1KHYG4yaGQfztX +YnPYb2knshGeGWmCzNoPrQI3Ug3LKyXwN2f2QYqeCahGztBkZPp3yy+CNSqMtI0H +iSwkDA0dGd2Fk6w4ZI/o+QUvOwSgsxx96LXaffCGbSpdeJXflyFRopM= +-----END CERTIFICATE----- diff --git a/certs/redis_certs/server-full-chain.pem b/certs/redis_certs/server-full-chain.pem new file mode 100644 index 0000000..931c5bf --- /dev/null +++ b/certs/redis_certs/server-full-chain.pem @@ -0,0 +1,38 @@ +-----BEGIN CERTIFICATE----- +MIIDJTCCAg0CFFhWSQG3H1J81Xt7M+sUWjv4RQK/MA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDIyMzUzWhcNMjYw +NDE2MDIyMzUzWjBTMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMRUwEwYDVQQDDAxkZXZsb2ctcmVkaXMw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQD6WlIHMwItBuZD2HLs0sJb +OwQTvZmeCvcqCkTCc19GSWTAO0yuw8aZlsaNFKlOamGknDXVzUOKOojNNAzOeW4Y +/sbG+AtgFiFLblRPGIbKs4fNJa9TMJkt7u69YNVgRLnXRHIcoaQz/Ah5+398Puyw +HFSZOYpAmcxVCalYTT3+NnX4+Umnq/a7YFZFiiHBKWvmhCc+zp6CFQ1JjLKb/bdN +YiNxQoQ4RQf5/FquuQT5ZigsLGRMnnKA1IB3nmLR4PklGGEyYYakh+mFBnmWGWny +uxOj/6Q9ZsCs1jsrwl/W1EtWqjFZpWCJmkJbnNXMCgtriz1sE3P/RqbAF/1H9exF +AgMBAAEwDQYJKoZIhvcNAQELBQADggEBACR9DxcobWtjqiiR3r7/jQYMdnUVwGkv +euMJXRCzzPVV1PkgEMotGTksoZ0GElProZo5TK/DiGmc5zi3Ql7rADk6blDk1kj3 +W0i9/rDwBkgC5r7NFHoxS/73yumAZsrIjz0sYlRgzUHc33jv7pb2RPmkFZ4Qj5By +R3W7/rN+9+GflTvMgVeTljPfyS5/Tt5svnp1oE97vd/TMPZgMF1KHYG4yaGQfztX +YnPYb2knshGeGWmCzNoPrQI3Ug3LKyXwN2f2QYqeCahGztBkZPp3yy+CNSqMtI0H +iSwkDA0dGd2Fk6w4ZI/o+QUvOwSgsxx96LXaffCGbSpdeJXflyFRopM= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDHTCCAgUCFFw5T+2r/PXbAFYdJUeCO9NurTMhMA0GCSqGSIb3DQEBCwUAMEsx +CzAJBgNVBAYTAktSMQ4wDAYDVQQIDAVQdXNhbjEOMAwGA1UEBwwFUHVzYW4xDTAL +BgNVBAoMBFdVREMxDTALBgNVBAMMBFdVREMwHhcNMjUwNDE2MDE0NzIwWhcNMzUw +NDE0MDE0NzIwWjBLMQswCQYDVQQGEwJLUjEOMAwGA1UECAwFUHVzYW4xDjAMBgNV +BAcMBVB1c2FuMQ0wCwYDVQQKDARXVURDMQ0wCwYDVQQDDARXVURDMIIBIjANBgkq +hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAntyNuLTcMjWIgfrSPi+pvf3hNsHLzlV1 +Ze+hXvqs4fE0p36qVbWRUkwv+Vp4drKrlXrY5Em7JCNZ0IT9PuZ3PwFbnG5FwV+O +H3B7WrRx4JHhLZU3w+WHFGfWFDZS2eYWrf1f1tDOlyq92xx0QEqDNE+gUMJ+z1N7 +5yoDForShgTmxD+KzYp4e7BvKKKn/U6YosBC8GyiMWdcka0IUMoTwpQRQ3Vlx/KX ++YGJlwWA9wvNWOqgcYCxeCIa7X+8K9i92Py01T82zqLMcr52Ri2JEdMzHz4pp/aZ +d56pfUhGuN6ET18fTTGeKuB1PrPu80OgfOYvJL9u1/twuO/xi/naBQIDAQABMA0G +CSqGSIb3DQEBCwUAA4IBAQAudZAxVxMh/aUeTj6WK5mpVpUINmAwt938FVSQ3nUC +mTafNjSNfu/Me+PvjCkmf9RNk5BSdHVwxseOBaU0WZnvLM056ayyQnSwWT53WwtZ +CSb3VHBbwaJqAiAnhQxCHHhWDK4rtIPrWZ/SCek2E5l29ccZHTuVnaFF3pG8YRs1 ++UXVWeCB0fIJwQdugtGkzVzgO1HW7tKyG1BqCP9q9FnfODia9d6jKAA5dScBPanG +yPNI8eq4Fpk4R6P1jM2jw9pCJ4CW4lpgGQD0KATJve+ooHJqL3Ri6xkljNymGfbP +UD8eyRxgUaKUloQY2UbafzO+cLn2uRiB4sfn/T8hybeW +-----END CERTIFICATE----- diff --git a/certs/redis_certs/server-key.pem b/certs/redis_certs/server-key.pem new file mode 100644 index 0000000..c9bda16 --- /dev/null +++ b/certs/redis_certs/server-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQD6WlIHMwItBuZD +2HLs0sJbOwQTvZmeCvcqCkTCc19GSWTAO0yuw8aZlsaNFKlOamGknDXVzUOKOojN +NAzOeW4Y/sbG+AtgFiFLblRPGIbKs4fNJa9TMJkt7u69YNVgRLnXRHIcoaQz/Ah5 ++398PuywHFSZOYpAmcxVCalYTT3+NnX4+Umnq/a7YFZFiiHBKWvmhCc+zp6CFQ1J +jLKb/bdNYiNxQoQ4RQf5/FquuQT5ZigsLGRMnnKA1IB3nmLR4PklGGEyYYakh+mF +BnmWGWnyuxOj/6Q9ZsCs1jsrwl/W1EtWqjFZpWCJmkJbnNXMCgtriz1sE3P/RqbA +F/1H9exFAgMBAAECggEAcPgyzabZ04GqhHD2oyETrOE7nMDQwf1GvQS3NFulYW0a +9peLqc0UIBhmbiElqQneF86ZiGnAkW6KbraPFNi8srC89+nUED7MNWKKzspNgMh0 +fkEEFXD3nj+UIYbBolhA1/+90meqVf7jwE+ZnnIykeGm1yGc3jvIdZMPhJYOZFhA +d6nZBro5ODubFwqEGXDj6caJKg82zQwsMzgW9Td3UfXVnOwgCtYAs4LFtwfCYKDW +B/8Wyw9m+RNsrJc/vFdXPgRtRkhBGyw+Bz3Wh48tSO5+fenED6rK09ZgumVWWvu/ +EsvCqIFdM7WeV4+H2b0hEjCLOhe1T5DsM+6fVZ4f3QKBgQD92eaDzijBI5QWEBUO +trJRe3oiJUYyVcipYEL3R6ixft1tgRiNLhtx0mLsoK30slqEuqbJB+e/0Z+T9Ak6 +UURHh7OOcmqSqXfA2dzUb86i8IE+wSZSY1WMIv8tW7AdYOHprLnBL0g9mhsSYD2G +1ZXXMycp3z0eitbl0NdFsT4PwwKBgQD8eNbG6wf+F4bUyL5BboJPc3VEMTAeS+dX +HQ9v4o40AvwAZ2JWM5D5xQ7LGxNsGlC0DFNq7dFMnGa6JmvsnH6A3eEaTHkePzaM +hIVhaJ7/jWPzXvbWud26qFFmkf6jKzs1YYLnJ/LNUCGivXzd8v3Rl+/BOIyM8xai +8xbTaKcbVwKBgARZjoSlrLcah/gU0HePE+Enj4E0SE6mf6i/WIZqOO7JsLumoagR +qTC7HSzW2ARAZiKuHBuHwiXcTrK3VM0mBViAb8g6F69prf6k/Q6617ydCGnt3108 +DIQ5OUwqrQrj1Rsu9YPk7oT3tg0LjyJXWJj2bG5gP+jdEi+f84BjOCAHAoGAY7fi +tOyN2pk2QwMKylM4muLydT2sULsQI4Mx3rIwEnD/UbWDzKJPZ6eigSwvjqoSKBsa +M4i8+zJnDK0yQWFKQeHhD/8Wc5DqHbQV7nRhIHSa/rwKaKM+5YOnqwiM96uSljHu +fxhzOQ6mSl3nXM6l28vUAZAxS1aUIUY8RMH4Br8CgYEAs47gDhMQJgo6oOu/OwcA +UpynyLjk8iy+8geIq4Of02HXIA+OQ8DHuhHm2n46OvrxNfjPOeuZ6k5NzeUcfA06 +CuU/4c6LPDoZk32Y0wxI8jTO+Rd/I6SYl+xX2UVJpgvZT8eG3ooXsb3zt6ZnBKCI +hEnOwGW6pKxNmdqLToQUumU= +-----END PRIVATE KEY----- diff --git a/certs/redis_certs/server.csr b/certs/redis_certs/server.csr new file mode 100644 index 0000000..52dfc6f --- /dev/null +++ b/certs/redis_certs/server.csr @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICmDCCAYACAQAwUzELMAkGA1UEBhMCS1IxDjAMBgNVBAgMBVB1c2FuMQ4wDAYD +VQQHDAVQdXNhbjENMAsGA1UECgwEV1VEQzEVMBMGA1UEAwwMZGV2bG9nLXJlZGlz +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA+lpSBzMCLQbmQ9hy7NLC +WzsEE72Zngr3KgpEwnNfRklkwDtMrsPGmZbGjRSpTmphpJw11c1DijqIzTQMznlu +GP7GxvgLYBYhS25UTxiGyrOHzSWvUzCZLe7uvWDVYES510RyHKGkM/wIeft/fD7s +sBxUmTmKQJnMVQmpWE09/jZ1+PlJp6v2u2BWRYohwSlr5oQnPs6eghUNSYyym/23 +TWIjcUKEOEUH+fxarrkE+WYoLCxkTJ5ygNSAd55i0eD5JRhhMmGGpIfphQZ5lhlp +8rsTo/+kPWbArNY7K8Jf1tRLVqoxWaVgiZpCW5zVzAoLa4s9bBNz/0amwBf9R/Xs +RQIDAQABoAAwDQYJKoZIhvcNAQELBQADggEBAI4pn7EAIuLnkQDbQ3M6UV76rXCN +TmgtYp/2VDoID6cvp4cuvrhqUe3Fnq3bBMKp3n75qrsP2H0mi6IU47hpP3A+qnJH +QXBkrihfCw6Ry1Ohtgb5hLhNeS1LTzzjgzRZavMjPvpKXu8xNk23RNS6MaCUhhLo +u0w5DPpew2AoUmxqH//A8JN1tOm/WfPvI3vcnut8QNc1w+bGO521LAr7uFBIaHrw +dNcr416q/qz7lfSRG4jEt+BGRlYjMpHpggW+Vf1NIAOtcm+fPwMQ7CsHvhogs4PH +WiDGm1NNrQinACgE4SSdXJqDfHKdZOwGlNcIcA/Vnx63KRQ2jTap2dKWYX8= +-----END CERTIFICATE REQUEST----- diff --git a/cleanup.sh b/cleanup.sh new file mode 100644 index 0000000..1c6390d --- /dev/null +++ b/cleanup.sh @@ -0,0 +1,72 @@ +#!/bin/bash + +echo "⚠️ 이 스크립트는 사용하지 않는 Docker 리소스, 로그 파일, Gradle 캐시 등을 삭제합니다." +read -p "계속하시겠습니까? (y/N): " confirm + +if [[ "$confirm" != "y" ]]; then + echo "❌ 작업이 취소되었습니다." + exit 1 +fi + +echo "" +echo "🧼 Docker 컨테이너/이미지/볼륨/네트워크 정리 중..." +docker container prune -f +docker image prune -a -f +docker volume prune -f +docker network prune -f +docker builder prune --all -f +docker image prune -f # Dangling 이미지 정리 + +echo "" +echo "🧼 Gradle 캐시 정리 중..." +rm -rf ~/.gradle/caches/ +rm -rf ~/.gradle/daemon/ +rm -rf ~/.gradle/native/ + +echo "" +echo "🧼 Docker build 캐시 경로 정리 중..." +rm -rf /var/lib/docker/tmp/* +rm -rf /var/lib/docker/overlay2/* +rm -rf /var/lib/docker/buildkit/* +rm -rf /var/lib/docker/containers/* + +echo "" +echo "🧼 시스템 로그 정리 중..." +sudo journalctl --vacuum-time=1d +sudo journalctl --vacuum-size=100M # 로그 크기 제한 + +echo "" +echo "🧼 임시 파일 정리 중..." +sudo rm -rf /tmp/* +sudo rm -rf /var/tmp/* + +echo "" +echo "🧼 apt 캐시 정리 중..." +sudo apt-get clean +sudo apt-get autoclean +sudo apt-get autoremove -y + +echo "" +echo "🧼 오래된 커널 이미지 정리 중..." +sudo apt-get remove --purge $(dpkg --list | grep linux-image | awk '{ print $2 }' | grep -v $(uname -r)) + +echo "" +echo "🧼 오래된 Snap 패키지 정리 중..." +sudo snap set system refresh.retain=2 +sudo snap remove $(sudo snap list --all | awk '/disabled/{print $1, $3}' | tr -s ' ' | cut -d ' ' -f 1,2 | tr '\n' ' ') + +echo "" +echo "🧼 Firefox 캐시 정리 중..." +rm -rf ~/.cache/mozilla/firefox/* + +echo "" +echo "🧼 Chrome 캐시 정리 중..." +rm -rf ~/.cache/google-chrome/* + +echo "" +echo "🧼 VSCode 캐시 정리 중..." +rm -rf ~/.config/Code/Cache + +echo "" +echo "✅ 디스크 정리 완료!" +df -h diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..c872d54 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,67 @@ +#!/bin/bash +set -e + +# Gradle 빌드 (테스트 제외) +echo "🔨 Gradle 빌드 중 (테스트 제외)..." +./gradlew clean build -x test + +# ~/apptive_devlog/certs/ 디렉토리 권한 및 소유자 설정 +echo "🔐 ~/apptive_devlog/certs/ 디렉토리 권한 및 소유자 설정 중..." +sudo chown -R root:root ~/apptive_devlog/certs/ +sudo find ~/apptive_devlog/certs/ -type f -exec chmod 644 {} \; +sudo find ~/apptive_devlog/certs/ -type d -exec chmod 755 {} \; + +# 추가된 경로들에 대해서도 권한 설정 +echo "🔐 추가 인증서 디렉토리 권한 및 소유자 설정 중..." +sudo chown -R root:root ~/apptive_devlog/certs/app_certs/ +sudo chown -R root:root ~/apptive_devlog/certs/mysql_certs/ +sudo chown -R root:root ~/apptive_devlog/certs/redis_certs/ +sudo chown -R root:root ~/apptive_devlog/certs/nginx_certs/ + +# 파일 권한 644 설정 +sudo find ~/apptive_devlog/certs/app_certs/ -type f -exec chmod 644 {} \; +sudo find ~/apptive_devlog/certs/mysql_certs/ -type f -exec chmod 644 {} \; +sudo find ~/apptive_devlog/certs/redis_certs/ -type f -exec chmod 644 {} \; +sudo find ~/apptive_devlog/certs/nginx_certs/ -type f -exec chmod 644 {} \; + +# 디렉토리 권한 755 설정 +sudo find ~/apptive_devlog/certs/app_certs/ -type d -exec chmod 755 {} \; +sudo find ~/apptive_devlog/certs/mysql_certs/ -type d -exec chmod 755 {} \; +sudo find ~/apptive_devlog/certs/redis_certs/ -type d -exec chmod 755 {} \; +sudo find ~/apptive_devlog/certs/nginx_certs/ -type d -exec chmod 755 {} \; + +chmod 644 ./my.cnf + +echo "✅ 권한 및 소유자 설정 완료" + +echo "🐳 Docker 이미지 빌드 중 (캐시 무시)..." +if ! docker-compose build --no-cache; then + echo "❌ Docker 이미지 빌드에 실패했습니다. 종료합니다." + exit 1 +fi + +echo "🚀 컨테이너 시작 중..." +if ! docker-compose up -d; then + echo "❌ Docker 컨테이너 실행에 실패했습니다. 종료합니다." + exit 1 +fi + +echo "✅ 모든 컨테이너가 실행 중입니다." + +# 컨테이너 로그 확인 +echo "📜 컨테이너 로그 확인 중..." +docker-compose logs -f & + +# 서비스 상태 확인 +echo "🧐 서비스 상태 확인 중..." +docker-compose ps + +# 환경 변수 검증 (생략 처리) +echo "🔍 환경 변수 검증 중... (건너뜀)" +true + +# 불필요한 Docker 리소스 정리 +echo "🧹 불필요한 Docker 리소스 정리 중..." +docker system prune -f + +echo "✅ 배포가 완료되었습니다!" diff --git a/local.properties b/local.properties new file mode 100644 index 0000000..310516d --- /dev/null +++ b/local.properties @@ -0,0 +1,8 @@ +## This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. +# +# Location of the SDK. This is only used by Gradle. +# For customization when using a Version Control System, please read the +# header note. +#Wed Apr 09 04:13:29 KST 2025 +sdk.dir=C\:\\Users\\qqwwe\\AppData\\Local\\Android\\Sdk diff --git a/my.cnf b/my.cnf new file mode 100644 index 0000000..4e16365 --- /dev/null +++ b/my.cnf @@ -0,0 +1,32 @@ +[mysqld] +# SSL 인증서 경로 설정 +ssl-ca=/etc/mysql/certs/ca.pem +ssl-cert=/etc/mysql/certs/server-cert.pem +ssl-key=/etc/mysql/certs/server-key-nopass.pem + +# mutual TLS 활성화 (클라이언트 인증서 검증) +require_secure_transport=ON + +port = 3306 + +# 기타 MySQL 서버 설정 +bind-address = 0.0.0.0 +host-cache-size=0 +skip-name-resolve +datadir=/var/lib/mysql +socket=/var/run/mysqld/mysqld.sock +secure-file-priv=/var/lib/mysql-files +user=root +pid-file=/var/run/mysqld/mysqld.pid + +# 예시로 설정된 RAM에 맞춘 InnoDB 버퍼 크기 +innodb_buffer_pool_size = 2G + +# 바이너리 로그 및 성능 관련 설정 +log_bin = /var/lib/mysql/mysql-bin +join_buffer_size = 128M +sort_buffer_size = 2M +read_rnd_buffer_size = 2M + +[client] +socket=/var/run/mysqld/mysqld.sock diff --git a/mysql.sh b/mysql.sh new file mode 100644 index 0000000..9b7b56f --- /dev/null +++ b/mysql.sh @@ -0,0 +1,3 @@ +docker logs devlog-mysql + +docker exec -it devlog-mysql bash diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..e6e6498 --- /dev/null +++ b/nginx.conf @@ -0,0 +1,68 @@ +user root; +worker_processes auto; + +error_log /var/log/nginx/error.log warn; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + access_log /var/log/nginx/access.log main; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + keepalive_timeout 65; + types_hash_max_size 2048; + + include /etc/nginx/mime.types; + default_type application/octet-stream; + + # 외부 클라이언트와의 단방향 HTTPS (공식 CA 인증서 사용) + server { + listen 443 ssl; + server_name wudc.link; # ngrok의 public URL 또는 원하는 도메인 + + # 공식 CA 인증서 (외부용) + ssl_certificate /certs/fullchain.pem; # fullchain.pem + ssl_certificate_key /certs/privkey.pem; # privkey.pem + #ssl_certificate /certs/server-cert.pem; + #ssl_certificate_key /certs/server-key-nopass.pem; + ssl_verify_client off; + + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers HIGH:!aNULL:!MD5; + ssl_prefer_server_ciphers on; + ssl_dhparam /certs/dhparam.pem; + + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + + location / { + # 내부 devlog-app과의 연결은 mutual TLS 사용 + proxy_pass https://devlog-app:443; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # nginx가 devlog-app에 요청할 때 클라이언트 인증서 제공 (mutual TLS) + proxy_ssl_certificate /certs/client-cert.pem; + proxy_ssl_certificate_key /certs/client-key-nopass.pem; + proxy_ssl_trusted_certificate /certs/ca.pem; + proxy_ssl_verify on; + } + } + + # HTTP → HTTPS 리디렉션 + server { + listen 80; + server_name wudc.link; + return 301 https://$host$request_uri; + } +} diff --git a/nginx.sh b/nginx.sh new file mode 100644 index 0000000..d349e44 --- /dev/null +++ b/nginx.sh @@ -0,0 +1,3 @@ +docker logs devlog-nginx + +docker exec -it devlog-nginx bash diff --git a/ngrok.log b/ngrok.log new file mode 100644 index 0000000..e69de29 diff --git a/redeploy.sh b/redeploy.sh new file mode 100644 index 0000000..9fa86c1 --- /dev/null +++ b/redeploy.sh @@ -0,0 +1,12 @@ +docker network create devlog-net + +docker network connect devlog-net devlog-mysql +docker network connect devlog-net devlog-redis +docker network connect devlog-net devlog-app +docker network connect devlog-net devlog-nginx + +docker restart devlog-mysql +docker restart devlog-redis +docker restart devlog-app +docker restart devlog-nginx + diff --git a/redis.conf b/redis.conf new file mode 100644 index 0000000..d440470 --- /dev/null +++ b/redis.conf @@ -0,0 +1,19 @@ +# 일반 포트는 사용 안 함 (보안상) +port 0 + +# TLS 포트 열기 +tls-port 6380 + +# TLS 인증서 경로 (컨테이너 내 기준) +tls-cert-file /certs/server-cert.pem +tls-key-file /certs/server-key.pem +tls-ca-cert-file /certs/ca.pem + +# 클라이언트 인증 강제 여부 (선택) +tls-auth-clients yes + +# (선택) TLS 버전 제한 +tls-protocols "TLSv1.2 TLSv1.3" + +# (선택) 로그 레벨 +loglevel notice diff --git a/redis.sh b/redis.sh new file mode 100644 index 0000000..13e99bb --- /dev/null +++ b/redis.sh @@ -0,0 +1,3 @@ +docker logs devlog-redis + +docker exec -it devlog-redis bash diff --git a/src/main/java/apptive/devlog/common/base/BaseEntity.java b/src/main/java/apptive/devlog/common/base/BaseEntity.java new file mode 100644 index 0000000..f30ab3e --- /dev/null +++ b/src/main/java/apptive/devlog/common/base/BaseEntity.java @@ -0,0 +1,34 @@ +package apptive.devlog.common.base; + +import jakarta.persistence.*; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@Getter +public abstract class BaseEntity { + + @Id + @Column(length = 26, updatable = false, nullable = false) + private String id; + + @CreatedDate + @Column(name = "created_at", nullable = false, updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name = "updated_at", nullable = false) + private LocalDateTime updatedAt; + + @PrePersist + public void assignId() { + if (this.id == null) { + this.id = com.github.f4b6a3.ulid.UlidCreator.getUlid().toString(); + } + } +} diff --git a/src/main/java/apptive/devlog/common/response/error/exception/CustomException.java b/src/main/java/apptive/devlog/common/response/error/exception/CustomException.java new file mode 100644 index 0000000..709f003 --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/CustomException.java @@ -0,0 +1,21 @@ +package apptive.devlog.common.response.error.exception; + +import apptive.devlog.common.response.error.model.ErrorCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public abstract class CustomException extends RuntimeException { + private final ErrorCode errorCode; + + public CustomException(ErrorCode errorCode, Throwable cause) { + super(errorCode.getMessage(), cause); + this.errorCode = errorCode; + } + + protected CustomException(ErrorCode errorCode, String customMessage) { + super(customMessage); + this.errorCode = errorCode; + } +} diff --git a/src/main/java/apptive/devlog/common/response/error/exception/DtoBindingFailedException.java b/src/main/java/apptive/devlog/common/response/error/exception/DtoBindingFailedException.java new file mode 100644 index 0000000..f385cff --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/DtoBindingFailedException.java @@ -0,0 +1,12 @@ +package apptive.devlog.common.response.error.exception; + +public class DtoBindingFailedException extends RuntimeException { + + public DtoBindingFailedException(String message) { + super(message); + } + + public DtoBindingFailedException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/apptive/devlog/common/response/error/exception/DuplicateEmailException.java b/src/main/java/apptive/devlog/common/response/error/exception/DuplicateEmailException.java new file mode 100644 index 0000000..c7c51fc --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/DuplicateEmailException.java @@ -0,0 +1,9 @@ +package apptive.devlog.common.response.error.exception; + +import apptive.devlog.common.response.error.model.ErrorCode; + +public class DuplicateEmailException extends CustomException { + public DuplicateEmailException() { + super(ErrorCode.DUPLICATE_EMAIL); + } +} diff --git a/src/main/java/apptive/devlog/common/response/error/exception/InvalidEmailOrPasswordException.java b/src/main/java/apptive/devlog/common/response/error/exception/InvalidEmailOrPasswordException.java new file mode 100644 index 0000000..071919b --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/InvalidEmailOrPasswordException.java @@ -0,0 +1,9 @@ +package apptive.devlog.common.response.error.exception; + +import apptive.devlog.common.response.error.model.ErrorCode; + +public class InvalidEmailOrPasswordException extends CustomException { + public InvalidEmailOrPasswordException() { + super(ErrorCode.INVALID_EMAIL_OR_PASSWORD); + } +} \ No newline at end of file diff --git a/src/main/java/apptive/devlog/common/response/error/exception/InvalidEmailVerificationTokenException.java b/src/main/java/apptive/devlog/common/response/error/exception/InvalidEmailVerificationTokenException.java new file mode 100644 index 0000000..ad7a472 --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/InvalidEmailVerificationTokenException.java @@ -0,0 +1,19 @@ +package apptive.devlog.common.response.error.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ResponseStatus; + +/** + * 이메일 인증 토큰이 유효하지 않을 경우 발생하는 예외 + */ +@ResponseStatus(HttpStatus.UNAUTHORIZED) // 401 Unauthorized +public class InvalidEmailVerificationTokenException extends RuntimeException { + + public InvalidEmailVerificationTokenException() { + super("유효하지 않은 이메일 인증 토큰입니다."); + } + + public InvalidEmailVerificationTokenException(String message) { + super(message); + } +} diff --git a/src/main/java/apptive/devlog/common/response/error/exception/InvalidOAuth2ResponseException.java b/src/main/java/apptive/devlog/common/response/error/exception/InvalidOAuth2ResponseException.java new file mode 100644 index 0000000..0744cbf --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/InvalidOAuth2ResponseException.java @@ -0,0 +1,7 @@ +package apptive.devlog.common.response.error.exception; + +public class InvalidOAuth2ResponseException extends RuntimeException { + public InvalidOAuth2ResponseException(String key) { + super("OAuth2 응답에서 예상한 '" + key + "' 구조가 올바르지 않습니다."); + } +} diff --git a/src/main/java/apptive/devlog/common/response/error/exception/InvalidPasswordException.java b/src/main/java/apptive/devlog/common/response/error/exception/InvalidPasswordException.java new file mode 100644 index 0000000..f71d69f --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/InvalidPasswordException.java @@ -0,0 +1,9 @@ +package apptive.devlog.common.response.error.exception; + +import apptive.devlog.common.response.error.model.ErrorCode; + +public class InvalidPasswordException extends CustomException { + public InvalidPasswordException() { + super(ErrorCode.INVALID_PASSWORD); + } +} diff --git a/src/main/java/apptive/devlog/common/response/error/exception/InvalidProviderException.java b/src/main/java/apptive/devlog/common/response/error/exception/InvalidProviderException.java new file mode 100644 index 0000000..a283203 --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/InvalidProviderException.java @@ -0,0 +1,9 @@ +package apptive.devlog.common.response.error.exception; + +import apptive.devlog.common.response.error.model.ErrorCode; + +public class InvalidProviderException extends CustomException { + public InvalidProviderException() { + super(ErrorCode.INVALID_PROVIDER); + } +} diff --git a/src/main/java/apptive/devlog/common/response/error/exception/InvalidRefreshTokenException.java b/src/main/java/apptive/devlog/common/response/error/exception/InvalidRefreshTokenException.java new file mode 100644 index 0000000..d187236 --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/InvalidRefreshTokenException.java @@ -0,0 +1,9 @@ +package apptive.devlog.common.response.error.exception; + +import apptive.devlog.common.response.error.model.ErrorCode; + +public class InvalidRefreshTokenException extends CustomException { + public InvalidRefreshTokenException() { + super(ErrorCode.INVALID_REFRESH_TOKEN); + } +} \ No newline at end of file diff --git a/src/main/java/apptive/devlog/common/response/error/exception/InvalidStateFormatException.java b/src/main/java/apptive/devlog/common/response/error/exception/InvalidStateFormatException.java new file mode 100644 index 0000000..c1db771 --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/InvalidStateFormatException.java @@ -0,0 +1,9 @@ +package apptive.devlog.common.response.error.exception; + +import apptive.devlog.common.response.error.model.ErrorCode; + +public class InvalidStateFormatException extends CustomException { + public InvalidStateFormatException() { + super(ErrorCode.INVALID_STATE_FORMAT); + } +} \ No newline at end of file diff --git a/src/main/java/apptive/devlog/common/response/error/exception/InvalidTokenException.java b/src/main/java/apptive/devlog/common/response/error/exception/InvalidTokenException.java new file mode 100644 index 0000000..95de2c4 --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/InvalidTokenException.java @@ -0,0 +1,9 @@ +package apptive.devlog.common.response.error.exception; + +import apptive.devlog.common.response.error.model.ErrorCode; + +public class InvalidTokenException extends CustomException { + public InvalidTokenException() { + super(ErrorCode.INVALID_TOKEN); + } +} diff --git a/src/main/java/apptive/devlog/common/response/error/exception/OAuth2TokenRequestException.java b/src/main/java/apptive/devlog/common/response/error/exception/OAuth2TokenRequestException.java new file mode 100644 index 0000000..5934e9e --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/OAuth2TokenRequestException.java @@ -0,0 +1,9 @@ +package apptive.devlog.common.response.error.exception; + +import apptive.devlog.common.response.error.model.ErrorCode; + +public class OAuth2TokenRequestException extends CustomException { + public OAuth2TokenRequestException(String message) { + super(ErrorCode.OAUTH2_TOKEN_REQUEST_FAILED, message); + } +} \ No newline at end of file diff --git a/src/main/java/apptive/devlog/common/response/error/exception/OAuth2UserInfoException.java b/src/main/java/apptive/devlog/common/response/error/exception/OAuth2UserInfoException.java new file mode 100644 index 0000000..ac33186 --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/OAuth2UserInfoException.java @@ -0,0 +1,9 @@ +package apptive.devlog.common.response.error.exception; + +import apptive.devlog.common.response.error.model.ErrorCode; + +public class OAuth2UserInfoException extends CustomException { + public OAuth2UserInfoException(String message) { + super(ErrorCode.OAUTH2_USERINFO_FAILED, message); + } +} \ No newline at end of file diff --git a/src/main/java/apptive/devlog/common/response/error/exception/TokenInjectionFailedException.java b/src/main/java/apptive/devlog/common/response/error/exception/TokenInjectionFailedException.java new file mode 100644 index 0000000..b55c780 --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/TokenInjectionFailedException.java @@ -0,0 +1,16 @@ +package apptive.devlog.common.response.error.exception; + +public class TokenInjectionFailedException extends RuntimeException { + + public TokenInjectionFailedException(String message) { + super("[Token 주입 실패] " + message); + } + + public TokenInjectionFailedException(String fieldName, Throwable cause) { + super(String.format("[Token 주입 실패] 필드 '%s'에 접근 중 오류 발생", fieldName), cause); + } + + public TokenInjectionFailedException(Throwable cause) { + super("[Token 주입 실패] 원인 미상", cause); + } +} diff --git a/src/main/java/apptive/devlog/common/response/error/exception/UnauthenticatedUserException.java b/src/main/java/apptive/devlog/common/response/error/exception/UnauthenticatedUserException.java new file mode 100644 index 0000000..44a2030 --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/UnauthenticatedUserException.java @@ -0,0 +1,16 @@ +package apptive.devlog.common.response.error.exception; + +public class UnauthenticatedUserException extends RuntimeException { + + public UnauthenticatedUserException() { + super("인증되지 않은 사용자입니다."); + } + + public UnauthenticatedUserException(String message) { + super(message); + } + + public UnauthenticatedUserException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/apptive/devlog/common/response/error/exception/UnsupportedProviderException.java b/src/main/java/apptive/devlog/common/response/error/exception/UnsupportedProviderException.java new file mode 100644 index 0000000..c566746 --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/UnsupportedProviderException.java @@ -0,0 +1,7 @@ +package apptive.devlog.common.response.error.exception; + +public class UnsupportedProviderException extends RuntimeException { + public UnsupportedProviderException(String provider) { + super("지원하지 않는 OAuth2 Provider입니다: " + provider); + } +} diff --git a/src/main/java/apptive/devlog/common/response/error/exception/UserNotFoundException.java b/src/main/java/apptive/devlog/common/response/error/exception/UserNotFoundException.java new file mode 100644 index 0000000..343c81b --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/exception/UserNotFoundException.java @@ -0,0 +1,9 @@ +package apptive.devlog.common.response.error.exception; + +import apptive.devlog.common.response.error.model.ErrorCode; + +public class UserNotFoundException extends CustomException { + public UserNotFoundException() { + super(ErrorCode.USER_NOT_FOUND); + } +} diff --git a/src/main/java/apptive/devlog/common/response/error/handler/GlobalExceptionHandler.java b/src/main/java/apptive/devlog/common/response/error/handler/GlobalExceptionHandler.java new file mode 100644 index 0000000..e413234 --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/handler/GlobalExceptionHandler.java @@ -0,0 +1,81 @@ +package apptive.devlog.common.response.error.handler; + +import apptive.devlog.common.response.error.exception.*; +import apptive.devlog.common.response.error.model.ErrorCode; +import apptive.devlog.common.response.error.model.ErrorResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@Slf4j +//@RestControllerAdvice +public class GlobalExceptionHandler { + + // 공통 응답 빌더 (기본 메시지 사용) + private ResponseEntity buildResponse(ErrorCode errorCode) { + return ResponseEntity + .status(errorCode.getStatus()) + .body(new ErrorResponse( + errorCode.getStatus(), + errorCode.getMessage(), + errorCode.getCode())); + } + + // 공통 응답 빌더 (커스텀 메시지 사용) + private ResponseEntity buildResponse(ErrorCode errorCode, String customMessage) { + return ResponseEntity + .status(errorCode.getStatus()) + .body(new ErrorResponse( + errorCode.getStatus(), + customMessage, + errorCode.getCode())); + } + + // 공통 커스텀 예외 처리 + @ExceptionHandler(CustomException.class) + public ResponseEntity handleCustomException(CustomException ex) { + log.warn("CustomException [{}]: {}", ex.getErrorCode().name(), ex.getMessage(), ex); + return buildResponse(ex.getErrorCode()); + } + + // DTO 바인딩 예외 처리 (직접 만든 예외 + Spring 기본 예외) + @ExceptionHandler({DtoBindingFailedException.class, MethodArgumentNotValidException.class}) + public ResponseEntity handleBindingException(Exception ex) { + log.warn("BindingException: {}", ex.getMessage(), ex); + return buildResponse(ErrorCode.INVALID_STATE_FORMAT, ex.getMessage()); + } + + // IllegalArgumentException 처리 + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgument(IllegalArgumentException ex) { + log.warn("IllegalArgumentException: {}", ex.getMessage(), ex); + return ResponseEntity + .status(HttpStatus.BAD_REQUEST) + .body(new ErrorResponse( + HttpStatus.BAD_REQUEST.value(), + ex.getMessage(), + "BAD_REQUEST")); + } + + // 런타임 예외 처리 (디버깅 및 추적용) + @ExceptionHandler(RuntimeException.class) + public ResponseEntity handleRuntimeException(RuntimeException ex) { + log.error("RuntimeException: {}", ex.getMessage(), ex); + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new ErrorResponse( + HttpStatus.INTERNAL_SERVER_ERROR.value(), + ex.getMessage(), + "INTERNAL_ERROR")); + } + + // 마지막 처리: 예상하지 못한 예외 + @ExceptionHandler(Exception.class) + public ResponseEntity handleUnhandledException(Exception ex) { + log.error("Unhandled Exception: {}", ex.getMessage(), ex); + return buildResponse(ErrorCode.UNKNOWN_ERROR); + } +} diff --git a/src/main/java/apptive/devlog/common/response/error/model/ErrorCode.java b/src/main/java/apptive/devlog/common/response/error/model/ErrorCode.java new file mode 100644 index 0000000..ca0d112 --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/model/ErrorCode.java @@ -0,0 +1,29 @@ +package apptive.devlog.common.response.error.model; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum ErrorCode { + USER_NOT_FOUND(404, "USER_001", "해당 사용자를 찾을 수 없습니다."), + DUPLICATE_EMAIL(400, "USER_002", "이미 존재하는 이메일입니다."), + INVALID_PROVIDER(400, "USER_003", "유효하지 않은 OAuth Provider입니다."), + INVALID_PASSWORD(401, "USER_004", "비밀번호가 일치하지 않습니다."), + UNAUTHENTICATED_USER(401, "USER_005", "사용자 인증 정보가 없습니다."), + INVALID_EMAIL_OR_PASSWORD(401, "USER_006", "이메일 또는 비밀번호가 잘못되었습니다."), + + INVALID_STATE_FORMAT(400, "PKCE_001", "State 파라미터 형식이 올바르지 않습니다."), + OAUTH2_TOKEN_REQUEST_FAILED(500, "PKCE_002", "OAuth2 토큰 요청에 실패했습니다."), + OAUTH2_USERINFO_FAILED(500, "PKCE_003", "OAuth2 사용자 정보 요청에 실패했습니다."), + + INVALID_TOKEN(401, "TOKEN_001", "유효하지 않은 토큰입니다."), + TOKEN_INJECTION_FAILED(500, "TOKEN_002", "토큰 주입에 실패했습니다."), + INVALID_REFRESH_TOKEN(401, "TOKEN_003", "유효하지 않은 리프레시 토큰입니다."), + + UNKNOWN_ERROR(500, "ERROR_001", "서버 내부 오류가 발생했습니다."); + + private final int status; + private final String code; + private final String message; +} diff --git a/src/main/java/apptive/devlog/common/response/error/model/ErrorResponse.java b/src/main/java/apptive/devlog/common/response/error/model/ErrorResponse.java new file mode 100644 index 0000000..58b0c06 --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/error/model/ErrorResponse.java @@ -0,0 +1,12 @@ +package apptive.devlog.common.response.error.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ErrorResponse { + private int status; + private String message; + private String code; +} \ No newline at end of file diff --git a/src/main/java/apptive/devlog/common/response/success/CommonResponse.java b/src/main/java/apptive/devlog/common/response/success/CommonResponse.java new file mode 100644 index 0000000..9289049 --- /dev/null +++ b/src/main/java/apptive/devlog/common/response/success/CommonResponse.java @@ -0,0 +1,105 @@ +package apptive.devlog.common.response.success; + +import com.fasterxml.jackson.annotation.JsonInclude; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.http.HttpStatus; + +import java.time.LocalDateTime; + +@NoArgsConstructor +@Getter +@JsonInclude(JsonInclude.Include.NON_NULL) +@Schema(description = "API 공통 응답 포맷") +public class CommonResponse { + + @Schema(description = "요청 성공 여부", example = "true") + private boolean success; + @Schema(description = "HTTP 상태 코드", example = "200") + private int status; + @Schema(description = "응답 메시지", example = "요청이 성공했습니다.") + private String message; + @Schema(description = "응답 데이터", implementation = Object.class) + private T data; + @Schema(description = "응답 시간", example = "2024-04-08T12:34:56") + private LocalDateTime timestamp; + + @Builder + private CommonResponse(boolean success, HttpStatus httpStatus, String message, T data, LocalDateTime timestamp) { + this.success = success; + this.status = httpStatus.value(); + this.message = message; + this.data = data; + this.timestamp = timestamp != null ? timestamp : LocalDateTime.now(); + } + + // ===== Success ===== + + public static CommonResponse success(HttpStatus status, String message, T data) { + return CommonResponse.builder() + .success(true) + .httpStatus(status) + .message(message) + .data(data) + .build(); + } + + public static CommonResponse success(HttpStatus status, T data) { + return CommonResponse.builder() + .success(true) + .httpStatus(status) + .message("요청이 성공했습니다.") + .data(data) + .build(); + } + + public static CommonResponse success(HttpStatus status, String message) { + return CommonResponse.builder() + .success(true) + .httpStatus(status) + .message(message) + .build(); + } + + public static CommonResponse success(HttpStatus status) { + return CommonResponse.builder() + .success(true) + .httpStatus(status) + .message("요청이 성공했습니다.") + .build(); + } + + // 기본 200 OK + public static CommonResponse ok(T data) { + return success(HttpStatus.OK, data); + } + + public static CommonResponse created(T data) { + return success(HttpStatus.CREATED, data); + } + + public static CommonResponse noContent() { + return success(HttpStatus.NO_CONTENT, (Void) null); + } + + // ===== Error ===== + + public static CommonResponse error(HttpStatus status, String message) { + return CommonResponse.builder() + .success(false) + .httpStatus(status) + .message(message) + .build(); + } + + public static CommonResponse error(HttpStatus status, String message, T data) { + return CommonResponse.builder() + .success(false) + .httpStatus(status) + .message(message) + .data(data) + .build(); + } +} diff --git a/src/main/java/apptive/devlog/documentation/common/GlobalApiResponse.java b/src/main/java/apptive/devlog/documentation/common/GlobalApiResponse.java new file mode 100644 index 0000000..78c5192 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/common/GlobalApiResponse.java @@ -0,0 +1,29 @@ +package apptive.devlog.documentation.common; + +import apptive.devlog.common.response.success.CommonResponse; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + +import java.lang.annotation.*; + +@Target({ ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ApiResponses({ + @ApiResponse(responseCode = "200", description = "요청 성공", content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "201", description = "리소스 생성 성공", content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "204", description = "응답 본문 없음", content = @Content(schema = @Schema())), + @ApiResponse(responseCode = "400", description = "잘못된 요청 (유효성 검증 실패 등)", content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "401", description = "인증되지 않음 (토큰 없음 또는 만료)", content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "403", description = "권한 없음", content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "404", description = "리소스를 찾을 수 없음", content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "409", description = "충돌 (중복 데이터 등)", content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "422", description = "처리할 수 없는 요청 (비즈니스 로직 실패 등)", content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "429", description = "너무 많은 요청", content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "500", description = "서버 내부 오류", content = @Content(schema = @Schema(implementation = CommonResponse.class))), + @ApiResponse(responseCode = "503", description = "서비스 이용 불가", content = @Content(schema = @Schema(implementation = CommonResponse.class))) +}) +public @interface GlobalApiResponse { +} diff --git a/src/main/java/apptive/devlog/documentation/config/SwaggerConfig.java b/src/main/java/apptive/devlog/documentation/config/SwaggerConfig.java new file mode 100644 index 0000000..0e6643e --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/config/SwaggerConfig.java @@ -0,0 +1,46 @@ +package apptive.devlog.documentation.config; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.enums.SecuritySchemeType; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Contact; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.info.License; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@OpenAPIDefinition( + info = @io.swagger.v3.oas.annotations.info.Info(title = "Devlog API", version = "v1") +) +@io.swagger.v3.oas.annotations.security.SecurityScheme( + name = "bearerAuth", + type = SecuritySchemeType.HTTP, + scheme = "bearer", + bearerFormat = "JWT" +) +@Configuration +public class SwaggerConfig { + + private static final String SECURITY_SCHEME_NAME = "bearerAuth"; + + @Bean + public OpenAPI openAPI() { + return new OpenAPI() + .info(new Info() + .title("Devlog API") + .description("Devlog 프로젝트의 API 명세서입니다.") + .version("v1.0") + .contact(new Contact().name("Devlog Team").email("devlog@example.com")) + .license(new License().name("Apache 2.0").url("http://springdoc.org"))) + .addSecurityItem(new SecurityRequirement().addList(SECURITY_SCHEME_NAME)) + .components(new io.swagger.v3.oas.models.Components() + .addSecuritySchemes(SECURITY_SCHEME_NAME, + new SecurityScheme() + .name(SECURITY_SCHEME_NAME) + .type(SecurityScheme.Type.HTTP) + .scheme("bearer") + .bearerFormat("JWT"))); + } +} diff --git a/src/main/java/apptive/devlog/documentation/config/SwaggerGroupConfig.java b/src/main/java/apptive/devlog/documentation/config/SwaggerGroupConfig.java new file mode 100644 index 0000000..b0acda2 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/config/SwaggerGroupConfig.java @@ -0,0 +1,41 @@ +package apptive.devlog.documentation.config; + +import org.springdoc.core.models.GroupedOpenApi; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerGroupConfig { + + @Bean + public GroupedOpenApi defaultAllApi() { + return GroupedOpenApi.builder() + .group("all") + .pathsToMatch("/**") + .build(); + } + + @Bean + public GroupedOpenApi publicApiAuth() { + return GroupedOpenApi.builder() + .group("auth") + .pathsToMatch("/auth/**") + .build(); + } + + @Bean + public GroupedOpenApi publicApiUser() { + return GroupedOpenApi.builder() + .group("user") + .pathsToMatch("/user/**") + .build(); + } + + @Bean + public GroupedOpenApi publicApiPost() { + return GroupedOpenApi.builder() + .group("post") + .pathsToMatch("/post/**") + .build(); + } +} diff --git a/src/main/java/apptive/devlog/documentation/controller/RootRedirectController.java b/src/main/java/apptive/devlog/documentation/controller/RootRedirectController.java new file mode 100644 index 0000000..2789178 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/controller/RootRedirectController.java @@ -0,0 +1,13 @@ +package apptive.devlog.documentation.controller; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; + +@Controller +public class RootRedirectController { + + @GetMapping("/") + public String redirectToSwagger() { + return "redirect:/swagger-ui/index.html"; + } +} diff --git a/src/main/java/apptive/devlog/documentation/domain/auth/AuthLoginDoc.java b/src/main/java/apptive/devlog/documentation/domain/auth/AuthLoginDoc.java new file mode 100644 index 0000000..015ebf9 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/auth/AuthLoginDoc.java @@ -0,0 +1,20 @@ +package apptive.devlog.documentation.domain.auth; + +import apptive.devlog.documentation.domain.user.wrapper.UserLoginResponseWrapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + +@Operation(summary = "회원 로그인", description = "이메일과 비밀번호로 로그인합니다.") +@ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "로그인 성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = UserLoginResponseWrapper.class) + )), + @ApiResponse(responseCode = "401", description = "인증 실패", content = @Content) +}) +public @interface AuthLoginDoc { +} diff --git a/src/main/java/apptive/devlog/documentation/domain/auth/AuthLogoutDoc.java b/src/main/java/apptive/devlog/documentation/domain/auth/AuthLogoutDoc.java new file mode 100644 index 0000000..98be83a --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/auth/AuthLogoutDoc.java @@ -0,0 +1,15 @@ +package apptive.devlog.documentation.domain.auth; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + +@Operation(summary = "로그아웃", description = "로그아웃 처리합니다.") +@ApiResponses(value = { + @ApiResponse(responseCode = "204", description = "로그아웃 성공", content = @Content) +}) +@SecurityRequirement(name = "bearerAuth") +public @interface AuthLogoutDoc { +} diff --git a/src/main/java/apptive/devlog/documentation/domain/auth/AuthRefreshDoc.java b/src/main/java/apptive/devlog/documentation/domain/auth/AuthRefreshDoc.java new file mode 100644 index 0000000..6ec6ea4 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/auth/AuthRefreshDoc.java @@ -0,0 +1,22 @@ +package apptive.devlog.documentation.domain.auth; + +import apptive.devlog.documentation.domain.user.wrapper.UserRefreshResponseWrapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + +@Operation(summary = "토큰 재발급", description = "Refresh Token을 이용하여 Access Token을 재발급합니다.") +@ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "토큰 재발급 성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = UserRefreshResponseWrapper.class) + )), + @ApiResponse(responseCode = "401", description = "유효하지 않은 토큰", content = @Content) +}) +@SecurityRequirement(name = "bearerAuth") +public @interface AuthRefreshDoc { +} diff --git a/src/main/java/apptive/devlog/documentation/domain/auth/AuthSignupDoc.java b/src/main/java/apptive/devlog/documentation/domain/auth/AuthSignupDoc.java new file mode 100644 index 0000000..9e38bb3 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/auth/AuthSignupDoc.java @@ -0,0 +1,22 @@ +package apptive.devlog.documentation.domain.auth; + +import apptive.devlog.common.response.success.CommonResponse; +import apptive.devlog.documentation.domain.user.wrapper.UserSignupResponseWrapper; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; + +@Operation(summary = "회원 가입", description = "회원으로 가입합니다.") +@ApiResponses(value = { + @ApiResponse(responseCode = "201", description = "회원가입 성공", + content = @Content( + mediaType = "application/json", + schema = @Schema(implementation = UserSignupResponseWrapper.class) + )), + @ApiResponse(responseCode = "400", description = "요청 파라미터 오류 또는 중복된 이메일", + content = @Content(schema = @Schema(implementation = CommonResponse.class))) +}) +public @interface AuthSignupDoc { +} diff --git a/src/main/java/apptive/devlog/documentation/domain/comment/CommentCreateDoc.java b/src/main/java/apptive/devlog/documentation/domain/comment/CommentCreateDoc.java new file mode 100644 index 0000000..0a57710 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/comment/CommentCreateDoc.java @@ -0,0 +1,9 @@ +package apptive.devlog.documentation.domain.comment; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + +@Operation(summary = "댓글 작성", description = "게시글에 댓글을 작성합니다.") +@SecurityRequirement(name = "bearerAuth") +public @interface CommentCreateDoc { +} diff --git a/src/main/java/apptive/devlog/documentation/domain/comment/CommentDeleteDoc.java b/src/main/java/apptive/devlog/documentation/domain/comment/CommentDeleteDoc.java new file mode 100644 index 0000000..5cf7d9f --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/comment/CommentDeleteDoc.java @@ -0,0 +1,9 @@ +package apptive.devlog.documentation.domain.comment; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + +@Operation(summary = "댓글 삭제", description = "본인의 댓글을 삭제합니다.") +@SecurityRequirement(name = "bearerAuth") +public @interface CommentDeleteDoc { +} diff --git a/src/main/java/apptive/devlog/documentation/domain/comment/CommentReadDoc.java b/src/main/java/apptive/devlog/documentation/domain/comment/CommentReadDoc.java new file mode 100644 index 0000000..6c738a6 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/comment/CommentReadDoc.java @@ -0,0 +1,7 @@ +package apptive.devlog.documentation.domain.comment; + +import io.swagger.v3.oas.annotations.Operation; + +@Operation(summary = "댓글 목록 조회", description = "게시글의 댓글 목록을 조회합니다.") +public @interface CommentReadDoc { +} diff --git a/src/main/java/apptive/devlog/documentation/domain/comment/CommentUpdateDoc.java b/src/main/java/apptive/devlog/documentation/domain/comment/CommentUpdateDoc.java new file mode 100644 index 0000000..d5bd083 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/comment/CommentUpdateDoc.java @@ -0,0 +1,9 @@ +package apptive.devlog.documentation.domain.comment; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + +@Operation(summary = "댓글 수정", description = "본인의 댓글을 수정합니다.") +@SecurityRequirement(name = "bearerAuth") +public @interface CommentUpdateDoc { +} diff --git a/src/main/java/apptive/devlog/documentation/domain/post/PostCreateDoc.java b/src/main/java/apptive/devlog/documentation/domain/post/PostCreateDoc.java new file mode 100644 index 0000000..9cb1b8e --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/post/PostCreateDoc.java @@ -0,0 +1,9 @@ +package apptive.devlog.documentation.domain.post; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + +@Operation(summary = "게시글 작성") +@SecurityRequirement(name = "bearerAuth") +public @interface PostCreateDoc { +} diff --git a/src/main/java/apptive/devlog/documentation/domain/post/PostDeleteDoc.java b/src/main/java/apptive/devlog/documentation/domain/post/PostDeleteDoc.java new file mode 100644 index 0000000..a8f9699 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/post/PostDeleteDoc.java @@ -0,0 +1,9 @@ +package apptive.devlog.documentation.domain.post; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + +@Operation(summary = "게시글 삭제") +@SecurityRequirement(name = "bearerAuth") +public @interface PostDeleteDoc { +} diff --git a/src/main/java/apptive/devlog/documentation/domain/post/PostReadDoc.java b/src/main/java/apptive/devlog/documentation/domain/post/PostReadDoc.java new file mode 100644 index 0000000..35e9669 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/post/PostReadDoc.java @@ -0,0 +1,7 @@ +package apptive.devlog.documentation.domain.post; + +import io.swagger.v3.oas.annotations.Operation; + +@Operation(summary = "게시글 조회") +public @interface PostReadDoc { +} diff --git a/src/main/java/apptive/devlog/documentation/domain/post/PostUpdateDoc.java b/src/main/java/apptive/devlog/documentation/domain/post/PostUpdateDoc.java new file mode 100644 index 0000000..8f714f2 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/post/PostUpdateDoc.java @@ -0,0 +1,9 @@ +package apptive.devlog.documentation.domain.post; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + +@Operation(summary = "게시글 수정") +@SecurityRequirement(name = "bearerAuth") +public @interface PostUpdateDoc { +} diff --git a/src/main/java/apptive/devlog/documentation/domain/user/UserProfileDoc.java b/src/main/java/apptive/devlog/documentation/domain/user/UserProfileDoc.java new file mode 100644 index 0000000..13424f9 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/user/UserProfileDoc.java @@ -0,0 +1,26 @@ +package apptive.devlog.documentation.domain.user; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.security.SecurityRequirement; + +@Operation(summary = "유저 프로필 조회", description = "이메일을 기반으로 유저 프로필 정보를 조회합니다.") +@ApiResponses(value = { + @ApiResponse( + responseCode = "200", + description = "조회 성공", + content = @Content(schema = @Schema(implementation = ApiResponse.class)) + ) +}) +@Parameter( + name = "requestDto", + description = "조회할 유저의 이메일", + example = "test@example.com" +) +@SecurityRequirement(name = "bearerAuth") +public @interface UserProfileDoc { +} diff --git a/src/main/java/apptive/devlog/documentation/domain/user/wrapper/UserLoginResponseWrapper.java b/src/main/java/apptive/devlog/documentation/domain/user/wrapper/UserLoginResponseWrapper.java new file mode 100644 index 0000000..b2c775e --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/user/wrapper/UserLoginResponseWrapper.java @@ -0,0 +1,8 @@ +package apptive.devlog.documentation.domain.user.wrapper; + +import apptive.devlog.common.response.success.CommonResponse; +import apptive.devlog.domain.auth.dto.UserLoginResponseDto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(name = "UserLoginResponseWrapper") +public class UserLoginResponseWrapper extends CommonResponse {} \ No newline at end of file diff --git a/src/main/java/apptive/devlog/documentation/domain/user/wrapper/UserRefreshResponseWrapper.java b/src/main/java/apptive/devlog/documentation/domain/user/wrapper/UserRefreshResponseWrapper.java new file mode 100644 index 0000000..1372552 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/user/wrapper/UserRefreshResponseWrapper.java @@ -0,0 +1,9 @@ +package apptive.devlog.documentation.domain.user.wrapper; + +import apptive.devlog.common.response.success.CommonResponse; +import apptive.devlog.domain.auth.dto.UserRefreshResponseDto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(name = "UserRefreshResponseWrapper") +public class UserRefreshResponseWrapper extends CommonResponse {} + diff --git a/src/main/java/apptive/devlog/documentation/domain/user/wrapper/UserSignupResponseWrapper.java b/src/main/java/apptive/devlog/documentation/domain/user/wrapper/UserSignupResponseWrapper.java new file mode 100644 index 0000000..fb27e00 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/domain/user/wrapper/UserSignupResponseWrapper.java @@ -0,0 +1,8 @@ +package apptive.devlog.documentation.domain.user.wrapper; + +import apptive.devlog.common.response.success.CommonResponse; +import apptive.devlog.domain.auth.dto.UserSignupResponseDto; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(name = "UserSignupResponseWrapper") +public class UserSignupResponseWrapper extends CommonResponse {} diff --git a/src/main/java/apptive/devlog/documentation/tags/AuthDocumentation.java b/src/main/java/apptive/devlog/documentation/tags/AuthDocumentation.java new file mode 100644 index 0000000..08ee36c --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/tags/AuthDocumentation.java @@ -0,0 +1,7 @@ +package apptive.devlog.documentation.tags; + +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Auth", description = "인증 관련 API (회원가입, 로그인, 로그아웃 등)") +public interface AuthDocumentation { +} diff --git a/src/main/java/apptive/devlog/documentation/tags/CommentDocumentation.java b/src/main/java/apptive/devlog/documentation/tags/CommentDocumentation.java new file mode 100644 index 0000000..362df96 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/tags/CommentDocumentation.java @@ -0,0 +1,7 @@ +package apptive.devlog.documentation.tags; + +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Comment", description = "Comment 관련 API (생성, 조회, 수정, 삭제)") +public interface CommentDocumentation { +} diff --git a/src/main/java/apptive/devlog/documentation/tags/PostDocumentation.java b/src/main/java/apptive/devlog/documentation/tags/PostDocumentation.java new file mode 100644 index 0000000..daee766 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/tags/PostDocumentation.java @@ -0,0 +1,7 @@ +package apptive.devlog.documentation.tags; + +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "Post", description = "게시글 관련 API (생성, 조회, 수정, 삭제)") +public interface PostDocumentation { +} diff --git a/src/main/java/apptive/devlog/documentation/tags/UserDocumentation.java b/src/main/java/apptive/devlog/documentation/tags/UserDocumentation.java new file mode 100644 index 0000000..de68904 --- /dev/null +++ b/src/main/java/apptive/devlog/documentation/tags/UserDocumentation.java @@ -0,0 +1,7 @@ +package apptive.devlog.documentation.tags; + +import io.swagger.v3.oas.annotations.tags.Tag; + +@Tag(name = "User", description = "사용자 관련 API") +public interface UserDocumentation { +} diff --git a/src/main/java/apptive/devlog/domain/auth/controller/AuthController.java b/src/main/java/apptive/devlog/domain/auth/controller/AuthController.java new file mode 100644 index 0000000..62d621c --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/controller/AuthController.java @@ -0,0 +1,58 @@ +package apptive.devlog.domain.auth.controller; + +import apptive.devlog.common.response.success.CommonResponse; +import apptive.devlog.documentation.domain.auth.AuthLoginDoc; +import apptive.devlog.documentation.domain.auth.AuthLogoutDoc; +import apptive.devlog.documentation.domain.auth.AuthRefreshDoc; +import apptive.devlog.documentation.domain.auth.AuthSignupDoc; +import apptive.devlog.documentation.tags.AuthDocumentation; +import apptive.devlog.domain.auth.dto.*; +import apptive.devlog.domain.auth.service.AuthService; +import apptive.devlog.global.annotation.InjectToken; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.Valid; + +@Slf4j +@RestController +@RequestMapping("/auth") +@RequiredArgsConstructor +public class AuthController implements AuthDocumentation { + private final AuthService authService; + + @AuthSignupDoc + @PostMapping("/signup") + @ResponseStatus(HttpStatus.CREATED) + public CommonResponse signup(@Valid @RequestBody UserSignupRequestDto requestDto) { + UserSignupResponseDto responseDto = authService.signup(requestDto); + return CommonResponse.created(responseDto); + } + + @AuthLoginDoc + @PostMapping("/login") + @ResponseStatus(HttpStatus.OK) + public CommonResponse login(@Valid @RequestBody UserLoginRequestDto requestDto) { + UserLoginResponseDto responseDto = authService.login(requestDto); + return CommonResponse.ok(responseDto); + } + + @AuthRefreshDoc + @PostMapping("/refresh") + @ResponseStatus(HttpStatus.OK) + public CommonResponse refresh(@Valid @InjectToken UserRefreshRequestDto requestDto) { + UserRefreshResponseDto responseDto = authService.refresh(requestDto); + return CommonResponse.ok(responseDto); + } + + @AuthLogoutDoc + @PostMapping("/logout") + @ResponseStatus(HttpStatus.OK) + public CommonResponse logout(@Valid @InjectToken UserLogoutRequestDto requestDto) { + authService.logout(requestDto); + return CommonResponse.noContent(); + } +} diff --git a/src/main/java/apptive/devlog/domain/auth/controller/EmailVerificationController.java b/src/main/java/apptive/devlog/domain/auth/controller/EmailVerificationController.java new file mode 100644 index 0000000..84c6ebb --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/controller/EmailVerificationController.java @@ -0,0 +1,32 @@ +package apptive.devlog.domain.auth.controller; + +import apptive.devlog.common.response.success.CommonResponse; +import apptive.devlog.domain.auth.dto.EmailSendRequestDto; +import apptive.devlog.domain.auth.dto.EmailVerifyRequestDto; +import apptive.devlog.domain.auth.dto.EmailVerifyResponseDto; +import apptive.devlog.domain.auth.service.EmailVerificationService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import jakarta.validation.Valid; + +@RestController +@RequestMapping("/auth/email") +@RequiredArgsConstructor +public class EmailVerificationController { + private final EmailVerificationService emailVerificationService; + + @PostMapping("/send") + @ResponseStatus(HttpStatus.OK) + public CommonResponse send(@Valid @RequestBody EmailSendRequestDto request) { + emailVerificationService.sendCode(request.getEmail()); + return CommonResponse.ok(null); + } + + @PostMapping("/verify") + @ResponseStatus(HttpStatus.OK) + public CommonResponse verify(@Valid @RequestBody EmailVerifyRequestDto request) { + return CommonResponse.ok(emailVerificationService.verifyCode(request.getEmail(), request.getCode())); + } +} diff --git a/src/main/java/apptive/devlog/domain/auth/dto/EmailSendRequestDto.java b/src/main/java/apptive/devlog/domain/auth/dto/EmailSendRequestDto.java new file mode 100644 index 0000000..46800c1 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/dto/EmailSendRequestDto.java @@ -0,0 +1,14 @@ +package apptive.devlog.domain.auth.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class EmailSendRequestDto { + @Email + @NotBlank + private String email; +} diff --git a/src/main/java/apptive/devlog/domain/auth/dto/EmailVerifyRequestDto.java b/src/main/java/apptive/devlog/domain/auth/dto/EmailVerifyRequestDto.java new file mode 100644 index 0000000..316733f --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/dto/EmailVerifyRequestDto.java @@ -0,0 +1,18 @@ +package apptive.devlog.domain.auth.dto; + +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class EmailVerifyRequestDto { + @Email + @NotBlank + private String email; + + @NotBlank + private String code; +} \ No newline at end of file diff --git a/src/main/java/apptive/devlog/domain/auth/dto/EmailVerifyResponseDto.java b/src/main/java/apptive/devlog/domain/auth/dto/EmailVerifyResponseDto.java new file mode 100644 index 0000000..a012f8d --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/dto/EmailVerifyResponseDto.java @@ -0,0 +1,13 @@ +package apptive.devlog.domain.auth.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Schema(description = "이메일 인증 응답 DTO") +public class EmailVerifyResponseDto { + @Schema(description = "이메일 인증 토큰", example = "eyJhbGciOiJIUzI1...") + private String token; +} diff --git a/src/main/java/apptive/devlog/domain/auth/dto/UserLoginRequestDto.java b/src/main/java/apptive/devlog/domain/auth/dto/UserLoginRequestDto.java new file mode 100644 index 0000000..417eb1b --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/dto/UserLoginRequestDto.java @@ -0,0 +1,23 @@ +package apptive.devlog.domain.auth.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "로그인 요청 DTO") +public class UserLoginRequestDto { + @Schema(description = "사용자 이메일", example = "user@example.com", requiredMode = Schema.RequiredMode.REQUIRED) + @Email + @NotBlank + private String email; + + @Schema(description = "사용자 비밀번호", example = "securePassword123!", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank + private String password; +} diff --git a/src/main/java/apptive/devlog/domain/auth/dto/UserLoginResponseDto.java b/src/main/java/apptive/devlog/domain/auth/dto/UserLoginResponseDto.java new file mode 100644 index 0000000..0752d7c --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/dto/UserLoginResponseDto.java @@ -0,0 +1,16 @@ +package apptive.devlog.domain.auth.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Schema(description = "로그인 응답 DTO") +public class UserLoginResponseDto { + @Schema(description = "엑세스 토큰", example = "eyJhbGciOiJIUzI1...") + private String accessToken; + + @Schema(description = "리프레시 토큰", example = "eyJhbGciOiJIUzI1...") + private String refreshToken; +} diff --git a/src/main/java/apptive/devlog/domain/auth/dto/UserLogoutRequestDto.java b/src/main/java/apptive/devlog/domain/auth/dto/UserLogoutRequestDto.java new file mode 100644 index 0000000..f2809d2 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/dto/UserLogoutRequestDto.java @@ -0,0 +1,21 @@ +package apptive.devlog.domain.auth.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "사용자 로그아웃 요청 DTO") +public class UserLogoutRequestDto { + @Schema(description = "Access Token", example = "eyJhbGciOiJIUzI1NiJ9...") + @NotBlank(message = "Access Token은 필수입니다.") + private String accessToken; + + @Schema(description = "Refresh Token", example = "dGhpc2lzYXJlZnJlc2h0b2tlbg==") + @NotBlank(message = "Refresh Token은 필수입니다.") + private String refreshToken; +} diff --git a/src/main/java/apptive/devlog/domain/auth/dto/UserRefreshRequestDto.java b/src/main/java/apptive/devlog/domain/auth/dto/UserRefreshRequestDto.java new file mode 100644 index 0000000..89d5668 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/dto/UserRefreshRequestDto.java @@ -0,0 +1,21 @@ +package apptive.devlog.domain.auth.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "Access Token과 Refresh Token 재발급 요청 DTO") +public class UserRefreshRequestDto { + @NotBlank + @Schema(description = "기존 Access Token", example = "eyJhbGciOiJIUzI1NiIsInR...") + private String accessToken; + + @NotBlank + @Schema(description = "Refresh Token", example = "dGhpc2lzYXJlZnJlc2h0b2tlbg==") + private String refreshToken; +} diff --git a/src/main/java/apptive/devlog/domain/auth/dto/UserRefreshResponseDto.java b/src/main/java/apptive/devlog/domain/auth/dto/UserRefreshResponseDto.java new file mode 100644 index 0000000..9132266 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/dto/UserRefreshResponseDto.java @@ -0,0 +1,16 @@ +package apptive.devlog.domain.auth.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@Schema(description = "액세스/리프레시 토큰 응답 DTO") +public class UserRefreshResponseDto { + @Schema(description = "Access Token", example = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...") + private String accessToken; + + @Schema(description = "Refresh Token", example = "dGhpc19pc19hX3JlZnJlc2hfdG9rZW4=") + private String refreshToken; +} \ No newline at end of file diff --git a/src/main/java/apptive/devlog/domain/auth/dto/UserSignupRequestDto.java b/src/main/java/apptive/devlog/domain/auth/dto/UserSignupRequestDto.java new file mode 100644 index 0000000..82ed07f --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/dto/UserSignupRequestDto.java @@ -0,0 +1,65 @@ +package apptive.devlog.domain.auth.dto; + +import apptive.devlog.domain.user.entity.User; +import apptive.devlog.domain.user.enums.Gender; +import apptive.devlog.domain.user.enums.Provider; +import apptive.devlog.domain.user.enums.Role; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; + +import java.time.LocalDate; + +@Builder +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Schema(description = "회원가입 요청 DTO") +public class UserSignupRequestDto { + @Schema(description = "이메일", example = "user@example.com", requiredMode = Schema.RequiredMode.REQUIRED) + @Email + @NotBlank + private String email; + + @Schema(description = "비밀번호 (8자 이상 20자 이하)", example = "securePassword123!", requiredMode = Schema.RequiredMode.REQUIRED) + @Size(min = 8, max = 20) + @NotBlank + private String password; + + @Schema(description = "이메일 인증 토큰", example = "", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank + private String token; + + @Schema(description = "이름", example = "홍길동", requiredMode = Schema.RequiredMode.REQUIRED) + @NotBlank + private String name; + + @Schema(description = "닉네임", example = "길동이") + private String nickname; + + @Schema(description = "생년월일", example = "1990-01-01") + private LocalDate birth; + + @Schema(description = "성별", example = "MALE") + private Gender gender; + + public User toEntity(PasswordEncoder passwordEncoder) { + User user = User.builder() + .email(email) + .name(name) + .nickname(nickname) + .birth(birth) + .gender(gender) + .password(passwordEncoder.encode(password)) + .role(Role.USER) + .build(); + user.addProvider(Provider.LOCAL); + return user; + } +} diff --git a/src/main/java/apptive/devlog/domain/auth/dto/UserSignupResponseDto.java b/src/main/java/apptive/devlog/domain/auth/dto/UserSignupResponseDto.java new file mode 100644 index 0000000..c2f1d55 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/dto/UserSignupResponseDto.java @@ -0,0 +1,17 @@ +package apptive.devlog.domain.auth.dto; + +import apptive.devlog.domain.user.entity.User; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; + +@Schema(description = "회원가입 결과 응답") +@Getter +public class UserSignupResponseDto { + @Schema(description = "가입된 유저의 이메일", example = "test@example.com") + private final String email; + + public UserSignupResponseDto(User user) { + this.email = user.getEmail(); + } +} diff --git a/src/main/java/apptive/devlog/domain/auth/repository/VerifiedEmailRepository.java b/src/main/java/apptive/devlog/domain/auth/repository/VerifiedEmailRepository.java new file mode 100644 index 0000000..3d813e8 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/repository/VerifiedEmailRepository.java @@ -0,0 +1,48 @@ +package apptive.devlog.domain.auth.repository; + +import apptive.devlog.common.response.error.exception.InvalidEmailVerificationTokenException; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; + +import java.time.Duration; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; + +@Repository +@RequiredArgsConstructor +public class VerifiedEmailRepository { + private static final String CODE_EMAIL_PREFIX = "CE:"; + private static final String VERIFY_EMAIL_PREFIX = "VE:"; + private final Duration EXPIRATION = Duration.ofMinutes(3); + private final RedisTemplate redisTemplate; + + public void saveCode(String email, String code) { + redisTemplate.opsForValue().set(CODE_EMAIL_PREFIX + email, code, EXPIRATION); + } + + public Optional getCode(String email) { + return Optional.ofNullable(redisTemplate.opsForValue().get(CODE_EMAIL_PREFIX + email)); + } + + public void deleteCode(String email) { + redisTemplate.delete(CODE_EMAIL_PREFIX + email); + } + + public String markVerified(String email) { + String token = UUID.randomUUID().toString(); + redisTemplate.opsForValue().set(VERIFY_EMAIL_PREFIX + email, token, EXPIRATION); + return token; + } + + public boolean isVerified(String email, String token) { + return Boolean.TRUE.equals(Optional.ofNullable(redisTemplate.opsForValue().get(VERIFY_EMAIL_PREFIX + email)) + .map(storedToken -> Objects.equals(token, storedToken)) + .orElseThrow(InvalidEmailVerificationTokenException::new)); + } + + public void deleteVerified(String email) { + redisTemplate.delete(VERIFY_EMAIL_PREFIX + email); + } +} diff --git a/src/main/java/apptive/devlog/domain/auth/service/AuthService.java b/src/main/java/apptive/devlog/domain/auth/service/AuthService.java new file mode 100644 index 0000000..324d186 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/service/AuthService.java @@ -0,0 +1,79 @@ +package apptive.devlog.domain.auth.service; + +import apptive.devlog.common.response.error.exception.*; +import apptive.devlog.domain.auth.dto.*; +import apptive.devlog.domain.user.enums.Provider; +import apptive.devlog.global.security.jwt.JwtTokenProvider; +import apptive.devlog.infrastructure.redis.repository.RedisRepository; +import apptive.devlog.domain.user.entity.User; +import apptive.devlog.domain.user.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +@Slf4j +@Service +@RequiredArgsConstructor +public class AuthService { + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final JwtTokenProvider jwtTokenProvider; + private final RedisRepository redisRepository; + private final EmailVerificationService emailVerificationService; + + @Transactional + public UserSignupResponseDto signup(UserSignupRequestDto requestDto) { + if (userRepository.existsByEmail(requestDto.getEmail())) { + throw new DuplicateEmailException(); + } + if (!emailVerificationService.isVerified(requestDto.getEmail(), requestDto.getToken())) { + throw new IllegalArgumentException("이메일 인증이 필요합니다."); + } + + User user = requestDto.toEntity(passwordEncoder); + userRepository.save(user); + return new UserSignupResponseDto(user); + } + + @Transactional + public UserLoginResponseDto login(UserLoginRequestDto requestDto) { + User user = userRepository.findByEmail(requestDto.getEmail()).orElseThrow(UserNotFoundException::new); + if (!passwordEncoder.matches(requestDto.getPassword(), user.getPassword())) { + throw new InvalidEmailOrPasswordException(); + } + if (!user.getProviders().contains(Provider.LOCAL)) { + throw new InvalidProviderException(); + } + String accessToken = jwtTokenProvider.generateAccessToken(user.getEmail()); + String refreshToken = jwtTokenProvider.generateRefreshToken(user.getEmail()); + return new UserLoginResponseDto(accessToken, refreshToken); + } + + @Transactional + public UserRefreshResponseDto refresh(UserRefreshRequestDto requestDto) { + final String accessToken = requestDto.getAccessToken(); + final String refreshToken = requestDto.getRefreshToken(); + + if (!jwtTokenProvider.validateRefreshToken(refreshToken)) { + throw new InvalidRefreshTokenException(); + } + + final String email = jwtTokenProvider.getEmailFromToken(refreshToken); + + redisRepository.deleteAccessToken(accessToken); + redisRepository.deleteRefreshToken(refreshToken); + + final String newAccessToken = jwtTokenProvider.generateAccessToken(email); + final String newRefreshToken = jwtTokenProvider.generateRefreshToken(email); + + return new UserRefreshResponseDto(newAccessToken, newRefreshToken); + } + + @Transactional + public void logout(UserLogoutRequestDto requestDto) { + redisRepository.deleteAccessToken(requestDto.getAccessToken()); + redisRepository.deleteRefreshToken(requestDto.getRefreshToken()); + } +} diff --git a/src/main/java/apptive/devlog/domain/auth/service/EmailVerificationService.java b/src/main/java/apptive/devlog/domain/auth/service/EmailVerificationService.java new file mode 100644 index 0000000..291303f --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/service/EmailVerificationService.java @@ -0,0 +1,38 @@ +package apptive.devlog.domain.auth.service; + +import apptive.devlog.domain.auth.dto.EmailVerifyResponseDto; +import apptive.devlog.domain.auth.repository.VerifiedEmailRepository; +import apptive.devlog.domain.auth.util.MailSender; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Random; + +@Service +@RequiredArgsConstructor +public class EmailVerificationService { + private final MailSender mailSender; + private final VerifiedEmailRepository verifiedEmailRepository; + + public void sendCode(String email) { + String code = generateCode(); + verifiedEmailRepository.saveCode(email, code); + mailSender.sendEmail(email, "[Devlog] 인증 코드", "인증 코드: " + code); + } + + public EmailVerifyResponseDto verifyCode(String email, String code) { + String storedCode = verifiedEmailRepository.getCode(email) + .orElseThrow(() -> new IllegalArgumentException("코드가 만료되었거나 존재하지 않습니다.")); + if (!storedCode.equals(code)) throw new IllegalArgumentException("코드가 일치하지 않습니다."); + verifiedEmailRepository.deleteCode(email); + return new EmailVerifyResponseDto(verifiedEmailRepository.markVerified(email)); + } + + public boolean isVerified(String email, String token) { + return verifiedEmailRepository.isVerified(email, token); + } + + private String generateCode() { + return String.format("%06d", new Random().nextInt(1_000_000)); + } +} diff --git a/src/main/java/apptive/devlog/domain/auth/util/MailSender.java b/src/main/java/apptive/devlog/domain/auth/util/MailSender.java new file mode 100644 index 0000000..1cc1dc3 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/util/MailSender.java @@ -0,0 +1,41 @@ +package apptive.devlog.domain.auth.util; + +import jakarta.mail.MessagingException; +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.MailSendException; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Component; + +import java.nio.charset.StandardCharsets; + +@Component +@RequiredArgsConstructor +public class MailSender { + private final JavaMailSender javaMailSender; + @Value("${spring.mail.username}") + private String fromAddress; + + public void sendEmail(String to, String subject, String text) { + try { + MimeMessage message = javaMailSender.createMimeMessage(); + // multipart=false, encoding=UTF-8 + MimeMessageHelper helper = new MimeMessageHelper( + message, + /* multipart */ false, + StandardCharsets.UTF_8.name() + ); + helper.setFrom(fromAddress); + helper.setTo(to); + helper.setSubject(subject); + helper.setText(text, /* isHtml */ false); + + javaMailSender.send(message); + } catch (MessagingException e) { + throw new MailSendException("메일 전송 실패", e); + } + } +} + diff --git a/src/main/java/apptive/devlog/domain/auth/util/RandomCodeGenerator.java b/src/main/java/apptive/devlog/domain/auth/util/RandomCodeGenerator.java new file mode 100644 index 0000000..3b4f962 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/auth/util/RandomCodeGenerator.java @@ -0,0 +1,11 @@ +package apptive.devlog.domain.auth.util; + +import java.util.Random; + +public class RandomCodeGenerator { + public static String generate6DigitCode() { + Random random = new Random(); + int code = 100000 + random.nextInt(900000); + return String.valueOf(code); + } +} diff --git a/src/main/java/apptive/devlog/domain/comment/controller/CommentController.java b/src/main/java/apptive/devlog/domain/comment/controller/CommentController.java new file mode 100644 index 0000000..73fc66b --- /dev/null +++ b/src/main/java/apptive/devlog/domain/comment/controller/CommentController.java @@ -0,0 +1,52 @@ +package apptive.devlog.domain.comment.controller; + +import apptive.devlog.common.response.success.CommonResponse; +import apptive.devlog.documentation.domain.comment.CommentCreateDoc; +import apptive.devlog.documentation.domain.comment.CommentDeleteDoc; +import apptive.devlog.documentation.domain.comment.CommentReadDoc; +import apptive.devlog.documentation.domain.comment.CommentUpdateDoc; +import apptive.devlog.documentation.tags.CommentDocumentation; +import apptive.devlog.domain.comment.dto.CommentDto; +import apptive.devlog.domain.comment.service.CommentService; +import apptive.devlog.domain.user.entity.User; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("post/{postId}/comment") +@RequiredArgsConstructor +public class CommentController implements CommentDocumentation { + private final CommentService commentService; + + @CommentCreateDoc + @PostMapping + public ResponseEntity> create(@PathVariable String postId, @Valid @RequestBody CommentDto.Create dto, @RequestAttribute("user") User user) { + CommentDto.Response response = commentService.create(postId, dto, user); + return ResponseEntity.status(HttpStatus.CREATED).body(CommonResponse.created(response)); + } + + @CommentReadDoc + @GetMapping + public ResponseEntity>> read(@PathVariable String postId) { + return ResponseEntity.ok().body(CommonResponse.ok(commentService.read(postId))); + } + + @CommentUpdateDoc + @PutMapping("/{commentId}") + public ResponseEntity> update(@PathVariable String postId, @PathVariable String commentId, @Valid @RequestBody CommentDto.Update dto, @RequestAttribute("user") User user) { + CommentDto.Response response = commentService.update(commentId, dto, user); + return ResponseEntity.ok().body(CommonResponse.ok(response)); + } + + @CommentDeleteDoc + @DeleteMapping("/{commentId}") + public ResponseEntity> delete(@PathVariable String postId, @PathVariable String commentId, @RequestAttribute("user") User user) { + commentService.delete(commentId, user); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/apptive/devlog/domain/comment/dto/CommentDto.java b/src/main/java/apptive/devlog/domain/comment/dto/CommentDto.java new file mode 100644 index 0000000..ddd598f --- /dev/null +++ b/src/main/java/apptive/devlog/domain/comment/dto/CommentDto.java @@ -0,0 +1,17 @@ +package apptive.devlog.domain.comment.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; + +import java.util.List; + +public class CommentDto { + @Builder + public record Create(@NotBlank String text, String parentId) {} + + @Builder + public record Update(@NotBlank String text) {} + + @Builder + public record Response(String id, String text, String authorId, String parentId, List replies) {} +} diff --git a/src/main/java/apptive/devlog/domain/comment/entity/Comment.java b/src/main/java/apptive/devlog/domain/comment/entity/Comment.java new file mode 100644 index 0000000..110409d --- /dev/null +++ b/src/main/java/apptive/devlog/domain/comment/entity/Comment.java @@ -0,0 +1,48 @@ +package apptive.devlog.domain.comment.entity; + +import apptive.devlog.common.base.BaseEntity; +import apptive.devlog.domain.post.entity.Post; +import apptive.devlog.domain.user.entity.User; +import de.huxhorn.sulky.ulid.ULID; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import lombok.*; + +import java.util.List; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder +@Table(name = "comments") +public class Comment extends BaseEntity { + @NotBlank + @Column(columnDefinition = "TEXT") + private String text; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "author_id", nullable = false) + private User author; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id", nullable = false) + private Post post; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "parent_id") + private Comment parent; + + @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) + private List replies; + + @Column(nullable = false) + private boolean deleted; + + @Transient + private int depth; + + public void updateText(String newText) { + this.text = newText; + } +} diff --git a/src/main/java/apptive/devlog/domain/comment/repository/CommentRepository.java b/src/main/java/apptive/devlog/domain/comment/repository/CommentRepository.java new file mode 100644 index 0000000..b18302b --- /dev/null +++ b/src/main/java/apptive/devlog/domain/comment/repository/CommentRepository.java @@ -0,0 +1,20 @@ +package apptive.devlog.domain.comment.repository; + +import apptive.devlog.domain.comment.entity.Comment; +import apptive.devlog.domain.post.entity.Post; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; +import java.util.Optional; + +@Repository +public interface CommentRepository extends JpaRepository { + List findByPostIdAndParentIsNullOrderByCreatedAtDesc(String postId); + List findByParentIdOrderByCreatedAtDesc(String parentId); + + List findByPostAndParentIsNull(Post post); + Optional findByIdAndDeletedFalse(String id); + + String post(Post post); +} diff --git a/src/main/java/apptive/devlog/domain/comment/service/CommentService.java b/src/main/java/apptive/devlog/domain/comment/service/CommentService.java new file mode 100644 index 0000000..4901d78 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/comment/service/CommentService.java @@ -0,0 +1,82 @@ +package apptive.devlog.domain.comment.service; + +import apptive.devlog.domain.comment.dto.CommentDto; +import apptive.devlog.domain.comment.entity.Comment; +import apptive.devlog.domain.comment.repository.CommentRepository; +import apptive.devlog.domain.post.entity.Post; +import apptive.devlog.domain.post.repository.PostRepository; +import apptive.devlog.domain.user.entity.User; +import apptive.devlog.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +public class CommentService { + private final CommentRepository commentRepository; + private final PostRepository postRepository; + private final UserRepository userRepository; + + private static final int MAX_DEPTH = 3; + + @Transactional + public CommentDto.Response create(String postId, CommentDto.Create dto, User author) { + Post post = postRepository.findById(postId).orElseThrow(() -> new IllegalArgumentException("post not found")); + User user = userRepository.findById(author.getId()).orElseThrow(() -> new IllegalArgumentException("user not found")); + + Comment parent = null; + int depth = 0; + if (dto.parentId() != null) { + parent = commentRepository.findByIdAndDeletedFalse(dto.parentId()).orElseThrow(() -> new IllegalArgumentException("parent comment not found")); + depth = calculateDepth(parent); + if (depth >= MAX_DEPTH) { + throw new IllegalArgumentException("댓글은 최대 %d단계까지만 가능합니다.".formatted(MAX_DEPTH)); + } + } + + Comment comment = Comment.builder().text(dto.text()).author(author).parent(parent).post(post).build(); + commentRepository.save(comment); + return toResponse(comment); + } + + @Transactional(readOnly = true) + public List read(String postId) { + return commentRepository.findByPostIdAndParentIsNullOrderByCreatedAtDesc(postId).stream().map(this::toResponse).toList(); + } + + @Transactional + public CommentDto.Response update(String commentId, CommentDto.Update dto, User user) { + Comment comment = commentRepository.findById(commentId).orElseThrow(() -> new IllegalArgumentException("comment not found")); + if (!comment.getAuthor().getId().equals(user.getId())) { + throw new IllegalArgumentException("수정 권한이 없습니다"); + } + comment.updateText(dto.text()); + return toResponse(comment); + } + + @Transactional + public void delete(String commentId, User user) { + Comment comment = commentRepository.findById(commentId).orElseThrow(() -> new IllegalArgumentException("comment not found")); + if (!comment.getAuthor().getId().equals(user.getId())) { + throw new IllegalArgumentException("삭제 권한이 없습니다."); + } + commentRepository.delete(comment); + } + + private int calculateDepth(Comment comment) { + int depth = 0; + while (comment.getParent() != null) { + comment = comment.getParent(); + depth++; + } + return depth; + } + + private CommentDto.Response toResponse(Comment comment) { + List replies = comment.getReplies() == null ? List.of() : comment.getReplies().stream().map(this::toResponse).toList(); + return CommentDto.Response.builder().id(comment.getId()).text(comment.getText()).authorId(comment.getAuthor().getId()).parentId(comment.getParent() != null ? comment.getParent().getId() : null).replies(replies).build(); + } +} diff --git a/src/main/java/apptive/devlog/domain/oauth2/controller/OAuth2PkceController.java b/src/main/java/apptive/devlog/domain/oauth2/controller/OAuth2PkceController.java new file mode 100644 index 0000000..218a80a --- /dev/null +++ b/src/main/java/apptive/devlog/domain/oauth2/controller/OAuth2PkceController.java @@ -0,0 +1,24 @@ +package apptive.devlog.domain.oauth2.controller; + +import apptive.devlog.domain.oauth2.dto.OAuth2CallbackRequestDto; +import apptive.devlog.domain.oauth2.dto.OAuth2CallbackResponseDto; +import apptive.devlog.domain.oauth2.service.OAuth2PkceService; +import apptive.devlog.common.response.success.CommonResponse; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/oauth2") +public class OAuth2PkceController { + + private final OAuth2PkceService pkceService; + + @PostMapping("/pkce/callback") + public ResponseEntity> handleCallback(@Valid @RequestBody OAuth2CallbackRequestDto requestDto) { + OAuth2CallbackResponseDto responseDto = pkceService.handleCallback(requestDto); + return ResponseEntity.ok(CommonResponse.ok(responseDto)); + } +} diff --git a/src/main/java/apptive/devlog/domain/oauth2/dto/OAuth2CallbackRequestDto.java b/src/main/java/apptive/devlog/domain/oauth2/dto/OAuth2CallbackRequestDto.java new file mode 100644 index 0000000..c20e88d --- /dev/null +++ b/src/main/java/apptive/devlog/domain/oauth2/dto/OAuth2CallbackRequestDto.java @@ -0,0 +1,18 @@ +package apptive.devlog.domain.oauth2.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class OAuth2CallbackRequestDto { + @NotBlank + private String code; + @NotBlank + private String state; + @NotBlank + private String provider; +} diff --git a/src/main/java/apptive/devlog/domain/oauth2/dto/OAuth2CallbackResponseDto.java b/src/main/java/apptive/devlog/domain/oauth2/dto/OAuth2CallbackResponseDto.java new file mode 100644 index 0000000..ff9179e --- /dev/null +++ b/src/main/java/apptive/devlog/domain/oauth2/dto/OAuth2CallbackResponseDto.java @@ -0,0 +1,11 @@ +package apptive.devlog.domain.oauth2.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class OAuth2CallbackResponseDto { + private String accessToken; + private String refreshToken; +} diff --git a/src/main/java/apptive/devlog/domain/oauth2/handler/OAuth2FailureHandler.java b/src/main/java/apptive/devlog/domain/oauth2/handler/OAuth2FailureHandler.java new file mode 100644 index 0000000..31dce41 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/oauth2/handler/OAuth2FailureHandler.java @@ -0,0 +1,43 @@ +package apptive.devlog.domain.oauth2.handler; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class OAuth2FailureHandler implements AuthenticationFailureHandler { + + private final ObjectMapper objectMapper; + + @Override + public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException { + setResponseHeader(response, HttpStatus.UNAUTHORIZED); + writeErrorResponse(response, buildErrorResponse(exception)); + } + + private void setResponseHeader(HttpServletResponse response, HttpStatus status) { + response.setStatus(status.value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + } + + private Map buildErrorResponse(AuthenticationException exception) { + return Map.of("success", false, "error", "OAuth2 인증 실패", "message", exception.getMessage()); + } + + private void writeErrorResponse(HttpServletResponse response, Map body) throws IOException { + String json = objectMapper.writeValueAsString(body); + response.getWriter().write(json); + } +} diff --git a/src/main/java/apptive/devlog/domain/oauth2/handler/OAuth2SuccessHandler.java b/src/main/java/apptive/devlog/domain/oauth2/handler/OAuth2SuccessHandler.java new file mode 100644 index 0000000..50f8a86 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/oauth2/handler/OAuth2SuccessHandler.java @@ -0,0 +1,44 @@ +package apptive.devlog.domain.oauth2.handler; + +import apptive.devlog.global.security.jwt.JwtTokenProvider; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.MediaType; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +@Component +@RequiredArgsConstructor +public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler { + + private final JwtTokenProvider jwtTokenProvider; + private final ObjectMapper objectMapper; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { + OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); + String email = oAuth2User.getAttribute("email"); + + String accessToken = jwtTokenProvider.generateAccessToken(email); + String refreshToken = jwtTokenProvider.generateRefreshToken(email); + + writeJsonResponse(response, Map.of("accessToken", accessToken, "refreshToken", refreshToken)); + } + + private void writeJsonResponse(HttpServletResponse response, Map body) throws IOException { + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding(StandardCharsets.UTF_8.name()); + + String json = objectMapper.writeValueAsString(body); + response.getWriter().write(json); + } +} diff --git a/src/main/java/apptive/devlog/domain/oauth2/handler/OAuth2SuccessHandlerForUnity.java b/src/main/java/apptive/devlog/domain/oauth2/handler/OAuth2SuccessHandlerForUnity.java new file mode 100644 index 0000000..3e32654 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/oauth2/handler/OAuth2SuccessHandlerForUnity.java @@ -0,0 +1,36 @@ +package apptive.devlog.domain.oauth2.handler; + +import apptive.devlog.global.security.jwt.JwtTokenProvider; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; +import org.springframework.stereotype.Component; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; + +@Component +@RequiredArgsConstructor +public class OAuth2SuccessHandlerForUnity extends SimpleUrlAuthenticationSuccessHandler { + + private final JwtTokenProvider jwtTokenProvider; + + @Override + public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { + OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); + String email = oAuth2User.getAttribute("email"); + + String accessToken = jwtTokenProvider.generateAccessToken(email); + String refreshToken = jwtTokenProvider.generateRefreshToken(email); + + String redirectUrl = "http://localhost:5000/callback" + + "?accessToken=" + URLEncoder.encode(accessToken, StandardCharsets.UTF_8) + + "&refreshToken=" + URLEncoder.encode(refreshToken, StandardCharsets.UTF_8); + + getRedirectStrategy().sendRedirect(request, response, redirectUrl); + } +} diff --git a/src/main/java/apptive/devlog/domain/oauth2/model/OAuth2Attributes.java b/src/main/java/apptive/devlog/domain/oauth2/model/OAuth2Attributes.java new file mode 100644 index 0000000..e4da52c --- /dev/null +++ b/src/main/java/apptive/devlog/domain/oauth2/model/OAuth2Attributes.java @@ -0,0 +1,88 @@ +package apptive.devlog.domain.oauth2.model; + +import apptive.devlog.common.response.error.exception.InvalidOAuth2ResponseException; +import apptive.devlog.domain.user.entity.User; +import apptive.devlog.domain.user.enums.Provider; +import apptive.devlog.domain.user.enums.Role; +import lombok.Getter; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +@Getter +public class OAuth2Attributes { + + private final String nameAttributeKey; + private final Provider provider; + private final Map attributes; + private final String email; + private final String name; + + private OAuth2Attributes(String nameAttributeKey, Provider provider, Map attributes, String email, String name) { + this.nameAttributeKey = nameAttributeKey; + this.provider = provider; + this.email = email; + this.name = name; + + Map copied = new HashMap<>(attributes); + copied.put("email", email); + this.attributes = Collections.unmodifiableMap(copied); + } + + public static OAuth2Attributes of(String registrationId, String userNameAttributeName, Map attributes) { + return switch (registrationId.toLowerCase()) { + case "kakao" -> ofKakao(attributes); + case "naver" -> ofNaver(attributes); + case "google" -> ofGoogle(userNameAttributeName, attributes); + default -> throw new UnsupportedOperationException(registrationId); + }; + } + + private static OAuth2Attributes ofGoogle(String userNameAttributeName, Map attributes) { + return new OAuth2Attributes( + userNameAttributeName, + Provider.GOOGLE, + attributes, + (String) attributes.get("email"), + (String) attributes.get("name") + ); + } + + private static OAuth2Attributes ofNaver(Map attributes) { + Map response = getMap(attributes, "response"); + + return new OAuth2Attributes( + "id", + Provider.NAVER, + response, + (String) response.get("email"), + (String) response.get("name") + ); + } + + private static OAuth2Attributes ofKakao(Map attributes) { + Map kakaoAccount = getMap(attributes, "kakao_account"); + Map profile = getMap(kakaoAccount, "profile"); + + return new OAuth2Attributes( + "id", + Provider.KAKAO, + attributes, + (String) kakaoAccount.get("email"), + (String) profile.get("nickname") + ); + } + + private static Map getMap(Map source, String key) { + Object value = source.get(key); + if (value instanceof Map map) { + return (Map) map; + } + throw new InvalidOAuth2ResponseException(key); + } + + public User toEntity() { + return User.of(email, name, provider, Role.USER); + } +} diff --git a/src/main/java/apptive/devlog/domain/oauth2/service/CustomOAuth2UserService.java b/src/main/java/apptive/devlog/domain/oauth2/service/CustomOAuth2UserService.java new file mode 100644 index 0000000..0ac6996 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/oauth2/service/CustomOAuth2UserService.java @@ -0,0 +1,52 @@ +package apptive.devlog.domain.oauth2.service; + +import apptive.devlog.domain.oauth2.model.OAuth2Attributes; +import apptive.devlog.domain.user.entity.User; +import apptive.devlog.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; +import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.core.user.DefaultOAuth2User; +import org.springframework.security.oauth2.core.user.OAuth2User; +import org.springframework.stereotype.Service; + +import java.util.Collections; + +@RequiredArgsConstructor +@Service +public class CustomOAuth2UserService implements OAuth2UserService { + + private final UserRepository userRepository; + private final DefaultOAuth2UserService delegate = new DefaultOAuth2UserService(); + + @Override + public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { + OAuth2User oAuth2User = delegate.loadUser(userRequest); + OAuth2Attributes attributes = extractAttributes(userRequest, oAuth2User); + User user = saveOrUpdateUser(attributes); + + return createOAuth2User(user, attributes); + } + + private OAuth2Attributes extractAttributes(OAuth2UserRequest userRequest, OAuth2User oAuth2User) { + String registrationId = userRequest.getClientRegistration().getRegistrationId(); + String userNameAttr = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName(); + + return OAuth2Attributes.of(registrationId, userNameAttr, oAuth2User.getAttributes()); + } + + private User saveOrUpdateUser(OAuth2Attributes attributes) { + return userRepository.findByEmail(attributes.getEmail()).orElseGet(() -> userRepository.save(attributes.toEntity())); + } + + private OAuth2User createOAuth2User(User user, OAuth2Attributes attributes) { + return new DefaultOAuth2User( + Collections.singleton(new SimpleGrantedAuthority(user.getRole().name())), + attributes.getAttributes(), + attributes.getNameAttributeKey() + ); + } +} diff --git a/src/main/java/apptive/devlog/domain/oauth2/service/OAuth2PkceService.java b/src/main/java/apptive/devlog/domain/oauth2/service/OAuth2PkceService.java new file mode 100644 index 0000000..8b38f1c --- /dev/null +++ b/src/main/java/apptive/devlog/domain/oauth2/service/OAuth2PkceService.java @@ -0,0 +1,162 @@ +package apptive.devlog.domain.oauth2.service; + +import apptive.devlog.domain.oauth2.dto.OAuth2CallbackRequestDto; +import apptive.devlog.domain.oauth2.dto.OAuth2CallbackResponseDto; +import apptive.devlog.common.response.error.exception.InvalidStateFormatException; +import apptive.devlog.common.response.error.exception.OAuth2TokenRequestException; +import apptive.devlog.common.response.error.exception.OAuth2UserInfoException; +import apptive.devlog.global.security.jwt.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientResponseException; + +import java.util.Map; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class OAuth2PkceService { + + private final JwtTokenProvider jwtTokenProvider; + private final WebClient webClient; + + @Value("${spring.security.oauth2.client.registration.google.client-id}") + private String googleClientId; + @Value("${spring.security.oauth2.client.registration.google.client-secret}") + private String googleClientSecret; + @Value("${spring.security.oauth2.client.registration.google.redirect-uri}") + private String googleRedirectUri; + + @Value("${spring.security.oauth2.client.registration.kakao.client-id}") + private String kakaoClientId; + @Value("${spring.security.oauth2.client.registration.kakao.client-secret}") + private String kakaoClientSecret; + @Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}") + private String kakaoRedirectUri; + + @Value("${spring.security.oauth2.client.registration.naver.client-id}") + private String naverClientId; + @Value("${spring.security.oauth2.client.registration.naver.client-secret}") + private String naverClientSecret; + @Value("${spring.security.oauth2.client.registration.naver.redirect-uri}") + private String naverRedirectUri; + + public OAuth2CallbackResponseDto handleCallback(OAuth2CallbackRequestDto requestDto) { + String code = requestDto.getCode(); + String state = requestDto.getState(); + String provider = requestDto.getProvider(); + + String[] parts = state != null ? state.split("::") : new String[0]; + if (parts.length != 2) { + throw new InvalidStateFormatException(); + } + String deviceId = parts[0]; + String codeVerifier = parts[1]; + + Map tokenResponse = requestAccessToken(provider, code, codeVerifier); + String accessTokenFromProvider = Optional.ofNullable(tokenResponse.get("access_token")) + .map(Object::toString) + .orElseThrow(() -> new OAuth2TokenRequestException("Access token not found")); + + Map userInfo = fetchUserInfo(provider, accessTokenFromProvider); + String email = extractEmail(provider, userInfo); + + String accessToken = jwtTokenProvider.generateAccessToken(email); + String refreshToken = jwtTokenProvider.generateRefreshToken(email); + + return new OAuth2CallbackResponseDto(accessToken, refreshToken); + } + + private Map requestAccessToken(String provider, String code, String codeVerifier) { + MultiValueMap tokenRequest = new LinkedMultiValueMap<>(); + String tokenUri; + switch (provider.toLowerCase()) { + case "google" -> { + tokenRequest.add("client_id", googleClientId); + tokenRequest.add("client_secret", googleClientSecret); + tokenRequest.add("grant_type", "authorization_code"); + tokenRequest.add("code", code); + tokenRequest.add("redirect_uri", googleRedirectUri); + tokenRequest.add("code_verifier", codeVerifier); + tokenUri = "https://oauth2.googleapis.com/token"; + } + case "kakao" -> { + tokenRequest.add("grant_type", "authorization_code"); + tokenRequest.add("client_id", kakaoClientId); + tokenRequest.add("client_secret", kakaoClientSecret); + tokenRequest.add("code", code); + tokenRequest.add("redirect_uri", kakaoRedirectUri); + tokenUri = "https://kauth.kakao.com/oauth/token"; + } + case "naver" -> { + tokenRequest.add("grant_type", "authorization_code"); + tokenRequest.add("client_id", naverClientId); + tokenRequest.add("client_secret", naverClientSecret); + tokenRequest.add("code", code); + tokenRequest.add("redirect_uri", naverRedirectUri); + tokenUri = "https://nid.naver.com/oauth2.0/token"; + } + default -> throw new OAuth2TokenRequestException("Unsupported OAuth2 provider: " + provider); + } + + try { + return webClient.post() + .uri(tokenUri) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .bodyValue(tokenRequest) + .retrieve() + .bodyToMono(new ParameterizedTypeReference>() {}) + .block(); + } catch (WebClientResponseException ex) { + throw new OAuth2TokenRequestException("Failed to get token from " + provider + ": " + ex.getResponseBodyAsString()); + } + } + + private Map fetchUserInfo(String provider, String accessToken) { + String userInfoUri = switch (provider.toLowerCase()) { + case "google" -> "https://www.googleapis.com/oauth2/v3/userinfo"; + case "kakao" -> "https://kapi.kakao.com/v2/user/me"; + case "naver" -> "https://openapi.naver.com/v1/nid/me"; + default -> throw new OAuth2UserInfoException("Unsupported OAuth2 provider: " + provider); + }; + + try { + return webClient.get() + .uri(userInfoUri) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .retrieve() + .bodyToMono(new ParameterizedTypeReference>() {}) + .block(); + } catch (WebClientResponseException ex) { + throw new OAuth2UserInfoException("Failed to get user info from " + provider + ": " + ex.getResponseBodyAsString()); + } + } + + private String extractEmail(String provider, Map userInfo) { + return switch (provider.toLowerCase()) { + case "google" -> Optional.ofNullable(userInfo.get("email")) + .map(Object::toString) + .orElseThrow(() -> new OAuth2UserInfoException("Email not found in Google user info")); + case "kakao" -> { + Map kakaoAccount = (Map) userInfo.get("kakao_account"); + yield Optional.ofNullable(kakaoAccount.get("email")) + .map(Object::toString) + .orElseThrow(() -> new OAuth2UserInfoException("Email not found in Kakao user info")); + } + case "naver" -> { + Map response = (Map) userInfo.get("response"); + yield Optional.ofNullable(response.get("email")) + .map(Object::toString) + .orElseThrow(() -> new OAuth2UserInfoException("Email not found in Naver user info")); + } + default -> throw new OAuth2UserInfoException("Unsupported OAuth2 provider: " + provider); + }; + } +} diff --git a/src/main/java/apptive/devlog/domain/post/controller/PostController.java b/src/main/java/apptive/devlog/domain/post/controller/PostController.java new file mode 100644 index 0000000..b8f015d --- /dev/null +++ b/src/main/java/apptive/devlog/domain/post/controller/PostController.java @@ -0,0 +1,50 @@ +package apptive.devlog.domain.post.controller; + +import apptive.devlog.common.response.success.CommonResponse; +import apptive.devlog.documentation.domain.post.PostCreateDoc; +import apptive.devlog.documentation.domain.post.PostDeleteDoc; +import apptive.devlog.documentation.domain.post.PostReadDoc; +import apptive.devlog.documentation.domain.post.PostUpdateDoc; +import apptive.devlog.documentation.tags.PostDocumentation; +import apptive.devlog.domain.post.dto.PostDto; +import apptive.devlog.domain.post.service.PostService; +import apptive.devlog.domain.user.entity.User; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/post") +@RequiredArgsConstructor +public class PostController implements PostDocumentation { + private final PostService postService; + + @PostCreateDoc + @PostMapping + public ResponseEntity> create(@Valid @RequestBody PostDto.Create postDto, @RequestAttribute("user") User user) { + PostDto.Response responseDto = postService.create(postDto, user); + return ResponseEntity.status(HttpStatus.CREATED).body(CommonResponse.created(responseDto)); + } + + @PostReadDoc + @GetMapping("/{id}") + public ResponseEntity> read(@PathVariable String id) { + return ResponseEntity.ok().body(CommonResponse.ok(postService.read(id))); + } + + @PostUpdateDoc + @PutMapping("/{id}") + public ResponseEntity> update(@PathVariable String id, @Valid @RequestBody PostDto.Update dto, @RequestAttribute("user") User user) { + PostDto.Response response = postService.update(id, dto, user); + return ResponseEntity.ok().body(CommonResponse.ok(response)); + } + + @PostDeleteDoc + @DeleteMapping("/{id}") + public ResponseEntity> delete(@PathVariable String id, @RequestAttribute("user") User user) { + postService.delete(id, user); + return ResponseEntity.noContent().build(); + } +} diff --git a/src/main/java/apptive/devlog/domain/post/dto/PostDto.java b/src/main/java/apptive/devlog/domain/post/dto/PostDto.java new file mode 100644 index 0000000..d8dacde --- /dev/null +++ b/src/main/java/apptive/devlog/domain/post/dto/PostDto.java @@ -0,0 +1,15 @@ +package apptive.devlog.domain.post.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Builder; + +public class PostDto { + @Builder + public record Create(@NotBlank String title, @NotBlank String content) {} + + @Builder + public record Update(@NotBlank String title, @NotBlank String content) {} + + @Builder + public record Response(String id, String title, String content, String authorId) {} +} diff --git a/src/main/java/apptive/devlog/domain/post/entity/Post.java b/src/main/java/apptive/devlog/domain/post/entity/Post.java new file mode 100644 index 0000000..c6d28c6 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/post/entity/Post.java @@ -0,0 +1,40 @@ +package apptive.devlog.domain.post.entity; + +import apptive.devlog.common.base.BaseEntity; +import apptive.devlog.domain.comment.entity.Comment; +import apptive.devlog.domain.user.entity.User; +import de.huxhorn.sulky.ulid.ULID; +import jakarta.persistence.*; +import jakarta.validation.constraints.NotBlank; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder +@Table(name = "posts") +public class Post extends BaseEntity { + @NotBlank + private String title; + + @NotBlank + @Column(columnDefinition = "TEXT") + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "author_id", nullable = false) + private User author; + + @Builder.Default + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true) + private List comments = new ArrayList<>(); + + public void update(String title, String content) { + this.title = title; + this.content = content; + } +} diff --git a/src/main/java/apptive/devlog/domain/post/repository/PostRepository.java b/src/main/java/apptive/devlog/domain/post/repository/PostRepository.java new file mode 100644 index 0000000..beb77a6 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/post/repository/PostRepository.java @@ -0,0 +1,9 @@ +package apptive.devlog.domain.post.repository; + +import apptive.devlog.domain.post.entity.Post; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PostRepository extends JpaRepository { +} diff --git a/src/main/java/apptive/devlog/domain/post/service/PostService.java b/src/main/java/apptive/devlog/domain/post/service/PostService.java new file mode 100644 index 0000000..c4ef2a8 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/post/service/PostService.java @@ -0,0 +1,47 @@ +package apptive.devlog.domain.post.service; + +import apptive.devlog.domain.post.dto.PostDto; +import apptive.devlog.domain.post.entity.Post; +import apptive.devlog.domain.post.repository.PostRepository; +import apptive.devlog.domain.user.entity.User; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +public class PostService { + private final PostRepository postRepository; + + public PostDto.Response create(PostDto.Create dto, User author) { + Post post = Post.builder().title(dto.title()).content(dto.content()).author(author).build(); + postRepository.save(post); + return toResponse(post); + } + + public PostDto.Response read(String postId) { + return postRepository.findById(postId).map(this::toResponse).orElseThrow(() -> new IllegalArgumentException("Post not found")); + } + + public PostDto.Response update(String postId, PostDto.Update dto, User user) { + Post post = postRepository.findById(postId).orElseThrow(() -> new IllegalArgumentException("Post not found")); + if (!post.getAuthor().getId().equals(user.getId())) { + throw new IllegalArgumentException("작성자만 수정할 수 있습니다."); + } + post.update(dto.title(), dto.content()); + return toResponse(post); + } + + public void delete(String postId, User user) { + Post post = postRepository.findById(postId).orElseThrow(() -> new IllegalArgumentException("Post not found")); + if (!post.getAuthor().getId().equals(user.getId())) { + throw new IllegalArgumentException("작성자만 삭제할 수 있습니다."); + } + postRepository.delete(post); + } + + private PostDto.Response toResponse(Post post) { + return PostDto.Response.builder().id(post.getId()).title(post.getTitle()).content(post.getContent()).authorId(post.getAuthor().getId()).build(); + } +} diff --git a/src/main/java/apptive/devlog/domain/user/controller/UserController.java b/src/main/java/apptive/devlog/domain/user/controller/UserController.java new file mode 100644 index 0000000..fe23874 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/user/controller/UserController.java @@ -0,0 +1,29 @@ +package apptive.devlog.domain.user.controller; + +import apptive.devlog.common.response.success.CommonResponse; +import apptive.devlog.documentation.tags.UserDocumentation; +import apptive.devlog.documentation.domain.user.UserProfileDoc; +import apptive.devlog.domain.user.dto.UserProfileRequestDto; +import apptive.devlog.domain.user.dto.UserProfileResponseDto; +import apptive.devlog.domain.user.service.UserService; +import apptive.devlog.global.annotation.InjectEmail; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/user") +@RequiredArgsConstructor +public class UserController implements UserDocumentation { + + private final UserService userService; + + @UserProfileDoc + @GetMapping("/profile") + public ResponseEntity> getUserProfile(@Valid @InjectEmail UserProfileRequestDto requestDto) { + UserProfileResponseDto responseDto = userService.getUserProfile(requestDto); + return ResponseEntity.status(HttpStatus.OK).body(CommonResponse.ok(responseDto)); + } +} diff --git a/src/main/java/apptive/devlog/domain/user/dto/UserProfileRequestDto.java b/src/main/java/apptive/devlog/domain/user/dto/UserProfileRequestDto.java new file mode 100644 index 0000000..5169e76 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/user/dto/UserProfileRequestDto.java @@ -0,0 +1,15 @@ +package apptive.devlog.domain.user.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@NoArgsConstructor +@Schema(description = "유저 프로필 요청 DTO") +public class UserProfileRequestDto { + @Schema(description = "유저 이메일", example = "user@example.com", required = true) + @NotBlank + private String email; +} diff --git a/src/main/java/apptive/devlog/domain/user/dto/UserProfileResponseDto.java b/src/main/java/apptive/devlog/domain/user/dto/UserProfileResponseDto.java new file mode 100644 index 0000000..825adf2 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/user/dto/UserProfileResponseDto.java @@ -0,0 +1,31 @@ +package apptive.devlog.domain.user.dto; + +import apptive.devlog.domain.user.entity.User; +import apptive.devlog.domain.user.enums.Gender; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +import java.time.LocalDate; + +@Getter +@Schema(description = "유저 프로필 응답 DTO") +public class UserProfileResponseDto { + @Schema(description = "이메일", example = "user@example.com") + private final String email; + @Schema(description = "닉네임", example = "nickname123") + private final String nickname; + @Schema(description = "이름", example = "홍길동") + private final String name; + @Schema(description = "생일", example = "1995-05-20") + private final LocalDate birthday; + @Schema(description = "성별", example = "MALE") + private final Gender gender; + + public UserProfileResponseDto(User user) { + this.email = user.getEmail(); + this.nickname = user.getNickname(); + this.name = user.getName(); + this.birthday = user.getBirth(); + this.gender = user.getGender(); + } +} diff --git a/src/main/java/apptive/devlog/domain/user/entity/User.java b/src/main/java/apptive/devlog/domain/user/entity/User.java new file mode 100644 index 0000000..d5f69cb --- /dev/null +++ b/src/main/java/apptive/devlog/domain/user/entity/User.java @@ -0,0 +1,65 @@ +package apptive.devlog.domain.user.entity; + +import apptive.devlog.common.base.BaseEntity; +import apptive.devlog.domain.user.enums.Gender; +import apptive.devlog.domain.user.enums.Provider; +import apptive.devlog.domain.user.enums.Role; +import de.huxhorn.sulky.ulid.ULID; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDate; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder +@Table(name = "users") +public class User extends BaseEntity { + @Column(nullable = false, unique = true) + private String email; + + private String nickname; + + @Column(nullable = false) + private String name; + + private LocalDate birth; + + @Enumerated(EnumType.STRING) + private Gender gender; + + private String password; + + private String providerId; + + @ElementCollection(fetch = FetchType.LAZY) + @CollectionTable(name = "user_providers", joinColumns = @JoinColumn(name = "user_id")) + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Set providers; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Role role; + + public User(String email, String name, Provider provider, Role role) { + this.email = email; + this.name = name; + this.role = role; + this.providers = new HashSet<>(Collections.singletonList(provider)); + } + + public void addProvider(Provider provider) { + if (providers == null) { this.providers = new HashSet<>(); } + this.providers.add(provider); + } + + public static User of(String email, String name, Provider provider, Role role) { + return new User(email, name, provider, role); + } +} diff --git a/src/main/java/apptive/devlog/domain/user/enums/Gender.java b/src/main/java/apptive/devlog/domain/user/enums/Gender.java new file mode 100644 index 0000000..9191dcf --- /dev/null +++ b/src/main/java/apptive/devlog/domain/user/enums/Gender.java @@ -0,0 +1,5 @@ +package apptive.devlog.domain.user.enums; + +public enum Gender { + MALE, FEMALE, OTHER +} diff --git a/src/main/java/apptive/devlog/domain/user/enums/Provider.java b/src/main/java/apptive/devlog/domain/user/enums/Provider.java new file mode 100644 index 0000000..95307f0 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/user/enums/Provider.java @@ -0,0 +1,5 @@ +package apptive.devlog.domain.user.enums; + +public enum Provider { + LOCAL, GOOGLE, KAKAO, NAVER +} diff --git a/src/main/java/apptive/devlog/domain/user/enums/Role.java b/src/main/java/apptive/devlog/domain/user/enums/Role.java new file mode 100644 index 0000000..84fa108 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/user/enums/Role.java @@ -0,0 +1,5 @@ +package apptive.devlog.domain.user.enums; + +public enum Role { + USER +} diff --git a/src/main/java/apptive/devlog/domain/user/repository/UserRepository.java b/src/main/java/apptive/devlog/domain/user/repository/UserRepository.java new file mode 100644 index 0000000..3fe4764 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/user/repository/UserRepository.java @@ -0,0 +1,11 @@ +package apptive.devlog.domain.user.repository; + +import apptive.devlog.domain.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface UserRepository extends JpaRepository { + Optional findByEmail(String email); + boolean existsByEmail(String email); +} diff --git a/src/main/java/apptive/devlog/domain/user/service/UserService.java b/src/main/java/apptive/devlog/domain/user/service/UserService.java new file mode 100644 index 0000000..28cc592 --- /dev/null +++ b/src/main/java/apptive/devlog/domain/user/service/UserService.java @@ -0,0 +1,21 @@ +package apptive.devlog.domain.user.service; + +import apptive.devlog.domain.user.dto.UserProfileRequestDto; +import apptive.devlog.domain.user.dto.UserProfileResponseDto; +import apptive.devlog.domain.user.entity.User; +import apptive.devlog.domain.user.repository.UserRepository; +import apptive.devlog.common.response.error.exception.UserNotFoundException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class UserService { + private final UserRepository userRepository; + + public UserProfileResponseDto getUserProfile(UserProfileRequestDto requestDto) { + User user = userRepository.findByEmail(requestDto.getEmail()).orElseThrow(UserNotFoundException::new); + return new UserProfileResponseDto(user); + } +} diff --git a/src/main/java/apptive/devlog/global/annotation/InjectEmail.java b/src/main/java/apptive/devlog/global/annotation/InjectEmail.java new file mode 100644 index 0000000..7aace98 --- /dev/null +++ b/src/main/java/apptive/devlog/global/annotation/InjectEmail.java @@ -0,0 +1,9 @@ +package apptive.devlog.global.annotation; + +import java.lang.annotation.*; + +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface InjectEmail { +} diff --git a/src/main/java/apptive/devlog/global/annotation/InjectToken.java b/src/main/java/apptive/devlog/global/annotation/InjectToken.java new file mode 100644 index 0000000..1260b94 --- /dev/null +++ b/src/main/java/apptive/devlog/global/annotation/InjectToken.java @@ -0,0 +1,9 @@ +package apptive.devlog.global.annotation; + +import java.lang.annotation.*; + +@Target({ElementType.PARAMETER}) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface InjectToken { +} diff --git a/src/main/java/apptive/devlog/global/config/JpaConfig.java b/src/main/java/apptive/devlog/global/config/JpaConfig.java new file mode 100644 index 0000000..7acd138 --- /dev/null +++ b/src/main/java/apptive/devlog/global/config/JpaConfig.java @@ -0,0 +1,9 @@ +package apptive.devlog.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@Configuration +@EnableJpaAuditing +public class JpaConfig { +} diff --git a/src/main/java/apptive/devlog/global/config/MailConfig.java b/src/main/java/apptive/devlog/global/config/MailConfig.java new file mode 100644 index 0000000..22dd913 --- /dev/null +++ b/src/main/java/apptive/devlog/global/config/MailConfig.java @@ -0,0 +1,40 @@ +package apptive.devlog.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.JavaMailSenderImpl; + +import java.util.Properties; + +@Configuration +public class MailConfig { + + @Bean + public JavaMailSender javaMailSender( + @Value("${spring.mail.host}") String host, + @Value("${spring.mail.port}") int port, + @Value("${spring.mail.username}") String username, + @Value("${spring.mail.password}") String password + ) { + JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); + mailSender.setHost(host); + mailSender.setPort(port); + mailSender.setUsername(username); + mailSender.setPassword(password); + + Properties props = mailSender.getJavaMailProperties(); + props.put("mail.smtp.auth", "true"); + props.put("mail.smtp.starttls.enable", "true"); + props.put("mail.debug", "false"); + + // ↓ 추가: UTF-8 헤더·본문 인코딩 허용 + props.put("mail.mime.charset", "UTF-8"); + props.put("mail.mime.allowutf8", "true"); + props.put("mail.mime.allowutf8header", "true"); + props.put("mail.smtp.allow8bitmime", "true"); + + return mailSender; + } +} diff --git a/src/main/java/apptive/devlog/global/config/WebConfig.java b/src/main/java/apptive/devlog/global/config/WebConfig.java new file mode 100644 index 0000000..2aac3b4 --- /dev/null +++ b/src/main/java/apptive/devlog/global/config/WebConfig.java @@ -0,0 +1,38 @@ +package apptive.devlog.global.config; + +import apptive.devlog.global.resolver.InjectTokenArgumentResolver; +import apptive.devlog.global.resolver.InjectEmailArgumentResolver; +import apptive.devlog.global.security.interceptor.JwtAuthenticationInterceptor; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@RequiredArgsConstructor +@Configuration +public class WebConfig implements WebMvcConfigurer { + private final InjectEmailArgumentResolver injectEmailArgumentResolver; + private final InjectTokenArgumentResolver injectTokenArgumentResolver; + private final JwtAuthenticationInterceptor jwtAuthenticationInterceptor; + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(injectEmailArgumentResolver); + resolvers.add(injectTokenArgumentResolver); + } + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(jwtAuthenticationInterceptor).addPathPatterns("/**"); + } + + @Bean + public WebClient webClient() { + return WebClient.builder().build(); + } +} diff --git a/src/main/java/apptive/devlog/global/resolver/InjectEmailArgumentResolver.java b/src/main/java/apptive/devlog/global/resolver/InjectEmailArgumentResolver.java new file mode 100644 index 0000000..5ec5fdc --- /dev/null +++ b/src/main/java/apptive/devlog/global/resolver/InjectEmailArgumentResolver.java @@ -0,0 +1,82 @@ +package apptive.devlog.global.resolver; + +import apptive.devlog.common.response.error.exception.DtoBindingFailedException; +import apptive.devlog.global.annotation.InjectEmail; +import apptive.devlog.common.response.error.exception.TokenInjectionFailedException; +import apptive.devlog.common.response.error.exception.UnauthenticatedUserException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.MethodParameter; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +import java.io.IOException; +import java.lang.reflect.Field; + +@Slf4j +@Component +@RequiredArgsConstructor +public class InjectEmailArgumentResolver implements HandlerMethodArgumentResolver { + + private final ObjectMapper objectMapper; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(InjectEmail.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, + ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, + WebDataBinderFactory binderFactory) throws Exception { + + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + Object dto = createDtoInstance(request, parameter.getParameterType()); + String email = extractCurrentUserEmail(); + + injectFieldIfPresent(dto, "email", email); + return dto; + } + + private Object createDtoInstance(HttpServletRequest request, Class parameterType) throws Exception { + String method = request.getMethod().toUpperCase(); + try { + if ("POST".equals(method) || "PUT".equals(method)) { + return objectMapper.readValue(request.getInputStream(), parameterType); + } + return parameterType.getDeclaredConstructor().newInstance(); + } catch (IOException e) { + throw new DtoBindingFailedException("[InjectEmailArgumentResolver] DTO 변환 실패", e); + } + } + + private String extractCurrentUserEmail() { + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || !authentication.isAuthenticated()) { + throw new UnauthenticatedUserException("현재 인증된 사용자가 없습니다."); + } + return authentication.getName(); + } + + private void injectFieldIfPresent(Object target, String fieldName, String value) { + try { + Field field = target.getClass().getDeclaredField(fieldName); + if (field.getType().equals(String.class)) { + field.setAccessible(true); + field.set(target, value); + } + } catch (NoSuchFieldException ignored) { + log.debug("필드 '{}' 가 존재하지 않아 주입하지 않았습니다.", fieldName); + } catch (IllegalAccessException e) { + throw new TokenInjectionFailedException("필드 주입 실패: " + fieldName, e); + } + } +} diff --git a/src/main/java/apptive/devlog/global/resolver/InjectTokenArgumentResolver.java b/src/main/java/apptive/devlog/global/resolver/InjectTokenArgumentResolver.java new file mode 100644 index 0000000..d87ae3d --- /dev/null +++ b/src/main/java/apptive/devlog/global/resolver/InjectTokenArgumentResolver.java @@ -0,0 +1,88 @@ +package apptive.devlog.global.resolver; + +import apptive.devlog.global.annotation.InjectToken; +import apptive.devlog.common.response.error.exception.InvalidTokenException; +import apptive.devlog.common.response.error.exception.TokenInjectionFailedException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.Order; +import org.springframework.stereotype.Component; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; + +@Slf4j +@Component +@RequiredArgsConstructor +@Order(0) +public class InjectTokenArgumentResolver implements HandlerMethodArgumentResolver { + + private final ObjectMapper objectMapper; + + @Override + public boolean supportsParameter(MethodParameter parameter) { + return parameter.hasParameterAnnotation(InjectToken.class); + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest(); + + byte[] requestBodyBytes = getRequestBodyAsBytes(request); + Object dto; + try { + dto = objectMapper.readValue(requestBodyBytes, parameter.getParameterType()); + } catch (IOException e) { + log.error("DTO 역직렬화 실패: {}", e.getMessage(), e); + throw new TokenInjectionFailedException("DTO 역직렬화 실패", e); + } + + String accessToken = extractAccessToken(request); + injectFieldIfExists(dto, "accessToken", accessToken); + + return dto; + } + + private String extractAccessToken(HttpServletRequest request) { + String header = request.getHeader("Authorization"); + if (header != null && header.startsWith("Bearer ")) { + return header.substring(7); + } + throw new InvalidTokenException(); + } + + private void injectFieldIfExists(Object target, String fieldName, String value) { + try { + Field field = target.getClass().getDeclaredField(fieldName); + if (field.getType().equals(String.class)) { + field.setAccessible(true); + field.set(target, value); + log.debug("필드 '{}' 에 accessToken 주입 성공", fieldName); + } + } catch (NoSuchFieldException e) { + log.warn("DTO에 '{}' 필드가 존재하지 않음: {}", fieldName, target.getClass().getSimpleName()); + } catch (IllegalAccessException e) { + throw new TokenInjectionFailedException(fieldName, e); + } + } + + private byte[] getRequestBodyAsBytes(HttpServletRequest request) throws IOException { + InputStream inputStream = request.getInputStream(); + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + byte[] data = new byte[4096]; + int nRead; + while ((nRead = inputStream.read(data, 0, data.length)) != -1) { + buffer.write(data, 0, nRead); + } + return buffer.toByteArray(); + } +} diff --git a/src/main/java/apptive/devlog/global/security/config/SecurityConfig.java b/src/main/java/apptive/devlog/global/security/config/SecurityConfig.java new file mode 100644 index 0000000..00d0361 --- /dev/null +++ b/src/main/java/apptive/devlog/global/security/config/SecurityConfig.java @@ -0,0 +1,77 @@ +package apptive.devlog.global.security.config; + +import apptive.devlog.domain.oauth2.handler.OAuth2SuccessHandlerForUnity; +import apptive.devlog.global.security.filter.JwtAuthenticationFilter; +import apptive.devlog.domain.oauth2.handler.OAuth2FailureHandler; +import apptive.devlog.domain.oauth2.handler.OAuth2SuccessHandler; +import apptive.devlog.domain.oauth2.service.CustomOAuth2UserService; +import apptive.devlog.global.security.service.CustomUserDetailsService; +import apptive.devlog.global.security.jwt.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.ProviderManager; +import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + private final JwtTokenProvider jwtTokenProvider; + private final CustomUserDetailsService customUserDetailsService; + private final CustomOAuth2UserService customOAuth2UserService; + private final OAuth2SuccessHandler oAuth2SuccessHandler; + private final OAuth2FailureHandler oAuth2FailureHandler; + private final OAuth2SuccessHandlerForUnity oAuth2SuccessHandlerForUnity; + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + public AuthenticationManager authenticationManager(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) { + DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); + authenticationProvider.setUserDetailsService(userDetailsService); + authenticationProvider.setPasswordEncoder(passwordEncoder); + return new ProviderManager(authenticationProvider); + } + + @Bean + public JwtAuthenticationFilter jwtAuthenticationFilter() { + return new JwtAuthenticationFilter(jwtTokenProvider, customUserDetailsService); + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http + .csrf(AbstractHttpConfigurer::disable) + .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/", "/auth/signup", "/auth/login", "/auth/refresh", "/oauth2/**", "/auth/email/*").permitAll() + .requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**", "/v3/api-docs", "/v3/api-docs.yaml", "/swagger-resources/**", "/webjars/**", "/api-docs/**").permitAll() + .requestMatchers("/auth/logout", "/user/profile").hasRole("USER") + .requestMatchers("/post/**", "/comment/**").hasRole("USER") + .anyRequest().authenticated() + ) + .oauth2Login(oauth2 -> oauth2 + .authorizationEndpoint(endpoint -> endpoint.baseUri("/oauth2/authorization")) + .redirectionEndpoint(endpoint -> endpoint.baseUri("/oauth2/callback/*")) + .userInfoEndpoint(endpoint -> endpoint.userService(customOAuth2UserService)) + .successHandler(oAuth2SuccessHandlerForUnity) + .failureHandler(oAuth2FailureHandler) + ) + .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) + .build(); + } +} diff --git a/src/main/java/apptive/devlog/global/security/filter/JwtAuthenticationFilter.java b/src/main/java/apptive/devlog/global/security/filter/JwtAuthenticationFilter.java new file mode 100644 index 0000000..6181f4b --- /dev/null +++ b/src/main/java/apptive/devlog/global/security/filter/JwtAuthenticationFilter.java @@ -0,0 +1,43 @@ +package apptive.devlog.global.security.filter; + +import apptive.devlog.global.security.service.CustomUserDetailsService; +import apptive.devlog.global.security.jwt.JwtTokenProvider; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Slf4j +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + private final JwtTokenProvider jwtTokenProvider; + private final CustomUserDetailsService customUserDetailsService; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + String token = jwtTokenProvider.resolveToken(request); + if (token != null && jwtTokenProvider.validateAccessToken(token)) { + authenticate(token); + } else { + log.debug("No valid JWT token found for request to {}", request.getRequestURI()); + } + filterChain.doFilter(request, response); + } + + private void authenticate(String token) { + String email = jwtTokenProvider.getEmailFromToken(token); + UserDetails userDetails = customUserDetailsService.loadUserByUsername(email); + UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + log.debug("Authenticated user: {}", email); + } +} diff --git a/src/main/java/apptive/devlog/global/security/interceptor/JwtAuthenticationInterceptor.java b/src/main/java/apptive/devlog/global/security/interceptor/JwtAuthenticationInterceptor.java new file mode 100644 index 0000000..0888778 --- /dev/null +++ b/src/main/java/apptive/devlog/global/security/interceptor/JwtAuthenticationInterceptor.java @@ -0,0 +1,30 @@ +package apptive.devlog.global.security.interceptor; + +import apptive.devlog.domain.user.entity.User; +import apptive.devlog.domain.user.repository.UserRepository; +import apptive.devlog.global.security.jwt.JwtTokenProvider; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Slf4j +@Component +@RequiredArgsConstructor +public class JwtAuthenticationInterceptor implements HandlerInterceptor { + private final JwtTokenProvider jwtTokenProvider; + private final UserRepository userRepository; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { + String token = jwtTokenProvider.resolveToken(request); + if (token != null && jwtTokenProvider.validateAccessToken(token)) { + String email = jwtTokenProvider.getEmailFromToken(token); + User user = userRepository.findByEmail(email).orElseThrow(() -> new IllegalArgumentException("User not found")); + request.setAttribute("user", user); + } + return true; + } +} diff --git a/src/main/java/apptive/devlog/global/security/jwt/JwtTokenProvider.java b/src/main/java/apptive/devlog/global/security/jwt/JwtTokenProvider.java new file mode 100644 index 0000000..bbcc0d8 --- /dev/null +++ b/src/main/java/apptive/devlog/global/security/jwt/JwtTokenProvider.java @@ -0,0 +1,93 @@ +package apptive.devlog.global.security.jwt; + +import apptive.devlog.infrastructure.redis.repository.RedisRepository; +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Value; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.time.Instant; +import java.util.Date; + +@Slf4j +@Component +public class JwtTokenProvider { + private final Key key; + private final long accessTokenExpiration; + private final long refreshTokenExpiration; + private final RedisRepository redisRepository; + private final JwtParser jwtParser; + + public JwtTokenProvider( + @Value("${jwt.secret}") String secretKey, + @Value("${jwt.access-token-expiration}") long accessTokenExpiration, + @Value("${jwt.refresh-token-expiration}") long refreshTokenExpiration, + RedisRepository redisRepository) { + this.key = Keys.hmacShaKeyFor(secretKey.getBytes()); + this.accessTokenExpiration = accessTokenExpiration; + this.refreshTokenExpiration = refreshTokenExpiration; + this.redisRepository = redisRepository; + this.jwtParser = Jwts.parserBuilder().setSigningKey(key).build(); + } + + public String generateToken(String email, long expiration) { + Instant now = Instant.now(); + return Jwts.builder() + .setSubject(email) + .setIssuedAt(Date.from(now)) + .setExpiration(Date.from(now.plusSeconds(expiration))) + .signWith(key, SignatureAlgorithm.HS256) + .compact(); + } + + public String generateAccessToken(String email) { + String accessToken = generateToken(email, accessTokenExpiration); + redisRepository.saveAccessToken(accessToken, email); + return accessToken; + } + + public String generateRefreshToken(String email) { + String refreshToken = generateToken(email, refreshTokenExpiration); + redisRepository.saveRefreshToken(refreshToken, email); + return refreshToken; + } + + public boolean validateToken(String token) { + try { + jwtParser.parseClaimsJws(token); + return true; + } catch (ExpiredJwtException e) { + log.warn("Token expired: {}", e.getMessage()); + } catch (UnsupportedJwtException e) { + log.warn("Unsupported token: {}", e.getMessage()); + } catch (MalformedJwtException e) { + log.warn("Malformed token: {}", e.getMessage()); + } catch (SecurityException | IllegalArgumentException e) { + log.warn("Invalid token: {}", e.getMessage()); + } + return false; + } + + public boolean validateAccessToken(String token) { + return validateToken(token) && redisRepository.hasAccessToken(token); + } + + public boolean validateRefreshToken(String token) { + return validateToken(token) && redisRepository.hasRefreshToken(token); + } + + public String getEmailFromToken(String token) { + return jwtParser.parseClaimsJws(token).getBody().getSubject(); + } + + public String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (bearerToken != null && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; + } +} diff --git a/src/main/java/apptive/devlog/global/security/model/CustomUserDetails.java b/src/main/java/apptive/devlog/global/security/model/CustomUserDetails.java new file mode 100644 index 0000000..3ca3f59 --- /dev/null +++ b/src/main/java/apptive/devlog/global/security/model/CustomUserDetails.java @@ -0,0 +1,53 @@ +package apptive.devlog.global.security.model; + +import apptive.devlog.domain.user.entity.User; +import lombok.Getter; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.UserDetails; + +import java.util.Collection; +import java.util.Collections; + +@Getter +public class CustomUserDetails implements UserDetails { + private final User user; + + public CustomUserDetails(User user) { + this.user = user; + } + + @Override + public Collection getAuthorities() { + return Collections.singleton(() -> "ROLE_" + user.getRole().name()); + } + + @Override + public String getPassword() { + return user.getPassword(); + } + + @Override + public String getUsername() { + return user.getEmail(); + } + + @Override + public boolean isAccountNonExpired() { + return true; + } + + @Override + public boolean isAccountNonLocked() { + return true; + } + + @Override + public boolean isCredentialsNonExpired() { + return true; + } + + @Override + public boolean isEnabled() { + return true; + } +} diff --git a/src/main/java/apptive/devlog/global/security/service/CustomUserDetailsService.java b/src/main/java/apptive/devlog/global/security/service/CustomUserDetailsService.java new file mode 100644 index 0000000..30ae2db --- /dev/null +++ b/src/main/java/apptive/devlog/global/security/service/CustomUserDetailsService.java @@ -0,0 +1,22 @@ +package apptive.devlog.global.security.service; + +import apptive.devlog.domain.user.entity.User; +import apptive.devlog.domain.user.repository.UserRepository; +import apptive.devlog.global.security.model.CustomUserDetails; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CustomUserDetailsService implements UserDetailsService { + private final UserRepository userRepository; + + @Override + public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { + User user = userRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException("User not found: " + email)); + return new CustomUserDetails(user); + } +} diff --git a/src/main/java/apptive/devlog/infrastructure/redis/config/RedisJedisMutualTlsConfig.java b/src/main/java/apptive/devlog/infrastructure/redis/config/RedisJedisMutualTlsConfig.java new file mode 100644 index 0000000..ef8f9aa --- /dev/null +++ b/src/main/java/apptive/devlog/infrastructure/redis/config/RedisJedisMutualTlsConfig.java @@ -0,0 +1,94 @@ +package apptive.devlog.infrastructure.redis.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.data.redis.connection.RedisPassword; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +import javax.net.ssl.*; +import java.io.FileInputStream; +import java.io.InputStream; +import java.security.KeyStore; + +@Configuration +@Profile("prod") +public class RedisJedisMutualTlsConfig { + + @Value("${spring.data.redis.host}") + private String redisHost; + + @Value("${spring.data.redis.port}") + private int redisPort; + + @Value("${spring.data.redis.password}") + private String redisPassword; + + @Value("${spring.redis.ssl.keystore-path}") + private String keyStorePath; + @Value("${spring.redis.ssl.keystore-password}") + private String keyStorePassword; + @Value("${spring.redis.ssl.keystore-type}") + private String keyStoreType; + + @Value("${spring.redis.ssl.truststore-path}") + private String trustStorePath; + @Value("${spring.redis.ssl.truststore-password}") + private String trustStorePassword; + @Value("${spring.redis.ssl.truststore-type}") + private String trustStoreType; + + @Bean + public JedisConnectionFactory jedisConnectionFactory() throws Exception { + + // --- SSLContext 생성 (keystore + truststore 로 mutual TLS) --- + char[] keyPass = keyStorePassword.toCharArray(); + KeyStore ks = KeyStore.getInstance(keyStoreType); + try (InputStream ksStream = new FileInputStream(keyStorePath)) { + ks.load(ksStream, keyPass); + } + KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); + kmf.init(ks, keyPass); + + char[] trustPass = trustStorePassword.toCharArray(); + KeyStore ts = KeyStore.getInstance(trustStoreType); + try (InputStream tsStream = new FileInputStream(trustStorePath)) { + ts.load(tsStream, trustPass); + } + TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); + tmf.init(ts); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + // --- JedisClientConfiguration 설정 --- + JedisClientConfiguration jedisClientConfig = JedisClientConfiguration.builder() + .useSsl() + .sslSocketFactory(sslContext.getSocketFactory()) + .build(); + + // --- RedisStandaloneConfiguration 설정 --- + RedisStandaloneConfiguration serverConfig = new RedisStandaloneConfiguration(redisHost, redisPort); + serverConfig.setPassword(RedisPassword.of(redisPassword)); + + return new JedisConnectionFactory(serverConfig, jedisClientConfig); + } + + @Bean + public RedisTemplate redisTemplate(JedisConnectionFactory cf) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(cf); + StringRedisSerializer s = new StringRedisSerializer(); + template.setKeySerializer(s); + template.setValueSerializer(s); + template.setHashKeySerializer(s); + template.setHashValueSerializer(s); + template.afterPropertiesSet(); + return template; + } +} diff --git a/src/main/java/apptive/devlog/infrastructure/redis/config/RedisLocalConfig.java b/src/main/java/apptive/devlog/infrastructure/redis/config/RedisLocalConfig.java new file mode 100644 index 0000000..e3f551d --- /dev/null +++ b/src/main/java/apptive/devlog/infrastructure/redis/config/RedisLocalConfig.java @@ -0,0 +1,40 @@ +package apptive.devlog.infrastructure.redis.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; +import org.springframework.data.redis.connection.RedisStandaloneConfiguration; +import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration +@Profile("local") +public class RedisLocalConfig { + + @Value("${spring.data.redis.host}") + private String redisHost; + + @Value("${spring.data.redis.port}") + private int redisPort; + + @Bean + public JedisConnectionFactory jedisConnectionFactory() { + RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(redisHost, redisPort); + return new JedisConnectionFactory(config); + } + + @Bean + public RedisTemplate redisTemplate(JedisConnectionFactory cf) { + RedisTemplate template = new RedisTemplate<>(); + template.setConnectionFactory(cf); + StringRedisSerializer s = new StringRedisSerializer(); + template.setKeySerializer(s); + template.setValueSerializer(s); + template.setHashKeySerializer(s); + template.setHashValueSerializer(s); + template.afterPropertiesSet(); + return template; + } +} diff --git a/src/main/java/apptive/devlog/infrastructure/redis/repository/RedisRepository.java b/src/main/java/apptive/devlog/infrastructure/redis/repository/RedisRepository.java new file mode 100644 index 0000000..dc3e530 --- /dev/null +++ b/src/main/java/apptive/devlog/infrastructure/redis/repository/RedisRepository.java @@ -0,0 +1,50 @@ +package apptive.devlog.infrastructure.redis.repository; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Repository; + +import java.time.Duration; +import java.util.concurrent.TimeUnit; + +@Repository +public class RedisRepository { + private static final String ACCESS_TOKEN_PREFIX = "AT:"; + private static final String REFRESH_TOKEN_PREFIX = "RT:"; + private final long accessTokenExpiration; + private final long refreshTokenExpiration; + private final RedisTemplate redisTemplate; + + public RedisRepository( + @Value("${jwt.access-token-expiration}") long accessTokenExpiration, + @Value("${jwt.refresh-token-expiration}") long refreshTokenExpiration, + RedisTemplate redisTemplate) { + this.accessTokenExpiration = accessTokenExpiration; + this.refreshTokenExpiration = refreshTokenExpiration; + this.redisTemplate = redisTemplate; + } + + public void saveAccessToken(String accessToken, String email) { + redisTemplate.opsForValue().set(ACCESS_TOKEN_PREFIX + accessToken, email, accessTokenExpiration, TimeUnit.MILLISECONDS); + } + + public void saveRefreshToken(String refreshToken, String email) { + redisTemplate.opsForValue().set(REFRESH_TOKEN_PREFIX + refreshToken, email, refreshTokenExpiration, TimeUnit.MILLISECONDS); + } + + public boolean hasAccessToken(String accessToken) { + return Boolean.TRUE.equals(redisTemplate.hasKey(ACCESS_TOKEN_PREFIX + accessToken)); + } + + public boolean hasRefreshToken(String refreshToken) { + return Boolean.TRUE.equals(redisTemplate.hasKey(REFRESH_TOKEN_PREFIX + refreshToken)); + } + + public void deleteAccessToken(String accessToken) { + redisTemplate.delete(ACCESS_TOKEN_PREFIX + accessToken); + } + + public void deleteRefreshToken(String refreshToken) { + redisTemplate.delete(REFRESH_TOKEN_PREFIX + refreshToken); + } +} diff --git a/src/main/java/apptive/devlog/support/testconfig/redis/RedisInitializer.java b/src/main/java/apptive/devlog/support/testconfig/redis/RedisInitializer.java new file mode 100644 index 0000000..3657ba6 --- /dev/null +++ b/src/main/java/apptive/devlog/support/testconfig/redis/RedisInitializer.java @@ -0,0 +1,31 @@ +package apptive.devlog.support.testconfig.redis; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.ApplicationArguments; +import org.springframework.boot.ApplicationRunner; +import org.springframework.context.annotation.Profile; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.Set; + +@Slf4j +@RequiredArgsConstructor +@Component +@Profile("local") +public class RedisInitializer implements ApplicationRunner { + private final RedisTemplate redisTemplate; + + @Override + public void run(ApplicationArguments args) { + Set keys = redisTemplate.keys("*"); + + if (!keys.isEmpty()) { + redisTemplate.delete(keys); + log.info("Redis 초기화 완료 - {}개의 키 삭제됨", keys.size()); + } else { + log.info("Redis 초기화 생략 - 삭제할 키 없음"); + } + } +} diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties new file mode 100644 index 0000000..bc3dbf9 --- /dev/null +++ b/src/main/resources/application-dev.properties @@ -0,0 +1,128 @@ +# =============================== +# DB CONFIG +# =============================== +spring.datasource.url=jdbc:mysql://devlog-mysql:3306/devlog?allowPublicKeyRetrieval=true&useSSL=true&verifyServerCertificate=true&requireSSL=true&serverTimezone=Asia/Seoul&characterEncoding=UTF-8&sslMode=VERIFY_CA&sslCa=/certs/ca.pem&sslCert=/certs/server-cert.pem&sslKey=/certs/server-key.pem +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.username=root + +spring.datasource.hikari.data-source-properties.useSSL=true +spring.datasource.hikari.data-source-properties.requireSSL=true +spring.datasource.hikari.data-source-properties.trustCertificateKeyStoreUrl=file:/certs/truststore.p12 +spring.datasource.hikari.data-source-properties.trustCertificateKeyStoreType=PKCS12 +spring.datasource.hikari.data-source-properties.keyStore=file:/certs/keystore.p12 +spring.datasource.hikari.data-source-properties.keyStoreType=PKCS12 +spring.datasource.hikari.data-source-properties.keyAlias=devlog-app +spring.datasource.hikari.data-source-properties.clientCertificateKeyStoreUrl=file:/certs/keystore.p12 +spring.datasource.hikari.data-source-properties.clientCertificateKeyStoreType=PKCS12 + +spring.datasource.hikari.connection-timeout=30000 +spring.datasource.hikari.maximum-pool-size=10 + +# =============================== +# REDIS CONFIG +# =============================== +spring.data.redis.host=devlog-redis +spring.data.redis.port=6380 +spring.data.redis.timeout=5s +spring.data.redis.ssl.enabled=true +spring.data.redis.url=rediss://devlog-redis:6380 +spring.data.redis.client-type=jedis +spring.redis.ssl.keystore-path=/certs/keystore.p12 +spring.redis.ssl.truststore-path=/certs/truststore.p12 +spring.redis.ssl.keystore-type=PKCS12 +spring.redis.ssl.truststore-type=PKCS12s + +# =============================== +# SERVER CONFIG +# =============================== +server.address=0.0.0.0 +server.port=443 +server.ssl.enabled=true +server.ssl.key-alias=devlog-app +server.ssl.key-store=/certs/keystore.p12 +server.ssl.key-store-type=PKCS12 +server.ssl.client-auth=need +server.ssl.trust-store=/certs/truststore.p12 +server.ssl.trust-store-type=PKCS12 + +# =============================== +# JPA CONFIG +# =============================== +logging.level.org.springframework.boot.web.embedded.tomcat.SslConnectorCustomizer=DEBUG +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect +spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.properties.hibernate.show_sql=false +spring.jpa.open-in-view=false +spring.jpa.show-sql=false + +# =============================== +# LOGGING +# =============================== +logging.level.org.springframework.boot.context.config=DEBUG +logging.level.org.springframework.security=DEBUG +logging.level.com.apptive.devlog=DEBUG +logging.level.io.netty=DEBUG +logging.level.root=DEBUG +logging.level.org.springframework.web=DEBUG +logging.level.org.springframework.web.client.RestTemplate=DEBUG +logging.level.org.springframework.http=DEBUG +logging.level.org.springframework.boot.autoconfigure.security=DEBUG + + +# =============================== +# SWAGGER +# =============================== +springdoc.api-docs.enabled=true +springdoc.swagger-ui.enabled=true +springdoc.api-docs.path=/api-docs +springdoc.swagger-ui.path=/swagger-ui +springdoc.default-consumes-media-type=application/json +springdoc.default-produces-media-type=application/json +springdoc.swagger-ui.disable-swagger-default-url=true + +# =============================== +# JWT CONFIG +# =============================== +jwt.access-token-expiration=3600000 +jwt.refresh-token-expiration=1209600000 + +# =============================== +# OAUTH2 CONFIG - GOOGLE +# =============================== +spring.security.oauth2.client.registration.google.redirect-uri=https://wudc.link/oauth2/callback/{registrationId} +spring.security.oauth2.client.registration.google.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.google.scope=profile,email +spring.security.oauth2.client.registration.google.client-name=Google +spring.security.oauth2.client.registration.google.client-authentication-method=client_secret_post +spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/v2/auth +spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token +spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo +spring.security.oauth2.client.provider.google.user-name-attribute=sub + +# =============================== +# OAUTH2 CONFIG - NAVER +# =============================== +spring.security.oauth2.client.registration.naver.redirect-uri=https://wudc.link/oauth2/callback/{registrationId} +spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.naver.scope=name,email +spring.security.oauth2.client.registration.naver.client-name=Naver +spring.security.oauth2.client.registration.naver.client-authentication-method=client_secret_post +spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize +spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token +spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me +spring.security.oauth2.client.provider.naver.user-name-attribute=response + +# =============================== +# OAUTH2 CONFIG - KAKAO +# =============================== +spring.security.oauth2.client.registration.kakao.redirect-uri=https://wudc.link/oauth2/callback/{registrationId} +spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.kakao.scope=profile_nickname,account_email +spring.security.oauth2.client.registration.kakao.client-name=Kakao +spring.security.oauth2.client.registration.kakao.client-authentication-method=client_secret_post +spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize +spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token +spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me +spring.security.oauth2.client.provider.kakao.user-name-attribute=id diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties new file mode 100644 index 0000000..d4ddeba --- /dev/null +++ b/src/main/resources/application-local.properties @@ -0,0 +1,89 @@ +# =============================== +# DB CONFIG +# =============================== +spring.datasource.url=jdbc:mysql://localhost:3306/devlog?useSSL=false&serverTimezone=Asia/Seoul&characterEncoding=UTF-8&allowPublicKeyRetrieval=true +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.username=root + +# =============================== +# JPA CONFIG +# =============================== +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect +spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect +spring.jpa.hibernate.ddl-auto=create +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.properties.hibernate.show_sql=true +spring.jpa.open-in-view=false +spring.jpa.show-sql=true + +# =============================== +# SERVER CONFIG +# =============================== +server.port=8080 +server.ssl.enabled=false + +# =============================== +# REDIS CONFIG +# =============================== +spring.data.redis.host=localhost +spring.data.redis.port=6379 + +# =============================== +# LOGGING +# =============================== +logging.level.org.springframework.security=DEBUG +logging.level.com.apptive.devlog=DEBUG + +# =============================== +# SWAGGER +# =============================== +springdoc.api-docs.path=/api-docs +springdoc.swagger-ui.path=/swagger-ui +springdoc.default-consumes-media-type=application/json +springdoc.default-produces-media-type=application/json +springdoc.swagger-ui.disable-swagger-default-url=true + +# =============================== +# JWT CONFIG +# =============================== +jwt.access-token-expiration=3600000 +jwt.refresh-token-expiration=1209600000 + +# =============================== +# OAUTH2 CONFIG - GOOGLE +# =============================== +spring.security.oauth2.client.registration.google.redirect-uri={baseUrl}/oauth2/callback/{registrationId} +spring.security.oauth2.client.registration.google.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.google.scope=profile,email +spring.security.oauth2.client.registration.google.client-name=Google +spring.security.oauth2.client.registration.google.client-authentication-method=client_secret_post +spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/v2/auth +spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token +spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo +spring.security.oauth2.client.provider.google.user-name-attribute=sub + +# =============================== +# OAUTH2 CONFIG - NAVER +# =============================== +spring.security.oauth2.client.registration.naver.redirect-uri={baseUrl}/oauth2/callback/{registrationId} +spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.naver.scope=name,email +spring.security.oauth2.client.registration.naver.client-name=Naver +spring.security.oauth2.client.registration.naver.client-authentication-method=client_secret_post +spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize +spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token +spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me +spring.security.oauth2.client.provider.naver.user-name-attribute=response + +# =============================== +# OAUTH2 CONFIG - KAKAO +# =============================== +spring.security.oauth2.client.registration.kakao.redirect-uri={baseUrl}/oauth2/callback/{registrationId} +spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.kakao.scope=profile_nickname,account_email +spring.security.oauth2.client.registration.kakao.client-name=Kakao +spring.security.oauth2.client.registration.kakao.client-authentication-method=client_secret_post +spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize +spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token +spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me +spring.security.oauth2.client.provider.kakao.user-name-attribute=id diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties new file mode 100644 index 0000000..bc3dbf9 --- /dev/null +++ b/src/main/resources/application-prod.properties @@ -0,0 +1,128 @@ +# =============================== +# DB CONFIG +# =============================== +spring.datasource.url=jdbc:mysql://devlog-mysql:3306/devlog?allowPublicKeyRetrieval=true&useSSL=true&verifyServerCertificate=true&requireSSL=true&serverTimezone=Asia/Seoul&characterEncoding=UTF-8&sslMode=VERIFY_CA&sslCa=/certs/ca.pem&sslCert=/certs/server-cert.pem&sslKey=/certs/server-key.pem +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.username=root + +spring.datasource.hikari.data-source-properties.useSSL=true +spring.datasource.hikari.data-source-properties.requireSSL=true +spring.datasource.hikari.data-source-properties.trustCertificateKeyStoreUrl=file:/certs/truststore.p12 +spring.datasource.hikari.data-source-properties.trustCertificateKeyStoreType=PKCS12 +spring.datasource.hikari.data-source-properties.keyStore=file:/certs/keystore.p12 +spring.datasource.hikari.data-source-properties.keyStoreType=PKCS12 +spring.datasource.hikari.data-source-properties.keyAlias=devlog-app +spring.datasource.hikari.data-source-properties.clientCertificateKeyStoreUrl=file:/certs/keystore.p12 +spring.datasource.hikari.data-source-properties.clientCertificateKeyStoreType=PKCS12 + +spring.datasource.hikari.connection-timeout=30000 +spring.datasource.hikari.maximum-pool-size=10 + +# =============================== +# REDIS CONFIG +# =============================== +spring.data.redis.host=devlog-redis +spring.data.redis.port=6380 +spring.data.redis.timeout=5s +spring.data.redis.ssl.enabled=true +spring.data.redis.url=rediss://devlog-redis:6380 +spring.data.redis.client-type=jedis +spring.redis.ssl.keystore-path=/certs/keystore.p12 +spring.redis.ssl.truststore-path=/certs/truststore.p12 +spring.redis.ssl.keystore-type=PKCS12 +spring.redis.ssl.truststore-type=PKCS12s + +# =============================== +# SERVER CONFIG +# =============================== +server.address=0.0.0.0 +server.port=443 +server.ssl.enabled=true +server.ssl.key-alias=devlog-app +server.ssl.key-store=/certs/keystore.p12 +server.ssl.key-store-type=PKCS12 +server.ssl.client-auth=need +server.ssl.trust-store=/certs/truststore.p12 +server.ssl.trust-store-type=PKCS12 + +# =============================== +# JPA CONFIG +# =============================== +logging.level.org.springframework.boot.web.embedded.tomcat.SslConnectorCustomizer=DEBUG +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect +spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.properties.hibernate.show_sql=false +spring.jpa.open-in-view=false +spring.jpa.show-sql=false + +# =============================== +# LOGGING +# =============================== +logging.level.org.springframework.boot.context.config=DEBUG +logging.level.org.springframework.security=DEBUG +logging.level.com.apptive.devlog=DEBUG +logging.level.io.netty=DEBUG +logging.level.root=DEBUG +logging.level.org.springframework.web=DEBUG +logging.level.org.springframework.web.client.RestTemplate=DEBUG +logging.level.org.springframework.http=DEBUG +logging.level.org.springframework.boot.autoconfigure.security=DEBUG + + +# =============================== +# SWAGGER +# =============================== +springdoc.api-docs.enabled=true +springdoc.swagger-ui.enabled=true +springdoc.api-docs.path=/api-docs +springdoc.swagger-ui.path=/swagger-ui +springdoc.default-consumes-media-type=application/json +springdoc.default-produces-media-type=application/json +springdoc.swagger-ui.disable-swagger-default-url=true + +# =============================== +# JWT CONFIG +# =============================== +jwt.access-token-expiration=3600000 +jwt.refresh-token-expiration=1209600000 + +# =============================== +# OAUTH2 CONFIG - GOOGLE +# =============================== +spring.security.oauth2.client.registration.google.redirect-uri=https://wudc.link/oauth2/callback/{registrationId} +spring.security.oauth2.client.registration.google.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.google.scope=profile,email +spring.security.oauth2.client.registration.google.client-name=Google +spring.security.oauth2.client.registration.google.client-authentication-method=client_secret_post +spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/v2/auth +spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token +spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo +spring.security.oauth2.client.provider.google.user-name-attribute=sub + +# =============================== +# OAUTH2 CONFIG - NAVER +# =============================== +spring.security.oauth2.client.registration.naver.redirect-uri=https://wudc.link/oauth2/callback/{registrationId} +spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.naver.scope=name,email +spring.security.oauth2.client.registration.naver.client-name=Naver +spring.security.oauth2.client.registration.naver.client-authentication-method=client_secret_post +spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize +spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token +spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me +spring.security.oauth2.client.provider.naver.user-name-attribute=response + +# =============================== +# OAUTH2 CONFIG - KAKAO +# =============================== +spring.security.oauth2.client.registration.kakao.redirect-uri=https://wudc.link/oauth2/callback/{registrationId} +spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.kakao.scope=profile_nickname,account_email +spring.security.oauth2.client.registration.kakao.client-name=Kakao +spring.security.oauth2.client.registration.kakao.client-authentication-method=client_secret_post +spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize +spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token +spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me +spring.security.oauth2.client.provider.kakao.user-name-attribute=id diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 95619f3..0a8cda1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,17 @@ +# =============================== +# APPLICATION CONFIG +# =============================== spring.application.name=devlog + +# =============================== +# PROFILE CONFIG +# =============================== +spring.profiles.active=local +spring.profiles.include=secret + +spring.mail.host=smtp.gmail.com +spring.mail.port=587 +spring.mail.username=qqwwee771441@gmail.com +spring.mail.properties.mail.smtp.auth=true +spring.mail.properties.mail.smtp.starttls.enable=true +spring.mail.default-encoding=UTF-8 diff --git a/src/test/java/apptive/devlog/domain/auth/controller/AuthControllerIntegrationTest.java b/src/test/java/apptive/devlog/domain/auth/controller/AuthControllerIntegrationTest.java new file mode 100644 index 0000000..80bc5fd --- /dev/null +++ b/src/test/java/apptive/devlog/domain/auth/controller/AuthControllerIntegrationTest.java @@ -0,0 +1,97 @@ +package apptive.devlog.domain.auth.controller; + +import apptive.devlog.domain.auth.dto.*; +import apptive.devlog.domain.auth.service.AuthService; +import apptive.devlog.domain.user.entity.User; +import apptive.devlog.domain.user.enums.Gender; +import apptive.devlog.domain.user.enums.Provider; +import apptive.devlog.domain.user.enums.Role; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import java.time.LocalDate; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +public class AuthControllerIntegrationTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockitoBean + private AuthService authService; + + @Nested + @DisplayName("회원가입 테스트") + class SignupTests { + @Test + @DisplayName("회원가입 성공") + void signupSuccess() throws Exception { + UserSignupRequestDto requestDto = new UserSignupRequestDto("test@example.com", "Password123!", "최광진", "qqwwee41", LocalDate.of(2000, 9, 26), Gender.MALE); + UserSignupResponseDto responseDto = new UserSignupResponseDto(new User("test@example.com", "최광진", Provider.LOCAL, Role.USER)); + + when(authService.signup(any(UserSignupRequestDto.class))).thenReturn(responseDto); + + mockMvc.perform(post("/auth/signup").with(csrf()).contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(requestDto)).characterEncoding("UTF-8")) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.status").value(201)) + .andExpect(jsonPath("$.data.email").value("test@example.com")); + } + } + + @Nested + @DisplayName("로그인 테스트") + class LoginTests { + @Test + void loginSuccess() throws Exception { + UserLoginRequestDto requestDto = new UserLoginRequestDto("test@example.com", "Password123!"); + UserLoginResponseDto responseDto = new UserLoginResponseDto("access-token", "refresh-token"); + + when(authService.login(any(UserLoginRequestDto.class))).thenReturn(responseDto); + + mockMvc.perform(post("/auth/login").with(csrf()).contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(requestDto)).characterEncoding("UTF-8")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status").value(200)) + .andExpect(jsonPath("$.data.accessToken").value("access-token")) + .andExpect(jsonPath("$.data.refreshToken").value("refresh-token")); + } + } + + @Nested + @DisplayName("리프레시 테스트") + class RefreshTests { + @Test + void refreshSuccess() throws Exception { + UserRefreshRequestDto requestDto = new UserRefreshRequestDto("access-token", "refresh-token"); + UserRefreshResponseDto responseDto = new UserRefreshResponseDto("access-token", "refresh-token"); + + } + } + + @Nested + @DisplayName("로그아웃 테스트") + class LogoutTests { + @Test + void logoutSuccess() throws Exception { + UserLogoutRequestDto requestDto = new UserLogoutRequestDto("access-token", "refresh-token"); + + } + } +} diff --git a/src/test/java/apptive/devlog/domain/auth/controller/AuthControllerTest.java b/src/test/java/apptive/devlog/domain/auth/controller/AuthControllerTest.java new file mode 100644 index 0000000..33a04b3 --- /dev/null +++ b/src/test/java/apptive/devlog/domain/auth/controller/AuthControllerTest.java @@ -0,0 +1,112 @@ +package apptive.devlog.domain.auth.controller; + +import apptive.devlog.domain.auth.dto.*; +import apptive.devlog.domain.auth.service.AuthService; +import apptive.devlog.domain.user.entity.User; +import apptive.devlog.domain.user.enums.Gender; +import apptive.devlog.domain.user.enums.Provider; +import apptive.devlog.domain.user.enums.Role; +import apptive.devlog.global.resolver.InjectTokenArgumentResolver; +import apptive.devlog.global.security.interceptor.JwtAuthenticationInterceptor; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.http.MediaType; +import org.springframework.security.test.context.support.WithMockUser; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.bean.override.mockito.MockitoBean; +import org.springframework.test.web.servlet.MockMvc; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +import java.time.LocalDate; + +@WithMockUser +@ExtendWith(MockitoExtension.class) +@WebMvcTest(AuthController.class) +@ActiveProfiles("test") +public class AuthControllerTest { + @Autowired + private MockMvc mockMvc; + + @Autowired + private ObjectMapper objectMapper; + + @MockitoBean + private AuthService authService; + + @MockitoBean + private JwtAuthenticationInterceptor jwtAuthenticationInterceptor; + + @MockitoBean + private InjectTokenArgumentResolver injectTokenArgumentResolver; + + @Nested + @DisplayName("회원가입 테스트") + public class SignupTests { + @Test + @DisplayName("회원가입 성공") + public void signupSuccess() throws Exception { + UserSignupRequestDto requestDto = new UserSignupRequestDto("test@example.com", "Password123!", "choi", "test", LocalDate.of(2000, 9, 26), Gender.MALE); + UserSignupResponseDto responseDto = new UserSignupResponseDto(new User("test@example.com", "choi", Provider.LOCAL, Role.USER)); + + when(jwtAuthenticationInterceptor.preHandle(any(), any(), any())).thenReturn(true); + when(authService.signup(any(UserSignupRequestDto.class))).thenReturn(responseDto); + + mockMvc.perform(post("/auth/signup").with(csrf()).contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(requestDto)).characterEncoding("UTF-8")) + .andExpect(status().isCreated()) + .andExpect(jsonPath("$.status").value(201)) + .andExpect(jsonPath("$.data.email").value("test@example.com"));; + } + } + + @Nested + @DisplayName("로그인 테스트") + class LoginTests { + @Test + void loginSuccess() throws Exception { + UserLoginRequestDto requestDto = new UserLoginRequestDto("test@example.com", "Password123!"); + UserLoginResponseDto responseDto = new UserLoginResponseDto("access-token", "refresh-token"); + + when(jwtAuthenticationInterceptor.preHandle(any(), any(), any())).thenReturn(true); + when(authService.login(any(UserLoginRequestDto.class))).thenReturn(responseDto); + + mockMvc.perform(post("/auth/login").with(csrf()).contentType(MediaType.APPLICATION_JSON).content(objectMapper.writeValueAsString(requestDto)).characterEncoding("UTF-8")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.status").value(200)) + .andExpect(jsonPath("$.data.accessToken").value("access-token")) + .andExpect(jsonPath("$.data.refreshToken").value("refresh-token")); + } + } + + @Nested + @DisplayName("리프레시 테스트") + class RefreshTests { + @Test + void refreshSuccess() throws Exception { + UserRefreshRequestDto requestDto = new UserRefreshRequestDto("access-token", "refresh-token"); + UserRefreshResponseDto responseDto = new UserRefreshResponseDto("access-token", "refresh-token"); + + } + } + + @Nested + @DisplayName("로그아웃 테스트") + class LogoutTests { + @Test + void logoutSuccess() throws Exception { + UserLogoutRequestDto requestDto = new UserLogoutRequestDto("access-token", "refresh-token"); + + } + } +} diff --git a/src/test/java/apptive/devlog/domain/auth/service/AuthServiceTest.java b/src/test/java/apptive/devlog/domain/auth/service/AuthServiceTest.java new file mode 100644 index 0000000..4a00ce1 --- /dev/null +++ b/src/test/java/apptive/devlog/domain/auth/service/AuthServiceTest.java @@ -0,0 +1,4 @@ +package apptive.devlog.domain.auth.service; + +public class AuthServiceTest { +} diff --git a/src/test/java/apptive/devlog/domain/comment/controller/CommentControllerTest.java b/src/test/java/apptive/devlog/domain/comment/controller/CommentControllerTest.java new file mode 100644 index 0000000..f80a33c --- /dev/null +++ b/src/test/java/apptive/devlog/domain/comment/controller/CommentControllerTest.java @@ -0,0 +1,4 @@ +package apptive.devlog.domain.comment.controller; + +public class CommentControllerTest { +} diff --git a/src/test/java/apptive/devlog/domain/comment/service/CommentServiceTest.java b/src/test/java/apptive/devlog/domain/comment/service/CommentServiceTest.java new file mode 100644 index 0000000..196b64c --- /dev/null +++ b/src/test/java/apptive/devlog/domain/comment/service/CommentServiceTest.java @@ -0,0 +1,4 @@ +package apptive.devlog.domain.comment.service; + +public class CommentServiceTest { +} diff --git a/src/test/java/apptive/devlog/domain/oauth2/handler/OAuth2FailureHandlerTest.java b/src/test/java/apptive/devlog/domain/oauth2/handler/OAuth2FailureHandlerTest.java new file mode 100644 index 0000000..2227f70 --- /dev/null +++ b/src/test/java/apptive/devlog/domain/oauth2/handler/OAuth2FailureHandlerTest.java @@ -0,0 +1,4 @@ +package apptive.devlog.domain.oauth2.handler; + +public class OAuth2FailureHandlerTest { +} diff --git a/src/test/java/apptive/devlog/domain/oauth2/handler/OAuth2SuccessHandlerTest.java b/src/test/java/apptive/devlog/domain/oauth2/handler/OAuth2SuccessHandlerTest.java new file mode 100644 index 0000000..1bc6c7a --- /dev/null +++ b/src/test/java/apptive/devlog/domain/oauth2/handler/OAuth2SuccessHandlerTest.java @@ -0,0 +1,4 @@ +package apptive.devlog.domain.oauth2.handler; + +public class OAuth2SuccessHandlerTest { +} diff --git a/src/test/java/apptive/devlog/domain/oauth2/service/CustomOAuth2UserServiceTest.java b/src/test/java/apptive/devlog/domain/oauth2/service/CustomOAuth2UserServiceTest.java new file mode 100644 index 0000000..c0f36a9 --- /dev/null +++ b/src/test/java/apptive/devlog/domain/oauth2/service/CustomOAuth2UserServiceTest.java @@ -0,0 +1,4 @@ +package apptive.devlog.domain.oauth2.service; + +public class CustomOAuth2UserServiceTest { +} diff --git a/src/test/java/apptive/devlog/domain/post/controller/PostControllerTest.java b/src/test/java/apptive/devlog/domain/post/controller/PostControllerTest.java new file mode 100644 index 0000000..4a73cb5 --- /dev/null +++ b/src/test/java/apptive/devlog/domain/post/controller/PostControllerTest.java @@ -0,0 +1,4 @@ +package apptive.devlog.domain.post.controller; + +public class PostControllerTest { +} diff --git a/src/test/java/apptive/devlog/domain/post/service/PostServiceTest.java b/src/test/java/apptive/devlog/domain/post/service/PostServiceTest.java new file mode 100644 index 0000000..456b734 --- /dev/null +++ b/src/test/java/apptive/devlog/domain/post/service/PostServiceTest.java @@ -0,0 +1,4 @@ +package apptive.devlog.domain.post.service; + +public class PostServiceTest { +} diff --git a/src/test/java/apptive/devlog/domain/user/controller/UserControllerTest.java b/src/test/java/apptive/devlog/domain/user/controller/UserControllerTest.java new file mode 100644 index 0000000..6fffcb7 --- /dev/null +++ b/src/test/java/apptive/devlog/domain/user/controller/UserControllerTest.java @@ -0,0 +1,4 @@ +package apptive.devlog.domain.user.controller; + +public class UserControllerTest { +} diff --git a/src/test/java/apptive/devlog/domain/user/service/UserServiceTest.java b/src/test/java/apptive/devlog/domain/user/service/UserServiceTest.java new file mode 100644 index 0000000..9e3f06a --- /dev/null +++ b/src/test/java/apptive/devlog/domain/user/service/UserServiceTest.java @@ -0,0 +1,4 @@ +package apptive.devlog.domain.user.service; + +public class UserServiceTest { +} diff --git a/src/test/resources/application-test.properties b/src/test/resources/application-test.properties new file mode 100644 index 0000000..fa0bd06 --- /dev/null +++ b/src/test/resources/application-test.properties @@ -0,0 +1,89 @@ +# =============================== +# DB CONFIG +# =============================== +spring.datasource.url=jdbc:mysql://localhost:3306/devlog?useSSL=false&serverTimezone=Asia/Seoul&characterEncoding=UTF-8&allowPublicKeyRetrieval=true +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver +spring.datasource.username=root + +# =============================== +# JPA CONFIG +# =============================== +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect +spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect +spring.jpa.hibernate.ddl-auto=create +spring.jpa.properties.hibernate.format_sql=true +spring.jpa.properties.hibernate.show_sql=true +spring.jpa.open-in-view=false +spring.jpa.show-sql=true + +# =============================== +# SERVER CONFIG +# =============================== +server.port=8080 +server.ssl.enabled=false + +# =============================== +# REDIS CONFIG +# =============================== +spring.data.redis.host=localhost +spring.data.redis.port=6379 + +# =============================== +# LOGGING +# =============================== +logging.level.org.springframework.security=DEBUG +logging.level.com.apptive.devlog=DEBUG + +# =============================== +# SWAGGER +# =============================== +springdoc.api-docs.path=/api-docs +springdoc.swagger-ui.path=/swagger-ui/index.html +springdoc.default-consumes-media-type=application/json +springdoc.default-produces-media-type=application/json +springdoc.swagger-ui.disable-swagger-default-url=true + +# =============================== +# JWT CONFIG +# =============================== +jwt.access-token-expiration=3600000 +jwt.refresh-token-expiration=1209600000 + +# =============================== +# OAUTH2 CONFIG - GOOGLE +# =============================== +spring.security.oauth2.client.registration.google.redirect-uri={baseUrl}/oauth2/callback/{registrationId} +spring.security.oauth2.client.registration.google.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.google.scope=profile,email +spring.security.oauth2.client.registration.google.client-name=Google +spring.security.oauth2.client.registration.google.client-authentication-method=client_secret_post +spring.security.oauth2.client.provider.google.authorization-uri=https://accounts.google.com/o/oauth2/v2/auth +spring.security.oauth2.client.provider.google.token-uri=https://oauth2.googleapis.com/token +spring.security.oauth2.client.provider.google.user-info-uri=https://www.googleapis.com/oauth2/v3/userinfo +spring.security.oauth2.client.provider.google.user-name-attribute=sub + +# =============================== +# OAUTH2 CONFIG - NAVER +# =============================== +spring.security.oauth2.client.registration.naver.redirect-uri={baseUrl}/oauth2/callback/{registrationId} +spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.naver.scope=name,email +spring.security.oauth2.client.registration.naver.client-name=Naver +spring.security.oauth2.client.registration.naver.client-authentication-method=client_secret_post +spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize +spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token +spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me +spring.security.oauth2.client.provider.naver.user-name-attribute=response + +# =============================== +# OAUTH2 CONFIG - KAKAO +# =============================== +spring.security.oauth2.client.registration.kakao.redirect-uri={baseUrl}/oauth2/callback/{registrationId} +spring.security.oauth2.client.registration.kakao.authorization-grant-type=authorization_code +spring.security.oauth2.client.registration.kakao.scope=profile_nickname,account_email +spring.security.oauth2.client.registration.kakao.client-name=Kakao +spring.security.oauth2.client.registration.kakao.client-authentication-method=client_secret_post +spring.security.oauth2.client.provider.kakao.authorization-uri=https://kauth.kakao.com/oauth/authorize +spring.security.oauth2.client.provider.kakao.token-uri=https://kauth.kakao.com/oauth/token +spring.security.oauth2.client.provider.kakao.user-info-uri=https://kapi.kakao.com/v2/user/me +spring.security.oauth2.client.provider.kakao.user-name-attribute=id diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000..16e5fac --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,10 @@ +# =============================== +# APPLICATION CONFIG +# =============================== +spring.application.name=devlog + +# =============================== +# PROFILE CONFIG +# =============================== +spring.profiles.active=test +spring.profiles.include=secret diff --git a/stop.sh b/stop.sh new file mode 100644 index 0000000..61cd526 --- /dev/null +++ b/stop.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +# Stop containers +docker stop devlog-mysql devlog-app devlog-redis devlog-nginx + +# Remove containers +docker rm devlog-mysql devlog-app devlog-redis devlog-nginx