This project is an OAuth2 social login demo application built with Spring Boot. It supports Google, Kakao, and Naver social login with JWT-based authentication system.
- ๐ Social Login (Google, Kakao, Naver)
- ๐ซ JWT-based Authentication (Access Token, Refresh Token)
- ๐ Security Configuration (CORS, CSRF)
- ๐ช Cookie-based Token Management
- ๐ User Terms Agreement Process
Java 21 Spring Boot 3.4.5 Spring Security Spring OAuth2 Client JWT (jjwt 0.12.3) MySQL Redis QueryDSL Lombok
- Social Login Configuration Configure each social login provider (Google, Kakao, Naver) using Spring Security OAuth2 Client Set required scope and user-info-uri for each provider Handle social login user information through CustomOAuth2UserService
- Authentication Process Handle OAuth2 authentication requests through CustomAuthorizationRequestResolver Issue JWT tokens on authentication success via OAuth2AuthenticationSuccessHandler Handle authentication failures through OAuth2AuthenticationFailureHandler
- Token Management Implement JWT-based Access Token and Refresh Token Store and manage Refresh Tokens using Redis Secure token transmission through HttpOnly cookies
- Security Configuration
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) {
return http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.csrf(AbstractHttpConfigurer::disable)
.formLogin(AbstractHttpConfigurer::disable)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/public/**").permitAll()
.requestMatchers("/private/**").authenticated()
.anyRequest().denyAll())
.oauth2Login(oauth2 -> oauth2
.authorizationEndpoint(endpoint -> endpoint
.authorizationRequestResolver(customAuthorizationRequestResolver))
.userInfoEndpoint(userInfo -> userInfo
.userService(customOAuth2UserService))
.successHandler(oAuth2AuthenticationSuccessHandler)
.failureHandler(oAuth2AuthenticationFailureHandler))
.build();
}
}
- Authentication Filters LoginAuthFilter: JWT token validation and authentication handling LogoutAuthFilter: Logout processing and token removal
- CORS Configuration
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);
return source;
}
- Security Enhancements Stateless session management for reduced server load CSRF protection disabled (REST API based) XSS attack prevention through HttpOnly cookies Enhanced security through Redis-based token management
Required Environment Variables properties
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
KAKAO_CLIENT_ID=your_kakao_client_id
KAKAO_CLIENT_SECRET=your_kakao_client_secret
NAVER_CLIENT_ID=your_naver_client_id
NAVER_CLIENT_SECRET=your_naver_client_secret
ACCESS_TOKEN_KEY=your_access_token_key
REFRESH_TOKEN_KEY=your_refresh_token_key
MYSQL_URL=jdbc:mysql://localhost:3306/oauth2_demo
MYSQL_USER_NAME=your_mysql_username
MYSQL_PASSWORD=your_mysql_password
src/main/java/com/example/oauth2demo/
โโโ auth/ # Authentication related code
โ โโโ application/ # Authentication service logic
โ โโโ domain/ # Domain models
โ โโโ dto/ # Data transfer objects
โ โโโ infrastructure/ # Infrastructure related code
โโโ common/ # Common configuration and utilities
โโโ terms/ # Terms related code
โโโ user/ # User related code
- Social Login Process Client requests social login (/api/public/oauth2/{provider}) Redirect to OAuth2 authentication page User authenticates with social login provider Redirect to callback URL on successful authentication Server obtains and processes user information Issue JWT tokens and store in cookies Redirect to main page
- User Registration Process Check for new user after successful social login Redirect to terms agreement page for new users Save user information: Social account information (email, social ID, provider) User role (ROLE_USER) Save terms agreement information Redirect to main page after registration completion
- Logout Process
- Client requests logout (/api/public/logout)
- LogoutAuthFilter processes the request
- Delete Refresh Token from Redis
- Delete Access Token and Refresh Token cookies:
Cookie deletedAccessCookie = cookieProvider.generateDeletedAccessTokenCookie();
Cookie deletedRefreshCookie = cookieProvider.generateDeletedRefreshTokenCookie();
response.addCookie(deletedAccessCookie);
response.addCookie(deletedRefreshCookie);
- Clear SecurityContext
- Redirect to login page
- Token Refresh Process When Access Token expires (30 minutes) Request new Access Token with Refresh Token Validate Refresh Token in Redis Issue new Access Token if valid Store new Access Token in cookie Maintain existing Refresh Token (2 days)
CORS Configuration: Allow cross-origin requests for API endpoints CSRF Protection: Disabled for REST API based architecture Session Management: Uses stateless approach Token Management: Uses HttpOnly cookies
./gradlew test
Set environment variables Start MySQL and Redis servers Run the application:
./gradlew bootRun
Swagger UI: http://localhost:8080/api/swagger-ui/index.html OpenAPI Documentation: http://localhost:8080/api/api-docs
Access Token: 30 minutes Refresh Token: 2 days
Java 21 or higher MySQL 8.0 or higher Redis 6.0 or higher Gradle 7.0 or higher
Fork the repository Create your feature branch (git checkout -b feature/AmazingFeature) Commit your changes (git commit -m 'Add some AmazingFeature') Push to the branch (git push origin feature/AmazingFeature) Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
Author: Your Name Email: your.email@example.com Project Link: https://github.com/yourusername/oauth2-demo
์ด ํ๋ก์ ํธ๋ Spring Boot๋ฅผ ์ฌ์ฉํ OAuth2 ์์ ๋ก๊ทธ์ธ ๋ฐ๋ชจ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๋ค. Google, Kakao, Naver ์์ ๋ก๊ทธ์ธ์ ์ง์ํ๋ฉฐ, JWT ๊ธฐ๋ฐ์ ์ธ์ฆ ์์คํ ์ ๊ตฌํํ์ต๋๋ค.
- ๐ ์์ ๋ก๊ทธ์ธ (Google, Kakao, Naver)
- ๐ซ JWT ๊ธฐ๋ฐ ์ธ์ฆ (Access Token, Refresh Token)
- ๐ ๋ณด์ ์ค์ (CORS, CSRF)
- ๐ช ์ฟ ํค ๊ธฐ๋ฐ ํ ํฐ ๊ด๋ฆฌ
- ๐ ์ฌ์ฉ์ ์ฝ๊ด ๋์ ํ๋ก์ธ์ค
- Java 21
- Spring Boot 3.4.5
- Spring Security
- Spring OAuth2 Client
- JWT (jjwt 0.12.3)
- MySQL
- Redis
- QueryDSL
- Lombok
-
์์ ๋ก๊ทธ์ธ ์ค์
- Spring Security OAuth2 Client๋ฅผ ์ฌ์ฉํ์ฌ ๊ฐ ์์ ๋ก๊ทธ์ธ ์ ๊ณต์(Google, Kakao, Naver) ์ค์
- ๊ฐ ์ ๊ณต์๋ณ๋ก ํ์ํ scope์ user-info-uri ์ค์
- CustomOAuth2UserService๋ฅผ ํตํด ์์ ๋ก๊ทธ์ธ ์ฌ์ฉ์ ์ ๋ณด ์ฒ๋ฆฌ
-
์ธ์ฆ ํ๋ก์ธ์ค
- CustomAuthorizationRequestResolver๋ฅผ ํตํ OAuth2 ์ธ์ฆ ์์ฒญ ์ฒ๋ฆฌ
- OAuth2AuthenticationSuccessHandler์์ ์ธ์ฆ ์ฑ๊ณต ์ JWT ํ ํฐ ๋ฐ๊ธ
- OAuth2AuthenticationFailureHandler์์ ์ธ์ฆ ์คํจ ์ฒ๋ฆฌ
-
ํ ํฐ ๊ด๋ฆฌ
- JWT ๊ธฐ๋ฐ์ Access Token๊ณผ Refresh Token ๊ตฌํ
- Redis๋ฅผ ์ฌ์ฉํ Refresh Token ์ ์ฅ ๋ฐ ๊ด๋ฆฌ
- HttpOnly ์ฟ ํค๋ฅผ ํตํ ์์ ํ ํ ํฐ ์ ์ก
-
๋ณด์ ์ค์
@Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) { return http .cors(cors -> cors.configurationSource(corsConfigurationSource())) .csrf(AbstractHttpConfigurer::disable) .formLogin(AbstractHttpConfigurer::disable) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(authorize -> authorize .requestMatchers("/public/**").permitAll() .requestMatchers("/private/**").authenticated() .anyRequest().denyAll()) .oauth2Login(oauth2 -> oauth2 .authorizationEndpoint(endpoint -> endpoint .authorizationRequestResolver(customAuthorizationRequestResolver)) .userInfoEndpoint(userInfo -> userInfo .userService(customOAuth2UserService)) .successHandler(oAuth2AuthenticationSuccessHandler) .failureHandler(oAuth2AuthenticationFailureHandler)) .build(); } }
-
์ธ์ฆ ํํฐ
- LoginAuthFilter: JWT ํ ํฐ ๊ฒ์ฆ ๋ฐ ์ธ์ฆ ์ฒ๋ฆฌ
- LogoutAuthFilter: ๋ก๊ทธ์์ ์ฒ๋ฆฌ ๋ฐ ํ ํฐ ์ญ์
-
CORS ์ค์
@Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration configuration = new CorsConfiguration(); configuration.setAllowedOrigins(Arrays.asList("*")); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")); configuration.setAllowedHeaders(Arrays.asList("*")); configuration.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/api/**", configuration); return source; }
-
๋ณด์ ๊ฐํ
- Stateless ์ธ์ ๊ด๋ฆฌ๋ก ์๋ฒ ๋ถํ ๊ฐ์
- CSRF ๋ณดํธ ๋นํ์ฑํ (REST API ๊ธฐ๋ฐ)
- HttpOnly ์ฟ ํค๋ฅผ ํตํ XSS ๊ณต๊ฒฉ ๋ฐฉ์ง
- Redis๋ฅผ ์ฌ์ฉํ ํ ํฐ ๊ด๋ฆฌ๋ก ๋ณด์์ฑ ๊ฐํ
# OAuth2 Client Credentials
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret
KAKAO_CLIENT_ID=your_kakao_client_id
KAKAO_CLIENT_SECRET=your_kakao_client_secret
NAVER_CLIENT_ID=your_naver_client_id
NAVER_CLIENT_SECRET=your_naver_client_secret
# JWT Keys
ACCESS_TOKEN_KEY=your_access_token_key
REFRESH_TOKEN_KEY=your_refresh_token_key
# Database
MYSQL_URL=jdbc:mysql://localhost:3306/oauth2_demo
MYSQL_USER_NAME=your_mysql_username
MYSQL_PASSWORD=your_mysql_passwordsrc/main/java/com/example/oauth2demo/
โโโ auth/ # ์ธ์ฆ ๊ด๋ จ ์ฝ๋
โ โโโ application/ # ์ธ์ฆ ์๋น์ค ๋ก์ง
โ โโโ domain/ # ๋๋ฉ์ธ ๋ชจ๋ธ
โ โโโ dto/ # ๋ฐ์ดํฐ ์ ์ก ๊ฐ์ฒด
โ โโโ infrastructure/ # ์ธํ๋ผ ๊ด๋ จ ์ฝ๋
โโโ common/ # ๊ณตํต ์ค์ ๋ฐ ์ ํธ๋ฆฌํฐ
โโโ terms/ # ์ฝ๊ด ๊ด๋ จ ์ฝ๋
โโโ user/ # ์ฌ์ฉ์ ๊ด๋ จ ์ฝ๋
- ํด๋ผ์ด์ธํธ์์ ์์
๋ก๊ทธ์ธ ์์ฒญ (
/api/public/oauth2/{provider}) - OAuth2 ์ธ์ฆ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ
- ์ฌ์ฉ์๊ฐ ์์ ๋ก๊ทธ์ธ ์ ๊ณต์์์ ์ธ์ฆ
- ์ธ์ฆ ์ฑ๊ณต ์ ์ฝ๋ฐฑ URL๋ก ๋ฆฌ๋ค์ด๋ ํธ
- ์๋ฒ์์ ์ฌ์ฉ์ ์ ๋ณด ํ๋ ๋ฐ ์ฒ๋ฆฌ
- JWT ํ ํฐ ๋ฐ๊ธ ๋ฐ ์ฟ ํค์ ์ ์ฅ
- ๋ฉ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ
- ์์ ๋ก๊ทธ์ธ ์ฑ๊ณต ํ ์ ๊ท ์ฌ์ฉ์ ํ์ธ
- ์ ๊ท ์ฌ์ฉ์์ธ ๊ฒฝ์ฐ ์ฝ๊ด ๋์ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ
- ์ฌ์ฉ์ ์ ๋ณด ์ ์ฅ
- ์์ ๊ณ์ ์ ๋ณด (์ด๋ฉ์ผ, ์์ ID, ์ ๊ณต์)
- ์ฌ์ฉ์ ์ญํ (ROLE_USER)
- ์ฝ๊ด ๋์ ์ ๋ณด ์ ์ฅ
- ํ์๊ฐ์ ์๋ฃ ํ ๋ฉ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ
- ํด๋ผ์ด์ธํธ์์ ๋ก๊ทธ์์ ์์ฒญ (
/api/public/logout) - LogoutAuthFilter์์ ์์ฒญ ์ฒ๋ฆฌ
- Redis์์ Refresh Token ์ญ์
- Access Token๊ณผ Refresh Token ์ฟ ํค ์ญ์
Cookie deletedAccessCookie = cookieProvider.generateDeletedAccessTokenCookie(); Cookie deletedRefreshCookie = cookieProvider.generateDeletedRefreshTokenCookie(); response.addCookie(deletedAccessCookie); response.addCookie(deletedRefreshCookie);
- SecurityContext ์ด๊ธฐํ
- ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ
- Access Token ๋ง๋ฃ ์ (30๋ถ)
- Refresh Token์ผ๋ก ์๋ก์ด Access Token ์์ฒญ
- Redis์์ Refresh Token ์ ํจ์ฑ ๊ฒ์ฆ
- ์ ํจํ ๊ฒฝ์ฐ ์๋ก์ด Access Token ๋ฐ๊ธ
- ์๋ก์ด Access Token์ ์ฟ ํค์ ์ ์ฅ
- ๊ธฐ์กด Refresh Token ์ ์ง (2์ผ)
- CORS ์ค์ : API ์๋ํฌ์ธํธ์ ๋ํ ํฌ๋ก์ค ์ค๋ฆฌ์ง ์์ฒญ ํ์ฉ
- CSRF ๋ณดํธ: REST API ๊ธฐ๋ฐ์ผ๋ก CSRF ๋นํ์ฑํ
- ์ธ์ ๊ด๋ฆฌ: Stateless ๋ฐฉ์ ์ฌ์ฉ
- ํ ํฐ ๊ด๋ฆฌ: HttpOnly ์ฟ ํค ์ฌ์ฉ
./gradlew test- ํ๊ฒฝ ๋ณ์ ์ค์
- MySQL ๋ฐ Redis ์๋ฒ ์คํ
- ์ ํ๋ฆฌ์ผ์ด์ ์คํ:
./gradlew bootRun- Swagger UI:
http://localhost:8080/api/swagger-ui/index.html - OpenAPI ๋ฌธ์:
http://localhost:8080/api/api-docs
- Access Token: 30๋ถ
- Refresh Token: 2์ผ