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

CSRF Filter, Refactoring 리뷰 요청드립니다. #3

Open
wants to merge 4 commits into
base: parkjun5
Choose a base branch
from
Open
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
108 changes: 19 additions & 89 deletions src/main/java/nextstep/app/SecurityConfig.java
Original file line number Diff line number Diff line change
@@ -1,61 +1,40 @@
package nextstep.app;

import nextstep.oauth2.OAuth2ClientProperties;
import nextstep.oauth2.authentication.OAuth2LoginAuthenticationProvider;
import nextstep.oauth2.registration.ClientRegistration;
import nextstep.oauth2.registration.ClientRegistrationRepository;
import nextstep.oauth2.userinfo.OAuth2UserService;
import nextstep.oauth2.web.OAuth2AuthorizationRequestRedirectFilter;
import nextstep.oauth2.web.OAuth2AuthorizedClientRepository;
import nextstep.oauth2.web.OAuth2LoginAuthenticationFilter;
import nextstep.security.access.AnyRequestMatcher;
import nextstep.security.access.MvcRequestMatcher;
import nextstep.security.access.RequestMatcherEntry;
import nextstep.security.access.hierarchicalroles.RoleHierarchy;
import nextstep.security.access.hierarchicalroles.RoleHierarchyImpl;
import nextstep.security.authentication.*;
import nextstep.security.authorization.*;
import nextstep.security.config.DefaultSecurityFilterChain;
import nextstep.security.config.DelegatingFilterProxy;
import nextstep.security.config.FilterChainProxy;
import nextstep.security.authorization.SecuredMethodInterceptor;
import nextstep.security.config.SecurityFilterChain;
import nextstep.security.context.SecurityContextHolderFilter;
import nextstep.security.userdetails.UserDetailsService;
import nextstep.security.config.annotation.EnableWebSecurity;
import nextstep.security.config.annotation.HttpSecurity;
import nextstep.security.config.annotation.configurer.Customizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
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.HashMap;
import java.util.List;
import java.util.Map;


@Configuration
@EnableAspectJAutoProxy
@EnableConfigurationProperties(OAuth2ClientProperties.class)
@EnableWebSecurity
public class SecurityConfig {

private final UserDetailsService userDetailsService;
private final OAuth2UserService oAuth2UserService;
private final OAuth2ClientProperties oAuth2ClientProperties;

public SecurityConfig(UserDetailsService userDetailsService, OAuth2UserService oAuth2UserService, OAuth2ClientProperties oAuth2ClientProperties) {
this.userDetailsService = userDetailsService;
this.oAuth2UserService = oAuth2UserService;
this.oAuth2ClientProperties = oAuth2ClientProperties;
}

@Bean
public DelegatingFilterProxy delegatingFilterProxy() {
return new DelegatingFilterProxy(filterChainProxy(List.of(securityFilterChain())));
}

@Bean
public FilterChainProxy filterChainProxy(List<SecurityFilterChain> securityFilterChains) {
return new FilterChainProxy(securityFilterChains);
public SecurityFilterChain securityFilterChain(HttpSecurity http) {
return http
.csrf(c -> c.ignoringRequestMatchers("/login", "/logout"))

Choose a reason for hiding this comment

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

/login, /logout은 제외하신 이유가 있을까요?

.authorizeHttpRequests(
authorizeHttp -> {
authorizeHttp.requestMatchers("/members").hasRole("ADMIN");
authorizeHttp.requestMatchers("/members/me").hasRole("USER");
authorizeHttp.anyRequest().permitAll();
}
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults())
.oauth2Login(Customizer.withDefaults())
.build();
}

@Bean
Expand All @@ -69,53 +48,4 @@ public RoleHierarchy roleHierarchy() {
.role("ADMIN").implies("USER")
.build();
}

@Bean
public AuthenticationManager authenticationManager() {
return new ProviderManager(List.of(
new DaoAuthenticationProvider(userDetailsService),
new OAuth2LoginAuthenticationProvider(oAuth2UserService)));
}

@Bean
public SecurityFilterChain securityFilterChain() {
return new DefaultSecurityFilterChain(
List.of(
new SecurityContextHolderFilter(),
new UsernamePasswordAuthenticationFilter(authenticationManager()),
new BasicAuthenticationFilter(authenticationManager()),
new OAuth2AuthorizationRequestRedirectFilter(clientRegistrationRepository()),
new OAuth2LoginAuthenticationFilter(clientRegistrationRepository(), new OAuth2AuthorizedClientRepository(), authenticationManager()),
new AuthorizationFilter(requestAuthorizationManager())
)
);
}

@Bean
public RequestMatcherDelegatingAuthorizationManager requestAuthorizationManager() {
List<RequestMatcherEntry<AuthorizationManager>> mappings = new ArrayList<>();
mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/members"), new AuthorityAuthorizationManager(roleHierarchy(), "ADMIN")));
mappings.add(new RequestMatcherEntry<>(new MvcRequestMatcher(HttpMethod.GET, "/members/me"), new AuthorityAuthorizationManager(roleHierarchy(), "USER")));
mappings.add(new RequestMatcherEntry<>(AnyRequestMatcher.INSTANCE, new PermitAllAuthorizationManager<Void>()));
return new RequestMatcherDelegatingAuthorizationManager(mappings);
}

@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
Map<String, ClientRegistration> registrations = getClientRegistrations(oAuth2ClientProperties);
return new ClientRegistrationRepository(registrations);
}

private static Map<String, ClientRegistration> getClientRegistrations(OAuth2ClientProperties properties) {
Map<String, ClientRegistration> clientRegistrations = new HashMap<>();
properties.getRegistration().forEach((key, value) -> clientRegistrations.put(key,
getClientRegistration(key, value, properties.getProvider().get(key))));
return clientRegistrations;
}

private static ClientRegistration getClientRegistration(String registrationId,
OAuth2ClientProperties.Registration registration, OAuth2ClientProperties.Provider provider) {
return new ClientRegistration(registrationId, registration.getClientId(), registration.getClientSecret(), registration.getRedirectUri(), registration.getScope(), provider.getAuthorizationUri(), provider.getTokenUri(), provider.getUserInfoUri(), provider.getUserNameAttributeName());
}
}

2 changes: 2 additions & 0 deletions src/main/java/nextstep/app/domain/MemberRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ public interface MemberRepository {
List<Member> findAll();

Member save(Member member);

void deleteAll();
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,9 @@ public Member save(Member member) {
members.put(member.getEmail(), member);
return member;
}

@Override
public void deleteAll() {
members.clear();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import nextstep.oauth2.registration.ClientRegistrationRepository;
import nextstep.security.authentication.AbstractAuthenticationProcessingFilter;
import nextstep.security.authentication.Authentication;
import nextstep.security.authentication.AuthenticationManager;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.Assert;
import org.springframework.util.MultiValueMap;
Expand All @@ -31,8 +30,8 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce

private final Converter<OAuth2LoginAuthenticationToken, OAuth2AuthenticationToken> authenticationResultConverter = this::createAuthenticationResult;

public OAuth2LoginAuthenticationFilter(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository, AuthenticationManager authenticationManager) {
super(DEFAULT_LOGIN_REQUEST_BASE_URI, authenticationManager);
public OAuth2LoginAuthenticationFilter(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository authorizedClientRepository) {
super(DEFAULT_LOGIN_REQUEST_BASE_URI);
this.clientRegistrationRepository = clientRegistrationRepository;
this.authorizedClientRepository = authorizedClientRepository;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ public abstract class AbstractAuthenticationProcessingFilter extends GenericFilt
private static final AuthenticationSuccessHandler successHandler = (request, response, authentication) -> response.sendRedirect("/");
private static final AuthenticationFailureHandler failureHandler = (request, response, exception) -> response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());

protected AbstractAuthenticationProcessingFilter(String filterProcessesUrl, AuthenticationManager authenticationManager) {
this(request -> {
protected AbstractAuthenticationProcessingFilter(String filterProcessesUrl) {
this.requiresAuthenticationRequestMatcher = request -> {
String uri = request.getRequestURI();
return uri.startsWith(filterProcessesUrl);
}, authenticationManager);
};
}

protected AbstractAuthenticationProcessingFilter(RequestMatcher requiresAuthenticationRequestMatcher, AuthenticationManager authenticationManager) {
Expand All @@ -44,7 +44,7 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
}

private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
if (!requiresAuthentication(request, response)) {
if (!requiresAuthentication(request)) {
chain.doFilter(request, response);
return;
}
Expand All @@ -66,16 +66,16 @@ private void successfulAuthentication(HttpServletRequest request, HttpServletRes
SecurityContextHolder.setContext(context);
this.securityContextRepository.saveContext(context, request, response);

this.successHandler.onAuthenticationSuccess(request, response, authResult);
successHandler.onAuthenticationSuccess(request, response, authResult);
}

private void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
SecurityContextHolder.clearContext();

this.failureHandler.onAuthenticationFailure(request, response, failed);
failureHandler.onAuthenticationFailure(request, response, failed);
}

protected boolean requiresAuthentication(HttpServletRequest request, HttpServletResponse response) {
protected boolean requiresAuthentication(HttpServletRequest request) {
return this.requiresAuthenticationRequestMatcher.matches(request);
}

Expand All @@ -85,4 +85,8 @@ public abstract Authentication attemptAuthentication(HttpServletRequest request,
protected AuthenticationManager getAuthenticationManager() {
return authenticationManager;
}

public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package nextstep.security.authentication;

import nextstep.security.userdetails.UserDetailsService;
import org.springframework.context.ApplicationContext;

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

public class AuthenticationManagerBuilder {

private final List<AuthenticationProvider> authenticationProviders = new ArrayList<>();

public AuthenticationManagerBuilder(ApplicationContext context) {
List<UserDetailsService> userDetailsServices = new ArrayList<>(context.getBeansOfType(UserDetailsService.class).values());
if (userDetailsServices.isEmpty()) {
return;
}
if (userDetailsServices.size() > 1) {
return;
}

UserDetailsService userDetailsService = userDetailsServices.get(0);
DaoAuthenticationProvider provider = new DaoAuthenticationProvider(userDetailsService);
authenticationProvider(provider);
}

public void authenticationProvider(AuthenticationProvider authenticationProvider) {
this.authenticationProviders.add(authenticationProvider);
}

public AuthenticationManager build() {
return new ProviderManager(this.authenticationProviders);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import nextstep.security.access.RequestMatcherEntry;
import nextstep.security.authentication.Authentication;

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

public class RequestMatcherDelegatingAuthorizationManager implements AuthorizationManager<HttpServletRequest> {
Expand All @@ -27,4 +28,22 @@ public AuthorizationDecision check(Authentication authentication, HttpServletReq

return new AuthorizationDecision(true);
}

public static Builder builder() {
return new Builder();
}

public static final class Builder {

private final List<RequestMatcherEntry<AuthorizationManager>> mappings = new ArrayList<>();

public Builder add(RequestMatcher matcher, AuthorizationManager manager) {
this.mappings.add(new RequestMatcherEntry<>(matcher, manager));
return this;
}

public RequestMatcherDelegatingAuthorizationManager build() {
return new RequestMatcherDelegatingAuthorizationManager(this.mappings);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package nextstep.security.autoconfig;

import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.context.annotation.Import;

@AutoConfiguration(before = SecurityAutoConfiguration.class)
@Import({OAuth2ClientRegistrationRepositoryConfiguration.class})
public class OAuth2ClientAutoConfiguration {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package nextstep.security.autoconfig;

import nextstep.oauth2.OAuth2ClientProperties;
import nextstep.oauth2.registration.ClientRegistration;
import nextstep.oauth2.registration.ClientRegistrationRepository;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(OAuth2ClientProperties.class)
public class OAuth2ClientRegistrationRepositoryConfiguration {

@Bean
@ConditionalOnMissingBean(ClientRegistrationRepository.class)
ClientRegistrationRepository clientRegistrationRepository(OAuth2ClientProperties properties) {
Map<String, ClientRegistration> registrations = getClientRegistrations(properties);
return new ClientRegistrationRepository(registrations);
}

private static Map<String, ClientRegistration> getClientRegistrations(OAuth2ClientProperties properties) {
Map<String, ClientRegistration> clientRegistrations = new HashMap<>();
properties.getRegistration().forEach((key, value) -> clientRegistrations.put(key,
getClientRegistration(key, value, properties.getProvider().get(key))));
return clientRegistrations;
}

private static ClientRegistration getClientRegistration(String registrationId,
OAuth2ClientProperties.Registration registration, OAuth2ClientProperties.Provider provider) {
return new ClientRegistration(registrationId, registration.getClientId(), registration.getClientSecret(), registration.getRedirectUri(), registration.getScope(), provider.getAuthorizationUri(), provider.getTokenUri(), provider.getUserInfoUri(), provider.getUserNameAttributeName());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package nextstep.security.autoconfig;

import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
import org.springframework.context.annotation.Bean;

public class SecurityFilterAutoConfiguration {

private static final String DEFAULT_FILTER_NAME = "springSecurityFilterChain";

@Bean
public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration() {
return new DelegatingFilterProxyRegistrationBean(DEFAULT_FILTER_NAME);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package nextstep.security.config.annotation;

import org.springframework.context.annotation.Import;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import({HttpSecurityConfiguration.class, WebSecurityConfiguration.class})
public @interface EnableWebSecurity {
}
Loading