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

7주차 과제 - Spring Security 인가(Authorization) 구현하기 #74

Merged
merged 32 commits into from
Sep 25, 2022
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
3d1e47f
Spring Security 추가
jdalma Sep 19, 2022
48a38f6
SpringSecurity Filter 추가로 인해 주석 처리
jdalma Sep 19, 2022
1d029ac
advice에서 InvalidTokenException 예외 주석 처리
jdalma Sep 19, 2022
aeb3fb1
TODO 추가
jdalma Sep 19, 2022
6d7e5b8
상품 삭제 시 토큰 없는 경우 테스트 코드 추가
jdalma Sep 19, 2022
79e26d0
SpringSecurity 인증 및 인가 추가
jdalma Sep 19, 2022
7d0ffd6
단일 import문으로 변경
jdalma Sep 19, 2022
4f9695f
User Password 암호화 , bcrypt 사용
jdalma Sep 19, 2022
2ae9974
Formatter 추가
jdalma Sep 20, 2022
f8f1731
authenticate() 테스트 코드 수정
jdalma Sep 20, 2022
b89ad93
User update() 테스트 코드 작성 중
jdalma Sep 20, 2022
43ef954
주석 삭제
jdalma Sep 21, 2022
0cb6ec0
try 수정
jdalma Sep 21, 2022
4587c85
사용자 테스트 코드 수정
jdalma Sep 21, 2022
9d7538d
DozerMapper 제거
jdalma Sep 22, 2022
05fc813
테스트 코드 DisplayName 수정
jdalma Sep 22, 2022
80d8717
DozerMapper 제거
jdalma Sep 22, 2022
f4614c9
ProductData toProduct() maker 필드 추가
jdalma Sep 22, 2022
bf1ef94
AuthenticationFilter filterWithPathAndMethod() 수정
jdalma Sep 22, 2022
15e299c
ProductController 테스트 코드 content 부분 수정
jdalma Sep 22, 2022
d803878
JwtAuthenticationFilter JavaDoc 작성
jdalma Sep 23, 2022
e5e9e6c
JwtUtil.encode() 수정
jdalma Sep 23, 2022
ef37c86
UserController create() 테스트 코드 추가
jdalma Sep 23, 2022
9e54f15
사용자별 권한 작업 중
jdalma Sep 23, 2022
3fd2645
JwtAuthenticationFilter 주석 수정
jdalma Sep 25, 2022
0d5abc2
user 자신의 정보만 수정 가능
jdalma Sep 25, 2022
edc9fcf
UserController 기존 테스트 통과하도록 수정
jdalma Sep 25, 2022
6c44e40
jwt secret 수정
jdalma Sep 25, 2022
63ea12a
DTO 기본 생성자 추가
jdalma Sep 25, 2022
e69315f
User 삭제는 관리자만 가능하게 수정
jdalma Sep 25, 2022
c585ae8
ddl-auto: update 수정
jdalma Sep 25, 2022
9079fd1
과제 풀이 - 권한 적용
jdalma Sep 25, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ Spring Security를 이용해서 구현해 주세요.
내부에서 비밀번호를 탈취를 하더라도 원래 비밀번호를 알아낼 수 없도록 비밀번호를 암호화하여
저장해 주세요.

### TODO

1. 자신의 정보만 수정 가능하게
2. 사용자 삭제는 관리자만 가능하게
3. userId에 따라서 권한을 따로 부여

### 로그인이 필요없는 API

* 로그인 - `POST /session`
Expand Down
3 changes: 3 additions & 0 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
runtimeOnly 'com.h2database:h2'

// Spring Security
implementation 'org.springframework.boot:spring-boot-starter-security'

// Spring Developer Tools
developmentOnly 'org.springframework.boot:spring-boot-devtools'

Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/com/codesoom/assignment/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@SpringBootApplication
public class App {
Expand All @@ -16,4 +18,9 @@ public static void main(String[] args) {
public Mapper dozerMapper() {
return DozerBeanMapperBuilder.buildDefault();
johngrib marked this conversation as resolved.
Show resolved Hide resolved
}

@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,18 @@
import com.codesoom.assignment.errors.LoginFailException;
import com.codesoom.assignment.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class AuthenticationService {
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
private final JwtUtil jwtUtil;

public AuthenticationService(UserRepository userRepository,
public AuthenticationService(PasswordEncoder passwordEncoder, UserRepository userRepository,
JwtUtil jwtUtil) {
this.passwordEncoder = passwordEncoder;
this.userRepository = userRepository;
this.jwtUtil = jwtUtil;
}
Expand All @@ -22,7 +25,7 @@ public String login(String email, String password) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new LoginFailException(email));

if (!user.authenticate(password)) {
if (!user.authenticate(password , passwordEncoder)) {
throw new LoginFailException(email);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.codesoom.assignment.config;

import com.codesoom.assignment.application.AuthenticationService;
import com.codesoom.assignment.filters.AuthenticationErrorFilter;
import com.codesoom.assignment.filters.JwtAuthenticationFilter;
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;

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityJavaConfig extends WebSecurityConfigurerAdapter {

private final AuthenticationService authenticationService;

public SecurityJavaConfig(AuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}

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

http.csrf().disable()
.addFilter(authenticationFilter)
// authenticationFilter를 필터로 등록
// authenticationFilter의 전처리 필터 authenticationErrorFilter를 등록
.addFilterBefore(authenticationErrorFilter , JwtAuthenticationFilter.class)
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
johngrib marked this conversation as resolved.
Show resolved Hide resolved
// super.configure(http);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ public class WebJavaConfig implements WebMvcConfigurer {

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authenticationInterceptor);
// SpringSecurity Filter 추가로 인해 주석 처리
// registry.addInterceptor(authenticationInterceptor);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.codesoom.assignment.controllers;

import com.codesoom.assignment.dto.ErrorResponse;
import com.codesoom.assignment.errors.*;
import com.codesoom.assignment.errors.LoginFailException;
import com.codesoom.assignment.errors.ProductNotFoundException;
import com.codesoom.assignment.errors.UserEmailDuplicationException;
import com.codesoom.assignment.errors.UserNotFoundException;
import com.codesoom.assignment.filters.AuthenticationErrorFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
Expand Down Expand Up @@ -39,12 +43,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,28 +4,35 @@

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 lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;
import java.util.List;

@Slf4j
@RestController
@RequestMapping("/products")
@CrossOrigin
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,26 +47,26 @@ public Product detail(@PathVariable Long id) {

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
@PreAuthorize("isAuthenticated() and hasAuthority('USER')")
johngrib marked this conversation as resolved.
Show resolved Hide resolved
public Product create(
@RequestAttribute Long userId,
@RequestBody @Valid ProductData productData
) {
return productService.createProduct(productData);
}

@PatchMapping("{id}")
@PreAuthorize("isAuthenticated()")
public Product update(
@RequestAttribute Long userId,
@PathVariable Long id,
@RequestBody @Valid ProductData productData
) {
return productService.updateProduct(id, productData);
}

@DeleteMapping("{id}")
@PreAuthorize("isAuthenticated()")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void destroy(
@RequestAttribute Long userId,
@PathVariable Long id
) {
productService.deleteProduct(id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@
import com.codesoom.assignment.dto.SessionRequestData;
import com.codesoom.assignment.dto.SessionResponseData;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/session")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,16 @@
import com.codesoom.assignment.dto.UserRegistrationData;
import com.codesoom.assignment.dto.UserResultData;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import javax.validation.Valid;

Expand All @@ -28,6 +37,7 @@ UserResultData create(@RequestBody @Valid UserRegistrationData registrationData)
}

@PatchMapping("{id}")
@PreAuthorize("isAuthenticated()")
UserResultData update(
@PathVariable Long id,
@RequestBody @Valid UserModificationData modificationData
Expand All @@ -37,6 +47,7 @@ UserResultData update(
}

@DeleteMapping("{id}")
@PreAuthorize("isAuthenticated()")
@ResponseStatus(HttpStatus.NO_CONTENT)
void destroy(@PathVariable Long id) {
userService.deleteUser(id);
Expand Down
22 changes: 15 additions & 7 deletions app/src/main/java/com/codesoom/assignment/domain/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
Expand All @@ -19,25 +21,31 @@ public class User {
@GeneratedValue
private Long id;

private String email;
@Builder.Default
private String email = "";

private String name;
@Builder.Default
private String name = "";

private String password;
@Builder.Default
private String password = "";

@Builder.Default
private boolean deleted = false;

public void changeWith(User source) {
name = source.name;
password = source.password;
this.name = source.name;
}

public void destroy() {
deleted = true;
}

public boolean authenticate(String password) {
return !deleted && password.equals(this.password);
public boolean authenticate(String password , PasswordEncoder passwordEncoder) {
return !deleted && passwordEncoder.matches(password , this.password);
}

public void changePassword(String password , PasswordEncoder passwordEncoder) {
this.password = passwordEncoder.encode(password);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package com.codesoom.assignment.dto;

import com.github.dozermapper.core.Mapping;
import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
johngrib marked this conversation as resolved.
Show resolved Hide resolved
import lombok.Setter;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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());
}
}
}