├── backend
└── sample
│ ├── settings.gradle
│ ├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
│ ├── src
│ ├── main
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── sample
│ │ │ │ ├── domain
│ │ │ │ ├── entity
│ │ │ │ │ ├── user
│ │ │ │ │ │ ├── Provider.java
│ │ │ │ │ │ ├── Role.java
│ │ │ │ │ │ ├── Token.java
│ │ │ │ │ │ └── User.java
│ │ │ │ │ └── time
│ │ │ │ │ │ └── DefaultTime.java
│ │ │ │ └── mapping
│ │ │ │ │ ├── BoardMapping.java
│ │ │ │ │ └── TokenMapping.java
│ │ │ │ ├── config
│ │ │ │ ├── security
│ │ │ │ │ ├── token
│ │ │ │ │ │ ├── CurrentUser.java
│ │ │ │ │ │ ├── CustomAuthenticationEntryPoint.java
│ │ │ │ │ │ ├── CustomOncePerRequestFilter.java
│ │ │ │ │ │ └── UserPrincipal.java
│ │ │ │ │ ├── auth
│ │ │ │ │ │ ├── OAuth2UserInfo.java
│ │ │ │ │ │ ├── company
│ │ │ │ │ │ │ ├── Google.java
│ │ │ │ │ │ │ ├── Github.java
│ │ │ │ │ │ │ ├── Facebook.java
│ │ │ │ │ │ │ ├── Kakao.java
│ │ │ │ │ │ │ └── Naver.java
│ │ │ │ │ │ └── OAuth2UserInfoFactory.java
│ │ │ │ │ ├── WebMvcConfig.java
│ │ │ │ │ ├── OAuth2Config.java
│ │ │ │ │ ├── handler
│ │ │ │ │ │ ├── CustomSimpleUrlAuthenticationFailureHandler.java
│ │ │ │ │ │ └── CustomSimpleUrlAuthenticationSuccessHandler.java
│ │ │ │ │ ├── util
│ │ │ │ │ │ └── CustomCookie.java
│ │ │ │ │ └── SecurityConfig.java
│ │ │ │ └── docs
│ │ │ │ │ └── OpenApiConfig.java
│ │ │ │ ├── advice
│ │ │ │ ├── error
│ │ │ │ │ ├── DefaultNullPointerException.java
│ │ │ │ │ ├── DefaultException.java
│ │ │ │ │ ├── InvalidParameterException.java
│ │ │ │ │ └── DefaultAuthenticationException.java
│ │ │ │ ├── payload
│ │ │ │ │ ├── ErrorCode.java
│ │ │ │ │ └── ErrorResponse.java
│ │ │ │ ├── assertThat
│ │ │ │ │ └── DefaultAssert.java
│ │ │ │ └── ApiControllerAdvice.java
│ │ │ │ ├── repository
│ │ │ │ ├── user
│ │ │ │ │ └── UserRepository.java
│ │ │ │ └── auth
│ │ │ │ │ ├── TokenRepository.java
│ │ │ │ │ └── CustomAuthorizationRequestRepository.java
│ │ │ │ ├── payload
│ │ │ │ ├── response
│ │ │ │ │ ├── Message.java
│ │ │ │ │ ├── ApiResponse.java
│ │ │ │ │ └── AuthResponse.java
│ │ │ │ └── request
│ │ │ │ │ └── auth
│ │ │ │ │ ├── SignInRequest.java
│ │ │ │ │ ├── SignUpRequest.java
│ │ │ │ │ ├── ChangePasswordRequest.java
│ │ │ │ │ └── RefreshTokenRequest.java
│ │ │ │ ├── SampleApplication.java
│ │ │ │ ├── aop
│ │ │ │ ├── AdviceAspect.java
│ │ │ │ ├── LogAspect.java
│ │ │ │ └── TimeAspect.java
│ │ │ │ ├── service
│ │ │ │ ├── user
│ │ │ │ │ └── UserService.java
│ │ │ │ └── auth
│ │ │ │ │ ├── CustomUserDetailsService.java
│ │ │ │ │ ├── CustomDefaultOAuth2UserService.java
│ │ │ │ │ ├── CustomTokenProviderService.java
│ │ │ │ │ └── AuthService.java
│ │ │ │ └── controller
│ │ │ │ └── auth
│ │ │ │ └── AuthController.java
│ │ └── resources
│ │ │ ├── application.properties
│ │ │ ├── database
│ │ │ └── database-sample.properties
│ │ │ ├── swagger
│ │ │ └── springdoc.properties
│ │ │ └── oauth2
│ │ │ └── oauth2-sample.properties
│ └── test
│ │ └── java
│ │ └── com
│ │ └── sample
│ │ ├── SampleApplicationTests.java
│ │ ├── lib
│ │ └── JsonUtils.java
│ │ └── controller
│ │ └── auth
│ │ └── AuthControllerTest.java
│ ├── .gitignore
│ ├── build.gradle
│ ├── gradlew.bat
│ └── gradlew
├── frontend
└── sample
│ ├── README.md
│ ├── public
│ ├── favicon.ico
│ ├── manifest.json
│ └── index.html
│ ├── src
│ ├── img
│ │ ├── fb-logo.png
│ │ ├── kakao-logo.png
│ │ ├── naver-logo.png
│ │ ├── github-logo.png
│ │ ├── google-logo.png
│ │ └── spring-boot-and-react-js.png
│ ├── app
│ │ ├── App.css
│ │ ├── App.test.js
│ │ └── App.js
│ ├── common
│ │ ├── LoadingIndicator.js
│ │ ├── NotFound.css
│ │ ├── PrivateRoute.js
│ │ ├── NotFound.js
│ │ ├── AppHeader.css
│ │ └── AppHeader.js
│ ├── index.js
│ ├── home
│ │ ├── Home.js
│ │ └── Home.css
│ ├── constants
│ │ └── index.js
│ ├── user
│ │ ├── signup
│ │ │ ├── Signup.css
│ │ │ └── Signup.js
│ │ ├── oauth2
│ │ │ └── OAuth2RedirectHandler.js
│ │ ├── profile
│ │ │ ├── Profile.css
│ │ │ └── Profile.js
│ │ └── login
│ │ │ ├── Login.css
│ │ │ └── Login.js
│ ├── util
│ │ └── APIUtils.js
│ ├── logo.svg
│ ├── registerServiceWorker.js
│ └── index.css
│ ├── package.json
│ └── .gitignore
├── README
├── main.png
├── register.png
├── google_login.png
├── local_login.png
├── register_success.png
├── google_login_success.png
└── local_login_success.png
├── .gitignore
└── README.md
/backend/sample/settings.gradle:
--------------------------------------------------------------------------------
1 | rootProject.name = 'sample'
2 |
--------------------------------------------------------------------------------
/frontend/sample/README.md:
--------------------------------------------------------------------------------
1 | # Spring-Boot-Security-Sample-Restful
2 |
--------------------------------------------------------------------------------
/README/main.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MizzleAa/Spring-Boot-Security-OAuth2-JWT/HEAD/README/main.png
--------------------------------------------------------------------------------
/README/register.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MizzleAa/Spring-Boot-Security-OAuth2-JWT/HEAD/README/register.png
--------------------------------------------------------------------------------
/README/google_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MizzleAa/Spring-Boot-Security-OAuth2-JWT/HEAD/README/google_login.png
--------------------------------------------------------------------------------
/README/local_login.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MizzleAa/Spring-Boot-Security-OAuth2-JWT/HEAD/README/local_login.png
--------------------------------------------------------------------------------
/README/register_success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MizzleAa/Spring-Boot-Security-OAuth2-JWT/HEAD/README/register_success.png
--------------------------------------------------------------------------------
/README/google_login_success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MizzleAa/Spring-Boot-Security-OAuth2-JWT/HEAD/README/google_login_success.png
--------------------------------------------------------------------------------
/README/local_login_success.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MizzleAa/Spring-Boot-Security-OAuth2-JWT/HEAD/README/local_login_success.png
--------------------------------------------------------------------------------
/frontend/sample/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MizzleAa/Spring-Boot-Security-OAuth2-JWT/HEAD/frontend/sample/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/sample/src/img/fb-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MizzleAa/Spring-Boot-Security-OAuth2-JWT/HEAD/frontend/sample/src/img/fb-logo.png
--------------------------------------------------------------------------------
/frontend/sample/src/img/kakao-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MizzleAa/Spring-Boot-Security-OAuth2-JWT/HEAD/frontend/sample/src/img/kakao-logo.png
--------------------------------------------------------------------------------
/frontend/sample/src/img/naver-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MizzleAa/Spring-Boot-Security-OAuth2-JWT/HEAD/frontend/sample/src/img/naver-logo.png
--------------------------------------------------------------------------------
/frontend/sample/src/img/github-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MizzleAa/Spring-Boot-Security-OAuth2-JWT/HEAD/frontend/sample/src/img/github-logo.png
--------------------------------------------------------------------------------
/frontend/sample/src/img/google-logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MizzleAa/Spring-Boot-Security-OAuth2-JWT/HEAD/frontend/sample/src/img/google-logo.png
--------------------------------------------------------------------------------
/frontend/sample/src/app/App.css:
--------------------------------------------------------------------------------
1 | .s-alert-box {
2 | min-width: 250px;
3 | }
4 |
5 | .s-alert-close::before, .s-alert-close::after {
6 | width: 2px;
7 | }
--------------------------------------------------------------------------------
/backend/sample/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MizzleAa/Spring-Boot-Security-OAuth2-JWT/HEAD/backend/sample/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/frontend/sample/src/img/spring-boot-and-react-js.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MizzleAa/Spring-Boot-Security-OAuth2-JWT/HEAD/frontend/sample/src/img/spring-boot-and-react-js.png
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/domain/entity/user/Provider.java:
--------------------------------------------------------------------------------
1 | package com.sample.domain.entity.user;
2 |
3 | public enum Provider {
4 | local,
5 | facebook,
6 | google,
7 | github,
8 | kakao,
9 | naver
10 | }
11 |
--------------------------------------------------------------------------------
/backend/sample/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/backend/sample/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | # logging 설정
2 | logging.level.org.hibernate.type.descriptor.sql=debug
3 | # console 색상
4 | spring.output.ansi.enabled=always
5 | #오류처리
6 | server.error.include-exception=true
7 | server.error.include-stacktrace=always
8 | # port 설정
9 | server.port=8080
10 |
--------------------------------------------------------------------------------
/frontend/sample/src/common/LoadingIndicator.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default function LoadingIndicator(props) {
4 | return (
5 |
6 | Loading ...
7 |
8 | );
9 | }
--------------------------------------------------------------------------------
/frontend/sample/src/app/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/backend/sample/src/test/java/com/sample/SampleApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.sample;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.boot.test.context.SpringBootTest;
5 |
6 | @SpringBootTest
7 | class SampleApplicationTests {
8 |
9 | @Test
10 | void contextLoads() {
11 | }
12 |
13 | }
14 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/domain/entity/user/Role.java:
--------------------------------------------------------------------------------
1 | package com.sample.domain.entity.user;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.Getter;
5 |
6 | @AllArgsConstructor
7 | @Getter
8 | public enum Role {
9 | ADMIN("ROLE_ADMIN"),
10 | USER("ROLE_USER");
11 |
12 | private String value;
13 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 |
--------------------------------------------------------------------------------
/frontend/sample/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/domain/mapping/BoardMapping.java:
--------------------------------------------------------------------------------
1 | package com.sample.domain.mapping;
2 |
3 | import java.time.LocalDateTime;
4 | /**
5 | * Mapping 예제
6 | */
7 | public interface BoardMapping {
8 | Long getId();
9 | LocalDateTime getCreatedDate();
10 | String getTitle();
11 | String getSubtitle();
12 | String getUsername();
13 | String getMarkdown();
14 | String getHtml();
15 | }
16 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/config/security/token/CurrentUser.java:
--------------------------------------------------------------------------------
1 | package com.sample.config.security.token;
2 |
3 | import java.lang.annotation.*;
4 | import org.springframework.security.core.annotation.AuthenticationPrincipal;
5 |
6 | @Target({ElementType.PARAMETER, ElementType.TYPE})
7 | @Retention(RetentionPolicy.RUNTIME)
8 | @Documented
9 | @AuthenticationPrincipal
10 | public @interface CurrentUser {
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/frontend/sample/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './app/App';
5 | import registerServiceWorker from './registerServiceWorker';
6 | import { BrowserRouter as Router } from 'react-router-dom';
7 |
8 | ReactDOM.render(
9 |
10 |
11 | ,
12 | document.getElementById('root')
13 | );
14 |
15 | registerServiceWorker();
16 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spring-Boot-OAuth2-JWT
2 |
3 |
4 | ### 메인화면
5 | 
6 |
7 | ### 사용자 등록(local)
8 | 
9 | 
10 |
11 | ### 사용자 로그인(local)
12 | 
13 | 
14 |
15 | ### 외부 사이트 로그인(google, etc)
16 | 
17 | 
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/advice/error/DefaultNullPointerException.java:
--------------------------------------------------------------------------------
1 | package com.sample.advice.error;
2 |
3 | import com.sample.advice.payload.ErrorCode;
4 |
5 | import lombok.Getter;
6 |
7 | @Getter
8 | public class DefaultNullPointerException extends NullPointerException{
9 |
10 | private ErrorCode errorCode;
11 |
12 | public DefaultNullPointerException(ErrorCode errorCode) {
13 | super(errorCode.getMessage());
14 | this.errorCode = errorCode;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/frontend/sample/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-social",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.5.2",
7 | "react-dom": "^16.5.2",
8 | "react-router-dom": "^4.3.1",
9 | "react-s-alert": "^1.4.1",
10 | "react-scripts": "1.1.5"
11 | },
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test --env=jsdom",
16 | "eject": "react-scripts eject"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/repository/user/UserRepository.java:
--------------------------------------------------------------------------------
1 | package com.sample.repository.user;
2 |
3 | import java.util.Optional;
4 |
5 | import com.sample.domain.entity.user.User;
6 |
7 | import org.springframework.data.jpa.repository.JpaRepository;
8 | import org.springframework.stereotype.Repository;
9 |
10 | @Repository
11 | public interface UserRepository extends JpaRepository{
12 |
13 | Optional findByEmail(String email);
14 | Boolean existsByEmail(String email);
15 | }
16 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/repository/auth/TokenRepository.java:
--------------------------------------------------------------------------------
1 | package com.sample.repository.auth;
2 |
3 | import org.springframework.data.jpa.repository.JpaRepository;
4 | import org.springframework.stereotype.Repository;
5 |
6 | import java.util.Optional;
7 |
8 | import com.sample.domain.entity.user.Token;
9 |
10 | @Repository
11 | public interface TokenRepository extends JpaRepository {
12 | Optional findByUserEmail(String userEmail);
13 | Optional findByRefreshToken(String refreshToken);
14 | }
--------------------------------------------------------------------------------
/frontend/sample/src/common/NotFound.css:
--------------------------------------------------------------------------------
1 | .page-not-found {
2 | background-color: rgb(31, 31, 31);
3 | max-width: 500px;
4 | margin: 0 auto;
5 | margin-top: 50px;
6 | padding: 40px;
7 | border: 1px solid #c8c8c8;
8 | text-align: center;
9 | }
10 |
11 | .page-not-found .title {
12 | font-size: 50px;
13 | letter-spacing: 10px;
14 | margin-bottom: 10px;
15 | }
16 |
17 | .page-not-found .desc {
18 | font-size: 20px;
19 | margin-bottom: 20px;
20 | }
21 |
22 | .go-back-btn {
23 | min-width: 160px;
24 | }
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/payload/response/Message.java:
--------------------------------------------------------------------------------
1 | package com.sample.payload.response;
2 |
3 | import io.swagger.v3.oas.annotations.media.Schema;
4 | import lombok.Builder;
5 | import lombok.Data;
6 | import lombok.ToString;
7 |
8 | @ToString
9 | @Data
10 | public class Message {
11 |
12 | @Schema( type = "string", example = "메시지 문구를 출력합니다.", description="메시지 입니다.")
13 | private String message;
14 |
15 | public Message(){};
16 |
17 | @Builder
18 | public Message(String message) {
19 | this.message = message;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/domain/mapping/TokenMapping.java:
--------------------------------------------------------------------------------
1 | package com.sample.domain.mapping;
2 |
3 | import lombok.Builder;
4 | import lombok.Data;
5 |
6 | @Data
7 | public class TokenMapping {
8 | private String userEmail;
9 | private String accessToken;
10 | private String refreshToken;
11 |
12 | public TokenMapping(){}
13 |
14 | @Builder
15 | public TokenMapping(String userEmail, String accessToken, String refreshToken){
16 | this.userEmail = userEmail;
17 | this.accessToken = accessToken;
18 | this.refreshToken = refreshToken;
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/backend/sample/src/main/resources/database/database-sample.properties:
--------------------------------------------------------------------------------
1 | # db 설정
2 | spring.datasource.driver-class-name={"Database Driver"}
3 | spring.datasource.url={"url"}
4 | spring.datasource.username={"username"}
5 | spring.datasource.password={"password"}
6 | # jpa 설정
7 | spring.jpa.database-platform={"Database Platform"}
8 | spring.jpa.hibernate.ddl-auto=update
9 | #spring.jpa.hibernate.ddl-auto=create-drop
10 | #spring.jpa.hibernate.ddl-auto=none
11 |
12 | # 배표할때는 open-in-view false 설정
13 | spring.jpa.open-in-view=false
14 | spring.jpa.properties.hibernate.show_sql=true
15 | spring.jpa.properties.hibernate.format_sql=true
16 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/advice/error/DefaultException.java:
--------------------------------------------------------------------------------
1 | package com.sample.advice.error;
2 |
3 |
4 | import com.sample.advice.payload.ErrorCode;
5 |
6 | import lombok.Getter;
7 |
8 | @Getter
9 | public class DefaultException extends RuntimeException{
10 |
11 | private ErrorCode errorCode;
12 |
13 | public DefaultException(ErrorCode errorCode) {
14 | super(errorCode.getMessage());
15 | this.errorCode = errorCode;
16 | }
17 |
18 | public DefaultException(ErrorCode errorCode, String message) {
19 | super(message);
20 | this.errorCode = errorCode;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/backend/sample/src/main/resources/swagger/springdoc.properties:
--------------------------------------------------------------------------------
1 | #sping doc 설정
2 | springdoc.swagger-ui.path=swagger
3 | #"사용해 보기" 섹션이 기본적으로 활성화되어야 하는지 여부를 제어
4 | springdoc.swagger-ui.tryItOutEnabled=true
5 | # filter 검색
6 | springdoc.swagger-ui.filter=true
7 | springdoc.swagger-ui.operationsSorter=method
8 | # ms단위 표시
9 | springdoc.swagger-ui.displayRequestDuration=true
10 | springdoc.swagger-ui.supportedSubmitMethods="get", "put", "post", "delete", "options", "head", "patch", "trace"
11 |
12 | #api-doc 비활성화
13 | #springdoc.api-docs.enabled=false
14 | #swagger-ui 비활성화
15 | #springdoc.swagger-ui.enabled=false
16 | # springdoc.swagger-ui.queryConfigEnabled=false
17 |
--------------------------------------------------------------------------------
/frontend/sample/src/common/PrivateRoute.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {
3 | Route,
4 | Redirect
5 | } from "react-router-dom";
6 |
7 |
8 | const PrivateRoute = ({ component: Component, authenticated, ...rest }) => (
9 |
12 | authenticated ? (
13 |
14 | ) : (
15 |
21 | )
22 | }
23 | />
24 | );
25 |
26 | export default PrivateRoute
--------------------------------------------------------------------------------
/backend/sample/.gitignore:
--------------------------------------------------------------------------------
1 | database.properties
2 | oauth2.properties
3 |
4 | HELP.md
5 | .gradle
6 | build/
7 | !gradle/wrapper/gradle-wrapper.jar
8 | !**/src/main/**/build/
9 | !**/src/test/**/build/
10 |
11 | ### STS ###
12 | .apt_generated
13 | .classpath
14 | .factorypath
15 | .project
16 | .settings
17 | .springBeans
18 | .sts4-cache
19 | bin/
20 | !**/src/main/**/bin/
21 | !**/src/test/**/bin/
22 |
23 | ### IntelliJ IDEA ###
24 | .idea
25 | *.iws
26 | *.iml
27 | *.ipr
28 | out/
29 | !**/src/main/**/out/
30 | !**/src/test/**/out/
31 |
32 | ### NetBeans ###
33 | /nbproject/private/
34 | /nbbuild/
35 | /dist/
36 | /nbdist/
37 | /.nb-gradle/
38 |
39 | ### VS Code ###
40 | .vscode/
41 |
--------------------------------------------------------------------------------
/frontend/sample/src/common/NotFound.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './NotFound.css';
3 | import { Link } from 'react-router-dom';
4 |
5 | class NotFound extends Component {
6 | render() {
7 | return (
8 |
9 |
10 | 404
11 |
12 |
13 | 해당 페이지는 찾을 수 없습니다..
14 |
15 |
16 |
17 | );
18 | }
19 | }
20 |
21 | export default NotFound;
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/config/security/auth/OAuth2UserInfo.java:
--------------------------------------------------------------------------------
1 | package com.sample.config.security.auth;
2 |
3 | import java.util.Map;
4 |
5 | public abstract class OAuth2UserInfo {
6 | protected Map attributes;
7 |
8 | public OAuth2UserInfo(Map attributes) {
9 | this.attributes = attributes;
10 | }
11 |
12 | public Map getAttributes() {
13 | return attributes;
14 | }
15 |
16 | public abstract String getProvider();
17 |
18 | public abstract String getId();
19 |
20 | public abstract String getName();
21 |
22 | public abstract String getEmail();
23 |
24 | public abstract String getImageUrl();
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/sample/src/home/Home.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './Home.css';
3 | import springReactImage from '../img/spring-boot-and-react-js.png';
4 |
5 | class Home extends Component {
6 | render() {
7 | return (
8 |
9 |
10 |

11 |
Sample OAuth2 & JWT
12 |
Copyright © 2022 Mizzle Inc. Policy Edit page on GitHub
13 |
14 |
15 | )
16 | }
17 | }
18 |
19 | export default Home;
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/payload/request/auth/SignInRequest.java:
--------------------------------------------------------------------------------
1 | package com.sample.payload.request.auth;
2 |
3 | import javax.validation.constraints.Email;
4 | import javax.validation.constraints.NotBlank;
5 | import javax.validation.constraints.NotNull;
6 |
7 | import io.swagger.v3.oas.annotations.media.Schema;
8 | import lombok.Data;
9 |
10 | @Data
11 | public class SignInRequest {
12 |
13 | @Schema( type = "string", example = "string@aa.bb", description="계정 이메일 입니다.")
14 | @NotBlank
15 | @NotNull
16 | @Email
17 | private String email;
18 |
19 | @Schema( type = "string", example = "string", description="계정 비밀번호 입니다.")
20 | @NotBlank
21 | @NotNull
22 | private String password;
23 | }
24 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/advice/error/InvalidParameterException.java:
--------------------------------------------------------------------------------
1 | package com.sample.advice.error;
2 |
3 | import java.util.List;
4 |
5 | import com.sample.advice.payload.ErrorCode;
6 |
7 | import org.springframework.validation.Errors;
8 | import org.springframework.validation.FieldError;
9 |
10 | import lombok.Getter;
11 |
12 | @Getter
13 | public class InvalidParameterException extends DefaultException{
14 |
15 | private final Errors errors;
16 |
17 | public InvalidParameterException(Errors errors) {
18 | super(ErrorCode.INVALID_PARAMETER);
19 | this.errors = errors;
20 | }
21 |
22 | public List getFieldErrors() {
23 | return errors.getFieldErrors();
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/payload/request/auth/SignUpRequest.java:
--------------------------------------------------------------------------------
1 | package com.sample.payload.request.auth;
2 |
3 | import javax.validation.constraints.Email;
4 | import javax.validation.constraints.NotBlank;
5 |
6 | import io.swagger.v3.oas.annotations.media.Schema;
7 | import lombok.Data;
8 |
9 | @Data
10 | public class SignUpRequest {
11 |
12 | @Schema( type = "string", example = "string", description="계정 명 입니다.")
13 | @NotBlank
14 | private String name;
15 |
16 | @Schema( type = "string", example = "string@aa.bb", description="계정 이메일 입니다.")
17 | @NotBlank
18 | @Email
19 | private String email;
20 |
21 | @Schema( type = "string", example = "string", description="계정 비밀번호 입니다.")
22 | @NotBlank
23 | private String password;
24 | }
25 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/SampleApplication.java:
--------------------------------------------------------------------------------
1 | package com.sample;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.context.annotation.PropertySource;
6 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
7 |
8 | @EnableJpaAuditing
9 | @SpringBootApplication
10 | @PropertySource(value = { "classpath:database/database.properties" })
11 | @PropertySource(value = { "classpath:oauth2/oauth2.properties" })
12 | @PropertySource(value = { "classpath:swagger/springdoc.properties" })
13 | public class SampleApplication {
14 |
15 | public static void main(String[] args) {
16 | SpringApplication.run(SampleApplication.class, args);
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/aop/AdviceAspect.java:
--------------------------------------------------------------------------------
1 | package com.sample.aop;
2 |
3 | import org.aspectj.lang.ProceedingJoinPoint;
4 | import org.aspectj.lang.annotation.Around;
5 | import org.aspectj.lang.annotation.Aspect;
6 | import org.springframework.stereotype.Component;
7 |
8 | import lombok.extern.slf4j.Slf4j;
9 |
10 | @Slf4j
11 | @Aspect
12 | @Component
13 | public class AdviceAspect {
14 |
15 | @Around("execution(* com.sample.advice.*.*(..))")
16 | public Object adviceController(ProceedingJoinPoint proceedingJoinPoint) throws Throwable, Exception {
17 | log.error("Adivce Error = {}.{}", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName());
18 | Object result = proceedingJoinPoint.proceed();
19 | return result;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/payload/response/ApiResponse.java:
--------------------------------------------------------------------------------
1 | package com.sample.payload.response;
2 |
3 | import io.swagger.v3.oas.annotations.media.Schema;
4 | import lombok.Builder;
5 | import lombok.Data;
6 | import lombok.ToString;
7 |
8 | @ToString
9 | @Data
10 | public class ApiResponse {
11 |
12 | @Schema( type = "boolean", example = "true", description="올바르게 로직을 처리했으면 True, 아니면 False를 반환합니다.")
13 | private boolean check;
14 |
15 | @Schema( type = "object", example = "information", description="restful의 정보를 감싸 표현합니다. object형식으로 표현합니다.")
16 | private Object information;
17 |
18 | public ApiResponse(){};
19 |
20 | @Builder
21 | public ApiResponse(boolean check, Object information) {
22 | this.check = check;
23 | this.information = information;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/frontend/sample/src/constants/index.js:
--------------------------------------------------------------------------------
1 | export const API_BASE_URL = 'http://localhost:8080';
2 | export const ACCESS_TOKEN = 'accessToken';
3 | export const REFRESH_TOKEN = 'refreshToken';
4 |
5 | export const OAUTH2_REDIRECT_URI = 'http://localhost:3000/oauth2/redirect'
6 |
7 | export const GOOGLE_AUTH_URL = API_BASE_URL + '/oauth2/authorize/google?redirect_uri=' + OAUTH2_REDIRECT_URI;
8 | export const FACEBOOK_AUTH_URL = API_BASE_URL + '/oauth2/authorize/facebook?redirect_uri=' + OAUTH2_REDIRECT_URI;
9 | export const GITHUB_AUTH_URL = API_BASE_URL + '/oauth2/authorize/github?redirect_uri=' + OAUTH2_REDIRECT_URI;
10 | export const KAKAO_AUTH_URL = API_BASE_URL + '/oauth2/authorize/kakao?redirect_uri=' + OAUTH2_REDIRECT_URI;
11 | export const NAVER_AUTH_URL = API_BASE_URL + '/oauth2/authorize/naver?redirect_uri=' + OAUTH2_REDIRECT_URI;
12 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/advice/payload/ErrorCode.java:
--------------------------------------------------------------------------------
1 | package com.sample.advice.payload;
2 |
3 | import lombok.Getter;
4 |
5 | @Getter
6 | public enum ErrorCode {
7 | INVALID_PARAMETER(400, null, "잘못된 요청 데이터 입니다."),
8 | INVALID_REPRESENTATION(400, null, "잘못된 표현 입니다."),
9 | INVALID_FILE_PATH(400, null, "잘못된 파일 경로 입니다."),
10 | INVALID_OPTIONAL_ISPRESENT(400, null, "해당 값이 존재하지 않습니다."),
11 | INVALID_CHECK(400, null, "해당 값이 유효하지 않습니다."),
12 | INVALID_AUTHENTICATION(400, null, "잘못된 인증입니다.");
13 |
14 | private final String code;
15 | private final String message;
16 | private final int status;
17 |
18 | ErrorCode(final int status, final String code, final String message) {
19 | this.status = status;
20 | this.message = message;
21 | this.code = code;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/domain/entity/time/DefaultTime.java:
--------------------------------------------------------------------------------
1 | package com.sample.domain.entity.time;
2 |
3 | import java.time.LocalDateTime;
4 |
5 | import javax.persistence.Column;
6 | import javax.persistence.EntityListeners;
7 | import javax.persistence.MappedSuperclass;
8 |
9 | import org.springframework.data.annotation.CreatedDate;
10 | import org.springframework.data.annotation.LastModifiedDate;
11 | import org.springframework.data.jpa.domain.support.AuditingEntityListener;
12 |
13 | import lombok.Getter;
14 |
15 | @Getter
16 | @MappedSuperclass
17 | @EntityListeners(AuditingEntityListener.class)
18 | public abstract class DefaultTime {
19 |
20 | @CreatedDate
21 | @Column(updatable=false)
22 | private LocalDateTime createdDate;
23 |
24 | @LastModifiedDate
25 | private LocalDateTime modifiedDate;
26 |
27 | }
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/payload/request/auth/ChangePasswordRequest.java:
--------------------------------------------------------------------------------
1 | package com.sample.payload.request.auth;
2 |
3 | import javax.validation.constraints.NotBlank;
4 | import javax.validation.constraints.NotNull;
5 |
6 | import io.swagger.v3.oas.annotations.media.Schema;
7 | import lombok.Data;
8 |
9 | @Data
10 | public class ChangePasswordRequest {
11 |
12 | @Schema( type = "string", example = "string", description="기존 비밀번호 입니다.")
13 | @NotBlank
14 | @NotNull
15 | private String oldPassword;
16 |
17 | @Schema( type = "string", example = "string123", description="신규 비밀번호 입니다.")
18 | @NotBlank
19 | @NotNull
20 | private String newPassword;
21 |
22 | @Schema( type = "string", example = "string123", description="신규 비밀번호 확인란 입니다.")
23 | @NotBlank
24 | @NotNull
25 | private String reNewPassword;
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/payload/request/auth/RefreshTokenRequest.java:
--------------------------------------------------------------------------------
1 | package com.sample.payload.request.auth;
2 |
3 | import javax.validation.constraints.NotBlank;
4 | import javax.validation.constraints.NotNull;
5 |
6 | import io.swagger.v3.oas.annotations.media.Schema;
7 |
8 | import lombok.Builder;
9 | import lombok.Data;
10 |
11 | @Data
12 | public class RefreshTokenRequest {
13 |
14 | @Schema( type = "string", example = "eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2NTI3OTgxOTh9.6CoxHB_siOuz6PxsxHYQCgUT1_QbdyKTUwStQDutEd1-cIIARbQ0cyrnAmpIgi3IBoLRaqK7N1vXO42nYy4g5g", description="refresh token 입니다." )
15 | @NotBlank
16 | @NotNull
17 | private String refreshToken;
18 |
19 | public RefreshTokenRequest(){}
20 |
21 | @Builder
22 | public RefreshTokenRequest(String refreshToken){
23 | this.refreshToken = refreshToken;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/advice/error/DefaultAuthenticationException.java:
--------------------------------------------------------------------------------
1 | package com.sample.advice.error;
2 |
3 | import com.sample.advice.payload.ErrorCode;
4 |
5 | import org.springframework.security.core.AuthenticationException;
6 |
7 | import lombok.Getter;
8 |
9 |
10 | @Getter
11 | public class DefaultAuthenticationException extends AuthenticationException{
12 |
13 | private ErrorCode errorCode;
14 |
15 | public DefaultAuthenticationException(String msg, Throwable t) {
16 | super(msg, t);
17 | this.errorCode = ErrorCode.INVALID_REPRESENTATION;
18 | }
19 |
20 | public DefaultAuthenticationException(String msg) {
21 | super(msg);
22 | }
23 |
24 | public DefaultAuthenticationException(ErrorCode errorCode) {
25 | super(errorCode.getMessage());
26 | this.errorCode = errorCode;
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/config/security/token/CustomAuthenticationEntryPoint.java:
--------------------------------------------------------------------------------
1 | package com.sample.config.security.token;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.ServletException;
6 | import javax.servlet.http.HttpServletRequest;
7 | import javax.servlet.http.HttpServletResponse;
8 |
9 | import org.springframework.security.core.AuthenticationException;
10 | import org.springframework.security.web.AuthenticationEntryPoint;
11 |
12 | public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint{
13 |
14 | @Override
15 | public void commence(HttpServletRequest request, HttpServletResponse response,
16 | AuthenticationException authException) throws IOException, ServletException {
17 |
18 | response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getLocalizedMessage());
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/aop/LogAspect.java:
--------------------------------------------------------------------------------
1 | package com.sample.aop;
2 |
3 | import org.aspectj.lang.ProceedingJoinPoint;
4 | import org.aspectj.lang.annotation.Around;
5 | import org.aspectj.lang.annotation.Aspect;
6 |
7 | import org.springframework.stereotype.Component;
8 |
9 |
10 | @Aspect
11 | @Component
12 | public class LogAspect {
13 |
14 | @Around("execution(* com.sample.controller.*.*(..))")
15 | public Object ControllerLogger(ProceedingJoinPoint proceedingJoinPoint) throws Throwable, Exception {
16 | //log.info("start = {} / {}", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName());
17 | Object result = proceedingJoinPoint.proceed();
18 | //log.info("end = {} / {}", proceedingJoinPoint.getSignature().getDeclaringTypeName(), proceedingJoinPoint.getSignature().getName());
19 | return result;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/backend/sample/src/test/java/com/sample/lib/JsonUtils.java:
--------------------------------------------------------------------------------
1 | package com.sample.lib;
2 |
3 | import org.json.simple.JSONObject;
4 | import org.json.simple.parser.JSONParser;
5 | import org.json.simple.parser.ParseException;
6 |
7 | import com.fasterxml.jackson.databind.ObjectMapper;
8 |
9 | public class JsonUtils {
10 |
11 | //dto를 object mapper로 통해 json 으로 저장
12 | public static String asJsonToString(Object object) {
13 | try {
14 | return new ObjectMapper().writeValueAsString(object);
15 | } catch (Exception e) {
16 | throw new RuntimeException(e);
17 | }
18 | }
19 |
20 | //string 값을 json 형식으로 변경
21 | public static JSONObject asStringToJson(String string) throws ParseException{
22 | JSONParser jsonParser = new JSONParser();
23 | Object object = jsonParser.parse( string );
24 | JSONObject jsonObject = (JSONObject) object;
25 | return jsonObject;
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/aop/TimeAspect.java:
--------------------------------------------------------------------------------
1 | package com.sample.aop;
2 |
3 |
4 | import org.aspectj.lang.ProceedingJoinPoint;
5 | import org.aspectj.lang.annotation.Around;
6 | import org.aspectj.lang.annotation.Aspect;
7 | import org.springframework.stereotype.Component;
8 |
9 | import lombok.extern.slf4j.Slf4j;
10 |
11 | @Slf4j
12 | @Aspect
13 | @Component
14 | public class TimeAspect {
15 |
16 | @Around("execution(* com.sample.controller.*.*(..))")
17 | public Object timerController(ProceedingJoinPoint proceedingJoinPoint) throws Throwable, Exception {
18 |
19 | long startTime = System.currentTimeMillis();
20 | Object result = proceedingJoinPoint.proceed();
21 | long endTime = System.currentTimeMillis();
22 | long totalTime = endTime - startTime;
23 |
24 | log.info("{}.{} = {}ms", proceedingJoinPoint.getSignature().getDeclaringTypeName(),proceedingJoinPoint.getSignature().getName() , totalTime);
25 | return result;
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/frontend/sample/src/common/AppHeader.css:
--------------------------------------------------------------------------------
1 | .app-header {
2 | background-color: rgb(25, 25, 25);
3 | height: 60px;
4 | position: relative;
5 | z-index: 10;
6 | }
7 |
8 | .app-title {
9 | color: antiquewhite;
10 | line-height: 60px;
11 | vertical-align: middle;
12 | font-size: 1.35em;
13 | }
14 |
15 | .app-title:hover {
16 | color:azure;
17 | }
18 |
19 | .app-branding {
20 | float: left;
21 | }
22 |
23 | .app-options {
24 | float: right;
25 | }
26 |
27 | .app-nav ul {
28 | list-style-position: none;
29 | margin: 0;
30 | padding: 0;
31 | }
32 |
33 | .app-nav ul li {
34 | list-style-type: none;
35 | display: inline-block;
36 | }
37 |
38 | .app-nav ul li a {
39 | color: antiquewhite;
40 | display: inline-block;
41 | line-height: 60px;
42 | vertical-align: middle;
43 | padding-left: 15px;
44 | padding-right: 15px;
45 | }
46 |
47 | .app-nav ul li a:hover {
48 | color: azure;
49 | }
50 |
51 | .app-nav ul li a.active {
52 | color: azure;
53 | }
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/config/security/WebMvcConfig.java:
--------------------------------------------------------------------------------
1 | package com.sample.config.security;
2 |
3 | import org.springframework.beans.factory.annotation.Value;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.web.servlet.config.annotation.CorsRegistry;
6 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
7 |
8 | @Configuration
9 | public class WebMvcConfig implements WebMvcConfigurer{
10 |
11 | private final long MAX_AGE_SECS = 3600;
12 |
13 | @Value("${app.cors.allowedOrigins}")
14 | private String[] allowedOrigins;
15 |
16 | @Override
17 | public void addCorsMappings(CorsRegistry registry) {
18 | registry.addMapping("/**")
19 | .allowedOrigins(allowedOrigins)
20 | .allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
21 | .allowedHeaders("*")
22 | .allowCredentials(true)
23 | .maxAge(MAX_AGE_SECS);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/service/user/UserService.java:
--------------------------------------------------------------------------------
1 | package com.sample.service.user;
2 |
3 | import java.util.Optional;
4 |
5 | import com.sample.advice.assertThat.DefaultAssert;
6 | import com.sample.config.security.token.UserPrincipal;
7 | import com.sample.domain.entity.user.User;
8 | import com.sample.payload.response.ApiResponse;
9 | import com.sample.repository.user.UserRepository;
10 |
11 | import org.springframework.http.ResponseEntity;
12 | import org.springframework.stereotype.Service;
13 |
14 | import lombok.RequiredArgsConstructor;
15 |
16 | @RequiredArgsConstructor
17 | @Service
18 | public class UserService {
19 | private final UserRepository userRepository;
20 |
21 | public ResponseEntity> readByUser(UserPrincipal userPrincipal){
22 | Optional user = userRepository.findById(userPrincipal.getId());
23 | DefaultAssert.isOptionalPresent(user);
24 | ApiResponse apiResponse = ApiResponse.builder().check(true).information(user.get()).build();
25 | return ResponseEntity.ok(apiResponse);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/domain/entity/user/Token.java:
--------------------------------------------------------------------------------
1 | package com.sample.domain.entity.user;
2 |
3 | import javax.persistence.Column;
4 | import javax.persistence.Entity;
5 | import javax.persistence.Id;
6 | import javax.persistence.Table;
7 |
8 | import com.sample.domain.entity.time.DefaultTime;
9 |
10 | import lombok.Builder;
11 | import lombok.Getter;
12 |
13 | @Getter
14 | @Table(name="token")
15 | @Entity
16 | public class Token extends DefaultTime{
17 |
18 | @Id
19 | @Column(name = "user_email", length = 1024 , nullable = false)
20 | private String userEmail;
21 |
22 | @Column(name = "refresh_token", length = 1024 , nullable = false)
23 | private String refreshToken;
24 |
25 | public Token(){}
26 |
27 | public Token updateRefreshToken(String refreshToken) {
28 | this.refreshToken = refreshToken;
29 | return this;
30 | }
31 |
32 | @Builder
33 | public Token(String userEmail, String refreshToken) {
34 | this.userEmail = userEmail;
35 | this.refreshToken = refreshToken;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/config/security/auth/company/Google.java:
--------------------------------------------------------------------------------
1 | package com.sample.config.security.auth.company;
2 |
3 | import java.util.Map;
4 |
5 | import com.sample.config.security.auth.OAuth2UserInfo;
6 | import com.sample.domain.entity.user.Provider;
7 |
8 | public class Google extends OAuth2UserInfo{
9 |
10 | public Google(Map attributes) {
11 | super(attributes);
12 | }
13 |
14 | @Override
15 | public String getId() {
16 |
17 | return (String) attributes.get("sub");
18 | }
19 |
20 | @Override
21 | public String getName() {
22 |
23 | return (String) attributes.get("name");
24 | }
25 |
26 | @Override
27 | public String getEmail() {
28 |
29 | return (String) attributes.get("email");
30 | }
31 |
32 | @Override
33 | public String getImageUrl() {
34 |
35 | return (String) attributes.get("picture");
36 | }
37 |
38 | @Override
39 | public String getProvider(){
40 | return Provider.google.toString();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/config/security/auth/company/Github.java:
--------------------------------------------------------------------------------
1 | package com.sample.config.security.auth.company;
2 |
3 | import java.util.Map;
4 |
5 | import com.sample.config.security.auth.OAuth2UserInfo;
6 | import com.sample.domain.entity.user.Provider;
7 |
8 | public class Github extends OAuth2UserInfo{
9 |
10 | public Github(Map attributes) {
11 | super(attributes);
12 | }
13 |
14 | @Override
15 | public String getId() {
16 |
17 | return ((Integer) attributes.get("id")).toString();
18 | }
19 |
20 | @Override
21 | public String getName() {
22 |
23 | return (String) attributes.get("name");
24 | }
25 |
26 | @Override
27 | public String getEmail() {
28 |
29 | return (String) attributes.get("email");
30 | }
31 |
32 | @Override
33 | public String getImageUrl() {
34 |
35 | return (String) attributes.get("avatar_url");
36 | }
37 |
38 | @Override
39 | public String getProvider(){
40 | return Provider.github.toString();
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/payload/response/AuthResponse.java:
--------------------------------------------------------------------------------
1 | package com.sample.payload.response;
2 |
3 | import io.swagger.v3.oas.annotations.media.Schema;
4 |
5 | import lombok.Builder;
6 | import lombok.Data;
7 |
8 | @Data
9 | public class AuthResponse {
10 |
11 | @Schema( type = "string", example = "eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2NTI3OTgxOTh9.6CoxHB_siOuz6PxsxHYQCgUT1_QbdyKTUwStQDutEd1-cIIARbQ0cyrnAmpIgi3IBoLRaqK7N1vXO42nYy4g5g" , description="access token 을 출력합니다.")
12 | private String accessToken;
13 |
14 | @Schema( type = "string", example = "eyJhbGciOiJIUzUxMiJ9.eyJleHAiOjE2NTI3OTgxOTh9.asdf8as4df865as4dfasdf65_asdfweioufsdoiuf_432jdsaFEWFSDV_sadf" , description="refresh token 을 출력합니다.")
15 | private String refreshToken;
16 |
17 | @Schema( type = "string", example ="Bearer", description="권한(Authorization) 값 해더의 명칭을 지정합니다.")
18 | private String tokenType = "Bearer";
19 |
20 | public AuthResponse(){};
21 |
22 | @Builder
23 | public AuthResponse(String accessToken, String refreshToken) {
24 | this.accessToken = accessToken;
25 | this.refreshToken = refreshToken;
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/frontend/sample/src/user/signup/Signup.css:
--------------------------------------------------------------------------------
1 | .signup-container {
2 | background-color: rgb(31, 31, 31);
3 | min-height: calc(100vh - 60px);
4 | text-align: center;
5 | }
6 |
7 |
8 | .signup-content {
9 | background: rgb(25, 25, 25);
10 | box-shadow: 0 1px 11px rgba(0, 0, 0, 0.27);
11 | border-radius: 2px;
12 | width: 500px;
13 | display: inline-block;
14 | margin-top: 30px;
15 | vertical-align: middle;
16 | position: relative;
17 | padding: 35px;
18 | }
19 |
20 | .social-btn {
21 | color: azure;
22 | margin-bottom: 15px;
23 | font-weight: 400;
24 | font-size: 16px;
25 | }
26 |
27 | .social-btn:hover {
28 | color:aliceblue;
29 | }
30 |
31 | .social-btn img {
32 | height: 32px;
33 | float: left;
34 | margin-top: 10px;
35 | }
36 |
37 | .social-btn.google {
38 | margin-top: 7px;
39 | }
40 |
41 | .social-btn.facebook img {
42 | height: 24px;
43 | margin-left: 3px;
44 | }
45 |
46 | .social-btn.github img {
47 | height: 24px;
48 | margin-left: 3px;
49 | }
50 |
51 | .login-link {
52 | color: azure;
53 | font-size: 14px;
54 | }
55 |
56 | .signup-title {
57 | font-size: 1.5em;
58 | font-weight: 500;
59 | margin-top: 0;
60 | margin-bottom: 30px;
61 | color: azure;
62 | }
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/config/security/OAuth2Config.java:
--------------------------------------------------------------------------------
1 | package com.sample.config.security;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import org.springframework.boot.context.properties.ConfigurationProperties;
7 | import org.springframework.context.annotation.Configuration;
8 |
9 | import lombok.Data;
10 |
11 | @Configuration
12 | @ConfigurationProperties(prefix = "app")
13 | public class OAuth2Config {
14 | private final Auth auth = new Auth();
15 | private final OAuth2 oauth2 = new OAuth2();
16 |
17 | @Data
18 | public static class Auth {
19 | private String tokenSecret;
20 | private long accessTokenExpirationMsec;
21 | private long refreshTokenExpirationMsec;
22 | }
23 |
24 | public static final class OAuth2 {
25 | private List authorizedRedirectUris = new ArrayList<>();
26 |
27 | public List getAuthorizedRedirectUris() {
28 | return authorizedRedirectUris;
29 | }
30 |
31 | public OAuth2 authorizedRedirectUris(List authorizedRedirectUris) {
32 | this.authorizedRedirectUris = authorizedRedirectUris;
33 | return this;
34 | }
35 | }
36 |
37 | public Auth getAuth() {
38 | return auth;
39 | }
40 |
41 | public OAuth2 getOauth2() {
42 | return oauth2;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/frontend/sample/src/user/oauth2/OAuth2RedirectHandler.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { ACCESS_TOKEN, REFRESH_TOKEN } from '../../constants';
3 | import { Redirect } from 'react-router-dom'
4 |
5 | class OAuth2RedirectHandler extends Component {
6 | getUrlParameter(name) {
7 | name = name.replace(/[\\[]/, '\\[').replace(/[\]]/, '\\]');
8 | var regex = new RegExp('[\\?&]' + name + '=([^]*)');
9 |
10 | var results = regex.exec(this.props.location.search);
11 | return results === null ? '' : decodeURIComponent(results[1].replace(/\+/g, ' '));
12 | };
13 |
14 | render() {
15 | const token = this.getUrlParameter('token');
16 | const error = this.getUrlParameter('error');
17 |
18 | if(token) {
19 | localStorage.setItem(ACCESS_TOKEN, token);
20 | localStorage.setItem(REFRESH_TOKEN, null);
21 | return ;
25 | } else {
26 | return ;
33 | }
34 | }
35 | }
36 |
37 | export default OAuth2RedirectHandler;
--------------------------------------------------------------------------------
/frontend/sample/src/user/profile/Profile.css:
--------------------------------------------------------------------------------
1 | .profile-container {
2 | background: rgb(25, 25, 25);
3 | min-height: calc(100vh - 60px);
4 | padding-top: 200px;
5 | }
6 |
7 | .profile-info {
8 | text-align: center;
9 | }
10 |
11 | .profile-info .profile-avatar img {
12 | border-radius: 50%;
13 | max-width: 250px;
14 | }
15 |
16 | .profile-info .profile-name {
17 | color:azure;
18 | font-weight: 500;
19 | font-size: 18px;
20 | }
21 |
22 | .profile-info .profile-email {
23 | color:azure;
24 | font-weight: 400;
25 | }
26 |
27 | .text-avatar {
28 | width: 200px;
29 | height: 200px;
30 | margin: 0 auto;
31 | vertical-align: middle;
32 | text-align: center;
33 | border-radius: 50%;
34 | background: rgb(82, 82, 82);
35 | /* background: linear-gradient(45deg,#46b5e5 1%,#1e88e5 64%,#40baf5 97%);
36 | background-image: -ms-linear-gradient(45deg,#46b5e5 1%,#1e88e5 64%,#40baf5 97%);
37 | background-image: -moz-linear-gradient(45deg,#46b5e5 1%,#1e88e5 64%,#40baf5 97%);
38 | background-image: -o-linear-gradient(45deg,#46b5e5 1%,#1e88e5 64%,#40baf5 97%);
39 | background-image: -webkit-linear-gradient(45deg,#46b5e5 1%,#1e88e5 64%,#40baf5 97%);
40 | background-image: linear-gradient(45deg,#46b5e5 1%,#1e88e5 64%,#40baf5 97%); */
41 | }
42 |
43 | .text-avatar span {
44 | line-height: 200px;
45 | color: #fff;
46 | font-size: 3em;
47 | }
--------------------------------------------------------------------------------
/frontend/sample/src/util/APIUtils.js:
--------------------------------------------------------------------------------
1 | import { API_BASE_URL, ACCESS_TOKEN } from '../constants';
2 |
3 | const request = (options) => {
4 | const headers = new Headers({
5 | 'Content-Type': 'application/json',
6 | })
7 |
8 | if(localStorage.getItem(ACCESS_TOKEN)) {
9 | headers.append('Authorization', 'Bearer ' + localStorage.getItem(ACCESS_TOKEN))
10 | }
11 |
12 | const defaults = {headers: headers};
13 | options = Object.assign({}, defaults, options);
14 |
15 | return fetch(options.url, options)
16 | .then(response =>
17 | response.json().then(json => {
18 | if(!response.ok) {
19 | return Promise.reject(json);
20 | }
21 | return json;
22 | })
23 | );
24 | };
25 |
26 | export function getCurrentUser() {
27 | if(!localStorage.getItem(ACCESS_TOKEN)) {
28 | return Promise.reject("No access token set.");
29 | }
30 |
31 | return request({
32 | url: API_BASE_URL + "/auth/",
33 | method: 'GET'
34 | });
35 | }
36 |
37 | export function login(loginRequest) {
38 | return request({
39 | url: API_BASE_URL + "/auth/signin",
40 | method: 'POST',
41 | body: JSON.stringify(loginRequest)
42 | });
43 | }
44 |
45 | export function signup(signupRequest) {
46 | return request({
47 | url: API_BASE_URL + "/auth/signup",
48 | method: 'POST',
49 | body: JSON.stringify(signupRequest)
50 | });
51 | }
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/config/security/auth/OAuth2UserInfoFactory.java:
--------------------------------------------------------------------------------
1 | package com.sample.config.security.auth;
2 |
3 | import java.util.Map;
4 |
5 | import com.sample.advice.assertThat.DefaultAssert;
6 | import com.sample.config.security.auth.company.Facebook;
7 | import com.sample.config.security.auth.company.Github;
8 | import com.sample.config.security.auth.company.Google;
9 | import com.sample.config.security.auth.company.Kakao;
10 | import com.sample.config.security.auth.company.Naver;
11 | import com.sample.domain.entity.user.Provider;
12 |
13 | public class OAuth2UserInfoFactory {
14 | public static OAuth2UserInfo getOAuth2UserInfo(String registrationId, Map attributes) {
15 | if(registrationId.equalsIgnoreCase(Provider.google.toString())) {
16 | return new Google(attributes);
17 | } else if (registrationId.equalsIgnoreCase(Provider.facebook.toString())) {
18 | return new Facebook(attributes);
19 | } else if (registrationId.equalsIgnoreCase(Provider.github.toString())) {
20 | return new Github(attributes);
21 | } else if (registrationId.equalsIgnoreCase(Provider.naver.toString())) {
22 | return new Naver(attributes);
23 | } else if (registrationId.equalsIgnoreCase(Provider.kakao.toString())) {
24 | return new Kakao(attributes);
25 | } else {
26 | DefaultAssert.isAuthentication("해당 oauth2 기능은 지원하지 않습니다.");
27 | }
28 | return null;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/config/security/auth/company/Facebook.java:
--------------------------------------------------------------------------------
1 | package com.sample.config.security.auth.company;
2 |
3 | import java.util.Map;
4 |
5 | import com.sample.config.security.auth.OAuth2UserInfo;
6 | import com.sample.domain.entity.user.Provider;
7 |
8 | public class Facebook extends OAuth2UserInfo{
9 |
10 | public Facebook(Map attributes) {
11 | super(attributes);
12 | }
13 |
14 | @Override
15 | public String getId() {
16 | return (String) attributes.get("id");
17 | }
18 |
19 | @Override
20 | public String getName() {
21 | return (String) attributes.get("name");
22 | }
23 |
24 | @Override
25 | public String getEmail() {
26 |
27 | return (String) attributes.get("email");
28 | }
29 |
30 | @Override
31 | public String getImageUrl() {
32 |
33 | if(attributes.containsKey("picture")) {
34 |
35 | Map pictureObj = (Map) attributes.get("picture");
36 | if(pictureObj.containsKey("data")) {
37 | Map dataObj = (Map) pictureObj.get("data");
38 | if(dataObj.containsKey("url")) {
39 | return (String) dataObj.get("url");
40 | }
41 | }
42 | }
43 | return null;
44 | }
45 |
46 | @Override
47 | public String getProvider(){
48 | return Provider.facebook.toString();
49 | }
50 |
51 | }
52 |
--------------------------------------------------------------------------------
/frontend/sample/src/user/login/Login.css:
--------------------------------------------------------------------------------
1 | .login-container {
2 | background-color: rgb(31, 31, 31);
3 | min-height: calc(100vh - 60px);
4 | text-align: center;
5 | }
6 |
7 | .login-content {
8 | background: rgb(25, 25, 25);
9 | box-shadow: 0 1px 11px rgba(0, 0, 0, 0.27);
10 | border-radius: 2px;
11 | width: 500px;
12 | display: inline-block;
13 | margin-top: 30px;
14 | vertical-align: middle;
15 | position: relative;
16 | padding: 35px;
17 |
18 | }
19 |
20 | .social-btn {
21 | color: azure;
22 | margin-bottom: 15px;
23 | font-weight: 400;
24 | font-size: 16px;
25 | border-color: rgb(62, 62, 62);
26 | background-color: rgb(82, 82, 82);
27 | }
28 |
29 |
30 | .social-btn:hover {
31 | color:aliceblue;
32 | }
33 |
34 | .social-btn img {
35 | height: 32px;
36 | float: left;
37 | margin-top: 10px;
38 | }
39 |
40 | .social-btn.google {
41 | margin-top: 7px;
42 | }
43 |
44 | .social-btn.facebook img {
45 | height: 24px;
46 | margin-left: 3px;
47 | }
48 |
49 | .social-btn.github img {
50 | height: 24px;
51 | margin-left: 3px;
52 | }
53 |
54 | .social-btn.kakao img {
55 | height: 24px;
56 | margin-left: 3px;
57 | }
58 |
59 | .social-btn.naver img {
60 | height: 24px;
61 | margin-left: 3px;
62 | }
63 |
64 | .signup-link {
65 | color: azure;
66 | font-size: 14px;
67 | }
68 |
69 | .login-title {
70 | font-size: 1.5em;
71 | font-weight: 500;
72 | margin-top: 0;
73 | margin-bottom: 30px;
74 | color: azure;
75 | }
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/service/auth/CustomUserDetailsService.java:
--------------------------------------------------------------------------------
1 | package com.sample.service.auth;
2 |
3 | import java.util.Optional;
4 |
5 | import javax.transaction.Transactional;
6 |
7 | import com.sample.advice.assertThat.DefaultAssert;
8 | import com.sample.config.security.token.UserPrincipal;
9 | import com.sample.domain.entity.user.User;
10 | import com.sample.repository.user.UserRepository;
11 |
12 | import org.springframework.security.core.userdetails.UserDetails;
13 | import org.springframework.security.core.userdetails.UserDetailsService;
14 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
15 | import org.springframework.stereotype.Service;
16 |
17 | import lombok.RequiredArgsConstructor;
18 |
19 | @RequiredArgsConstructor
20 | @Service
21 | public class CustomUserDetailsService implements UserDetailsService{
22 |
23 | private final UserRepository userRepository;
24 |
25 | @Override
26 | public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
27 |
28 | User user = userRepository.findByEmail(email)
29 | .orElseThrow(() ->
30 | new UsernameNotFoundException("유저 정보를 찾을 수 없습니다.")
31 | );
32 |
33 | return UserPrincipal.create(user);
34 | }
35 |
36 | @Transactional
37 | public UserDetails loadUserById(Long id) {
38 | Optional user = userRepository.findById(id);
39 | DefaultAssert.isOptionalPresent(user);
40 |
41 | return UserPrincipal.create(user.get());
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/frontend/sample/src/user/profile/Profile.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import './Profile.css';
3 |
4 | class Profile extends Component {
5 | constructor(props) {
6 | super(props);
7 | console.log(props);
8 | }
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 | {
16 | this.props.currentUser.information.imageUrl ? (
17 |

18 | ) : (
19 |
20 | {this.props.currentUser.information.name && this.props.currentUser.information.name[0]}
21 |
22 | )
23 | }
24 |
25 |
26 |
{this.props.currentUser.information.name}
27 |
{this.props.currentUser.information.email}
28 |
29 |
30 |
31 |
32 | );
33 | }
34 | }
35 |
36 | export default Profile
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/config/security/auth/company/Kakao.java:
--------------------------------------------------------------------------------
1 | package com.sample.config.security.auth.company;
2 |
3 | import java.util.Map;
4 |
5 | import com.sample.config.security.auth.OAuth2UserInfo;
6 | import com.sample.domain.entity.user.Provider;
7 |
8 | public class Kakao extends OAuth2UserInfo{
9 |
10 | public Kakao(Map attributes) {
11 | super(attributes);
12 | }
13 |
14 | @Override
15 | public String getId() {
16 | return attributes.get("id").toString();
17 | }
18 |
19 | @Override
20 | public String getName() {
21 | Map properties = (Map) attributes.get("properties");
22 |
23 | if (properties == null) {
24 | return null;
25 | }
26 |
27 | return (String) properties.get("nickname");
28 | }
29 |
30 | @Override
31 | public String getEmail() {
32 | Map properties = (Map) attributes.get("kakao_account");
33 | if (properties == null) {
34 | return null;
35 | }
36 | return (String) properties.get("email");
37 | }
38 |
39 | @Override
40 | public String getImageUrl() {
41 | Map properties = (Map) attributes.get("properties");
42 |
43 | if (properties == null) {
44 | return null;
45 | }
46 |
47 | return (String) properties.get("thumbnail_image");
48 | }
49 |
50 | @Override
51 | public String getProvider(){
52 | return Provider.kakao.toString();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/config/security/auth/company/Naver.java:
--------------------------------------------------------------------------------
1 | package com.sample.config.security.auth.company;
2 |
3 | import java.util.Map;
4 |
5 | import com.sample.config.security.auth.OAuth2UserInfo;
6 | import com.sample.domain.entity.user.Provider;
7 |
8 | public class Naver extends OAuth2UserInfo{
9 |
10 | public Naver(Map attributes) {
11 | super(attributes);
12 | }
13 |
14 | @Override
15 | public String getId() {
16 | Map response = (Map) attributes.get("response");
17 |
18 | if (response == null) {
19 | return null;
20 | }
21 |
22 | return (String) response.get("id");
23 | }
24 |
25 | @Override
26 | public String getName() {
27 | Map response = (Map) attributes.get("response");
28 |
29 | if (response == null) {
30 | return null;
31 | }
32 |
33 | return (String) response.get("nickname");
34 | }
35 |
36 | @Override
37 | public String getEmail() {
38 | Map response = (Map) attributes.get("response");
39 |
40 | if (response == null) {
41 | return null;
42 | }
43 |
44 | return (String) response.get("email");
45 | }
46 |
47 | @Override
48 | public String getImageUrl() {
49 | Map response = (Map) attributes.get("response");
50 |
51 | if (response == null) {
52 | return null;
53 | }
54 |
55 | return (String) response.get("profile_image");
56 | }
57 |
58 | @Override
59 | public String getProvider(){
60 | return Provider.naver.toString();
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/frontend/sample/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/backend/sample/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'org.springframework.boot' version '2.6.7'
3 | id 'io.spring.dependency-management' version '1.0.11.RELEASE'
4 | id 'java'
5 | }
6 |
7 | group = 'com'
8 | version = '0.0.1-SNAPSHOT'
9 | sourceCompatibility = '11'
10 |
11 | repositories {
12 | mavenCentral()
13 | }
14 |
15 | dependencies {
16 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
17 | implementation 'org.springframework.boot:spring-boot-starter-security'
18 | implementation 'org.springframework.boot:spring-boot-starter-validation'
19 | implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
20 | implementation 'org.springframework.boot:spring-boot-starter-web'
21 |
22 | implementation 'org.apache.poi:poi:5.2.0'
23 |
24 | implementation 'com.googlecode.json-simple:json-simple:1.1.1'
25 |
26 | implementation group: 'org.springdoc', name: 'springdoc-openapi-ui', version: '1.6.4'
27 | implementation group: 'org.springdoc', name: 'springdoc-openapi-security', version: '1.6.4'
28 | testImplementation group: 'org.springdoc', name: 'springdoc-openapi-webmvc-core', version: '1.6.4'
29 |
30 | implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
31 | runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
32 | runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'
33 |
34 | compileOnly 'org.projectlombok:lombok'
35 | developmentOnly 'org.springframework.boot:spring-boot-devtools'
36 | runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
37 | annotationProcessor 'org.projectlombok:lombok'
38 |
39 | testImplementation 'org.springframework.boot:spring-boot-starter-test'
40 | testImplementation 'org.springframework.security:spring-security-test'
41 | }
42 |
43 | tasks.named('test') {
44 | useJUnitPlatform()
45 | }
46 |
--------------------------------------------------------------------------------
/frontend/sample/src/common/AppHeader.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Link, NavLink } from 'react-router-dom';
3 | import './AppHeader.css';
4 |
5 | class AppHeader extends Component {
6 | render() {
7 | return (
8 |
38 | )
39 | }
40 | }
41 |
42 | export default AppHeader;
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/config/security/handler/CustomSimpleUrlAuthenticationFailureHandler.java:
--------------------------------------------------------------------------------
1 | package com.sample.config.security.handler;
2 |
3 | import org.springframework.security.core.AuthenticationException;
4 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
5 | import org.springframework.stereotype.Component;
6 | import org.springframework.web.util.UriComponentsBuilder;
7 |
8 | import lombok.RequiredArgsConstructor;
9 |
10 | import static com.sample.repository.auth.CustomAuthorizationRequestRepository.REDIRECT_URI_PARAM_COOKIE_NAME;
11 |
12 | import java.io.IOException;
13 |
14 | import javax.servlet.ServletException;
15 | import javax.servlet.http.Cookie;
16 | import javax.servlet.http.HttpServletRequest;
17 | import javax.servlet.http.HttpServletResponse;
18 |
19 | import com.sample.config.security.util.CustomCookie;
20 | import com.sample.repository.auth.CustomAuthorizationRequestRepository;
21 |
22 | @RequiredArgsConstructor
23 | @Component
24 | public class CustomSimpleUrlAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler{
25 | private final CustomAuthorizationRequestRepository customAuthorizationRequestRepository;
26 |
27 | @Override
28 | public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
29 | String targetUrl = CustomCookie.getCookie(request, REDIRECT_URI_PARAM_COOKIE_NAME)
30 | .map(Cookie::getValue)
31 | .orElse(("/"));
32 |
33 | targetUrl = UriComponentsBuilder.fromUriString(targetUrl)
34 | .queryParam("error", exception.getLocalizedMessage())
35 | .build().toUriString();
36 |
37 | customAuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response);
38 |
39 | getRedirectStrategy().sendRedirect(request, response, targetUrl);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/config/docs/OpenApiConfig.java:
--------------------------------------------------------------------------------
1 | package com.sample.config.docs;
2 |
3 | import org.springframework.beans.factory.annotation.Value;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 |
7 | import io.swagger.v3.oas.models.Components;
8 | import io.swagger.v3.oas.models.OpenAPI;
9 | import io.swagger.v3.oas.models.info.Contact;
10 | import io.swagger.v3.oas.models.info.Info;
11 | import io.swagger.v3.oas.models.info.License;
12 | import io.swagger.v3.oas.models.security.SecurityRequirement;
13 | import io.swagger.v3.oas.models.security.SecurityScheme;
14 |
15 | @Configuration
16 | public class OpenApiConfig {
17 |
18 | private final String securitySchemeName = "bearerAuth";
19 |
20 | @Bean
21 | public OpenAPI openAPI(@Value("OpenAPI") String appVersion) {
22 | Info info = new Info().title("Demo API").version(appVersion)
23 | .description("Spring Boot를 이용한 Demo 웹 애플리케이션 API입니다.")
24 | .termsOfService("http://swagger.io/terms/")
25 | .contact(new Contact().name("name").url("https://name.name.name/").email("name@name.name"))
26 | .license(new License().name("Apache License Version 2.0")
27 | .url("http://www.apache.org/licenses/LICENSE-2.0"));
28 |
29 | return new OpenAPI()
30 | .addSecurityItem(new SecurityRequirement().addList(securitySchemeName))
31 | .components(
32 | new Components()
33 | .addSecuritySchemes(securitySchemeName,
34 | new SecurityScheme()
35 | .name(securitySchemeName)
36 | .type(SecurityScheme.Type.HTTP)
37 | .scheme("bearer")
38 | .bearerFormat("JWT")
39 | )
40 | )
41 | .info(info);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/domain/entity/user/User.java:
--------------------------------------------------------------------------------
1 | package com.sample.domain.entity.user;
2 |
3 | import javax.persistence.Column;
4 | import javax.persistence.Entity;
5 | import javax.persistence.EnumType;
6 | import javax.persistence.Enumerated;
7 | import javax.persistence.GeneratedValue;
8 | import javax.persistence.GenerationType;
9 | import javax.persistence.Id;
10 | import javax.persistence.Table;
11 | import javax.validation.constraints.Email;
12 | import javax.validation.constraints.NotNull;
13 |
14 | import com.fasterxml.jackson.annotation.JsonIgnore;
15 | import com.sample.domain.entity.time.DefaultTime;
16 |
17 | import org.hibernate.annotations.DynamicUpdate;
18 |
19 | import lombok.Builder;
20 | import lombok.Getter;
21 |
22 | @DynamicUpdate
23 | @Entity
24 | @Getter
25 | @Table(name = "user")
26 | public class User extends DefaultTime{
27 | @Id
28 | @GeneratedValue(strategy = GenerationType.IDENTITY)
29 | private Long id;
30 |
31 | @Column(nullable = false)
32 | private String name;
33 |
34 | @Email
35 | @Column(nullable = false)
36 | private String email;
37 |
38 | private String imageUrl;
39 |
40 | @Column(nullable = false)
41 | private Boolean emailVerified = false;
42 |
43 | @JsonIgnore
44 | private String password;
45 |
46 | @NotNull
47 | @Enumerated(EnumType.STRING)
48 | private Provider provider;
49 |
50 | @Enumerated(EnumType.STRING)
51 | private Role role;
52 |
53 | private String providerId;
54 |
55 | public User(){}
56 |
57 | @Builder
58 | public User(String name, String email, String password, Role role, Provider provider, String providerId, String imageUrl){
59 | this.email = email;
60 | this.password = password;
61 | this.name = name;
62 | this.provider = provider;
63 | this.role = role;
64 | }
65 |
66 | public void updateName(String name){
67 | this.name = name;
68 | }
69 |
70 | public void updateImageUrl(String imageUrl){
71 | this.imageUrl = imageUrl;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/frontend/sample/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Diagnostic reports (https://nodejs.org/api/report.html)
10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 | *.lcov
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # TypeScript cache
48 | *.tsbuildinfo
49 |
50 | # Optional npm cache directory
51 | .npm
52 |
53 | # Optional eslint cache
54 | .eslintcache
55 |
56 | # Microbundle cache
57 | .rpt2_cache/
58 | .rts2_cache_cjs/
59 | .rts2_cache_es/
60 | .rts2_cache_umd/
61 |
62 | # Optional REPL history
63 | .node_repl_history
64 |
65 | # Output of 'npm pack'
66 | *.tgz
67 |
68 | # Yarn Integrity file
69 | .yarn-integrity
70 |
71 | # dotenv environment variables file
72 | .env
73 | .env.test
74 |
75 | # parcel-bundler cache (https://parceljs.org/)
76 | .cache
77 |
78 | # Next.js build output
79 | .next
80 |
81 | # Nuxt.js build / generate output
82 | .nuxt
83 | dist
84 |
85 | # Gatsby files
86 | .cache/
87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js
88 | # https://nextjs.org/blog/next-9-1#public-directory-support
89 | # public
90 |
91 | # vuepress build output
92 | .vuepress/dist
93 |
94 | # Serverless directories
95 | .serverless/
96 |
97 | # FuseBox cache
98 | .fusebox/
99 |
100 | # DynamoDB Local files
101 | .dynamodb/
102 |
103 | # TernJS port file
104 | .tern-port
105 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/config/security/util/CustomCookie.java:
--------------------------------------------------------------------------------
1 | package com.sample.config.security.util;
2 |
3 | import java.util.Base64;
4 | import java.util.Optional;
5 |
6 | import javax.servlet.http.Cookie;
7 | import javax.servlet.http.HttpServletRequest;
8 | import javax.servlet.http.HttpServletResponse;
9 |
10 | import org.springframework.util.SerializationUtils;
11 |
12 | public class CustomCookie {
13 |
14 | public static Optional getCookie(HttpServletRequest request, String name){
15 | Cookie[] cookies = request.getCookies();
16 |
17 | if (cookies != null && cookies.length > 0) {
18 | for (Cookie cookie : cookies) {
19 | if (cookie.getName().equals(name)) {
20 | return Optional.of(cookie);
21 | }
22 | }
23 | }
24 |
25 | return Optional.empty();
26 | }
27 |
28 | public static void addCookie(HttpServletResponse response, String name, String value, int maxAge) {
29 | Cookie cookie = new Cookie(name, value);
30 |
31 | cookie.setPath("/");
32 | cookie.setHttpOnly(true);
33 | cookie.setMaxAge(maxAge);
34 | response.addCookie(cookie);
35 | }
36 |
37 | public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String name) {
38 | Cookie[] cookies = request.getCookies();
39 | if (cookies != null && cookies.length > 0) {
40 | for (Cookie cookie: cookies) {
41 | if (cookie.getName().equals(name)) {
42 | cookie.setValue("");
43 | cookie.setPath("/");
44 | cookie.setMaxAge(0);
45 | response.addCookie(cookie);
46 | }
47 | }
48 | }
49 | }
50 |
51 |
52 | public static String serialize(Object object) {
53 | return Base64.getUrlEncoder().encodeToString(SerializationUtils.serialize(object));
54 | }
55 |
56 | public static T deserialize(Cookie cookie, Class cls) {
57 | return cls.cast(SerializationUtils.deserialize(Base64.getUrlDecoder().decode(cookie.getValue())));
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/config/security/token/CustomOncePerRequestFilter.java:
--------------------------------------------------------------------------------
1 | package com.sample.config.security.token;
2 |
3 | import java.io.IOException;
4 |
5 | import javax.servlet.FilterChain;
6 | import javax.servlet.ServletException;
7 | import javax.servlet.http.HttpServletRequest;
8 | import javax.servlet.http.HttpServletResponse;
9 |
10 | import com.sample.service.auth.CustomTokenProviderService;
11 |
12 | import org.springframework.beans.factory.annotation.Autowired;
13 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
14 | import org.springframework.security.core.context.SecurityContextHolder;
15 | import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
16 | import org.springframework.util.StringUtils;
17 | import org.springframework.web.filter.OncePerRequestFilter;
18 |
19 | import lombok.extern.slf4j.Slf4j;
20 |
21 | @Slf4j
22 | public class CustomOncePerRequestFilter extends OncePerRequestFilter{
23 |
24 | @Autowired
25 | private CustomTokenProviderService customTokenProviderService;
26 |
27 | @Override
28 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
29 | String jwt = getJwtFromRequest(request);
30 |
31 | if (StringUtils.hasText(jwt) && customTokenProviderService.validateToken(jwt)) {
32 | UsernamePasswordAuthenticationToken authentication = customTokenProviderService.getAuthenticationById(jwt);
33 | authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
34 | SecurityContextHolder.getContext().setAuthentication(authentication);
35 | }
36 |
37 | filterChain.doFilter(request, response);
38 | }
39 |
40 | private String getJwtFromRequest(HttpServletRequest request) {
41 | String bearerToken = request.getHeader("Authorization");
42 | if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer")) {
43 | log.info("bearerToken = {}", bearerToken.substring(7, bearerToken.length()));
44 | return bearerToken.substring(7, bearerToken.length());
45 | }
46 | return null;
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/backend/sample/src/main/java/com/sample/advice/assertThat/DefaultAssert.java:
--------------------------------------------------------------------------------
1 | package com.sample.advice.assertThat;
2 |
3 | import java.util.List;
4 | import java.util.Optional;
5 |
6 | import com.sample.advice.error.DefaultAuthenticationException;
7 | import com.sample.advice.error.DefaultException;
8 | import com.sample.advice.error.DefaultNullPointerException;
9 | import com.sample.advice.error.InvalidParameterException;
10 | import com.sample.advice.payload.ErrorCode;
11 |
12 | import org.springframework.util.Assert;
13 | import org.springframework.validation.Errors;
14 |
15 | public class DefaultAssert extends Assert{
16 |
17 | public static void isTrue(boolean value){
18 | if(!value){
19 | throw new DefaultException(ErrorCode.INVALID_CHECK);
20 | }
21 | }
22 |
23 | public static void isTrue(boolean value, String message){
24 | if(!value){
25 | throw new DefaultException(ErrorCode.INVALID_CHECK, message);
26 | }
27 | }
28 |
29 | public static void isValidParameter(Errors errors){
30 | if(errors.hasErrors()){
31 | throw new InvalidParameterException(errors);
32 | }
33 | }
34 |
35 | public static void isObjectNull(Object object){
36 | if(object == null){
37 | throw new DefaultNullPointerException(ErrorCode.INVALID_CHECK);
38 | }
39 | }
40 |
41 | public static void isListNull(List