Skip to content

Commit

Permalink
Merge pull request #8 from AI-SIP/develop
Browse files Browse the repository at this point in the history
구글 로그인 JWT 토큰 기능 추가
  • Loading branch information
KiSeungMin authored Jul 25, 2024
2 parents a1c23df + da0eb47 commit 7348ab9
Show file tree
Hide file tree
Showing 26 changed files with 408 additions and 322 deletions.
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
implementation 'com.google.api-client:google-api-client:2.4.0'
implementation 'com.auth0:java-jwt:4.4.0'
implementation group: 'com.google.auth', name: 'google-auth-library-oauth2-http', version: '1.23.0'
implementation group: 'com.google.http-client', name: 'google-http-client-jackson2', version: '1.44.2'
implementation group: 'org.springframework.security', name: 'spring-security-core', version: '6.3.1'
implementation 'org.springframework.boot:spring-boot-starter-security'
// Hibernate Core
implementation 'org.hibernate:hibernate-core:6.5.2.Final'
compileOnly 'org.projectlombok:lombok'
Expand Down
33 changes: 33 additions & 0 deletions src/main/java/com/aisip/OnO/backend/Auth/GoogleTokenVerifier.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.aisip.OnO.backend.Auth;

import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Collections;

@Component
public class GoogleTokenVerifier {

@Value("${spring.security.oauth2.client.registration.google.client-id}")
private String clientId; // static 제거

public GoogleIdToken.Payload verifyToken(String idTokenString) throws GeneralSecurityException, IOException {

GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), new JacksonFactory())
.setAudience(Collections.singletonList(clientId))
.build();

GoogleIdToken idToken = verifier.verify(idTokenString);
if (idToken != null) {
return idToken.getPayload();
} else {
throw new GeneralSecurityException("Invalid ID token.");
}
}
}
53 changes: 53 additions & 0 deletions src/main/java/com/aisip/OnO/backend/Auth/JwtTokenFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.aisip.OnO.backend.Auth;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Collections;

public class JwtTokenFilter extends OncePerRequestFilter {

private final String secret;

public JwtTokenFilter(String secret) {
this.secret = secret;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
try {
Algorithm algorithm = Algorithm.HMAC512(secret);
JWTVerifier verifier = JWT.require(algorithm).build();
DecodedJWT decodedJWT = verifier.verify(token);

String username = decodedJWT.getSubject();
Long userId = decodedJWT.getClaim("userId").asLong();

UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userId, null, Collections.emptyList());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

SecurityContextHolder.getContext().setAuthentication(authentication);
} catch (JWTVerificationException e) {
// JWT Verification failed
SecurityContextHolder.clearContext();
}
}

filterChain.doFilter(request, response);
}
}
35 changes: 35 additions & 0 deletions src/main/java/com/aisip/OnO/backend/Auth/JwtTokenProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.aisip.OnO.backend.Auth;

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Date;

@Component
public class JwtTokenProvider {

@Value("${spring.jwt.secret}")
private String secret;

@Value("${spring.jwt.expiration}")
private long expirationTime;

public String createToken(Long userId, String email) {
return JWT.create()
.withSubject(email)
.withClaim("userId", userId)
.withIssuedAt(new Date())
.withExpiresAt(new Date(System.currentTimeMillis() + expirationTime))
.sign(Algorithm.HMAC512(secret.getBytes()));
}

public Long getUserIdFromToken(String token) {
return JWT.decode(token).getClaim("userId").asLong();
}

public String getEmailFromToken(String token) {
return JWT.decode(token).getSubject();
}
}
39 changes: 39 additions & 0 deletions src/main/java/com/aisip/OnO/backend/Auth/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.aisip.OnO.backend.Auth;

import org.springframework.beans.factory.annotation.Value;
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 {

@Value("${spring.jwt.secret}")
private String secret;

@Bean
public JwtTokenFilter jwtTokenFilter() {
return new JwtTokenFilter(secret);
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests
.requestMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(sessionManagement ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);

return http.build();
}
}
2 changes: 2 additions & 0 deletions src/main/java/com/aisip/OnO/backend/BackendApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
public class BackendApplication {

public static void main(String[] args) {
Expand Down
27 changes: 0 additions & 27 deletions src/main/java/com/aisip/OnO/backend/Dto/User/UserRegisterDto.java

This file was deleted.

95 changes: 95 additions & 0 deletions src/main/java/com/aisip/OnO/backend/controller/AuthController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.aisip.OnO.backend.controller;

import com.aisip.OnO.backend.service.AuthService;
import com.aisip.OnO.backend.Auth.GoogleTokenVerifier;
import com.aisip.OnO.backend.Auth.JwtTokenProvider;
import com.aisip.OnO.backend.entity.User;
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
import com.google.common.io.BaseEncoding;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.security.GeneralSecurityException;

@RestController
@RequestMapping("/api/auth")
public class AuthController {

@Autowired
private GoogleTokenVerifier googleTokenVerifier;

@Autowired
private AuthService authService;

@Autowired
private JwtTokenProvider jwtTokenProvider;

@PostMapping("/google")
public ResponseEntity<?> googleLogin(@RequestBody TokenRequest tokenRequest) {
try {
GoogleIdToken.Payload payload = googleTokenVerifier.verifyToken(tokenRequest.getIdToken());
User user = authService.registerOrLoginUser(payload.getEmail(), (String) payload.get("name"));
String token = jwtTokenProvider.createToken(user.getUserId(), user.getEmail());
return ResponseEntity.ok(new AuthResponse(token));
} catch (IllegalArgumentException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponse("Invalid ID token format"));
} catch (BaseEncoding.DecodingException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(new ErrorResponse("ID token decoding error"));
} catch (GeneralSecurityException | IOException e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(new ErrorResponse("Invalid Google token"));
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(new ErrorResponse("Internal server error"));
}
}

public static class TokenRequest {
private String idToken;

public String getIdToken() {
return idToken;
}

public void setIdToken(String idToken) {
this.idToken = idToken;
}
}

public static class AuthResponse {
private String token;

public AuthResponse(String token) {
this.token = token;
}

public String getToken() {
return token;
}

public void setToken(String token) {
this.token = token;
}
}

public static class ErrorResponse {
private String error;

public ErrorResponse(String error) {
this.error = error;
}

public String getError() {
return error;
}

public void setError(String error) {
this.error = error;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
import com.aisip.OnO.backend.service.ProblemService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.coyote.Response;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import java.util.List;
Expand All @@ -23,10 +23,11 @@ public class ProblemController {

@GetMapping("/problem/{problemId}")
public ResponseEntity<?> getProblemByUserId(
@RequestHeader("userId") Long userId,
Authentication authentication,
@PathVariable("problemId") Long problemId
) {
try {
Long userId = (Long) authentication.getPrincipal();
ProblemResponseDto problemResponseDto = problemService.findProblemByUserId(userId, problemId);
return ResponseEntity.ok(problemResponseDto);
} catch (ProblemNotFoundException e) {
Expand All @@ -35,18 +36,19 @@ public ResponseEntity<?> getProblemByUserId(
}

@GetMapping("/problems")
public ResponseEntity<?> getProblemsByUserId(@RequestHeader("userId") Long userId){
public ResponseEntity<?> getProblemsByUserId(Authentication authentication) {
Long userId = (Long) authentication.getPrincipal();
List<ProblemResponseDto> problems = problemService.findAllProblemsByUserId(userId);
return ResponseEntity.ok(problems);
}

@PostMapping("/problem")
public ResponseEntity<?> registerProblem(
@RequestHeader("userId") Long userId,
Authentication authentication,
@ModelAttribute ProblemRegisterDto problemRegisterDto
) {
try {
System.out.println(problemRegisterDto.toString());
Long userId = (Long) authentication.getPrincipal();
boolean isSaved = problemService.saveProblem(userId, problemRegisterDto);

if(isSaved){
Expand All @@ -62,28 +64,13 @@ public ResponseEntity<?> registerProblem(
}
}

/*
@PutMapping("/problem/{problemId}")
public ResponseEntity<?> updateProblem(
@RequestHeader("userId") Long userId,
@PathVariable("problemId") Long problemId,
@RequestBody ProblemRegisterDto problemRegisterDto
) {
try {
ProblemResponseDto updatedProblem = problemService.updateProblem(userId, problemId, problemRegisterDto);
return ResponseEntity.ok(updatedProblem);
} catch (ProblemNotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage());
}
}
*/

@DeleteMapping("/problem")
public ResponseEntity<?> deleteProblem(
@RequestHeader("userId") Long userId,
Authentication authentication,
@RequestHeader("problemId") Long problemId
) {
try {
Long userId = (Long) authentication.getPrincipal();
problemService.deleteProblem(userId, problemId);
return ResponseEntity.ok("삭제를 완료했습니다.");
} catch (ProblemNotFoundException e) {
Expand Down
Loading

0 comments on commit 7348ab9

Please sign in to comment.