Skip to content

Commit

Permalink
feat : 카카오 로그인 구현 - #10
Browse files Browse the repository at this point in the history
  • Loading branch information
lreowy committed Jan 14, 2025
1 parent 02f7e3d commit bd1b2ba
Show file tree
Hide file tree
Showing 28 changed files with 756 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.cakey.common.auth;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

private final ObjectMapper mapper = new ObjectMapper();

@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
setResponse(response);
}

private void setResponse(HttpServletResponse response) throws IOException {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");

ResponseEntity responseEntity = new ResponseEntity(HttpStatus.NOT_FOUND);

response.getWriter().write(mapper.writeValueAsString(responseEntity));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.cakey.common.auth;

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

@Component
public class CustomJwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException){
setResponse(response);
}

private void setResponse(HttpServletResponse response){
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package com.cakey.common.auth;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;
import javax.crypto.SecretKey;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

@Component
public class JwtTokenProvider {

private static final String USER_ID = "userId";
private static final Long ACCESS_TOKEN_EXPIRATION_TIME = 2 * 60 * 60 * 1000L; //2시간
private static final Long REFRESH_TOKEN_EXPIRATION_TIME = 14* 24 * 60 * 60 * 1000L; //2주

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

@PostConstruct
protected void init() {
JWT_SECRET = Base64.getEncoder().encodeToString(JWT_SECRET.getBytes(StandardCharsets.UTF_8));
}

public String issueAccessToken(final Authentication authentication) {
return issueToken(authentication, ACCESS_TOKEN_EXPIRATION_TIME);
}

public String issueRefreshToken(final Authentication authentication) {
return issueToken(authentication, REFRESH_TOKEN_EXPIRATION_TIME);
}

private String issueToken(final Authentication authentication, final Long expireTime) {
final Date now = new Date();

final Claims claims = Jwts.claims()
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + expireTime));

claims.put(USER_ID, authentication.getPrincipal());
return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
.setClaims(claims)
.signWith(getSigningKey())
.compact();
}

private SecretKey getSigningKey() {
String encodedKey = Base64.getEncoder().encodeToString(JWT_SECRET.getBytes());
return Keys.hmacShaKeyFor(encodedKey.getBytes());
}

public JwtValidationType validateToken(final String token) {
try {
final Claims claims = getBody(token);
return JwtValidationType.VALID_JWT;
} catch (MalformedJwtException e) {
return JwtValidationType.INVALID_JWT_TOKEN;
} catch (ExpiredJwtException e) {
return JwtValidationType.EXPIRED_JWT_TOKEN;
} catch (UnsupportedJwtException e) {
return JwtValidationType.UNSUPPORTED_JWT_TOKEN;
} catch (IllegalArgumentException e) {
return JwtValidationType.EMPTY_JWT;
}

}

private Claims getBody(final String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}

public Long getUserFromJwt(final String token) {
Claims claims = getBody(token);
return Long.valueOf(claims.get(USER_ID).toString());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.cakey.common.auth;

import lombok.AllArgsConstructor;

@AllArgsConstructor
public enum JwtValidationType {
VALID_JWT("VALID_JWT"),
INVALID_JWT_SIGNATURE("INVALID_JWT_SIGNATURE"),
INVALID_JWT_TOKEN("INVALID_JWT_TOKEN"),
EXPIRED_JWT_TOKEN("EXPIRED_JWT_TOKEN"),
UNSUPPORTED_JWT_TOKEN("UNSUPPORTED_JWT_TOKEN"),
EMPTY_JWT("EMPTY_JWT")
;

private String valdationType;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.cakey.common.auth;

import java.util.Collection;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;

public class UserAuthentication extends UsernamePasswordAuthenticationToken {
public UserAuthentication(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
super(principal, credentials, authorities);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.cakey.common.auth.filter;

import com.cakey.common.auth.JwtTokenProvider;
import com.cakey.common.auth.UserAuthentication;
import com.cakey.jwt.service.TokenService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

@Component
@RequiredArgsConstructor
public class CustomAuthenticationFilter extends OncePerRequestFilter {

private final JwtTokenProvider jwtTokenProvider;
// private final TokenService tokenService;

@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
try {
final String accessToken = getAccessTokenFromCookie(request);
if (accessToken != null) {
final Long userId = jwtTokenProvider.getUserFromJwt(accessToken);
SecurityContextHolder.getContext().setAuthentication(new UserAuthentication(userId, null, null));
} else {
SecurityContextHolder.clearContext();
}
} catch (Exception e) {
}
filterChain.doFilter(request, response);
}

public String getAccessTokenFromCookie(@NonNull HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for (Cookie cookie : cookies) {
if (cookie.getName().equals("access_token")) {
return cookie.getValue();
}
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.cakey.common.auth.filter;

import com.cakey.common.auth.JwtTokenProvider;
import com.cakey.common.auth.UserAuthentication;
import com.cakey.common.auth.JwtValidationType;
import com.cakey.exception.CakeyException;
import com.cakey.exception.ErrorCode;
import com.cakey.jwt.service.TokenService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private final JwtTokenProvider jwtTokenProvider;

@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
try {
final String token = getAccessTokenFromCookie(request);
if (token == null) {
//todo: exception 처리
}

if (jwtTokenProvider.validateToken(token) == JwtValidationType.INVALID_JWT_TOKEN) {
//todo: exception 처리
}

final Long userId = jwtTokenProvider.getUserFromJwt(token);
SecurityContextHolder.getContext().setAuthentication(new UserAuthentication(userId, null, null));

} catch (Exception e) {
}
filterChain.doFilter(request, response);
}

private String getAccessTokenFromCookie(final HttpServletRequest request) {
Cookie[] cookies = request.getCookies();
if (cookies != null) {
for(Cookie cookie : cookies) {
if (cookie.getName().equals("accessToken")) {
return cookie.getValue();
}
}
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.cakey.user.controller;

import com.cakey.client.dto.LoginReq;
import com.cakey.common.resolver.user.UserId;
import com.cakey.common.response.ApiResponseUtil;
import com.cakey.common.response.BaseResponse;
import com.cakey.common.response.SuccessCode;
import com.cakey.jwt.service.TokenService;
import com.cakey.user.dto.LoginSuccessRes;
import com.cakey.user.service.UserService;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/v1/user")
@RequiredArgsConstructor
public class UserController {

private final UserService userService;
private final TokenService tokenService;

@PostMapping("/login")
public ResponseEntity<BaseResponse<?>> login(@RequestHeader(value = "Authorization") final String authorization,
@RequestBody final LoginReq loginReq,
HttpServletResponse response) {
LoginSuccessRes loginSuccessRes = userService.create(authorization, loginReq);
response.addHeader("Set-Cookie", userService.accessCookie(loginSuccessRes).toString());
response.addHeader("Set-Cookie", userService.refreshCookie(loginSuccessRes).toString());
return ApiResponseUtil.success(SuccessCode.OK);
}
}
Loading

0 comments on commit bd1b2ba

Please sign in to comment.