Skip to content

Commit

Permalink
Merge pull request #2 from Central-MakeUs/1-social-login-구현하기
Browse files Browse the repository at this point in the history
social login 구현하기
  • Loading branch information
tmddus2 authored Jul 15, 2024
2 parents 79fe549 + 7e18599 commit d778997
Show file tree
Hide file tree
Showing 13 changed files with 376 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Purithm_Server
# PURITHM-Server
CMC 15기 PURITHM 서버 레포지토리입니다.
7 changes: 7 additions & 0 deletions purithm/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,14 @@ repositories {

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.example.purithm.auth.config;

import com.example.purithm.auth.exception.JWTAuthenticationEntryPoint;
import com.example.purithm.auth.filter.JWTFilter;
import com.example.purithm.auth.handler.OAuth2AuthenticationFailureHandler;
import com.example.purithm.auth.handler.OAuth2AuthenticationSuccessHandler;
import com.example.purithm.auth.jwt.JWTUtil;
import com.example.purithm.auth.service.OAuth2UserService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

private final OAuth2UserService oAuth2UserService;
private final OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler;
private final OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler;
private final JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JWTUtil jwtUtil;

public SecurityConfig(
OAuth2UserService oAuth2UserService,
OAuth2AuthenticationSuccessHandler oAuth2AuthenticationSuccessHandler,
OAuth2AuthenticationFailureHandler oAuth2AuthenticationFailureHandler,
JWTAuthenticationEntryPoint jwtAuthenticationEntryPoint,
JWTUtil jwtUtil) {
this.oAuth2UserService = oAuth2UserService;
this.oAuth2AuthenticationSuccessHandler = oAuth2AuthenticationSuccessHandler;
this.oAuth2AuthenticationFailureHandler = oAuth2AuthenticationFailureHandler;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtUtil = jwtUtil;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable())
.formLogin(formLogin -> formLogin.disable())
.httpBasic(httpBasic -> httpBasic.disable())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(new JWTFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class)
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/api/**").authenticated()
.anyRequest().permitAll()
)
.oauth2Login(oauth2Login -> oauth2Login
.authorizationEndpoint(authorizationEndpoint -> authorizationEndpoint
.baseUri("/oauth2/authorization")
)
.redirectionEndpoint(redirectionEndpoint -> redirectionEndpoint
.baseUri("/login/oauth2/code/*")
)
.userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint.userService(oAuth2UserService))
.successHandler(oAuth2AuthenticationSuccessHandler)
.failureHandler(oAuth2AuthenticationFailureHandler)
)
.exceptionHandling(handler -> handler.authenticationEntryPoint(jwtAuthenticationEntryPoint));

return http.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.example.purithm.auth.dto.response;

import lombok.Builder;

@Builder
public record LoginSuccessResponseDto(
int code,
String message,
String token
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.example.purithm.auth.entity;

import java.util.Collection;
import java.util.Map;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.OAuth2User;

public class CustomOAuth2User implements OAuth2User {

private final String username;

public CustomOAuth2User(String username) {
this.username = username;
}

@Override
public <A> A getAttribute(String name) {
return OAuth2User.super.getAttribute(name);
}

@Override
public Map<String, Object> getAttributes() {
return null;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}

@Override
public String getName() {
return username;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.example.purithm.auth.exception;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

@Component
public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {

@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendError(
HttpServletResponse.SC_UNAUTHORIZED,
authException.getLocalizedMessage()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.example.purithm.auth.filter;

import com.example.purithm.auth.entity.CustomOAuth2User;
import com.example.purithm.auth.jwt.JWTUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

public class JWTFilter extends OncePerRequestFilter {

private final JWTUtil jwtUtil;

public JWTFilter(JWTUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
try {
String token = request.getHeader("Authorization");

if (token == null || !token.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}

token = token.substring(7);
if (jwtUtil.isExpired(token)) {
filterChain.doFilter(request, response);
return;
}

String username = jwtUtil.getUsername(token);

CustomOAuth2User oAuth2User = new CustomOAuth2User(username);
Authentication authToken = new UsernamePasswordAuthenticationToken(oAuth2User, null, null);
SecurityContextHolder.getContext().setAuthentication(authToken);
} catch (Exception e) {
request.setAttribute("exception", e);
}

filterChain.doFilter(request, response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.example.purithm.auth.handler;

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

@Component
public class OAuth2AuthenticationFailureHandler implements AuthenticationFailureHandler {

@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
response.setContentType("application/json");
response.setStatus(response.SC_UNAUTHORIZED);
response.getOutputStream().println("{\"error\": \"Unauthorized\", \"message\": \"" + exception.getMessage() + "\"}");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.example.purithm.auth.handler;

import com.example.purithm.auth.dto.response.LoginSuccessResponseDto;
import com.example.purithm.auth.entity.CustomOAuth2User;
import com.example.purithm.auth.jwt.JWTUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

@Component
public class OAuth2AuthenticationSuccessHandler implements AuthenticationSuccessHandler {

private final JWTUtil jwtUtil;

public OAuth2AuthenticationSuccessHandler(JWTUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}

@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
CustomOAuth2User oAuth2User = (CustomOAuth2User) authentication.getPrincipal();

String token = jwtUtil.createJwt(oAuth2User.getName(), 60 * 60 * 60 * 1000L);

response.setContentType("application/json");
response.setStatus(response.SC_OK);
LoginSuccessResponseDto body = LoginSuccessResponseDto.builder()
.code(200).message("login success").token(token).build();

ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(body);

response.getOutputStream().println(json);
}
}
36 changes: 36 additions & 0 deletions purithm/src/main/java/com/example/purithm/auth/jwt/JWTUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.example.purithm.auth.jwt;

import io.jsonwebtoken.Jwts;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class JWTUtil {
private SecretKey secretKey;

public JWTUtil(@Value("${spring.jwt.secret}")String secret) {
this.secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), Jwts.SIG.HS256.key().build().getAlgorithm());
}

public String getUsername(String token) {

return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().get("username", String.class);
}

public Boolean isExpired(String token) {
return Jwts.parser().verifyWith(secretKey).build().parseSignedClaims(token).getPayload().getExpiration().before(new Date());
}

public String createJwt(String nickname, Long expiredMs) {
return Jwts.builder()
.claim("nickname", nickname)
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + expiredMs))
.signWith(secretKey)
.compact();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.example.purithm.auth.service;

import com.example.purithm.auth.entity.CustomOAuth2User;
import com.example.purithm.user.entity.User;
import com.example.purithm.user.repository.UserRepository;
import java.util.Map;
import lombok.RequiredArgsConstructor;

import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class OAuth2UserService extends DefaultOAuth2UserService {

private final UserRepository userRepository;

@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);

String provider = userRequest.getClientRegistration().getRegistrationId().toUpperCase();
Map<String, Object> properties = oAuth2User.getAttribute("properties");
String id = oAuth2User.getAttributes().get("id").toString();

String username = provider+" "+id;
User existUser = userRepository.findByUsername(username);

if (existUser == null) {
User user = User.builder()
.profile((String) properties.get("thumbnail_image"))
.nickname((String) properties.get("nickname"))
.username(username)
.build();

userRepository.save(user);
}

return new CustomOAuth2User(username);
}

}
32 changes: 32 additions & 0 deletions purithm/src/main/java/com/example/purithm/user/entity/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.example.purithm.user.entity;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String username;

private String nickname;

private String profile;

@Builder
public User(String username, String nickname, String profile) {
this.username = username;
this.nickname = nickname;
this.profile = profile;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.example.purithm.user.repository;

import com.example.purithm.user.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}

0 comments on commit d778997

Please sign in to comment.