Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d4add2f
feat: add /members/me API
parkjun5 Feb 9, 2025
bc949f3
ref: refactoring CheckAuthenticationFilter to AuthorizationFilter
parkjun5 Feb 9, 2025
6d5c693
feat: add AuthorizationManager for authorize
parkjun5 Feb 9, 2025
646c495
feat: add request matcher for find AuthorizationManager
parkjun5 Feb 9, 2025
432239c
feat: add AuthorizationManagers for delegate Authorities
parkjun5 Feb 9, 2025
e301ed4
ref: change test and Add SecureAnnotation to Get /members API
parkjun5 Feb 9, 2025
8b70fdd
feat: Filter And Interceptor delegate authorization control to Authorโ€ฆ
parkjun5 Feb 9, 2025
1f37f27
feat: add RequestMatcherDelegatingAuthorizationManager and config chaโ€ฆ
parkjun5 Feb 9, 2025
a0c8b51
docs: ์š”๊ตฌ ์‚ฌํ•ญ ์ •๋ฆฌ
parkjun5 Feb 9, 2025
b83d2a2
polishing
parkjun5 Feb 9, 2025
c1a2089
ref: Filter ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ธ๊ฐ€์™€ Interceptor ์—์„œ ํ™•์ธํ•˜๋Š” ์ธ๊ฐ€ ๋ถ„๋ฆฌ
parkjun5 Feb 9, 2025
0179bf5
polishing
parkjun5 Feb 9, 2025
fbbf311
ref:
parkjun5 Feb 9, 2025
bb7a79e
polishing
parkjun5 Feb 9, 2025
9bf5f29
polishing
parkjun5 Feb 9, 2025
8126c74
ref: Change AuthorityAuthorizationManager to Generic
Feb 12, 2025
7e23368
ref: use verify not check method
Feb 12, 2025
7a213b0
ref: AuthorizationDecision ์„ static ๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•˜๋„๋ก ๋ณ€๊ฒฝ
Feb 12, 2025
0e5d010
ref: mvc match logic not Stream change to ForEach
Feb 12, 2025
5c49d3e
ref: SecuredAuthorizationManager ์—์„œ AuthorityAuthorizationManager ์—๊ฒŒ โ€ฆ
Feb 12, 2025
fc947f5
test: ์ธ์ฆ ์‹คํŒจ์—์„œ๋Š” AccessDeniedException ์ธ๊ฐ€ ์‹คํŒจ์—๋Š” ForbiddenException ๋‚˜๋„๋ก ๋ณ€๊ฒฝ
Feb 12, 2025
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
38 changes: 38 additions & 0 deletions README.md

Choose a reason for hiding this comment

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

ํ•„์š” ์š”๊ตฌ์‚ฌํ•ญ ์ž˜ ์ •๋ฆฌํ•ด์ฃผ์…จ๋„ค์š” ๐Ÿ‘

Original file line number Diff line number Diff line change
@@ -1 +1,39 @@
# spring-security-authorization

## ์‹ค์Šต

1. [x] GET /members/me ์—”๋“œํฌ์ธํŠธ ๊ตฌํ˜„ ๋ฐ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ
2. [x] ๊ถŒํ•œ ๊ฒ€์ฆ ๋กœ์ง์„ AuthorizationFilter๋กœ ๋ฆฌํŒฉํ„ฐ๋ง

## ๐Ÿš€ 1๋‹จ๊ณ„ - AuthorizationManager๋ฅผ ํ™œ์šฉ

์š”๊ตฌ์‚ฌํ•ญ

- [x] AuthorizationManager ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ธ๊ฐ€ ๊ณผ์ • ์ถ”์ƒํ™”
- [x] ์ธ๊ฐ€๋ฅผ ์ฒ˜๋ฆฌํ•ด์ค„ AuthorizationManager ์ƒ์„ฑ
- [x] RequestMatcherDelegatingAuthorizationManager ๋ฅผ ํ†ตํ•œ AuthorizationManager ํ•œ๋ฒˆ์— ๊ด€๋ฆฌ?
- [x] ์ธ๊ฐ€ ๊ณผ์ •์„ ์ถ”์ƒํ™”ํ•œ AuthorizationManager ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค. ์ด ๋•Œ ํ•„์š”ํ•œ AuthorizationDecision๋„ ํ•จ๊ป˜ ์ž‘์„ฑํ•œ๋‹ค. (์‹ค์ œ AuthorizationManager์—๋Š”
verify๋„ ์žˆ๋Š”๋ฐ ์ด ๋ถ€๋ถ„์— ๋Œ€ํ•œ ๊ตฌํ˜„์€ ์„ ํƒ)
- [x] SecuredMethodInterceptor์™€ Authorization Filter์—์„œ ์ž‘์„ฑ๋œ ์ธ๊ฐ€ ๋กœ์ง์„ AuthorizationManager๋กœ ๋ฆฌํŒฉํ„ฐ๋ง ํ•œ๋‹ค.

## ๐Ÿš€ 2๋‹จ๊ณ„ - ์š”์ฒญ๋ณ„ ๊ถŒํ•œ ๊ฒ€์ฆ ์ •๋ณด ๋ถ„๋ฆฌ

์š”๊ตฌ์‚ฌํ•ญ

- [x] ์š”์ฒญ๋ณ„ ๊ถŒํ•œ ๊ฒ€์ฆ ์ •๋ณด๋ฅผ ๋ณ„๋„์˜ ๊ฐ์ฒด๋กœ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ด€๋ฆฌ
- [x] RequestMatcherRegistry์™€ RequestMatcher๋ฅผ ์ž‘์„ฑํ•˜๊ณ , RequestMatcher์˜ ๊ตฌํ˜„์ฒด๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.
- [x] AnyRequestMatcher: ๋ชจ๋“  ๊ฒฝ์šฐ true๋ฅผ ๋ฆฌํ„ดํ•œ๋‹ค.
- [x] MvcRequestMatcher: method์™€ pattern(uri)๊ฐ€ ๊ฐ™์€์ง€ ๋น„๊ตํ•˜์—ฌ ๋ฆฌํ„ดํ•œ๋‹ค.
- [x] RequestMatcherEntry์˜ T entry๋Š” ์•„๋ž˜์— ํ•ด๋‹น๋˜๋Š” ๊ฐ ์š”์ฒญ๋ณ„ ์ธ๊ฐ€ ๋กœ์ง์„ ๋‹ด๋‹นํ•˜๋Š” AuthorizationManager๊ฐ€ ๋œ๋‹ค.
- [x] /login์€ ๋ชจ๋“  ์š”์ฒญ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก PermitAllAuthorizationManager๋กœ ์ฒ˜๋ฆฌ
- [x] /members/me๋Š” ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๋งŒ์—๊ฒŒ๋งŒ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๊ธฐ ์œ„ํ•ด AuthenticatedAuthorizationManager๋กœ ์ฒ˜๋ฆฌ
- [x] /members๋Š” "ADMIN" ์‚ฌ์šฉ์ž๋งŒ์—๊ฒŒ๋งŒ ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•˜๊ธฐ ์œ„ํ•ด HasAuthorityAuthorizationManager๋กœ ์ฒ˜๋ฆฌ
- [x] ๊ทธ ์™ธ ๋ชจ๋“  ์š”์ฒญ์€ ๊ถŒํ•œ์„ ์ œํ•œํ•˜๊ธฐ ์œ„ํ•ด DenyAllAuthorizationManager๋กœ ์ฒ˜๋ฆฌ

์•„๋ž˜ ๊ฐ์ฒด์™€ ์‹œํ๋ฆฌํ‹ฐ ์ฝ”๋“œ ํ™•์ธ
// SpEL
// Role Authority
// Role Hierarchy
// AuthoritiesAuthorizationManager
// SecureMethodSecurityConfiguration
// SecuredAuthorizationManager
24 changes: 20 additions & 4 deletions src/main/java/nextstep/app/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
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.CheckAuthenticationFilter;
import nextstep.security.authorization.SecuredAspect;
import nextstep.security.authorization.AuthorizationFilter;
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;
import nextstep.security.config.SecurityFilterChain;
import nextstep.security.context.SecurityContextHolderFilter;
import nextstep.security.matcher.AnyRequestMatcher;
import nextstep.security.matcher.MvcRequestMatcher;
import nextstep.security.matcher.RequestMatcherEntry;
import nextstep.security.userdetails.UserDetails;
import nextstep.security.userdetails.UserDetailsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.http.HttpMethod;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

Expand All @@ -44,21 +50,31 @@ public FilterChainProxy filterChainProxy(List<SecurityFilterChain> securityFilte

@Bean
public SecuredMethodInterceptor securedMethodInterceptor() {
return new SecuredMethodInterceptor();
return new SecuredMethodInterceptor(new SecuredAuthorizationManager());
}
// @Bean
// public SecuredAspect securedAspect() {
// return new SecuredAspect();
// }

@Bean
public RequestMatcherDelegatingAuthorizationManager requestMatcherDelegatingAuthorizationManager() {

Choose a reason for hiding this comment

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

์ ์ ˆํ•˜๊ฒŒ ์ž˜ ์ถ”๊ฐ€ํ•ด์ฃผ์…จ๋„ค์š” :)

List<RequestMatcherEntry<AuthorizationManager<HttpServletRequest>>> mappings = new ArrayList<>();
mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/members/me"), new AuthenticatedAuthorizationManager()));
mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/members"), new AuthorityAuthorizationManager(Set.of("ADMIN"))));
mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/search", "/login"), new PermitAllAuthorizationManager()));
mappings.add(new RequestMatcherEntry<>(new AnyRequestMatcher(), new DenyAllAuthorizationManager()));
return new RequestMatcherDelegatingAuthorizationManager(mappings);
}

@Bean
public SecurityFilterChain securityFilterChain() {
return new DefaultSecurityFilterChain(
List.of(
new SecurityContextHolderFilter(),
new UsernamePasswordAuthenticationFilter(userDetailsService()),
new BasicAuthenticationFilter(userDetailsService()),
new CheckAuthenticationFilter()
new AuthorizationFilter(requestMatcherDelegatingAuthorizationManager())
)
);
}
Expand Down
18 changes: 17 additions & 1 deletion src/main/java/nextstep/app/ui/MemberController.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@

import nextstep.app.domain.Member;
import nextstep.app.domain.MemberRepository;
import nextstep.security.authentication.AuthenticationException;
import nextstep.security.authentication.UsernamePasswordAuthenticationToken;
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.NoSuchElementException;

@RestController
public class MemberController {
Expand All @@ -18,16 +22,28 @@ public MemberController(MemberRepository memberRepository) {
this.memberRepository = memberRepository;
}

@Secured("ADMIN")
@GetMapping("/members")
public ResponseEntity<List<Member>> list() {
List<Member> members = memberRepository.findAll();
return ResponseEntity.ok(members);
}

@Secured("ADMIN")
@Secured({"ADMIN", "MEMBER"})
@GetMapping("/search")
public ResponseEntity<List<Member>> search() {
List<Member> members = memberRepository.findAll();
return ResponseEntity.ok(members);
}

@GetMapping("/members/me")
public ResponseEntity<Member> getMyInfo() {
if (SecurityContextHolder.getContext().getAuthentication() instanceof UsernamePasswordAuthenticationToken token
&& token.getPrincipal() instanceof String email) {
Member member = memberRepository.findByEmail(email).orElseThrow(NoSuchElementException::new);
return ResponseEntity.ok(member);
}

throw new AuthenticationException();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jakarta.servlet.FilterChain;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import nextstep.security.authorization.ForbiddenException;
import nextstep.security.context.SecurityContext;
import nextstep.security.context.SecurityContextHolder;
import nextstep.security.userdetails.UserDetailsService;
Expand Down Expand Up @@ -40,6 +41,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
SecurityContextHolder.setContext(context);

filterChain.doFilter(request, response);
} catch (ForbiddenException e) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
} catch (Exception e) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package nextstep.security.authorization;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;

@ResponseStatus(HttpStatus.FORBIDDEN)
public class AccessDeniedException extends RuntimeException {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package nextstep.security.authorization;

public class AuthorizationDecision {

Choose a reason for hiding this comment

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

๋ฏธ์…˜์—์„œ๋Š” spring security์˜ ๊ธฐ๋Šฅ์ด ์ถ•์†Œ๋˜์–ด true, false๊ฐ€ ์ž˜ ์•ˆ์™€๋‹ฟ์œผ์‹ค ์ˆ˜ ์žˆ์œผ๋‚˜ ์‹ค์ œ๋กœ๋Š” ๊ทธ๋ณด๋‹ค ๋” ๋‹ค์–‘ํ•œ ๊ตฌํ˜„์ฒด๋“ค์ด ์ƒ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

https://github.com/spring-projects/spring-security/blob/main/core/src/main/java/org/springframework/security/authorization/AuthorityAuthorizationDecision.java

private final boolean isGranted;

protected AuthorizationDecision(final boolean isGranted) {
this.isGranted = isGranted;
}

public static AuthorizationDecision granted() {
return new AuthorizationDecision(true);
}

public static AuthorizationDecision denied() {
return new AuthorizationDecision(false);
}

Choose a reason for hiding this comment

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

๋ฉ”์†Œ๋“œ๋กœ ์ •์˜ํ•  ์ˆ˜๋„ ์žˆ์ง€๋งŒ ๊ฒฐ๊ตญ ๋™์ผํ•œ ๊ฐ์ฒด๋ฅผ ์—ฌ๋Ÿฌ๋ฒˆ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋ถˆ๋ณ€ํ•œ ๊ฐ์ฒด๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋Š” ํ˜„์žฌ ์ƒํ™ฉ์ด๋ผ๋ฉด ๋‹จ์ˆœํžˆ ์ƒ์ˆ˜๋กœ ๋งŒ๋“ค๊ณ  ์žฌ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋„ ๋ฐฉ๋ฒ•์ด ๋˜๊ฒ ๋„ค์š” :)

Copy link
Author

Choose a reason for hiding this comment

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

AuthorizationDecision ์—์„œ ์„ฑ๊ณต ์‹คํŒจ๋งŒ ๊ฐ€์ง€๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ข‹์€ ๋ฐฉ๋ฒ•์ด๋„ค์š”.


public boolean isDenied() {
return !isGranted;
}
}
Original file line number Diff line number Diff line change
@@ -1,36 +1,31 @@
package nextstep.security.authorization;

import nextstep.security.authentication.Authentication;
import nextstep.security.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

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.RequestMatcherDelegatingAuthorizationManager;
import nextstep.security.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;
import java.util.Set;

public class CheckAuthenticationFilter extends OncePerRequestFilter {
private static final String DEFAULT_REQUEST_URI = "/members";
public class AuthorizationFilter extends OncePerRequestFilter {

private final RequestMatcherDelegatingAuthorizationManager authorizationManager;

public AuthorizationFilter(RequestMatcherDelegatingAuthorizationManager authorizationManager) {
this.authorizationManager = authorizationManager;
}

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (!DEFAULT_REQUEST_URI.equals(request.getRequestURI())) {
filterChain.doFilter(request, response);
return;
}

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null || !authentication.isAuthenticated()) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
return;
}

Set<String> authorities = authentication.getAuthorities();
if (!authorities.contains("ADMIN")) {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
return;
AuthorizationDecision authorizationDecision = authorizationManager.checkInFilter(request, authentication);
if (authorizationDecision.isDenied()) {
throw new AuthenticationException();
Copy link

@jinyoungchoi95 jinyoungchoi95 Feb 11, 2025

Choose a reason for hiding this comment

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

์ธ๊ฐ€๊ถŒํ•œ์ด ๊ฑฐ์ ˆ๋˜์—ˆ๋Š”๋ฐ AuthenticationException์ธ ์ธ์ฆ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ์ด ๋งž์„๊นŒ์š”?

Copy link
Author

Choose a reason for hiding this comment

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

Forbidden ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๋ณ€๊ฒฝํ•ด์•ผ๊ฒ ๋„ค์š”.

์ด ๋ถ€๋ถ„์—์„œ๋„ ์ด์ชฝ์—์„œ๋„ ์™œ check๋ณด๋‹ค verify๋ฅผ ๊ถŒ์žฅํ•˜๋Š” ์ง€ ๋ณด์—ฌ์ฃผ๋Š” ๊ฒƒ ๊ฐ™๋„ค์š”. AuthorizationDecision ์ธ๊ฐ€ ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ์— ์ผ๊ด€์„ฑ์„ ๋ณด์žฅํ•ด์ฃผ๋Š” ๊ฒƒ์ด ํ•„์š”ํ•ด ๋ณด์ด๋„ค์š”!

}

filterChain.doFilter(request, response);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/nextstep/security/authorization/Secured.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Secured {
String value();
String[] value() default {"ADMIN"};
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,24 @@
import org.aspectj.lang.reflect.MethodSignature;

import java.lang.reflect.Method;
import java.util.Arrays;

@Aspect
public class SecuredAspect {

@Before("@annotation(nextstep.security.authorization.Secured)")
public void checkSecured(JoinPoint joinPoint) throws NoSuchMethodException {
Method method = getMethodFromJoinPoint(joinPoint);
String secured = method.getAnnotation(Secured.class).value();
String[] secured = method.getAnnotation(Secured.class).value();
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new AuthenticationException();
}
if (!authentication.getAuthorities().contains(secured)) {

boolean hasNoRole = authentication.getAuthorities()
.stream()
.noneMatch(auth -> Arrays.asList(secured).contains(auth));
if (hasNoRole) {
throw new ForbiddenException();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import nextstep.security.authentication.Authentication;
import nextstep.security.authentication.AuthenticationException;
import nextstep.security.authorization.manager.AuthorizationManager;
import nextstep.security.context.SecurityContextHolder;
import org.aopalliance.aop.Advice;
import org.aopalliance.intercept.MethodInterceptor;
Expand All @@ -11,29 +12,24 @@
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 Pointcut pointcut;

public SecuredMethodInterceptor() {
private final AuthorizationManager<MethodInvocation> authorizationManager;
public SecuredMethodInterceptor(AuthorizationManager<MethodInvocation> authorizationManager) {
this.authorizationManager = authorizationManager;
this.pointcut = new AnnotationMatchingPointcut(null, Secured.class);
}

@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();
}
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
AuthorizationDecision authorizationDecision = authorizationManager.check(authentication, invocation);
if (authorizationDecision.isDenied()) {
throw new AuthenticationException();
}

return invocation.proceed();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package nextstep.security.authorization.manager;

import jakarta.servlet.http.HttpServletRequest;
import nextstep.security.authentication.Authentication;
import nextstep.security.authorization.AuthorizationDecision;

public class AuthenticatedAuthorizationManager implements AuthorizationManager<HttpServletRequest> {

@Override
public AuthorizationDecision check(Authentication authentication, HttpServletRequest object) {
if (authentication == null || !authentication.isAuthenticated()) {
return AuthorizationDecision.denied();
}

return AuthorizationDecision.granted();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package nextstep.security.authorization.manager;

import jakarta.servlet.http.HttpServletRequest;
import nextstep.security.authentication.Authentication;
import nextstep.security.authorization.AuthorizationDecision;
import nextstep.security.authorization.ForbiddenException;

import java.util.Set;

public class AuthorityAuthorizationManager implements AuthorizationManager<HttpServletRequest> {

Choose a reason for hiding this comment

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

Suggested change
public class AuthorityAuthorizationManager implements AuthorizationManager<HttpServletRequest> {
public class AuthorityAuthorizationManager implements AuthorizationManager<T> {

์œ„์—์„œ ๋‚˜์—ดํ•œ ์ด์œ ์—์„œ ์‚ฌ์‹ค HttpServletRequest๋ณด๋‹ค๋Š” ์ œ๋„ค๋ฆญ ํƒ€์ž…์„ ํ™œ์šฉํ•˜์—ฌ ๋‹ค๋ฅธ ๊ณณ์—์„œ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ๋” ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๊ฒ ์ฃ .

Copy link
Author

Choose a reason for hiding this comment

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

ํ•„ํ„ฐ์™€ ์ธํ„ฐ์…‰ํ„ฐ ํ˜น์€ ์ธํ„ฐ์…‰ํ„ฐ์—์„œ๋„ ์—ฌ๋Ÿฌ ์ข…๋ฅ˜ ๊ฐ’์„ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ ์ข‹์€ ์„ ํƒ ๊ฐ™์Šต๋‹ˆ๋‹ค,

๊ทธ๋ ‡๋‹ค๋ฉด AuthorityAuthorizationManager ๋Š” ์—ฌ๋Ÿฌ ์ข…๋ฅ˜์˜ ๊ตฌํ˜„์ฒด๋ฅผ ๋งŒ๋“ค์–ด์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ๋„ ์žˆ์„ ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด RequestMatcherDelegatingAuthorizationManager ์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ๊ตฌํ˜„์ฒด์— support ๋ฉ”์„œ๋“œ๋‚˜ Matcher๋ฅผ ๋งŒ๋“ญ๋‹ˆ๋‹ค.
์ด๋ฅผ ํ†ตํ•ด ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” ์ธ๊ฐ€ ์ž‘์—…์ธ์ง€ ํ™•์ธํ•˜๊ณ  ๊ทธ์— ๋งž๋Š” AuthorityAuthorizationManager ๊ตฌํ˜„์ฒด๊ฐ€ ์ž‘๋™ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์‰ฝ๊ฒŒ ๊ด€๋ฆฌ ๋  ๊ฒƒ ๊ฐ™์•„์š”!


private final Set<String> authorities;

public AuthorityAuthorizationManager(Set<String> authorities) {
this.authorities = authorities;
}


@Override
public AuthorizationDecision check(Authentication authentication, HttpServletRequest request) {
if (authentication == null || !authentication.isAuthenticated()) {
return AuthorizationDecision.denied();
}

boolean hasNoRole = authentication.getAuthorities()
.stream()
.noneMatch(authorities::contains);

if (hasNoRole) {
throw new ForbiddenException();
}

return AuthorizationDecision.granted();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package nextstep.security.authorization.manager;

import nextstep.security.authentication.Authentication;
import nextstep.security.authorization.AuthorizationDecision;

@FunctionalInterface

Choose a reason for hiding this comment

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

์„ ํƒ์‚ฌํ•ญ์— ๋‚˜์™€์žˆ๋Š” verify๋Š” ๊ตฌํ˜„ํ•˜์ง€ ์•Š์œผ์…”๋„ ๋˜๊ธด ํ•˜์ง€๋งŒ, check์™€ verify๋Š” ๊ฐ๊ฐ ์–ด๋–ค ์ƒํ™ฉ์—์„œ ์‚ฌ์šฉํ•  ํ•ด์•ผ ์ข‹์€์ง€ ์„ธ์ค€๋‹˜์˜ ์˜๊ฒฌ์€ ์–ด๋– ์‹ ๊ฐ€์š”?

Copy link
Author

Choose a reason for hiding this comment

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

์ œ๊ฐ€ ์ด ๋ฏธ์…˜์„ ์ง„ํ–‰ํ•˜๋ฉด์„œ ๋А๋‚Œ ์ ์€ ์„ธ ๊ฐ€์ง€์ž…๋‹ˆ๋‹ค.

  1. AuthorizationDecision ์˜ ๋„์˜ ์œ„ํ—˜์„ฑ
  2. AuthorizationDecision ๊ฐ€ ์‹คํŒจ๋ฌ์„ ๋•Œ์˜ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ์ผ๊ด€์„ฑ
  3. ์‚ฌ์šฉ๋„ ์•ˆํ•˜๋Š” ๊ฒฐ๊ณผ๋ฅผ ๋ฆฌํ„ด
    ์ด๋Ÿฐ ์ƒํ™ฉ๋•Œ๋ฌธ์— verify๋ฅผ ๊ถŒ์žฅํ•˜๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.
    ํ•˜์ง€๋งŒ AuthorizationDecision ์˜ ๊ฒฐ๊ณผ๋ฅผ ์ปค์Šคํ…€ ์˜ˆ์™ธ๋‚˜ ์กฐ๊ฑด๋ถ€ ๋กœ์ง์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ๋งŒ check์„ ์‚ฌ์šฉํ•ด ๊ฒฐ๊ณผ๋ฅผ ํ•ธ๋“ค๋ง ํ•  ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.

Choose a reason for hiding this comment

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

์‚ฌ์‹ค spring security์—์„œ๋Š” verify๋ฅผ ๊ถŒ์žฅํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ์—์š”. (์˜คํ•ด์˜ ์†Œ์ง€๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™์•„ ๋ฏธ๋ฆฌ ๋ง์”€๋“œ๋ฆฌ๋ฉด ์ •๋‹ต์„ ์š”๊ตฌํ•˜๊ธฐ ์œ„ํ•ด ๋‚จ๊ฒจ๋“œ๋ฆฐ ์งˆ๋ฌธ์€ ์•„๋‹™๋‹ˆ๋‹ค.)

๋ง์”€ํ•ด์ฃผ์‹  ์ด์œ  ์ค‘ ๋จผ์ € 1์— ๋Œ€ํ•ด์„œ ์ด์•ผ๊ธฐํ•˜๋ฉด null ์œ„ํ—˜์„ฑ์„ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๊ฒƒ์€ ๋งž์ง€๋งŒ AuthorizationDecision์˜ ๋ฐ˜ํ™˜์€ ์ธ๊ฐ€๋จ(true), ์ธ๊ฐ€์•ˆ๋จ(false)๋งŒ ์žˆ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์ธ๊ฐ€ ์ƒํ™ฉ์ด ์—†์Œ์„ ๋‚˜ํƒ€๋‚ด๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ€๋ น @Secured๊ฐ€ ๊ฑธ๋ฆฐ ๊ณณ์ด ์—†์–ด ๊ฒ€์ฆํ•  authorities๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๊ฒ ๋„ค์š”.

	public AuthorizationDecision check(Supplier<Authentication> authentication, MethodInvocation mi) {
		Set<String> authorities = getAuthorities(mi);
		return authorities.isEmpty() ? null : this.authoritiesAuthorizationManager.check(authentication, authorities);
	}

	private Set<String> getAuthorities(MethodInvocation methodInvocation) {
		Method method = methodInvocation.getMethod();
		Object target = methodInvocation.getThis();
		Class<?> targetClass = (target != null) ? target.getClass() : null;
		MethodClassKey cacheKey = new MethodClassKey(method, targetClass);
		return this.cachedAuthorities.computeIfAbsent(cacheKey, (k) -> resolveAuthorities(method, targetClass));
	}

	private Set<String> resolveAuthorities(Method method, Class<?> targetClass) {
		Secured secured = findSecuredAnnotation(method, targetClass);
		return (secured != null) ? Set.of(secured.value()) : Collections.emptySet();
	}

๋‹ค์Œ์œผ๋กœ ์ค‘์š”ํ•œ ๊ฒƒ์€ ์ธ๊ฐ€๋ฅผ ์ฒดํฌํ–ˆ์„๋•Œ ์ธ๊ฐ€๋˜์ง€ ์•Š์€ ์œ ์ €์ธ ๊ฒƒ์ด AuthorizationManager์ž…์žฅ์—์„œ๋Š” ์˜ˆ์™ธ์ƒํ™ฉ์€ ์•„๋‹ ์ˆ˜ ์žˆ๋‹ค์—์š”. ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๊ฒƒ์€ ์ด ์ƒํ™ฉ์ด ์‹คํŒจํ–ˆ์œผ๋‹ˆ ์‹คํŒจ๋ฅผ ์ „ํŒŒํ•˜๋Š” ๊ฒƒ์ด ๋ชฉ์ ์ผํ…๋ฐ์š”. spring security ์ž…์žฅ์—์„œ๋Š” AuthorizationManager๋Š” ์ธ๊ฐ€๊ฐ€ ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•ด์ฃผ๋Š” ๊ฐ์ฒด์ด๊ณ , ์‹ค์ œ ์—๋Ÿฌ๋Š” AuthorizationFilter ๋“ฑ๊ณผ ๊ฐ™์€ ํ•„ํ„ฐ์—์„œ ์ธ๊ฐ€๊ฐ€ ์•ˆ๋˜์—ˆ๋Š” ์ƒํ™ฉ์„ ํŒ๋‹จํ•˜์—ฌ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œ์ผœ์ฃผ๊ณ  ์žˆ์–ด์š”.

๊ทธ๋ž˜์„œ ๋‚ด๋ถ€์ ์œผ๋ก  ์กฐ๊ธˆ ๋” ๋ณต์žกํ•˜์ง€๋งŒ AuthorizationFilter์—์„œ๋Š” ์ธ๊ฐ€ ๊ฒฐ๊ณผ์— ๋Œ€ํ•œ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœํ–‰ํ•œ ๋‹ค์Œ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๊ธฐ๋„ ํ•ด์š”.

AuthorizationResult result = this.authorizationManager.authorize(this::getAuthentication, request);
this.eventPublisher.publishAuthorizationEvent(this::getAuthentication, request, result);
if (result != null && !result.isGranted()) {
	throw new AuthorizationDeniedException("Access Denied", result);
}
chain.doFilter(request, response);

spring security์˜ AuthorizationManager๋ฅผ ๋ณด๋ฉด ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์ฝ”๋“œ๊ฐ€ ์—†๋Š” ๊ฒƒ์„ ๋ณด์‹œ๊ฒŒ ๋ ํ…๋ฐ ๊ทธ๋Ÿฐ ๊ฒƒ๋“ค๋„ ์œ„์™€ ๊ฐ™์€ ์ด์œ ์—์„œ์ผ ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์Šต๋‹ˆ๋‹ค :)

Copy link
Author

Choose a reason for hiding this comment

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

์ž์„ธํ•œ ์„ค๋ช… ๊ฐ์‚ฌํ•ฉ๋‹ˆ๋‹ค. ๋‹จ์ˆœ ์ธ๊ฐ€ ์„ฑ๊ณต, ์‹คํŒจ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์—ฌ๋Ÿฌ ์ผ€์ด์Šค์— ๋Œ€ํ•ด์„œ๋Š” ๊นŠ์ด ์ƒ๊ฐ ๋ชปํ–ˆ์—ˆ๋Š”๋ฐ, ๊ณ ๋ฏผํ•ด๋ณผ ํฌ์ธํŠธ๋„ค์š”!

AuthorizationManager์—์„œ๋Š” ์ธ๊ฐ€ ํ™•์ธ, AuthorizationFilter ์ „์ฒด์ ์ธ ๋งฅ๋ฝ์„ ํŒŒ์•…ํ•˜์—ฌ ํ•„ํ„ฐ๋ง ํ•ด์ฃผ๋Š” ๊ฒƒ์ด๊ตฐ์š”!

public interface AuthorizationManager<T> {
AuthorizationDecision check(Authentication authentication, T object);

Choose a reason for hiding this comment

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

์ตœ๊ทผ spring security ๋ฒ„์ „์—์„œ๋Š” check๋Š” deprecated๋˜์—ˆ์Šต๋‹ˆ๋‹ค. AuthorizationDecision์ด ์ง€๊ธˆ ๋งŒ๋“ค์–ด์ฃผ์‹  ๊ฒƒ๊ณผ ๊ฐ™์ด ๊ตฌํ˜„์ฒด๋กœ ๋˜์–ด์žˆ๊ณ , ์ผ๋ฐ˜์ ์œผ๋กœ ์˜คํ”ˆ์†Œ์Šค๋“ค์ด ์ปค์งˆ์ˆ˜๋ก ๋‹จ์ˆœํ•˜๊ฒŒ ๊ตฌํ˜„ํ•ด๋‘์—ˆ๋˜ ๊ตฌํ˜„์ฒด๋“ค์„ ์ถ”์ƒํ™”ํ•˜๋Š” ํ˜•ํƒœ๋กœ ๊ฐœ์„ ํ•ด๋‚˜๊ฐ€๋Š”๋ฐ์š”.

๊ทธ๋ž˜์„œ ์ตœ๊ทผ ๋ฒ„์ „์—์„œ๋Š” ๊ตฌํ˜„์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” check๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ธฐ๋ณด๋‹ค๋Š” deprecatedํ•ด๋‘” ๋’ค ์ด๋ฅผ ์ถ”์ƒํ™”ํ•œ AuthorizationResult๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ ํ•ด๋‹น ๋ฉ”์†Œ๋“œ ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

https://github.com/franticticktick/spring-security/blob/main/core/src/main/java/org/springframework/security/authorization/AuthorizationManager.java

spring-projects/spring-security#14712
spring-projects/spring-security#14846

Copy link
Author

Choose a reason for hiding this comment

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

๋ฏธ์…˜์„ ํ•˜๋‹ค๋ณด๋‹ˆ ๋ง์”€ํ•ด์ฃผ์‹  ๋Œ€๋กœ AuthorizationDecision ๊ฐ€ deprecated๋œ ์ด์œ ๊ฐ€ ๊ฐ„์ ‘์ ์œผ๋กœ ๋А๊ปด์ง€๋Š” ๊ฒƒ ๊ฐ™์Šต๋‹ˆ๋‹ค.
RequestMatcherDelegatingAuthorizationManager ์—์„œ๋„ checkInFilter ๋ณด๋‹ค๋Š” verifyInFilter๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด
AuthorizationFilter ์—์„œ์˜ ์ธ๊ฐ€ ์š”์ฒญ, ์˜ˆ์™ธ์ฒ˜๋ฆฌ ๋‘๊ฐ€์ง€ ์—ญํ• ์—์„œ ์ธ๊ฐ€ ์š”์ฒญ๋งŒ ๋‚จ๊ฒŒ๋˜์–ด์„œ ํ›จ์”ฌ ๊น”๋”ํ•ฉ๋‹ˆ๋‹ค!

}
Loading