Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Seminar/#11 #12

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added .DS_Store
Binary file not shown.
1 change: 1 addition & 0 deletions week06/.idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions week06/practice/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@ dependencies {
//Security
implementation 'org.springframework.boot:spring-boot-starter-security'

//redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis:2.3.1.RELEASE'





}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,4 @@ public void isPrincipalNull(
throw new UnauthorizedException(ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
package org.sopt.practice.auth;

import lombok.RequiredArgsConstructor;

import org.sopt.practice.auth.filter.CustomAccessDeniedHandler;
import org.sopt.practice.auth.filter.CustomJwtAuthenticationEntryPoint;
import org.sopt.practice.auth.filter.JwtAuthenticationFilter;
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.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer;

import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@RequiredArgsConstructor
Expand Down Expand Up @@ -46,4 +46,4 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

return http.build();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package org.sopt.practice.auth;

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

import java.util.Collection;

public class UserAuthentication extends UsernamePasswordAuthenticationToken {

public UserAuthentication(Object principal, Object credentials, Collection<? extends GrantedAuthority> authorities) {
Expand All @@ -13,4 +13,4 @@ public UserAuthentication(Object principal, Object credentials, Collection<? ext
public static UserAuthentication createUserAuthentication(Long userId) {
return new UserAuthentication(userId, null, null);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException {
setResponse(response);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public class CustomJwtAuthenticationEntryPoint implements AuthenticationEntryPoi
private final ObjectMapper objectMapper;

@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException {
setResponse(response);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
package org.sopt.practice.auth.filter;

import static org.sopt.practice.common.jwt.JwtValidationType.VALID_JWT;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;

import org.sopt.practice.auth.UserAuthentication;
import org.sopt.practice.common.dto.ErrorMessage;
import org.sopt.practice.common.jwt.JwtTokenProvider;
import org.sopt.practice.common.jwt.JwtValidationType;
import org.sopt.practice.exception.UnauthorizedException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;


@Component
@RequiredArgsConstructor
public class JwtAuthenticationFilter extends OncePerRequestFilter {
Expand All @@ -31,12 +33,17 @@ protected void doFilterInternal(@NonNull HttpServletRequest request,
@NonNull FilterChain filterChain) throws ServletException, IOException {
try {
final String token = getJwtFromRequest(request);
if (jwtTokenProvider.validateToken(token) == VALID_JWT) {
JwtValidationType validationType = jwtTokenProvider.validateToken(token);
if (validationType == JwtValidationType.VALID_JWT) {
Long memberId = jwtTokenProvider.getUserFromJwt(token);
UserAuthentication authentication = UserAuthentication.createUserAuthentication(memberId);
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
UserAuthentication authentication = UserAuthentication.createUserAuthentication(
memberId);
authentication.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
} catch (UnauthorizedException unauthorizedException) {
throw unauthorizedException;
} catch (Exception exception) {
throw new UnauthorizedException(ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.sopt.practice.auth.redis.config;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
public class RedisConfig {

@Value("${spring.data.redis.host}")
private String host;

@Value("${spring.data.redis.port}")
private int port;

@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package org.sopt.practice.auth.redis.domain;


import jakarta.persistence.Id;
import jakarta.persistence.Index;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;


@RedisHash(value = "", timeToLive = 60 * 60 * 24 * 1000L * 14)
@AllArgsConstructor
@Getter
@Builder
public class Token {

@Id
private Long id;

@Indexed
private String refreshToken;

public static Token of(
final Long id,
final String refreshToken
){
return Token.builder()
.id(id)
.refreshToken(refreshToken)
.build();
}





}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.sopt.practice.auth.redis.repository;


import java.util.Optional;
import org.sopt.practice.auth.redis.domain.Token;
import org.springframework.data.repository.CrudRepository;

public interface RedisTokenRepository extends CrudRepository<Token, String> {
Optional<Token> findByRefreshToken(final String refreshToken);
Optional<Token> findById(final Long id);
}
Original file line number Diff line number Diff line change
@@ -1,38 +1,24 @@
package org.sopt.practice.common;

import org.sopt.practice.common.dto.ErrorResponse;
import org.sopt.practice.exception.ForbiddenException;
import org.sopt.practice.exception.NotFoundException;
import org.sopt.practice.exception.UnauthorizedException;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.Objects;

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ErrorResponse.of(HttpStatus.BAD_REQUEST.value(), Objects.requireNonNull(e.getBindingResult().getFieldError().getDefaultMessage())));
} //메세지가 없을 수도 있으니 Objects

@ExceptionHandler(NotFoundException.class)
protected ResponseEntity<ErrorResponse> handleNotFoundException(NotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ErrorResponse.of(HttpStatus.NOT_FOUND.value(), e.getMessage()));
}

@ExceptionHandler(ForbiddenException.class)
protected ResponseEntity<ErrorResponse> handleForbiddenException(ForbiddenException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ErrorResponse.of(HttpStatus.FORBIDDEN.value(), e.getMessage()));
}

@ExceptionHandler(UnauthorizedException.class)
protected ResponseEntity<ErrorResponse> handlerUnauthorizedException(UnauthorizedException e) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ErrorResponse.of(e.getErrorMessage().getStatus(), e.getErrorMessage().getMessage()));
}

@ExceptionHandler(NotFoundException.class)
protected ResponseEntity<ErrorResponse> handleNotFoundException(NotFoundException e) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ErrorResponse.of(e.getErrorMessage()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ public enum ErrorMessage {

JWT_UNAUTHORIZED_EXCEPTION(HttpStatus.UNAUTHORIZED.value(), "사용자의 로그인 검증을 실패했습니다."),

FORBIDDEN_MEMBER_ACCESS(HttpStatus.FORBIDDEN.value(), "이 멤버는 해당 블로그에 대한 접근 권한이 없습니다.")
FORBIDDEN_MEMBER_ACCESS(HttpStatus.FORBIDDEN.value(), "이 멤버는 해당 블로그에 대한 접근 권한이 없습니다."),

;
INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED.value(), "유효하지 않은 refresh 토큰입니다.");
private final int status;

private final String message;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
package org.sopt.practice.common.jwt;

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.*;
import io.jsonwebtoken.security.Keys;
import java.util.Base64;
import java.util.Date;
import javax.crypto.SecretKey;
import lombok.RequiredArgsConstructor;
import org.sopt.practice.common.jwt.JwtValidationType;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Value;
import javax.crypto.SecretKey;
import java.util.Base64;
import java.util.Date;

@Component
@RequiredArgsConstructor
public class JwtTokenProvider {

private static final String USER_ID = "userId";

private static final Long ACCESS_TOKEN_EXPIRATION_TIME = 24 * 60 * 60 * 1000L * 14;
private static final Long ACCESS_TOKEN_EXPIRATION_TIME = 24 * 60 * 60 * 1000L;
private static final Long REFRESH_TOKEN_EXPIRATION_TIME = 24 * 60 * 60 * 1000L * 14;

@Value("${jwt.secret}")
private String JWT_SECRET;
Expand All @@ -32,6 +27,9 @@ public String issueAccessToken(final Authentication authentication) {
return generateToken(authentication, ACCESS_TOKEN_EXPIRATION_TIME);
}

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

public String generateToken(Authentication authentication, Long tokenExpirationTime) {
final Date now = new Date();
Expand Down Expand Up @@ -80,4 +78,5 @@ public Long getUserFromJwt(String token) {
Claims claims = getBody(token);
return Long.valueOf(claims.get(USER_ID).toString());
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ public class BlogController {

private final BlogService blogService;

@PostMapping("/blog")
public ResponseEntity createBlog(
@ModelAttribute BlogCreateRequest blogCreateRequest
) {
return ResponseEntity.created(URI.create(blogService.create(
principalHandler.getUserIdFromPrincipal(), blogCreateRequest))).build();
}

@PatchMapping("/blog/{blogId}/title")
public ResponseEntity updateBlogTitle(
Expand All @@ -31,12 +38,12 @@ public ResponseEntity updateBlogTitle(
}

private final PrincipalHandler principalHandler;

/*
@PostMapping("/blog")
public ResponseEntity createBlog(
BlogCreateRequest blogCreateRequest
) {
return ResponseEntity.created(URI.create(blogService.create(
principalHandler.getUserIdFromPrincipal(), blogCreateRequest))).build();
}
}*/
}
Loading