diff --git a/README.md b/README.md index 61f417c..25a7a98 100644 --- a/README.md +++ b/README.md @@ -1 +1,17 @@ # spring-security-authorization + +# ๐Ÿš€ 1๋‹จ๊ณ„ - AuthorizationManager๋ฅผ ํ™œ์šฉ + +- [x] AuthorizationManager๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ธ๊ฐ€ ๊ณผ์ • ์ถ”์ƒํ™” + - [x] SecuredAuthorizationManager ๊ตฌํ˜„ + - [x] RequestAuthorizationManager ๊ตฌํ˜„ + - [x] SecuredAuthorizationManager ๊ตฌํ˜„ + +# ๐Ÿš€ 2๋‹จ๊ณ„ - ์š”์ฒญ๋ณ„ ๊ถŒํ•œ ๊ฒ€์ฆ ์ •๋ณด ๋ถ„๋ฆฌ + +- [x] ์š”์ฒญ๋ณ„ ๊ถŒํ•œ ๊ฒ€์ฆ ์ •๋ณด๋ฅผ ๋ณ„๋„์˜ ๊ฐ์ฒด๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ด€๋ฆฌ + - [x] RequestMatcher ์ž‘์„ฑ + - [x] AnyRequestMatcher ๊ตฌํ˜„ + - [x] MvcRequestMatcher ๊ตฌํ˜„ + - [x] RequestMatcherEntry ์ž‘์„ฑ + diff --git a/src/main/java/nextstep/app/SecurityConfig.java b/src/main/java/nextstep/app/SecurityConfig.java index 1683058..12d04aa 100644 --- a/src/main/java/nextstep/app/SecurityConfig.java +++ b/src/main/java/nextstep/app/SecurityConfig.java @@ -1,13 +1,16 @@ package nextstep.app; +import jakarta.servlet.http.HttpServletRequest; import nextstep.app.domain.Member; import nextstep.app.domain.MemberRepository; import nextstep.security.authentication.AuthenticationException; import nextstep.security.authentication.BasicAuthenticationFilter; import nextstep.security.authentication.UsernamePasswordAuthenticationFilter; +import nextstep.security.authorization.AuthorizationFilter; import nextstep.security.authorization.CheckAuthenticationFilter; import nextstep.security.authorization.SecuredAspect; import nextstep.security.authorization.SecuredMethodInterceptor; +import nextstep.security.authorization.manager.*; import nextstep.security.config.DefaultSecurityFilterChain; import nextstep.security.config.DelegatingFilterProxy; import nextstep.security.config.FilterChainProxy; @@ -22,6 +25,10 @@ import java.util.List; import java.util.Set; +import static nextstep.security.authorization.matcher.RequestMatcherEntry.createDefaultMatcher; +import static nextstep.security.authorization.matcher.RequestMatcherEntry.createMvcMatcher; +import static org.springframework.http.HttpMethod.GET; + @EnableAspectJAutoProxy @Configuration public class SecurityConfig { @@ -46,19 +53,20 @@ public FilterChainProxy filterChainProxy(List securityFilte public SecuredMethodInterceptor securedMethodInterceptor() { return new SecuredMethodInterceptor(); } -// @Bean -// public SecuredAspect securedAspect() { -// return new SecuredAspect(); -// } @Bean public SecurityFilterChain securityFilterChain() { + final AuthorizationManager authorizationManager = new RequestAuthorizationManager(List.of( + createMvcMatcher(GET, "/members", new AuthorityAuthorizationManager<>("ADMIN")), + createMvcMatcher(GET, "/members/me", new AuthenticatedAuthorizationManager<>()), + createMvcMatcher(GET, "/search", new PermitAllAuthorizationManager<>()) + ), createDefaultMatcher(new DenyAllAuthorizationManager<>())); return new DefaultSecurityFilterChain( List.of( new SecurityContextHolderFilter(), new UsernamePasswordAuthenticationFilter(userDetailsService()), new BasicAuthenticationFilter(userDetailsService()), - new CheckAuthenticationFilter() + new AuthorizationFilter(authorizationManager) ) ); } diff --git a/src/main/java/nextstep/app/ui/MemberController.java b/src/main/java/nextstep/app/ui/MemberController.java index 823cf7e..12e8c83 100644 --- a/src/main/java/nextstep/app/ui/MemberController.java +++ b/src/main/java/nextstep/app/ui/MemberController.java @@ -2,12 +2,16 @@ import nextstep.app.domain.Member; import nextstep.app.domain.MemberRepository; +import nextstep.security.authentication.Authentication; +import nextstep.security.authentication.AuthenticationException; import nextstep.security.authorization.Secured; +import nextstep.security.context.SecurityContextHolder; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; +import java.util.Optional; @RestController public class MemberController { @@ -30,4 +34,17 @@ public ResponseEntity> search() { List members = memberRepository.findAll(); return ResponseEntity.ok(members); } + + @Secured("USER") + @GetMapping("/members/me") + public ResponseEntity me() { + final Authentication authentication = SecurityContextHolder + .getContext().getAuthentication(); + + Member member = memberRepository.findByEmail( + authentication.getPrincipal().toString() + ).orElseThrow(AuthenticationException::new); + + return ResponseEntity.ok(member); + } } diff --git a/src/main/java/nextstep/security/authentication/BasicAuthenticationFilter.java b/src/main/java/nextstep/security/authentication/BasicAuthenticationFilter.java index 406116f..4cf3809 100644 --- a/src/main/java/nextstep/security/authentication/BasicAuthenticationFilter.java +++ b/src/main/java/nextstep/security/authentication/BasicAuthenticationFilter.java @@ -1,6 +1,7 @@ package nextstep.security.authentication; import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import nextstep.security.context.SecurityContext; @@ -10,6 +11,7 @@ import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.List; @@ -26,23 +28,23 @@ public BasicAuthenticationFilter(UserDetailsService userDetailsService) { } @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) { - try { - Authentication authentication = convert(request); - if (authentication == null) { - filterChain.doFilter(request, response); - return; - } - - Authentication authResult = this.authenticationManager.authenticate(authentication); - SecurityContext context = SecurityContextHolder.createEmptyContext(); - context.setAuthentication(authResult); - SecurityContextHolder.setContext(context); - + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + Authentication authentication = convert(request); + if (authentication == null) { filterChain.doFilter(request, response); - } catch (Exception e) { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + return; } + + Authentication authResult = this.authenticationManager.authenticate(authentication); + SecurityContext context = SecurityContextHolder.createEmptyContext(); + context.setAuthentication(authResult); + SecurityContextHolder.setContext(context); + + filterChain.doFilter(request, response); } private Authentication convert(HttpServletRequest request) { diff --git a/src/main/java/nextstep/security/authorization/AuthorizationFilter.java b/src/main/java/nextstep/security/authorization/AuthorizationFilter.java new file mode 100644 index 0000000..091eb77 --- /dev/null +++ b/src/main/java/nextstep/security/authorization/AuthorizationFilter.java @@ -0,0 +1,37 @@ +package nextstep.security.authorization; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import nextstep.security.authentication.Authentication; +import nextstep.security.authentication.AuthenticationException; +import nextstep.security.authorization.manager.AuthorizationManager; +import nextstep.security.context.SecurityContextHolder; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +public class AuthorizationFilter extends OncePerRequestFilter { + private final AuthorizationManager authorizationManager; + + public AuthorizationFilter(AuthorizationManager authorizationManager) { + this.authorizationManager = authorizationManager; + } + + @Override + protected void doFilterInternal( + HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain + ) throws ServletException, IOException { + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null || !authentication.isAuthenticated()) { + throw new AuthenticationException(); + } + if (!authorizationManager.authorize(authentication, request).isGranted()) { + throw new ForbiddenException(); + } + filterChain.doFilter(request, response); + } +} diff --git a/src/main/java/nextstep/security/authorization/SecuredMethodInterceptor.java b/src/main/java/nextstep/security/authorization/SecuredMethodInterceptor.java index 8ee7409..76d14bc 100644 --- a/src/main/java/nextstep/security/authorization/SecuredMethodInterceptor.java +++ b/src/main/java/nextstep/security/authorization/SecuredMethodInterceptor.java @@ -2,6 +2,7 @@ import nextstep.security.authentication.Authentication; import nextstep.security.authentication.AuthenticationException; +import nextstep.security.authorization.manager.SecuredAuthorizationManager; import nextstep.security.context.SecurityContextHolder; import org.aopalliance.aop.Advice; import org.aopalliance.intercept.MethodInterceptor; @@ -11,32 +12,42 @@ import org.springframework.aop.framework.AopInfrastructureBean; import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; -import java.lang.reflect.Method; - public class SecuredMethodInterceptor implements MethodInterceptor, PointcutAdvisor, AopInfrastructureBean { + private final SecuredAuthorizationManager securedAuthorizationManager; + private final Pointcut pointcut; public SecuredMethodInterceptor() { this.pointcut = new AnnotationMatchingPointcut(null, Secured.class); + this.securedAuthorizationManager = new SecuredAuthorizationManager(); } @Override public Object invoke(MethodInvocation invocation) throws Throwable { - Method method = invocation.getMethod(); - if (method.isAnnotationPresent(Secured.class)) { - Secured secured = method.getAnnotation(Secured.class); - Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); - if (authentication == null) { - throw new AuthenticationException(); - } - if (!authentication.getAuthorities().contains(secured.value())) { - throw new ForbiddenException(); - } + if (!invocation.getMethod().isAnnotationPresent(Secured.class)) { + return invocation.proceed(); } + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + + checkAuthenticated(authentication); + checkAuthorize(authentication, invocation); + return invocation.proceed(); } + private void checkAuthenticated(Authentication authentication) { + if (authentication == null || !authentication.isAuthenticated()) { + throw new AuthenticationException(); + } + } + + private void checkAuthorize(Authentication authentication, MethodInvocation invocation) { + if (!securedAuthorizationManager.authorize(authentication, invocation).isGranted()) { + throw new ForbiddenException(); + } + } + @Override public Pointcut getPointcut() { return pointcut; diff --git a/src/main/java/nextstep/security/authorization/manager/AuthenticatedAuthorizationManager.java b/src/main/java/nextstep/security/authorization/manager/AuthenticatedAuthorizationManager.java new file mode 100644 index 0000000..f2c7053 --- /dev/null +++ b/src/main/java/nextstep/security/authorization/manager/AuthenticatedAuthorizationManager.java @@ -0,0 +1,19 @@ +package nextstep.security.authorization.manager; + +import nextstep.security.authentication.Authentication; + +import static nextstep.security.authorization.manager.AuthorizationDecision.GRANTED; +import static nextstep.security.authorization.manager.AuthorizationDecision.NOT_GRANTED; + +public class AuthenticatedAuthorizationManager implements AuthorizationManager { + @Override + public AuthorizationResult authorize(Authentication authentication, T target) { + if(authentication != null && + authentication.isAuthenticated() + ) { + return GRANTED; + } + + return NOT_GRANTED; + } +} diff --git a/src/main/java/nextstep/security/authorization/manager/AuthorityAuthorizationManager.java b/src/main/java/nextstep/security/authorization/manager/AuthorityAuthorizationManager.java new file mode 100644 index 0000000..0e3444f --- /dev/null +++ b/src/main/java/nextstep/security/authorization/manager/AuthorityAuthorizationManager.java @@ -0,0 +1,29 @@ +package nextstep.security.authorization.manager; + +import nextstep.security.authentication.Authentication; + +import java.util.Set; + +public class AuthorityAuthorizationManager implements AuthorizationManager { + private final Set authorities; + + public AuthorityAuthorizationManager(String... authorities) { + this.authorities = Set.of(authorities); + } + + @Override + public AuthorizationResult authorize(Authentication authentication, T target) { + return AuthorizationDecision.from(isGranted(authentication)); + } + + private boolean isGranted(Authentication authentication) { + return authentication != null + && authentication.isAuthenticated() + && anyMatch(authentication); + } + + private boolean anyMatch(Authentication authentication) { + return authentication.getAuthorities().stream() + .anyMatch(authorities::contains); + } +} diff --git a/src/main/java/nextstep/security/authorization/manager/AuthorizationDecision.java b/src/main/java/nextstep/security/authorization/manager/AuthorizationDecision.java new file mode 100644 index 0000000..06edbf8 --- /dev/null +++ b/src/main/java/nextstep/security/authorization/manager/AuthorizationDecision.java @@ -0,0 +1,20 @@ +package nextstep.security.authorization.manager; + +public enum AuthorizationDecision implements AuthorizationResult { + GRANTED(true), + NOT_GRANTED(false); + + private final boolean granted; + + AuthorizationDecision(final boolean granted) { + this.granted = granted; + } + + public boolean isGranted() { + return this.granted; + } + + public static AuthorizationResult from(boolean granted) { + return granted ? GRANTED : NOT_GRANTED; + } +} diff --git a/src/main/java/nextstep/security/authorization/manager/AuthorizationManager.java b/src/main/java/nextstep/security/authorization/manager/AuthorizationManager.java new file mode 100644 index 0000000..51a5922 --- /dev/null +++ b/src/main/java/nextstep/security/authorization/manager/AuthorizationManager.java @@ -0,0 +1,8 @@ +package nextstep.security.authorization.manager; + +import nextstep.security.authentication.Authentication; + +@FunctionalInterface +public interface AuthorizationManager { + AuthorizationResult authorize(Authentication authentication, T target); +} diff --git a/src/main/java/nextstep/security/authorization/manager/AuthorizationResult.java b/src/main/java/nextstep/security/authorization/manager/AuthorizationResult.java new file mode 100644 index 0000000..987157f --- /dev/null +++ b/src/main/java/nextstep/security/authorization/manager/AuthorizationResult.java @@ -0,0 +1,5 @@ +package nextstep.security.authorization.manager; + +public interface AuthorizationResult { + boolean isGranted(); +} diff --git a/src/main/java/nextstep/security/authorization/manager/DenyAllAuthorizationManager.java b/src/main/java/nextstep/security/authorization/manager/DenyAllAuthorizationManager.java new file mode 100644 index 0000000..a611aa2 --- /dev/null +++ b/src/main/java/nextstep/security/authorization/manager/DenyAllAuthorizationManager.java @@ -0,0 +1,12 @@ +package nextstep.security.authorization.manager; + +import nextstep.security.authentication.Authentication; + +import static nextstep.security.authorization.manager.AuthorizationDecision.NOT_GRANTED; + +public class DenyAllAuthorizationManager implements AuthorizationManager { + @Override + public AuthorizationResult authorize(Authentication authentication, T target) { + return NOT_GRANTED; + } +} diff --git a/src/main/java/nextstep/security/authorization/manager/PermitAllAuthorizationManager.java b/src/main/java/nextstep/security/authorization/manager/PermitAllAuthorizationManager.java new file mode 100644 index 0000000..4df5d5d --- /dev/null +++ b/src/main/java/nextstep/security/authorization/manager/PermitAllAuthorizationManager.java @@ -0,0 +1,12 @@ +package nextstep.security.authorization.manager; + +import nextstep.security.authentication.Authentication; + +import static nextstep.security.authorization.manager.AuthorizationDecision.GRANTED; + +public class PermitAllAuthorizationManager implements AuthorizationManager { + @Override + public AuthorizationResult authorize(Authentication authentication, T target) { + return GRANTED; + } +} diff --git a/src/main/java/nextstep/security/authorization/manager/RequestAuthorizationManager.java b/src/main/java/nextstep/security/authorization/manager/RequestAuthorizationManager.java new file mode 100644 index 0000000..53cfa0a --- /dev/null +++ b/src/main/java/nextstep/security/authorization/manager/RequestAuthorizationManager.java @@ -0,0 +1,58 @@ +package nextstep.security.authorization.manager; + +import jakarta.servlet.http.HttpServletRequest; +import nextstep.security.authentication.Authentication; +import nextstep.security.authorization.matcher.RequestMatcherEntry; + +import java.util.List; + +public class RequestAuthorizationManager implements AuthorizationManager { + private final List>> entries; + private final RequestMatcherEntry> defaultEntry; + + public RequestAuthorizationManager( + List>> entries, + RequestMatcherEntry> defaultEntry + ) { + this.entries = entries; + this.defaultEntry = defaultEntry; + } + + @Override + public AuthorizationResult authorize(Authentication authentication, HttpServletRequest target) { + if (noneMatch(target)) { + return AuthorizationDecision.from( + check(authentication, target, defaultEntry) + ); + } + return AuthorizationDecision.from( + allMatch(authentication, target) + ); + } + + private boolean noneMatch(HttpServletRequest request) { + for (var entry : entries) { + if (entry.requestMatcher().matches(request)) { + return false; + } + } + return true; + } + + private boolean allMatch(Authentication authentication, HttpServletRequest request) { + for (var entry : entries) { + if (!check(authentication, request, entry)) { + return false; + } + } + return true; + } + + private boolean check( + Authentication authentication, HttpServletRequest request, + RequestMatcherEntry> matcherEntry + ) { + return !matcherEntry.requestMatcher().matches(request) + || matcherEntry.entry().authorize(authentication, request).isGranted(); + } +} diff --git a/src/main/java/nextstep/security/authorization/manager/SecuredAuthorizationManager.java b/src/main/java/nextstep/security/authorization/manager/SecuredAuthorizationManager.java new file mode 100644 index 0000000..06c91d2 --- /dev/null +++ b/src/main/java/nextstep/security/authorization/manager/SecuredAuthorizationManager.java @@ -0,0 +1,25 @@ +package nextstep.security.authorization.manager; + +import nextstep.security.authentication.Authentication; +import nextstep.security.authorization.Secured; +import org.aopalliance.intercept.MethodInvocation; + +import java.lang.reflect.Method; + +public class SecuredAuthorizationManager implements AuthorizationManager { + + @Override + public AuthorizationResult authorize(Authentication authentication, MethodInvocation target) { + return AuthorizationDecision.from(hasAuthority(authentication, target.getMethod())); + } + + private boolean hasAuthority(Authentication authentication, Method method) { + if (!method.isAnnotationPresent(Secured.class)) { + return false; + } + return authentication != null + && authentication.isAuthenticated() + && authentication.getAuthorities() + .contains(method.getAnnotation(Secured.class).value()); + } +} diff --git a/src/main/java/nextstep/security/authorization/matcher/AnyRequestMatcher.java b/src/main/java/nextstep/security/authorization/matcher/AnyRequestMatcher.java new file mode 100644 index 0000000..3a8c640 --- /dev/null +++ b/src/main/java/nextstep/security/authorization/matcher/AnyRequestMatcher.java @@ -0,0 +1,11 @@ +package nextstep.security.authorization.matcher; + +import jakarta.servlet.http.HttpServletRequest; + +public class AnyRequestMatcher implements RequestMatcher { + + @Override + public boolean matches(HttpServletRequest request) { + return true; + } +} diff --git a/src/main/java/nextstep/security/authorization/matcher/MvcRequestMatcher.java b/src/main/java/nextstep/security/authorization/matcher/MvcRequestMatcher.java new file mode 100644 index 0000000..e8266c4 --- /dev/null +++ b/src/main/java/nextstep/security/authorization/matcher/MvcRequestMatcher.java @@ -0,0 +1,36 @@ +package nextstep.security.authorization.matcher; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.http.HttpMethod; + +import java.util.regex.Pattern; + +public class MvcRequestMatcher implements RequestMatcher { + private final HttpMethod method; + private final Pattern pattern; + + public MvcRequestMatcher(HttpMethod method, String pattern) { + this.method = method; + this.pattern = compile(pattern); + } + + private Pattern compile(String regex) { + if (regex == null || regex.isBlank()) { + return null; + } + return Pattern.compile(regex); + } + + @Override + public boolean matches(HttpServletRequest request) { + return matchMethod(request) && matchPattern(request); + } + + private boolean matchMethod(HttpServletRequest request) { + return method == null || method.name().equals(request.getMethod()); + } + + private boolean matchPattern(HttpServletRequest request) { + return pattern == null || pattern.matcher(request.getRequestURI()).matches(); + } +} diff --git a/src/main/java/nextstep/security/authorization/matcher/RequestMatcher.java b/src/main/java/nextstep/security/authorization/matcher/RequestMatcher.java new file mode 100644 index 0000000..3878f78 --- /dev/null +++ b/src/main/java/nextstep/security/authorization/matcher/RequestMatcher.java @@ -0,0 +1,7 @@ +package nextstep.security.authorization.matcher; + +import jakarta.servlet.http.HttpServletRequest; + +public interface RequestMatcher { + boolean matches(HttpServletRequest request); +} diff --git a/src/main/java/nextstep/security/authorization/matcher/RequestMatcherEntry.java b/src/main/java/nextstep/security/authorization/matcher/RequestMatcherEntry.java new file mode 100644 index 0000000..ab14e3f --- /dev/null +++ b/src/main/java/nextstep/security/authorization/matcher/RequestMatcherEntry.java @@ -0,0 +1,29 @@ +package nextstep.security.authorization.matcher; + +import jakarta.servlet.http.HttpServletRequest; +import nextstep.security.authorization.manager.AuthorizationManager; +import org.springframework.http.HttpMethod; + +public record RequestMatcherEntry( + RequestMatcher requestMatcher, + T entry +) { + public static RequestMatcherEntry> createMvcMatcher( + HttpMethod method, String pattern, + AuthorizationManager authorizationManager + ) { + return new RequestMatcherEntry<>( + new MvcRequestMatcher(method, pattern), + authorizationManager + ); + } + + public static RequestMatcherEntry> createDefaultMatcher( + AuthorizationManager authorizationManager + ) { + return new RequestMatcherEntry<>( + new AnyRequestMatcher(), + authorizationManager + ); + } +} diff --git a/src/main/java/nextstep/security/config/FilterChainProxy.java b/src/main/java/nextstep/security/config/FilterChainProxy.java index e40cea2..410bc65 100644 --- a/src/main/java/nextstep/security/config/FilterChainProxy.java +++ b/src/main/java/nextstep/security/config/FilterChainProxy.java @@ -1,5 +1,8 @@ package nextstep.security.config; +import jakarta.servlet.http.HttpServletResponse; +import nextstep.security.authentication.AuthenticationException; +import nextstep.security.authorization.ForbiddenException; import org.springframework.web.filter.GenericFilterBean; import jakarta.servlet.*; @@ -7,6 +10,8 @@ import java.io.IOException; import java.util.List; +import static jakarta.servlet.http.HttpServletResponse.*; + public class FilterChainProxy extends GenericFilterBean { private final List filterChains; @@ -16,10 +21,19 @@ public FilterChainProxy(List filterChains) { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - List filters = getFilters((HttpServletRequest) request); - - VirtualFilterChain virtualFilterChain = new VirtualFilterChain(chain, filters); - virtualFilterChain.doFilter(request, response); + final HttpServletRequest httpRequest = (HttpServletRequest) request; + final HttpServletResponse httpResponse = (HttpServletResponse) response; + try { + new VirtualFilterChain( + chain, getFilters(httpRequest) + ).doFilter(request, response); + } catch (AuthenticationException e) { + httpResponse.setStatus(SC_UNAUTHORIZED); + } catch (ForbiddenException e) { + httpResponse.setStatus(SC_FORBIDDEN); + } catch (Exception e) { + httpResponse.setStatus(SC_INTERNAL_SERVER_ERROR); + } } private List getFilters(HttpServletRequest request) { diff --git a/src/test/java/nextstep/fixture/MemberFixture.java b/src/test/java/nextstep/fixture/MemberFixture.java new file mode 100644 index 0000000..2a0e6b3 --- /dev/null +++ b/src/test/java/nextstep/fixture/MemberFixture.java @@ -0,0 +1,36 @@ +package nextstep.fixture; + +import nextstep.app.domain.Member; + +import java.util.Set; + +public enum MemberFixture { + TEST_ADMIN_MEMBER(new Member( + "a@a.com", "password", + "a", "", + Set.of("USER", "ADMIN") + )), + TEST_USER_MEMBER(new Member( + "b@b.com", "password", + "b", "", + Set.of("USER") + )); + + private final Member member; + + MemberFixture(Member member) { + this.member = member; + } + + public Member getMember() { + return member; + } + + public String getEmail() { + return member.getEmail(); + } + + public String getPassword() { + return member.getPassword(); + } +} diff --git a/src/test/java/nextstep/security/authorization/manager/AuthenticatedAuthorizationManagerTest.java b/src/test/java/nextstep/security/authorization/manager/AuthenticatedAuthorizationManagerTest.java new file mode 100644 index 0000000..b4588c0 --- /dev/null +++ b/src/test/java/nextstep/security/authorization/manager/AuthenticatedAuthorizationManagerTest.java @@ -0,0 +1,50 @@ +package nextstep.security.authorization.manager; + +import nextstep.security.authentication.Authentication; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static nextstep.security.authorization.manager.AuthorizationDecision.GRANTED; +import static nextstep.security.authorization.manager.AuthorizationDecision.NOT_GRANTED; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class AuthenticatedAuthorizationManagerTest { + private final AuthorizationManager manager = new AuthenticatedAuthorizationManager<>(); + + @DisplayName("์œ ์ €๊ฐ€ ์ธ์ฆ๋˜์ง€ ์•Š์œผ๋ฉด ์ธ๊ฐ€๋˜์ง€ ์•Š์€ ์ƒํƒœ์ด๋‹ค.") + @Test + void notAuthenticated() { + assertAll( + () -> assertThat(manager.authorize(null, null)).isEqualTo(NOT_GRANTED), + () -> assertThat(manager.authorize(unauthenticatedUser(), null)).isEqualTo(NOT_GRANTED) + ); + } + + @DisplayName("์œ ์ €๊ฐ€ ์ธ์ฆ๋˜๋ฉด ์ธ๊ฐ€๋œ ์ƒํƒœ์ด๋‹ค.") + @Test + void authenticated() { + assertThat(manager.authorize(authenticatedUser(), null)).isEqualTo(GRANTED); + } + + private Authentication authenticatedUser() { + return createAuthentication(true); + } + + private Authentication unauthenticatedUser() { + return createAuthentication(false); + } + + private Authentication createAuthentication(boolean isAuthenticated) { + Authentication authentication = mock(Authentication.class); + when(authentication.isAuthenticated()).thenReturn(isAuthenticated); + when(authentication.getAuthorities()).thenReturn(Set.of()); + when(authentication.getCredentials()).thenReturn("PASSWORD"); + when(authentication.getPrincipal()).thenReturn("USERNAME"); + return authentication; + } +} diff --git a/src/test/java/nextstep/security/authorization/manager/AuthorityAuthorizationManagerTest.java b/src/test/java/nextstep/security/authorization/manager/AuthorityAuthorizationManagerTest.java new file mode 100644 index 0000000..7a43837 --- /dev/null +++ b/src/test/java/nextstep/security/authorization/manager/AuthorityAuthorizationManagerTest.java @@ -0,0 +1,62 @@ +package nextstep.security.authorization.manager; + +import nextstep.security.authentication.Authentication; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static nextstep.security.authorization.manager.AuthorizationDecision.GRANTED; +import static nextstep.security.authorization.manager.AuthorizationDecision.NOT_GRANTED; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class AuthorityAuthorizationManagerTest { + private final AuthorizationManager manager = new AuthorityAuthorizationManager<>( + "ADMIN", "USER" + ); + + @DisplayName("์ธ์ฆ๋˜์ง€ ์•Š๋Š” ์œ ์ €๋Š” ์ธ๊ฐ€๋ฅผ ๋ฐ›์ง€ ์•Š๋Š”๋‹ค.") + @Test + void notAuthenticated() { + assertAll( + () -> assertThat(manager.authorize(null, null)).isEqualTo(NOT_GRANTED), + () -> assertThat(manager.authorize(unauthenticatedUser(), null)).isEqualTo(NOT_GRANTED) + ); + } + + @DisplayName("์–ด๋“œ๋ฏผ ์œ ์ €๋Š” ์ธ๊ฐ€์„ ๋ฐ›๋Š”๋‹ค.") + @Test + void adminWithAuthority() { + AuthorizationResult admin = manager.authorize(authenticatedUserWithAuthorities("ADMIN"), null); + + assertThat(admin).isEqualTo(GRANTED); + } + + @DisplayName("์ผ๋ฐ˜ ์œ ์ €๋Š” ์ธ๊ฐ€๋ฅผ ๋ฐ›๋Š”๋‹ค.") + @Test + void userWithAuthority() { + AuthorizationResult user = manager.authorize(authenticatedUserWithAuthorities("USER"), null); + + assertThat(user).isEqualTo(GRANTED); + } + + private Authentication authenticatedUserWithAuthorities(String... authorities) { + return createAuthentication(true, authorities); + } + + private Authentication unauthenticatedUser() { + return createAuthentication(false); + } + + private Authentication createAuthentication(boolean isAuthenticated, String... authorities) { + Authentication authentication = mock(Authentication.class); + when(authentication.isAuthenticated()).thenReturn(isAuthenticated); + when(authentication.getAuthorities()).thenReturn(Set.of(authorities)); + when(authentication.getCredentials()).thenReturn("PASSWORD"); + when(authentication.getPrincipal()).thenReturn("USERNAME"); + return authentication; + } +}