Skip to content

Commit df42efa

Browse files
committed
default
1 parent 7b20b19 commit df42efa

File tree

9 files changed

+384
-66
lines changed

9 files changed

+384
-66
lines changed

build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ dependencies {
2121
implementation 'org.springframework.boot:spring-boot-starter-web'
2222
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
2323
implementation 'org.springframework.boot:spring-boot-starter-aop'
24+
2425
testImplementation 'org.springframework.boot:spring-boot-starter-test'
26+
testImplementation 'org.springframework.cloud:spring-cloud-contract-wiremock:4.1.4'
2527
}
2628

2729
test {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package nextstep.app.ui;
2+
3+
import jakarta.servlet.http.Cookie;
4+
import jakarta.servlet.http.HttpServletRequest;
5+
import jakarta.servlet.http.HttpServletResponse;
6+
import jakarta.servlet.http.HttpSession;
7+
//import nextstep.oauth2.userinfo.OAuth2User;
8+
import nextstep.security.authentication.Authentication;
9+
import nextstep.security.context.SecurityContextHolder;
10+
import org.springframework.stereotype.Controller;
11+
import org.springframework.ui.Model;
12+
import org.springframework.web.bind.annotation.GetMapping;
13+
import org.springframework.web.bind.annotation.PostMapping;
14+
15+
16+
@Controller
17+
public class LoginController {
18+
19+
@GetMapping("/")
20+
public String login(Model model) {
21+
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
22+
if (authentication != null && authentication.isAuthenticated()) {
23+
model.addAttribute("isAuthenticated", true);
24+
String username = extractUsername(authentication);
25+
model.addAttribute("userName", username);
26+
} else {
27+
model.addAttribute("isAuthenticated", false);
28+
}
29+
return "index";
30+
}
31+
32+
private String extractUsername(Authentication authentication) {
33+
if (authentication.getPrincipal() instanceof String) {
34+
return (String) authentication.getPrincipal();
35+
}
36+
// if (authentication.getPrincipal() instanceof OAuth2User) {
37+
// String userNameAttributeName = ((OAuth2User) authentication.getPrincipal()).getUserNameAttributeName();
38+
// return (String) ((OAuth2User) authentication.getPrincipal()).getAttributes().get(userNameAttributeName);
39+
// }
40+
return "";
41+
}
42+
43+
@PostMapping("/logout")
44+
public String logout(HttpServletRequest request, HttpServletResponse response) {
45+
HttpSession session = request.getSession(false);
46+
if (session != null) {
47+
session.invalidate();
48+
}
49+
50+
Cookie cookie = new Cookie("JSESSIONID", null);
51+
cookie.setPath("/");
52+
cookie.setHttpOnly(true);
53+
cookie.setMaxAge(0);
54+
response.addCookie(cookie);
55+
56+
return "redirect:/";
57+
}
58+
}
Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +0,0 @@
1-
github.access-token-uri: https://github.com/login/oauth/access_token
2-
github.profile-uri: https://api.github.com/user
3-
github.client-id: Ov23liTBhugSIcf8VX1v
4-
github.client-secret: 6971230149831276c87e6855d139f2b9dfab24e6

src/main/resources/static/index.html

Lines changed: 0 additions & 25 deletions
This file was deleted.

src/main/resources/static/login.html

Lines changed: 0 additions & 37 deletions
This file was deleted.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<!DOCTYPE html>
2+
<html lang="en" xmlns:th="http://www.thymeleaf.org">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Home Page</title>
7+
</head>
8+
<body>
9+
<h1>Welcome</h1>
10+
<div th:if="${isAuthenticated}">
11+
<p>Welcome, <span th:text="${userName}"></span>!</p>
12+
<form method="post" action="/logout">
13+
<button type="submit">Logout</button>
14+
</form>
15+
</div>
16+
<div th:if="${!isAuthenticated}">
17+
<h2>Login with OAuth2 Providers</h2>
18+
<a href="/oauth2/authorization/github">Login with Github</a><br>
19+
<a href="/oauth2/authorization/google">Login with Google</a><br>
20+
</div>
21+
</body>
22+
</html>
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package nextstep.app.oauth2;
2+
3+
import com.fasterxml.jackson.databind.ObjectMapper;
4+
import nextstep.app.domain.Member;
5+
import nextstep.app.domain.MemberRepository;
6+
import org.junit.jupiter.api.BeforeEach;
7+
import org.junit.jupiter.params.ParameterizedTest;
8+
import org.junit.jupiter.params.provider.MethodSource;
9+
import org.springframework.beans.factory.annotation.Autowired;
10+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
11+
import org.springframework.boot.test.context.SpringBootTest;
12+
import org.springframework.cloud.contract.wiremock.AutoConfigureWireMock;
13+
import org.springframework.http.HttpHeaders;
14+
import org.springframework.mock.web.MockHttpSession;
15+
import org.springframework.test.context.ActiveProfiles;
16+
import org.springframework.test.web.servlet.MockMvc;
17+
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
18+
import org.springframework.test.web.servlet.result.MockMvcResultMatchers;
19+
20+
import java.util.HashMap;
21+
import java.util.Map;
22+
import java.util.stream.Stream;
23+
24+
import static com.github.tomakehurst.wiremock.client.WireMock.*;
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
27+
28+
@ActiveProfiles("test")
29+
@SpringBootTest
30+
@AutoConfigureMockMvc
31+
@AutoConfigureWireMock(port = 8089)
32+
class GithubAuthenticationFilterTest {
33+
34+
@Autowired
35+
private MockMvc mockMvc;
36+
37+
@Autowired
38+
private MemberRepository memberRepository;
39+
40+
@BeforeEach
41+
void setupMockServer() throws Exception {
42+
UserStub userB = new UserStub("b", "b_access_token", "[email protected]", "b", "b_avatar_url");
43+
UserStub userC = new UserStub("c", "c_access_token", "[email protected]", "c", "c_avatar_url");
44+
45+
setupUserStub(userB);
46+
setupUserStub(userC);
47+
}
48+
49+
@ParameterizedTest
50+
@MethodSource("userProvider")
51+
void authenticationFilter(UserStub user) throws Exception {
52+
String requestUri = "/login/oauth2/code/github?code=" + user.code;
53+
54+
mockMvc.perform(MockMvcRequestBuilders.get(requestUri))
55+
.andDo(print())
56+
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
57+
.andExpect(MockMvcResultMatchers.redirectedUrl("/"));
58+
59+
Member savedMember = memberRepository.findByEmail(user.email).get();
60+
assertThat(savedMember).isNotNull();
61+
assertThat(savedMember.getEmail()).isEqualTo(user.email);
62+
assertThat(savedMember.getName()).isEqualTo(user.name);
63+
}
64+
65+
@ParameterizedTest
66+
@MethodSource("userProvider")
67+
void authenticationFilterWithState(UserStub user) throws Exception {
68+
MockHttpSession session = new MockHttpSession();
69+
70+
String state = mockMvc.perform(MockMvcRequestBuilders.get("/oauth2/authorization/github").session(session))
71+
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
72+
.andReturn().getResponse().getHeader(HttpHeaders.LOCATION).split("&state=")[1];
73+
74+
String requestUri = "/login/oauth2/code/github?code=" + user.code + "&state=" + state;
75+
76+
mockMvc.perform(MockMvcRequestBuilders.get(requestUri).session(session))
77+
.andDo(print())
78+
.andExpect(MockMvcResultMatchers.status().is3xxRedirection())
79+
.andExpect(MockMvcResultMatchers.redirectedUrl("/"));
80+
81+
Member savedMember = memberRepository.findByEmail(user.email).get();
82+
assertThat(savedMember).isNotNull();
83+
assertThat(savedMember.getEmail()).isEqualTo(user.email);
84+
assertThat(savedMember.getName()).isEqualTo(user.name);
85+
}
86+
87+
private void setupUserStub(UserStub user) throws Exception {
88+
Map<String, String> accessTokenResponseBody = new HashMap<>();
89+
accessTokenResponseBody.put("access_token", user.accessToken);
90+
accessTokenResponseBody.put("token_type", "bearer");
91+
String accessTokenJsonResponse = new ObjectMapper().writeValueAsString(accessTokenResponseBody);
92+
93+
stubFor(post(urlEqualTo("/login/oauth/access_token"))
94+
.willReturn(aResponse()
95+
.withHeader(HttpHeaders.CONTENT_TYPE, "application/json")
96+
.withBody(accessTokenJsonResponse)));
97+
98+
Map<String, String> userProfile = new HashMap<>();
99+
userProfile.put("email", user.email);
100+
userProfile.put("name", user.name);
101+
userProfile.put("avatar_url", user.avatarUrl);
102+
String profileJsonResponse = new ObjectMapper().writeValueAsString(userProfile);
103+
104+
stubFor(get(urlEqualTo("/user"))
105+
.willReturn(aResponse()
106+
.withHeader(HttpHeaders.CONTENT_TYPE, "application/json")
107+
.withBody(profileJsonResponse)));
108+
109+
}
110+
111+
static Stream<UserStub> userProvider() {
112+
return Stream.of(
113+
new UserStub("b", "b_access_token", "[email protected]", "b", "b_avatar_url"),
114+
new UserStub("c", "c_access_token", "[email protected]", "c", "c_avatar_url")
115+
);
116+
}
117+
118+
static class UserStub {
119+
String code;
120+
String accessToken;
121+
String email;
122+
String name;
123+
String avatarUrl;
124+
125+
public UserStub(String code, String accessToken, String email, String name, String avatarUrl) {
126+
this.code = code;
127+
this.accessToken = accessToken;
128+
this.email = email;
129+
this.name = name;
130+
this.avatarUrl = avatarUrl;
131+
}
132+
}
133+
}

0 commit comments

Comments
 (0)