-
Notifications
You must be signed in to change notification settings - Fork 37
๐ 2๋จ๊ณ - ์ธ๊ฐ(Authorization) ๋ฆฌ๋ทฐ ์์ฒญ ๋๋ฆฝ๋๋ค. #10
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
Changes from 15 commits
d4add2f
bc949f3
6d5c693
646c495
432239c
e301ed4
8b70fdd
1f37f27
a0c8b51
b83d2a2
c1a2089
0179bf5
fbbf311
bb7a79e
9bf5f29
8126c74
7e23368
7a213b0
0e5d010
5c49d3e
fc947f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 |
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; | ||
|
||
|
@@ -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() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()) | ||
) | ||
); | ||
} | ||
|
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๋ฏธ์ ์์๋ spring security์ ๊ธฐ๋ฅ์ด ์ถ์๋์ด true, false๊ฐ ์ ์์๋ฟ์ผ์ค ์ ์์ผ๋ ์ค์ ๋ก๋ ๊ทธ๋ณด๋ค ๋ ๋ค์ํ ๊ตฌํ์ฒด๋ค์ด ์๊ธธ ์ ์์ต๋๋ค. |
||
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); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๋ฉ์๋๋ก ์ ์ํ ์๋ ์์ง๋ง ๊ฒฐ๊ตญ ๋์ผํ ๊ฐ์ฒด๋ฅผ ์ฌ๋ฌ๋ฒ ์์ฑํ๋ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ๋ถ๋ณํ ๊ฐ์ฒด๋ฅผ ๋ง๋ค ์ ์๋ ํ์ฌ ์ํฉ์ด๋ผ๋ฉด ๋จ์ํ ์์๋ก ๋ง๋ค๊ณ ์ฌ์ฌ์ฉํ๋ ๊ฒ๋ ๋ฐฉ๋ฒ์ด ๋๊ฒ ๋ค์ :) There was a problem hiding this comment. Choose a reason for hiding this commentThe 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(); | ||
|
||
} | ||
|
||
filterChain.doFilter(request, response); | ||
|
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> { | ||||||
|
public class AuthorityAuthorizationManager implements AuthorizationManager<HttpServletRequest> { | |
public class AuthorityAuthorizationManager implements AuthorizationManager<T> { |
์์์ ๋์ดํ ์ด์ ์์ ์ฌ์ค HttpServletRequest
๋ณด๋ค๋ ์ ๋ค๋ฆญ ํ์
์ ํ์ฉํ์ฌ ๋ค๋ฅธ ๊ณณ์์ ํ์ฉํ ์ ์๊ฒ๋ ํ๋ ๊ฒ์ด ์ข๊ฒ ์ฃ .
There was a problem hiding this comment.
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 ๊ตฌํ์ฒด๊ฐ ์๋ํ๋ ๋ฐฉ์์ผ๋ก ์ฝ๊ฒ ๊ด๋ฆฌ ๋ ๊ฒ ๊ฐ์์!
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ ํ์ฌํญ์ ๋์์๋ verify๋ ๊ตฌํํ์ง ์์ผ์ ๋ ๋๊ธด ํ์ง๋ง, check์ verify๋ ๊ฐ๊ฐ ์ด๋ค ์ํฉ์์ ์ฌ์ฉํ ํด์ผ ์ข์์ง ์ธ์ค๋์ ์๊ฒฌ์ ์ด๋ ์ ๊ฐ์? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ ๊ฐ ์ด ๋ฏธ์ ์ ์งํํ๋ฉด์ ๋๋ ์ ์ ์ธ ๊ฐ์ง์ ๋๋ค.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ฌ์ค spring security์์๋ ๋ง์ํด์ฃผ์ ์ด์ ์ค ๋จผ์ 1์ ๋ํด์ ์ด์ผ๊ธฐํ๋ฉด null ์ํ์ฑ์ ๋ฐ์์ํค๋ ๊ฒ์ ๋ง์ง๋ง 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();
} ๋ค์์ผ๋ก ์ค์ํ ๊ฒ์ ์ธ๊ฐ๋ฅผ ์ฒดํฌํ์๋ ์ธ๊ฐ๋์ง ์์ ์ ์ ์ธ ๊ฒ์ด ๊ทธ๋์ ๋ด๋ถ์ ์ผ๋ก ์กฐ๊ธ ๋ ๋ณต์กํ์ง๋ง 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์ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์์ธํ ์ค๋ช ๊ฐ์ฌํฉ๋๋ค. ๋จ์ ์ธ๊ฐ ์ฑ๊ณต, ์คํจ ๋ฟ๋ง ์๋๋ผ ์ฌ๋ฌ ์ผ์ด์ค์ ๋ํด์๋ ๊น์ด ์๊ฐ ๋ชปํ์๋๋ฐ, ๊ณ ๋ฏผํด๋ณผ ํฌ์ธํธ๋ค์! AuthorizationManager์์๋ ์ธ๊ฐ ํ์ธ, AuthorizationFilter ์ ์ฒด์ ์ธ ๋งฅ๋ฝ์ ํ์ ํ์ฌ ํํฐ๋ง ํด์ฃผ๋ ๊ฒ์ด๊ตฐ์! |
||
public interface AuthorizationManager<T> { | ||
AuthorizationDecision check(Authentication authentication, T object); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ์ต๊ทผ spring security ๋ฒ์ ์์๋ ๊ทธ๋์ ์ต๊ทผ ๋ฒ์ ์์๋ ๊ตฌํ์ฒด๋ฅผ ๋ฐํํ๋ spring-projects/spring-security#14712 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ๋ฏธ์
์ ํ๋ค๋ณด๋ ๋ง์ํด์ฃผ์ ๋๋ก AuthorizationDecision ๊ฐ deprecated๋ ์ด์ ๊ฐ ๊ฐ์ ์ ์ผ๋ก ๋๊ปด์ง๋ ๊ฒ ๊ฐ์ต๋๋ค. |
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ํ์ ์๊ตฌ์ฌํญ ์ ์ ๋ฆฌํด์ฃผ์ จ๋ค์ ๐