Skip to content
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
6290b43
build : SpringSecurity 의존성 추가
tmxhsk99 Aug 13, 2023
d0243f6
feat : SpringSecurity 설정 및 필터 추가
tmxhsk99 Aug 13, 2023
e3d1669
refactor : 인증 인터셉터 관련 로직 삭제
tmxhsk99 Aug 13, 2023
0974cbf
7-1 Spring security, 7-2 인증 까지 보고 진행
tmxhsk99 Aug 14, 2023
f92be3b
기존 TestHelper를 사용하는 계층형 테스트 구조로 수정
tmxhsk99 Aug 15, 2023
ac873fb
fix : 잘못된 테스트 이전 로직 제거
tmxhsk99 Aug 16, 2023
efd50a3
feat : claims 가 null인 경우 에러 처리 로직 추가
tmxhsk99 Aug 16, 2023
a3750b6
feat : 수정 삭제 요청시 Authentication 받도록 매개변수 추가
tmxhsk99 Aug 16, 2023
6178cae
fix : 기존에 하드 코딩되어있던 부분을 수정
tmxhsk99 Aug 16, 2023
6bb268a
feat : 메서드와 경로에 맞게 인증 로직을 수행하도록 관련 로직 추가
tmxhsk99 Aug 16, 2023
4cce0b9
refactor : 불필요한 주석 제거
tmxhsk99 Aug 16, 2023
79bc305
test : 유저 패스워드 기능 테스트 추가 및 해당 변경에 따른 테스트 수정
tmxhsk99 Aug 17, 2023
7e94f4b
feat : 유저 패스워드 암호화 기능 추가
tmxhsk99 Aug 17, 2023
58215a5
build : aop 관련 의존성 추가
tmxhsk99 Aug 18, 2023
3a54892
test : 다른 사용자 토큰 픽스쳐추가
tmxhsk99 Aug 18, 2023
9c49112
fix : 주석 직관적으로 수정, 경로 접근권한 수정
tmxhsk99 Aug 18, 2023
cbdcc48
chore : 불필요한 주석 삭제
tmxhsk99 Aug 18, 2023
1beee7a
test : 상품수정 권한 체크 테스트 추가
tmxhsk99 Aug 18, 2023
887bfb2
feat : 본인권한 관련 AOP 및 어노테이션 추가
tmxhsk99 Aug 18, 2023
9c3aa22
feat : 유저 권한 없음 에러 로직 추가
tmxhsk99 Aug 18, 2023
90903bb
test : secret 값이 서로 다르게 사용하던 것을 통일
tmxhsk99 Aug 19, 2023
3576adf
test : 상품 수정 삭제 권한 테스트 추가
tmxhsk99 Aug 19, 2023
1c516dd
feat : 상품정보에 추가한 유저아이디 속성 추가
tmxhsk99 Aug 19, 2023
d1a71ee
fix : 상품정보의 등록자정보를 가져와 비교하도록 로직 수정, 상품아이디 매개변수식 , 여러개 매개변수있어도 적용되도록 수정
tmxhsk99 Aug 19, 2023
b6eb6eb
feat : 상품 삭제, 상품 수정 시 권한 관련 로직 추가
tmxhsk99 Aug 19, 2023
e1d9b4d
chore : 상품생성 시 메서드 인터페이스 변화에 따른 수정
tmxhsk99 Aug 19, 2023
bf63a41
feat : 상품생성 시 생성자 정보도 추가하도록 로직 수정
tmxhsk99 Aug 19, 2023
29f2d12
chore : 상품생성 시 인터페이스 변경에 따른 수정
tmxhsk99 Aug 19, 2023
17b2f05
chore : 상품생성 시 인터페이스 변경에 따른 수정
tmxhsk99 Aug 19, 2023
5cb56c2
test : 테스트 전용 설정 파일 추가
tmxhsk99 Aug 19, 2023
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
2 changes: 2 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ dependencies {
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
// Spring Security
implementation 'org.springframework.boot:spring-boot-starter-security'
}

application {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.codesoom.assignment.domain.UserRepository;
import com.codesoom.assignment.errors.LoginFailException;
import com.codesoom.assignment.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Service;

@Service
Expand All @@ -26,11 +25,7 @@ public String login(String email, String password) {
throw new LoginFailException(email);
}

return jwtUtil.encode(1L);
return jwtUtil.encode(user.getId());
}

public Long parseToken(String accessToken) {
Claims claims = jwtUtil.decode(accessToken);
return claims.get("userId", Long.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.codesoom.assignment.config;

import com.codesoom.assignment.filters.AuthenticationErrorFilter;
import com.codesoom.assignment.filters.JwtAuthenticationFilter;
import com.codesoom.assignment.utils.JwtUtil;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.HttpStatusEntryPoint;

import javax.servlet.Filter;
import javax.servlet.http.HttpServletRequest;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {
private final JwtUtil jwtUtil;
public SecurityJavaConfig(JwtUtil jwtUtil) {
this.jwtUtil = jwtUtil;
}


@Override
protected void configure(HttpSecurity http) throws Exception {
Filter authenticationFilter = new JwtAuthenticationFilter(authenticationManager(), jwtUtil);
Filter authenticationErrorFilter = new AuthenticationErrorFilter();

configureAuthorizations(http);

http
.csrf().disable()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 csrf는 왜 disable하는지 이번 기회에 알아보면 좋습니다

.addFilter(authenticationFilter)
.addFilterBefore(authenticationErrorFilter,
JwtAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기서 stateless가 어떤 의미인지 이번기회에 알아보면 좋습니다. 특히 REST API의 특성도 같이 공부하면 좋아요

.and()
.exceptionHandling()
.authenticationEntryPoint(
new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
}

private void configureAuthorizations(HttpSecurity http) throws Exception {
http.authorizeRequests()
.requestMatchers(this::matchesPostProductRequest).authenticated()
.and()
.authorizeRequests()
.requestMatchers(this::matchesPatchProductRequest).authenticated()
.and()
.authorizeRequests()
.requestMatchers(this::matchesDeleteProductRequest).authenticated()
.and()
.authorizeRequests()
.requestMatchers(this::matchesPostUserRequest).authenticated()
.and()
.authorizeRequests()
.requestMatchers(this::matchesDeleteUserRequest).authenticated();
}
/**
* POST /products or /products/{상품아이디} 요청에 대해서만 인증을 요구합니다.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이건 코드를 그대로 풀어서 쓴 것 같아요. 코드가 변경되면 주석이 바로 영향이 받습니다. 코드를 변경했는데 실수로 주석을 수정하지 않으면 큰 혼돈이 올거예요.

* @param req
* @return
*/
private boolean matchesPostProductRequest(HttpServletRequest req) {
return req.getMethod().equals("POST") &&
(req.getRequestURI().matches("^/products$") ||
req.getRequestURI().matches("^/products/[0-9]+$"));
}

/**
* PATCH /products or /products/{상품아이디} 요청에 대해서만 인증을 요구합니다.
* @param req
* @return
*/
private boolean matchesPatchProductRequest(HttpServletRequest req) {
return req.getMethod().equals("PATCH") &&
req.getRequestURI().matches("^/products/[0-9]+$");
}

/**
* DELETE /products or /products/{상품아이디} 요청에 대해서만 인증을 요구합니다.
* @param req
* @return
*/
private boolean matchesDeleteProductRequest(HttpServletRequest req) {
return req.getMethod().equals("DELETE") &&
req.getRequestURI().matches("^/products/[0-9]+$");
}

/**
* POST /users/{유저아이디} 요청에 대해서만 인증을 요구합니다.
* @param req
* @return
*/
private boolean matchesPostUserRequest(HttpServletRequest req) {
return req.getMethod().equals("POST") && req.getRequestURI().matches("^/users/[0-9]+$");
}

/**
* DELETE /users/{유저아이디} 요청에 대해서만 인증을 요구합니다.
* @param req
* @return
*/
private boolean matchesDeleteUserRequest(HttpServletRequest req) {
return req.getMethod().equals("DELETE") && req.getRequestURI().matches("^/users/[0-9]+$");
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,6 @@ public ErrorResponse handleLoginFailException() {
return new ErrorResponse("Log-in failed");
}

@ResponseStatus(HttpStatus.UNAUTHORIZED)
@ExceptionHandler(InvalidTokenException.class)
public ErrorResponse handleInvalidAccessTokenException() {
return new ErrorResponse("Invalid access token");
}

@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(ConstraintViolationException.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

package com.codesoom.assignment.controllers;

import com.codesoom.assignment.application.AuthenticationService;
import com.codesoom.assignment.application.ProductService;
import com.codesoom.assignment.domain.Product;
import com.codesoom.assignment.dto.ProductData;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

import javax.validation.Valid;
Expand All @@ -20,12 +21,8 @@
public class ProductController {
private final ProductService productService;

private final AuthenticationService authenticationService;

public ProductController(ProductService productService,
AuthenticationService authenticationService) {
public ProductController(ProductService productService) {
this.productService = productService;
this.authenticationService = authenticationService;
}

@GetMapping
Expand All @@ -40,16 +37,17 @@ public Product detail(@PathVariable Long id) {

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@PreAuthorize("isAuthenticated() and hasAuthority('USER')")
public Product create(
@RequestAttribute Long userId,
@RequestBody @Valid ProductData productData
@RequestBody @Valid ProductData productData,
Authentication authentication
) {
return productService.createProduct(productData);
}

@PatchMapping("{id}")
public Product update(
@RequestAttribute Long userId,
Authentication authentication,
@PathVariable Long id,
@RequestBody @Valid ProductData productData
) {
Expand All @@ -59,7 +57,7 @@ public Product update(
@DeleteMapping("{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void destroy(
@RequestAttribute Long userId,
Authentication authentication,
@PathVariable Long id
) {
productService.deleteProduct(id);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package com.codesoom.assignment.dto;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class SessionRequestData {
private String email;
private String password;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.codesoom.assignment.filters;

import com.codesoom.assignment.errors.InvalidTokenException;
import org.springframework.http.HttpStatus;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class AuthenticationErrorFilter extends HttpFilter {
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
chain.doFilter(request,response);
}catch (InvalidTokenException e){
response.sendError(HttpStatus.UNAUTHORIZED.value());
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.codesoom.assignment.filters;

import com.codesoom.assignment.errors.InvalidTokenException;
import com.codesoom.assignment.security.UserAuthentication;
import com.codesoom.assignment.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
private JwtUtil jwtUtil;

public JwtAuthenticationFilter(AuthenticationManager authenticationManager, JwtUtil jwtUtil) {
super(authenticationManager);
this.jwtUtil = jwtUtil;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {

String authorization = request.getHeader("Authorization");

if (authorization != null) {
String accessToken = authorization.substring("Bearer ".length());
Claims claims = jwtUtil.decode(accessToken);

if(claims == null){
throw new InvalidTokenException(accessToken);
}

Long userId = claims.get("userId", Long.class);
Authentication authentication = new UserAuthentication(userId);


SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(authentication);
}

chain.doFilter(request, response);

}

}

This file was deleted.

Loading