Skip to content

Commit 555da01

Browse files
committed
(step3) httpSecurity - AuthorizeHttpRequestsConfigurer를 이용한 인가 관리 리팩토링
1 parent 9329446 commit 555da01

File tree

6 files changed

+142
-43
lines changed

6 files changed

+142
-43
lines changed

src/main/java/nextstep/app/SecurityConfig.java

Lines changed: 10 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,12 @@
55
import nextstep.oauth2.registration.ClientRegistration;
66
import nextstep.oauth2.registration.ClientRegistrationRepository;
77
import nextstep.oauth2.userinfo.OAuth2UserService;
8-
import nextstep.security.access.AnyRequestMatcher;
9-
import nextstep.security.access.MvcRequestMatcher;
10-
import nextstep.security.access.RequestMatcherEntry;
118
import nextstep.security.access.hierarchicalroles.RoleHierarchy;
129
import nextstep.security.access.hierarchicalroles.RoleHierarchyImpl;
1310
import nextstep.security.authentication.AuthenticationManager;
1411
import nextstep.security.authentication.DaoAuthenticationProvider;
1512
import nextstep.security.authentication.ProviderManager;
16-
import nextstep.security.authorization.*;
13+
import nextstep.security.authorization.SecuredMethodInterceptor;
1714
import nextstep.security.config.Customizer;
1815
import nextstep.security.config.DelegatingFilterProxy;
1916
import nextstep.security.config.FilterChainProxy;
@@ -25,9 +22,7 @@
2522
import org.springframework.context.annotation.Bean;
2623
import org.springframework.context.annotation.Configuration;
2724
import org.springframework.context.annotation.EnableAspectJAutoProxy;
28-
import org.springframework.http.HttpMethod;
2925

30-
import java.util.ArrayList;
3126
import java.util.HashMap;
3227
import java.util.List;
3328
import java.util.Map;
@@ -63,13 +58,6 @@ public SecuredMethodInterceptor securedMethodInterceptor() {
6358
return new SecuredMethodInterceptor();
6459
}
6560

66-
@Bean
67-
public RoleHierarchy roleHierarchy() {
68-
return RoleHierarchyImpl.with()
69-
.role("ADMIN").implies("USER")
70-
.build();
71-
}
72-
7361
@Bean
7462
public AuthenticationManager authenticationManager() {
7563
return new ProviderManager(List.of(
@@ -80,35 +68,23 @@ public AuthenticationManager authenticationManager() {
8068
@Bean
8169
public SecurityFilterChain securityFilterChain2(HttpSecurity http) {
8270
return http
71+
.authorizeHttpRequests(authorize -> authorize
72+
.requestMatchers("/members").hasRole("ADMIN")
73+
.requestMatchers("/members/me").authenticated()
74+
.anyRequest().permitAll()
75+
)
8376
.csrf(c -> c.ignoringRequestMatchers("/login"))
8477
.formLogin(Customizer.withDefaults())
8578
.httpBasic(Customizer.withDefaults())
8679
.oauth2Login(Customizer.withDefaults())
8780
.build();
8881
}
8982

90-
// @Bean
91-
// public SecurityFilterChain securityFilterChain() {
92-
// return new DefaultSecurityFilterChain(
93-
// List.of(
94-
// new SecurityContextHolderFilter(),
95-
// new CsrfFilter(Set.of(new MvcRequestMatcher(HttpMethod.POST, "/login"))),
96-
// new UsernamePasswordAuthenticationFilter(authenticationManager()),
97-
// new BasicAuthenticationFilter(authenticationManager()),
98-
// new OAuth2AuthorizationRequestRedirectFilter(clientRegistrationRepository()),
99-
// new OAuth2LoginAuthenticationFilter(clientRegistrationRepository(), new OAuth2AuthorizedClientRepository(), authenticationManager()),
100-
// new AuthorizationFilter(requestAuthorizationManager())
101-
// )
102-
// );
103-
// }
104-
10583
@Bean
106-
public RequestMatcherDelegatingAuthorizationManager requestAuthorizationManager() {
107-
List<RequestMatcherEntry<AuthorizationManager>> mappings = new ArrayList<>();
108-
mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/members"), new AuthorityAuthorizationManager(roleHierarchy(), "ADMIN")));
109-
mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/members/me"), new AuthorityAuthorizationManager(roleHierarchy(), "USER")));
110-
mappings.add(new RequestMatcherEntry<>(AnyRequestMatcher.INSTANCE, new PermitAllAuthorizationManager<Void>()));
111-
return new RequestMatcherDelegatingAuthorizationManager(mappings);
84+
public RoleHierarchy roleHierarchy() {
85+
return RoleHierarchyImpl.with()
86+
.role("ADMIN").implies("USER")
87+
.build();
11288
}
11389

11490
@Bean

src/main/java/nextstep/security/authorization/AuthenticatedAuthorizationManager.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
import nextstep.security.authentication.Authentication;
44

55
public class AuthenticatedAuthorizationManager<T> implements AuthorizationManager<T> {
6+
67
@Override
78
public AuthorizationDecision check(Authentication authentication, T object) {
8-
boolean granted = authentication.isAuthenticated();
9-
return new AuthorizationDecision(granted);
9+
return new AuthorizationDecision(isGranted(authentication));
10+
}
11+
12+
private boolean isGranted(Authentication authentication) {
13+
return authentication != null && authentication.isAuthenticated();
1014
}
1115
}

src/main/java/nextstep/security/authorization/AuthorizationFilter.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,27 @@ public void doFilter(ServletRequest servletRequest, ServletResponse servletRespo
2929
try {
3030
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
3131
AuthorizationDecision decision = this.authorizationManager.check(authentication, request);
32-
if (decision != null && !decision.isGranted()) {
33-
throw new AccessDeniedException("Access Denied");
32+
33+
if (authorizationSucceed(decision)) {
34+
chain.doFilter(servletRequest, servletResponse);
35+
return;
36+
}
37+
38+
// 인가에 실패했는데 인증에 실패해서 인가에 실패한 경우
39+
if (authentication == null || !authentication.isAuthenticated()) {
40+
throw new AuthenticationException();
3441
}
3542

36-
chain.doFilter(request, response);
43+
// 인가에 실패한 경우
44+
throw new AccessDeniedException();
3745
} catch (AccessDeniedException e) {
3846
response.sendError(HttpServletResponse.SC_FORBIDDEN);
3947
} catch (AuthenticationException e) {
4048
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
4149
}
4250
}
51+
52+
private boolean authorizationSucceed(AuthorizationDecision decision) {
53+
return decision != null && decision.isGranted();
54+
}
4355
}

src/main/java/nextstep/security/authorization/RequestMatcherDelegatingAuthorizationManager.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ public AuthorizationDecision check(Authentication authentication, HttpServletReq
2020
RequestMatcher matcher = mapping.getRequestMatcher();
2121
if (matcher.matches(request)) {
2222
AuthorizationManager manager = mapping.getEntry();
23-
2423
return manager.check(authentication, request);
2524
}
2625
}

src/main/java/nextstep/security/config/annotation/web/builders/HttpSecurity.java

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
package nextstep.security.config.annotation.web.builders;
22

33
import jakarta.servlet.Filter;
4+
import nextstep.security.access.hierarchicalroles.RoleHierarchy;
45
import nextstep.security.authentication.AuthenticationManager;
6+
import nextstep.security.authorization.AuthorizationFilter;
57
import nextstep.security.config.Customizer;
68
import nextstep.security.config.DefaultSecurityFilterChain;
79
import nextstep.security.config.SecurityFilterChain;
810
import nextstep.security.config.annotation.web.SecurityConfigurer;
911
import nextstep.security.config.annotation.web.configurers.*;
12+
import nextstep.security.context.SecurityContextHolderFilter;
13+
import org.springframework.context.ApplicationContext;
14+
import org.springframework.util.Assert;
1015

1116
import java.util.*;
1217

@@ -25,14 +30,14 @@ public <C> C getSharedObject(Class<C> sharedType) {
2530
return (C) this.sharedObjects.get(sharedType);
2631
}
2732

28-
private <C> void setSharedObject(Class<C> sharedType, C object) {
33+
public <C> void setSharedObject(Class<C> sharedType, C object) {
2934
this.sharedObjects.put(sharedType, object);
3035
}
3136

3237
public SecurityFilterChain build() {
3338
init();
3439
configure();
35-
return new DefaultSecurityFilterChain(filters);
40+
return new DefaultSecurityFilterChain(orderFilters());
3641
}
3742

3843
private void init() {
@@ -67,7 +72,10 @@ public HttpSecurity oauth2Login(Customizer<OAuth2LoginConfigurer> oauth2LoginCus
6772
return this;
6873
}
6974

70-
public HttpSecurity authorizeHttpRequests() {
75+
public HttpSecurity authorizeHttpRequests(Customizer<AuthorizeHttpRequestsConfigurer> requestsCustomizer) {
76+
RoleHierarchy roleHierarchy = getSharedObject(ApplicationContext.class).getBean(RoleHierarchy.class);
77+
Assert.notNull(roleHierarchy, "roleHierarchy must not be null");
78+
requestsCustomizer.customize(getOrApply(new AuthorizeHttpRequestsConfigurer(roleHierarchy)));
7179
return this;
7280
}
7381

@@ -91,4 +99,28 @@ private <C extends SecurityConfigurer> C getOrApply(C configurer) {
9199
this.configurers.put(clazz, configurer);
92100
return configurer;
93101
}
102+
103+
private List<Filter> orderFilters() {
104+
List<Filter> orderedFilters = new ArrayList<>(this.filters);
105+
106+
// SecurityContext는 인증 전부터 관리되므로, SecurityContextHolder 필터를 인증 필터보다 앞에 위치.
107+
orderedFilters.stream()
108+
.filter(filter -> filter instanceof SecurityContextHolderFilter)
109+
.findFirst()
110+
.ifPresent(filter -> {
111+
orderedFilters.remove(filter);
112+
orderedFilters.add(0, filter);
113+
});
114+
115+
// 인증 후에 인가를 실행해야하므로, 인가 필터를 가장 마지막에 위치
116+
orderedFilters.stream()
117+
.filter(filter -> filter instanceof AuthorizationFilter)
118+
.findFirst()
119+
.ifPresent(filter -> {
120+
orderedFilters.remove(filter);
121+
orderedFilters.add(filter);
122+
});
123+
124+
return orderedFilters;
125+
}
94126
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package nextstep.security.config.annotation.web.configurers;
2+
3+
import nextstep.security.access.AnyRequestMatcher;
4+
import nextstep.security.access.MvcRequestMatcher;
5+
import nextstep.security.access.RequestMatcher;
6+
import nextstep.security.access.RequestMatcherEntry;
7+
import nextstep.security.access.hierarchicalroles.RoleHierarchy;
8+
import nextstep.security.authorization.*;
9+
import nextstep.security.config.annotation.web.SecurityConfigurer;
10+
import nextstep.security.config.annotation.web.builders.HttpSecurity;
11+
import org.springframework.util.Assert;
12+
13+
import java.util.ArrayList;
14+
import java.util.Arrays;
15+
import java.util.List;
16+
17+
public class AuthorizeHttpRequestsConfigurer implements SecurityConfigurer {
18+
19+
List<RequestMatcherEntry<AuthorizationManager>> mappings = new ArrayList<>();
20+
private List<? extends RequestMatcher> currentMatchers;
21+
private final RoleHierarchy roleHierarchy;
22+
23+
public AuthorizeHttpRequestsConfigurer(RoleHierarchy roleHierarchy) {
24+
Assert.notNull(roleHierarchy, "roleHierarchy cannot be null");
25+
this.roleHierarchy = roleHierarchy;
26+
}
27+
28+
@Override
29+
public void init(HttpSecurity http) {
30+
// AuthorizeHttpRequestsConfigurer.hasRole() 보다 HttpSecurity.build()가 먼저 수행되므로,
31+
// init()에서 roleHierarchy 초기화를 진행 시 .hasRole() 시점에 roleHierarchy가 null이 되는 문제 발생 -> 생성자에서 roleHierarchy 주입.
32+
}
33+
34+
@Override
35+
public void configure(HttpSecurity http) {
36+
RequestMatcherDelegatingAuthorizationManager manager = new RequestMatcherDelegatingAuthorizationManager(mappings);
37+
AuthorizationFilter filter = new AuthorizationFilter(manager);
38+
http.addFilter(filter);
39+
}
40+
41+
public AuthorizeHttpRequestsConfigurer requestMatchers(String... patterns) {
42+
this.currentMatchers = Arrays.stream(patterns)
43+
.map(pattern -> new MvcRequestMatcher(null, pattern))
44+
.toList();
45+
46+
return this;
47+
}
48+
49+
public AuthorizeHttpRequestsConfigurer anyRequest() {
50+
this.currentMatchers = List.of(AnyRequestMatcher.INSTANCE);
51+
return this;
52+
}
53+
54+
public AuthorizeHttpRequestsConfigurer hasRole(String role) {
55+
addMappings(new AuthorityAuthorizationManager<>(this.roleHierarchy, role));
56+
return this;
57+
}
58+
59+
public AuthorizeHttpRequestsConfigurer permitAll() {
60+
addMappings(new PermitAllAuthorizationManager<>());
61+
return this;
62+
}
63+
64+
public AuthorizeHttpRequestsConfigurer authenticated() {
65+
addMappings(new AuthenticatedAuthorizationManager<>());
66+
return this;
67+
}
68+
69+
private <T> void addMappings(AuthorizationManager<T> authorizationManager) {
70+
for (RequestMatcher matcher : this.currentMatchers) {
71+
this.mappings.add(
72+
new RequestMatcherEntry<>(matcher, authorizationManager)
73+
);
74+
}
75+
}
76+
}

0 commit comments

Comments
 (0)