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

[Step1] OAuth 2.0 Login #2

Open
wants to merge 3 commits into
base: hyunssooo
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
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ repositories {
}

dependencies {
// implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'

Expand Down
50 changes: 38 additions & 12 deletions src/main/java/nextstep/app/config/AuthConfig.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package nextstep.app.config;

import nextstep.app.infrastructure.GithubClient;
import nextstep.security.access.AuthorizeRequestMatcherRegistry;
import nextstep.security.access.matcher.AnyRequestMatcher;
import nextstep.security.access.matcher.MvcRequestMatcher;
import nextstep.security.authentication.AuthenticationManager;
import nextstep.security.authentication.BasicAuthenticationFilter;
import nextstep.security.authentication.Oauth2AuthenticationProvider;
import nextstep.security.authentication.Oauth2LoginAuthenticationFilter;
import nextstep.security.authentication.UsernamePasswordAuthenticationFilter;
import nextstep.security.authentication.UsernamePasswordAuthenticationProvider;
import nextstep.security.authorization.AuthorizationFilter;
import nextstep.security.authorization.Oauth2AuthorizationRequestRedirectFilter;
import nextstep.security.authorization.SecurityContextHolderFilter;
import nextstep.security.authorization.manager.RequestAuthorizationManager;
import nextstep.security.config.DefaultSecurityFilterChain;
Expand All @@ -26,16 +30,19 @@
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.servlet.Filter;
import java.util.ArrayList;
import java.util.List;

@Configuration
public class AuthConfig implements WebMvcConfigurer {

private static final String OAUTH2_REDIRECT_URL = "https://github.com/login/oauth/authorize?response_type=code&client_id=7fc956935c0618c560da&scope=read:user&redirect_uri=http://localhost:8080/oauth2/access";

private final UserDetailsService userDetailsService;
private final GithubClient githubClient;

public AuthConfig(UserDetailsService userDetailsService) {
public AuthConfig(UserDetailsService userDetailsService, GithubClient githubClient) {
this.userDetailsService = userDetailsService;
this.githubClient = githubClient;
}

@Bean
Expand All @@ -44,18 +51,26 @@ public DelegatingFilterProxy securityFilterChainProxy() {
}

@Bean
public FilterChainProxy filterChainProxy() {
return new FilterChainProxy(List.of(securityFilterChainNew()));
public FilterChainProxy filterChainProxy(SecurityFilterChain securityFilterChain) {
return new FilterChainProxy(List.of(securityFilterChain));
}

@Bean
public SecurityFilterChain securityFilterChainNew() {
List<Filter> filters = new ArrayList<>();
filters.add(new SecurityContextHolderFilter(securityContextRepository()));
filters.add(new UsernamePasswordAuthenticationFilter(authenticationManager(), securityContextRepository()));
filters.add(new BasicAuthenticationFilter(authenticationManager()));
filters.add(new ExceptionTranslateFilter(requestCache()));
filters.add(new AuthorizationFilter(authorizationManager()));
public SecurityFilterChain securityFilterChain() {
List<Filter> filters = List.of(
new SecurityContextHolderFilter(securityContextRepository()),
new UsernamePasswordAuthenticationFilter(authenticationManager(), securityContextRepository()),
new BasicAuthenticationFilter(authenticationManager()),
new ExceptionTranslateFilter(requestCache()),
new Oauth2AuthorizationRequestRedirectFilter(new MvcRequestMatcher(HttpMethod.GET, "/oauth2/authorization/github"),OAUTH2_REDIRECT_URL),
new Oauth2LoginAuthenticationFilter(
new MvcRequestMatcher(HttpMethod.GET, "/oauth2/access"),
authenticationManager(),
securityContextRepository(),
"/members/authentication"
),
new AuthorizationFilter(authorizationManager())
);
return new DefaultSecurityFilterChain(AnyRequestMatcher.INSTANCE, filters);
}

Expand All @@ -79,9 +94,20 @@ public RequestAuthorizationManager authorizationManager() {
return new RequestAuthorizationManager(requestMatcherRegistry);
}

@Bean
public RequestAuthorizationManager oauth2authorizationManager() {
AuthorizeRequestMatcherRegistry requestMatcherRegistry = new AuthorizeRequestMatcherRegistry();
requestMatcherRegistry
.matcher(new MvcRequestMatcher(HttpMethod.GET, "/oauth2/authorization/github"));
return new RequestAuthorizationManager(requestMatcherRegistry);
}

@Bean
public AuthenticationManager authenticationManager() {
return new AuthenticationManager(new UsernamePasswordAuthenticationProvider(userDetailsService));
return new AuthenticationManager(
new UsernamePasswordAuthenticationProvider(userDetailsService),
new Oauth2AuthenticationProvider(githubClient)
);
}

}
18 changes: 18 additions & 0 deletions src/main/java/nextstep/app/config/WebClientConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package nextstep.app.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.client.WebClient;

@Configuration
public class WebClientConfig {

@Bean
public WebClient webClient() {
return WebClient.builder()
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
}
}
47 changes: 47 additions & 0 deletions src/main/java/nextstep/app/infrastructure/GithubClient.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package nextstep.app.infrastructure;

import nextstep.app.infrastructure.dto.AccessRequest;
import nextstep.app.infrastructure.dto.AccessResponse;
import nextstep.app.infrastructure.dto.UserResponse;
import nextstep.security.oauth2user.BaseOauth2User;
import nextstep.security.oauth2user.OAuth2User;
import nextstep.security.oauth2user.Oauth2UserService;
import nextstep.security.exception.AuthenticationException;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.Set;


@Component
public class GithubClient implements Oauth2UserService {

private final WebClient webClient;

public GithubClient(WebClient webClient) {
this.webClient = webClient;
}

@Override
public OAuth2User loadUser(String accessToken) throws AuthenticationException {
final AccessResponse accessResponse = webClient.post()
.uri("https://github.com/login/oauth/access_token")
.accept(MediaType.APPLICATION_JSON)
.body(
BodyInserters.fromValue(new AccessRequest(accessToken))
)
.retrieve()
.bodyToMono(AccessResponse.class)
.block();

final UserResponse userResponse = webClient.get()
.uri("https://api.github.com/user")
.header("Authorization", "Bearer " + accessResponse.getAccessToken())
.retrieve()
.bodyToMono(UserResponse.class)
.block();
return new BaseOauth2User(userResponse.getId(), Set.of());
}
}
37 changes: 37 additions & 0 deletions src/main/java/nextstep/app/infrastructure/dto/AccessRequest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package nextstep.app.infrastructure.dto;

import com.fasterxml.jackson.annotation.JsonProperty;

public class AccessRequest {
@JsonProperty("client_id")
private String clientId = "7fc956935c0618c560da";
@JsonProperty("client_secret")
private String clientSecret = "a3fd00e8f3146bf81e2c5b2ea328ccb8d330cd45";
private String code;
@JsonProperty("redirect_uri")
private String redirectUri;

public AccessRequest(String code) {
this.code = code;
}

public String getClientId() {
return clientId;
}

public String getClientSecret() {
return clientSecret;
}

public String getCode() {
return code;
}

public String getRedirectUri() {
return redirectUri;
}

public void setRedirectUri(String redirectUri) {
this.redirectUri = redirectUri;
}
}
35 changes: 35 additions & 0 deletions src/main/java/nextstep/app/infrastructure/dto/AccessResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package nextstep.app.infrastructure.dto;

import com.fasterxml.jackson.annotation.JsonProperty;

public class AccessResponse {
@JsonProperty("access_token")
private String accessToken;
private String scope;
@JsonProperty("token_type")
private String tokenType;

public String getAccessToken() {
return accessToken;
}

public void setAccessToken(String accessToken) {
this.accessToken = accessToken;
}

public String getScope() {
return scope;
}

public void setScope(String scope) {
this.scope = scope;
}

public String getTokenType() {
return tokenType;
}

public void setTokenType(String tokenType) {
this.tokenType = tokenType;
}
}
13 changes: 13 additions & 0 deletions src/main/java/nextstep/app/infrastructure/dto/UserResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package nextstep.app.infrastructure.dto;

public class UserResponse {
private String id;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package nextstep.security.authentication;

import java.util.Set;

public class Oauth2Authentication implements Authentication {

private final String id;
private final Set<String> authorities;
private final boolean authenticated;

public Oauth2Authentication(
String id,
Set<String> authorities,
boolean authenticated
) {
this.id = id;
this.authorities = authorities;
this.authenticated = authenticated;
}

public static Oauth2Authentication ofRequest(String accessCode) {
return new Oauth2Authentication(accessCode, Set.of(), false);
}

public static Authentication ofAuthenticated(String name, Set<String> authorities) {
return new Oauth2Authentication(name, Set.of(), true);
}

@Override
public Object getPrincipal() {
return id;
}

@Override
public Object getCredentials() {
return null;
}

@Override
public Set<String> getAuthorities() {
return authorities;
}

@Override
public boolean isAuthenticated() {
return authenticated;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package nextstep.security.authentication;

import nextstep.security.oauth2user.OAuth2User;
import nextstep.security.oauth2user.Oauth2UserService;

public class Oauth2AuthenticationProvider implements AuthenticationProvider {

private final Oauth2UserService oauth2UserService;

public Oauth2AuthenticationProvider(Oauth2UserService oauth2UserService) {
this.oauth2UserService = oauth2UserService;
}

@Override
public Authentication authenticate(Authentication authentication) {
final OAuth2User oAuth2User = oauth2UserService.loadUser(authentication.getPrincipal().toString());
return Oauth2Authentication.ofAuthenticated(oAuth2User.getName(), oAuth2User.getAuthorities());
}

@Override
public boolean supports(Class<?> authentication) {
return Oauth2Authentication.class.isAssignableFrom(authentication);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package nextstep.security.authentication;

import nextstep.security.access.matcher.RequestMatcher;
import nextstep.security.context.SecurityContext;
import nextstep.security.context.SecurityContextHolder;
import nextstep.security.context.SecurityContextRepository;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class Oauth2LoginAuthenticationFilter extends OncePerRequestFilter {

private final RequestMatcher requestMatcher;
private final AuthenticationManager authenticationManager;
private final SecurityContextRepository securityContextRepository;
private final String redirectUrl;

public Oauth2LoginAuthenticationFilter(
RequestMatcher requestMatcher,
AuthenticationManager authenticationManager,
SecurityContextRepository securityContextRepository,
String redirectUrl
) {
this.requestMatcher = requestMatcher;
this.authenticationManager = authenticationManager;
this.securityContextRepository = securityContextRepository;
this.redirectUrl = redirectUrl;
}

@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain
) throws ServletException, IOException {
if (requestMatcher.matches(request)) {
final String accessToken = request.getParameter("code");

final SecurityContext context = SecurityContextHolder.getContext();

final Authentication authRequest = Oauth2Authentication.ofRequest(accessToken);
final Authentication authResult = authenticationManager.authenticate(authRequest);

context.setAuthentication(authResult);
securityContextRepository.saveContext(context, request, response);
response.sendRedirect(redirectUrl);
return;
}

filterChain.doFilter(request, response);
}
}
Loading