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

[Day-7] 회원 역할 생성 후 회원 삭제는 관리자만 가능하도록 범위를 제한한다 #7

Merged
merged 3 commits into from
Nov 27, 2022
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ protected void configure(HttpSecurity http) throws Exception {

http
.csrf().disable()
.headers()
.frameOptions().disable() // h2-console
.and()
.addFilterBefore(authenticationErrorFilter, JwtAuthenticationFilter.class)
.addFilter(authenticationFilter)
.sessionManagement()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.codesoom.assignment.common.authentication.security.UserAuthentication;
import com.codesoom.assignment.session.application.AuthenticationService;
import com.codesoom.assignment.session.domain.Role;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
Expand All @@ -14,6 +15,7 @@
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
private final AuthenticationService authenticationService;
Expand All @@ -32,14 +34,15 @@ protected void doFilterInternal(final HttpServletRequest request,

if (authorization != null) {
Long userId = getUserIdByAccessToken(authorization);
setAuthentication(userId);
List<Role> roles = authenticationService.roles(userId);
setAuthentication(userId, roles);
}

chain.doFilter(request, response);
}

private static void setAuthentication(Long userId) {
Authentication authentication = new UserAuthentication(userId);
private static void setAuthentication(Long userId, List<Role> roles) {
Authentication authentication = new UserAuthentication(userId, roles);

SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(authentication);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
package com.codesoom.assignment.common.authentication.security;

import com.codesoom.assignment.session.domain.Role;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class UserAuthentication extends AbstractAuthenticationToken {
private final Long userId;

public UserAuthentication(Long userId) {
super(authorities());
public UserAuthentication(Long userId, List<Role> roles) {
super(authorities(roles));
this.userId = userId;
}

Expand All @@ -34,10 +35,9 @@ public boolean isAuthenticated() {
return true;
}

private static List<GrantedAuthority> authorities() {
List<GrantedAuthority> authorities = new ArrayList<>();
// TODO: userId에 따라서 다른 권한 부여
authorities.add(new SimpleGrantedAuthority("USER"));
return authorities;
private static List<GrantedAuthority> authorities(List<Role> roles) {
return roles.stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,26 @@

import com.codesoom.assignment.common.utils.JwtUtil;
import com.codesoom.assignment.session.application.exception.LoginFailException;
import com.codesoom.assignment.session.domain.Role;
import com.codesoom.assignment.session.domain.RoleRepository;
import com.codesoom.assignment.user.domain.User;
import com.codesoom.assignment.user.domain.UserRepository;
import io.jsonwebtoken.Claims;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class AuthenticationService {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
private final JwtUtil jwtUtil;

public AuthenticationService(final UserRepository userRepository,
final RoleRepository roleRepository,
final JwtUtil jwtUtil) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
this.jwtUtil = jwtUtil;
}

Expand All @@ -34,4 +41,8 @@ public Long parseToken(final String accessToken) {
Claims claims = jwtUtil.decode(accessToken);
return claims.get("userId", Long.class);
}

public List<Role> roles(Long userId) {
return roleRepository.findAllByUserId(userId);
}
}
29 changes: 29 additions & 0 deletions app/src/main/java/com/codesoom/assignment/session/domain/Role.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.codesoom.assignment.session.domain;

import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
@NoArgsConstructor
public class Role {
@Id
@GeneratedValue
private Long id;

private Long userId;
@Getter
private String name;

public Role(Long userId, String name) {
this.userId = userId;
this.name = name;
}

public Role(String name) {
this(null, name);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.codesoom.assignment.session.domain;

import java.util.List;

public interface RoleRepository {
List<Role> findAllByUserId(Long userId);

Role save(Role role);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.codesoom.assignment.session.infra;

import com.codesoom.assignment.session.domain.Role;
import com.codesoom.assignment.session.domain.RoleRepository;
import org.springframework.data.repository.CrudRepository;

import java.util.List;

public interface JpaRoleRepository
extends RoleRepository, CrudRepository<Role, Long> {
List<Role> findAllByUserId(Long userId);
Role save(Role role);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.codesoom.assignment.user.application;

import com.codesoom.assignment.session.domain.Role;
import com.codesoom.assignment.session.domain.RoleRepository;
import com.codesoom.assignment.user.application.exception.UserEmailDuplicationException;
import com.codesoom.assignment.user.application.exception.UserNotFoundException;
import com.codesoom.assignment.user.domain.User;
Expand All @@ -16,10 +18,14 @@
public class UserService {
private final Mapper mapper;
private final UserRepository userRepository;
private final RoleRepository roleRepository;

public UserService(final Mapper dozerMapper, final UserRepository userRepository) {
public UserService(final Mapper dozerMapper,
final UserRepository userRepository,
RoleRepository roleRepository) {
this.mapper = dozerMapper;
this.userRepository = userRepository;
this.roleRepository = roleRepository;
}

public User registerUser(final UserRegistrationData registrationData) {
Expand All @@ -29,8 +35,15 @@ public User registerUser(final UserRegistrationData registrationData) {
throw new UserEmailDuplicationException(email);
}

User user = mapper.map(registrationData, User.class);
return userRepository.save(user);
User user = userRepository.save(
mapper.map(registrationData, User.class)
);

roleRepository.save(
new Role(user.getId(), "USER")
);

return user;
}

public User updateUser(final Long id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ UserResultData create(@RequestBody @Valid final UserRegistrationData registratio
}

@PatchMapping("{id}")
@PreAuthorize("isAuthenticated()")
@PreAuthorize("isAuthenticated() and hasAuthority('USER')")
@IdVerification
UserResultData update(@PathVariable final Long id,
@RequestBody @Valid final UserModificationData modificationData,
Expand All @@ -51,7 +51,7 @@ UserResultData update(@PathVariable final Long id,

@DeleteMapping("{id}")
@ResponseStatus(HttpStatus.NO_CONTENT)
@PreAuthorize("isAuthenticated() and (hasAuthority('USER') or hasAuthority('ADMIN'))")
@PreAuthorize("isAuthenticated() and hasAuthority('ADMIN')")
void destroy(@PathVariable final Long id,
final Authentication authentication) {
userService.deleteUser(id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.codesoom.assignment.common.utils.JwtUtil;
import com.codesoom.assignment.session.application.exception.InvalidTokenException;
import com.codesoom.assignment.session.application.exception.LoginFailException;
import com.codesoom.assignment.session.domain.Role;
import com.codesoom.assignment.session.domain.RoleRepository;
import com.codesoom.assignment.user.domain.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
Expand All @@ -11,15 +13,19 @@
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;

import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Collectors;

import static com.codesoom.assignment.support.AuthHeaderFixture.INVALID_VALUE_TOKEN_1;
import static com.codesoom.assignment.support.AuthHeaderFixture.VALID_ADMIN_TOKEN_1004;
import static com.codesoom.assignment.support.AuthHeaderFixture.VALID_TOKEN_1;
import static com.codesoom.assignment.support.IdFixture.ID_MIN;
import static com.codesoom.assignment.support.UserFixture.USER_1;
import static com.codesoom.assignment.support.UserFixture.USER_2;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
Expand All @@ -31,11 +37,12 @@ class AuthenticationServiceTest {
private AuthenticationService authenticationService;

private final UserRepository userRepository = mock(UserRepository.class);
private final RoleRepository roleRepository = mock(RoleRepository.class);

@BeforeEach
void setUp() {
JwtUtil jwtUtil = new JwtUtil(SECRET);
authenticationService = new AuthenticationService(userRepository, jwtUtil);
authenticationService = new AuthenticationService(userRepository, roleRepository, jwtUtil);
}

@Nested
Expand Down Expand Up @@ -139,4 +146,51 @@ void it_returns_excpetion() {
}
}
}

@Nested
@DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
class roles_메서드는 {

@Nested
@DisplayName("유저 토큰이 주어지면")
class Context_with_user_token {

@BeforeEach
void setUp() {
given(roleRepository.findAllByUserId(eq(VALID_TOKEN_1.아이디())))
.willReturn(Arrays.asList(new Role(VALID_TOKEN_1.아이디(), "USER")));
}

@Test
@DisplayName("\"USER\"를 반환한다")
void it_returs_users() {
assertThat(
authenticationService.roles(VALID_TOKEN_1.아이디()).stream()
.map(Role::getName)
.collect(Collectors.toList())
).isEqualTo(Arrays.asList("USER"));
}
}

@Nested
@DisplayName("관리자 토큰이 주어지면")
class Context_with_admin_token {

@BeforeEach
void setUp() {
given(roleRepository.findAllByUserId(eq(VALID_ADMIN_TOKEN_1004.아이디())))
.willReturn(Arrays.asList(new Role(VALID_ADMIN_TOKEN_1004.아이디(), "ADMIN")));
}

@Test
@DisplayName("\"ADMIN\"를 반환한다")
void it_returs_admin() {
assertThat(
authenticationService.roles(VALID_ADMIN_TOKEN_1004.아이디()).stream()
.map(Role::getName)
.collect(Collectors.toList())
).isEqualTo(Arrays.asList("ADMIN"));
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ public enum AuthHeaderFixture {
"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjJ9.i-iHszAs6H2JFTdm3vOVuN18tb_w6n2FqEYIRtr6gaU",
"Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjJ9.i-iHszAs6H2JFTdm3vOVuN18tb_w6n2FqEYIRtr6gaU"
),
VALID_ADMIN_TOKEN_1004(
"Bearer",
"12345678901234567890123456789010",
1004L,
"eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEwMDR9.iWkN84MBJ12T5IN0ZR_UVrPdfrh6cZP6R2McdZpW8zk",
"Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VySWQiOjEwMDR9.iWkN84MBJ12T5IN0ZR_UVrPdfrh6cZP6R2McdZpW8zk"
),
INVALID_TYPE_TOKEN_1(
"Gibeom",
"12345678901234567890123456789010",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
public enum IdFixture {
ID_MIN(1L),
ID_2(2L),
ID_1004(1004L),
ID_MAX(Long.MAX_VALUE),
;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.codesoom.assignment.user.application;

import com.codesoom.assignment.session.domain.Role;
import com.codesoom.assignment.session.domain.RoleRepository;
import com.codesoom.assignment.support.UserFixture;
import com.codesoom.assignment.user.application.exception.UserEmailDuplicationException;
import com.codesoom.assignment.user.application.exception.UserNotFoundException;
Expand Down Expand Up @@ -33,13 +35,13 @@ class UserServiceTest {
private static final Long DELETED_USER_ID = 200L;

private UserService userService;

private final UserRepository userRepository = mock(UserRepository.class);
private final RoleRepository roleRepository = mock(RoleRepository.class);

@BeforeEach
void setUp() {
Mapper mapper = DozerBeanMapperBuilder.buildDefault();
userService = new UserService(mapper, userRepository);
userService = new UserService(mapper, userRepository, roleRepository);
}

@Nested
Expand Down Expand Up @@ -78,6 +80,7 @@ void it_returns_user() {

verify(userRepository).existsByEmail(USER_1.이메일());
verify(userRepository).save(USER_1.회원_엔티티_생성());
verify(roleRepository).save(any(Role.class));
}
}

Expand Down
Loading