Skip to content

Commit c1f69ce

Browse files
committed
Add oidcLogin MockMvc Test Support
Fixes gh-7618
1 parent 3f39a4b commit c1f69ce

File tree

5 files changed

+492
-0
lines changed

5 files changed

+492
-0
lines changed

samples/boot/oauth2login/src/integration-test/java/sample/OAuth2LoginApplicationTests.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.net.URI;
1919
import java.net.URL;
2020
import java.net.URLDecoder;
21+
import java.util.Collections;
2122
import java.util.HashMap;
2223
import java.util.HashSet;
2324
import java.util.List;
@@ -40,6 +41,7 @@
4041
import org.springframework.beans.factory.annotation.Autowired;
4142
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
4243
import org.springframework.boot.test.context.SpringBootTest;
44+
import org.springframework.context.annotation.Bean;
4345
import org.springframework.http.HttpStatus;
4446
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
4547
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -51,7 +53,9 @@
5153
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
5254
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
5355
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
56+
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository;
5457
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
58+
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
5559
import org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter;
5660
import org.springframework.security.oauth2.core.OAuth2AccessToken;
5761
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
@@ -61,13 +65,18 @@
6165
import org.springframework.security.oauth2.core.user.OAuth2User;
6266
import org.springframework.security.oauth2.core.user.OAuth2UserAuthority;
6367
import org.springframework.test.context.junit4.SpringRunner;
68+
import org.springframework.test.web.servlet.MockMvc;
6469
import org.springframework.web.util.UriComponents;
6570
import org.springframework.web.util.UriComponentsBuilder;
6671

6772
import static org.assertj.core.api.Assertions.assertThat;
6873
import static org.mockito.ArgumentMatchers.any;
6974
import static org.mockito.Mockito.mock;
7075
import static org.mockito.Mockito.when;
76+
import static org.springframework.security.oauth2.core.oidc.IdTokenClaimNames.SUB;
77+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin;
78+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
79+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
7180

7281
/**
7382
* Integration tests for the OAuth 2.0 client filters {@link OAuth2AuthorizationRequestRedirectFilter}
@@ -87,6 +96,9 @@ public class OAuth2LoginApplicationTests {
8796
@Autowired
8897
private WebClient webClient;
8998

99+
@Autowired
100+
private MockMvc mvc;
101+
90102
@Autowired
91103
private ClientRegistrationRepository clientRegistrationRepository;
92104

@@ -284,6 +296,15 @@ public void requestAuthorizationCodeGrantWhenInvalidRedirectUriThenDisplayLoginP
284296
assertThat(errorElement.asText()).contains("invalid_redirect_uri_parameter");
285297
}
286298

299+
@Test
300+
public void requestWhenMockOidcLoginThenIndex() throws Exception {
301+
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github");
302+
this.mvc.perform(get("/").with(oidcLogin().clientRegistration(clientRegistration)))
303+
.andExpect(model().attribute("userName", "test-subject"))
304+
.andExpect(model().attribute("clientName", "GitHub"))
305+
.andExpect(model().attribute("userAttributes", Collections.singletonMap(SUB, "test-subject")));
306+
}
307+
287308
private void assertLoginPage(HtmlPage page) {
288309
assertThat(page.getTitleText()).isEqualTo("Please sign in");
289310

@@ -397,5 +418,10 @@ private OAuth2UserService<OAuth2UserRequest, OAuth2User> mockUserService() {
397418
when(userService.loadUser(any())).thenReturn(user);
398419
return userService;
399420
}
421+
422+
@Bean
423+
OAuth2AuthorizedClientRepository authorizedClientRepository() {
424+
return new HttpSessionOAuth2AuthorizedClientRepository();
425+
}
400426
}
401427
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package sample.web;
18+
19+
import java.util.Collections;
20+
21+
import org.junit.Test;
22+
import org.junit.runner.RunWith;
23+
24+
import org.springframework.beans.factory.annotation.Autowired;
25+
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
26+
import org.springframework.context.annotation.Bean;
27+
import org.springframework.context.annotation.Configuration;
28+
import org.springframework.context.annotation.Import;
29+
import org.springframework.security.oauth2.client.registration.ClientRegistration;
30+
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
31+
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
32+
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository;
33+
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
34+
import org.springframework.security.oauth2.core.AuthorizationGrantType;
35+
import org.springframework.test.context.junit4.SpringRunner;
36+
import org.springframework.test.web.servlet.MockMvc;
37+
38+
import static org.springframework.security.oauth2.core.oidc.IdTokenClaimNames.SUB;
39+
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin;
40+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
41+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.model;
42+
43+
/**
44+
* Tests for {@link OAuth2LoginController}
45+
*
46+
* @author Josh Cummings
47+
*/
48+
@RunWith(SpringRunner.class)
49+
@WebMvcTest
50+
@Import({OAuth2LoginController.class, OAuth2LoginControllerTests.OAuth2ClientConfig.class})
51+
public class OAuth2LoginControllerTests {
52+
53+
static ClientRegistration clientRegistration = ClientRegistration.withRegistrationId("test")
54+
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
55+
.clientId("my-client-id")
56+
.clientName("my-client-name")
57+
.tokenUri("https://token-uri.example.org")
58+
.build();
59+
60+
@Autowired
61+
MockMvc mvc;
62+
63+
@Test
64+
public void rootWhenAuthenticatedReturnsUserAndClient() throws Exception {
65+
this.mvc.perform(get("/").with(oidcLogin()))
66+
.andExpect(model().attribute("userName", "test-subject"))
67+
.andExpect(model().attribute("clientName", "test"))
68+
.andExpect(model().attribute("userAttributes", Collections.singletonMap(SUB, "test-subject")));
69+
}
70+
71+
@Test
72+
public void rootWhenOverridingClientRegistrationReturnsAccordingly() throws Exception {
73+
this.mvc.perform(get("/").with(oidcLogin()
74+
.clientRegistration(clientRegistration)
75+
.idToken(i -> i.subject("spring-security"))))
76+
.andExpect(model().attribute("userName", "spring-security"))
77+
.andExpect(model().attribute("clientName", "my-client-name"))
78+
.andExpect(model().attribute("userAttributes", Collections.singletonMap(SUB, "spring-security")));
79+
}
80+
81+
@Configuration
82+
static class OAuth2ClientConfig {
83+
84+
@Bean
85+
ClientRegistrationRepository clientRegistrationRepository() {
86+
return new InMemoryClientRegistrationRepository(clientRegistration);
87+
}
88+
89+
@Bean
90+
OAuth2AuthorizedClientRepository authorizedClientRepository() {
91+
return new HttpSessionOAuth2AuthorizedClientRepository();
92+
}
93+
}
94+
}

test/spring-security-test.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ dependencies {
77
compile 'org.springframework:spring-test'
88

99
optional project(':spring-security-config')
10+
optional project(':spring-security-oauth2-client')
1011
optional project(':spring-security-oauth2-jose')
1112
optional project(':spring-security-oauth2-resource-server')
1213
optional 'io.projectreactor:reactor-core'

test/src/main/java/org/springframework/security/test/web/servlet/request/SecurityMockMvcRequestPostProcessors.java

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525
import java.util.Arrays;
2626
import java.util.Base64;
2727
import java.util.Collection;
28+
import java.util.Collections;
29+
import java.util.LinkedHashSet;
2830
import java.util.List;
31+
import java.util.Set;
2932
import java.util.function.Consumer;
3033
import javax.servlet.http.HttpServletRequest;
3134
import javax.servlet.http.HttpServletResponse;
@@ -46,6 +49,19 @@
4649
import org.springframework.security.core.context.SecurityContextHolder;
4750
import org.springframework.security.core.userdetails.User;
4851
import org.springframework.security.core.userdetails.UserDetails;
52+
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
53+
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
54+
import org.springframework.security.oauth2.client.registration.ClientRegistration;
55+
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository;
56+
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
57+
import org.springframework.security.oauth2.core.AuthorizationGrantType;
58+
import org.springframework.security.oauth2.core.OAuth2AccessToken;
59+
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
60+
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
61+
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
62+
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
63+
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
64+
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
4965
import org.springframework.security.oauth2.jwt.Jwt;
5066
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
5167
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
@@ -314,6 +330,11 @@ public static RequestPostProcessor httpBasic(String username, String password) {
314330
return new HttpBasicRequestPostProcessor(username, password);
315331
}
316332

333+
public static OidcLoginRequestPostProcessor oidcLogin() {
334+
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "access-token", null, null, Collections.singleton("user"));
335+
return new OidcLoginRequestPostProcessor(accessToken);
336+
}
337+
317338
/**
318339
* Populates the X509Certificate instances onto the request
319340
*/
@@ -1024,6 +1045,161 @@ public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request)
10241045

10251046
}
10261047

1048+
/**
1049+
* @author Josh Cummings
1050+
* @since 5.3
1051+
*/
1052+
public final static class OidcLoginRequestPostProcessor implements RequestPostProcessor {
1053+
private ClientRegistration clientRegistration;
1054+
private OAuth2AccessToken accessToken;
1055+
private OidcIdToken idToken;
1056+
private OidcUserInfo userInfo;
1057+
private OidcUser oidcUser;
1058+
private Collection<GrantedAuthority> authorities;
1059+
1060+
private OidcLoginRequestPostProcessor(OAuth2AccessToken accessToken) {
1061+
this.accessToken = accessToken;
1062+
this.clientRegistration = clientRegistrationBuilder().build();
1063+
}
1064+
1065+
/**
1066+
* Use the provided authorities in the {@link Authentication}
1067+
*
1068+
* @param authorities the authorities to use
1069+
* @return the {@link OidcLoginRequestPostProcessor} for further configuration
1070+
*/
1071+
public OidcLoginRequestPostProcessor authorities(Collection<GrantedAuthority> authorities) {
1072+
Assert.notNull(authorities, "authorities cannot be null");
1073+
this.authorities = authorities;
1074+
return this;
1075+
}
1076+
1077+
/**
1078+
* Use the provided authorities in the {@link Authentication}
1079+
*
1080+
* @param authorities the authorities to use
1081+
* @return the {@link OidcLoginRequestPostProcessor} for further configuration
1082+
*/
1083+
public OidcLoginRequestPostProcessor authorities(GrantedAuthority... authorities) {
1084+
Assert.notNull(authorities, "authorities cannot be null");
1085+
this.authorities = Arrays.asList(authorities);
1086+
return this;
1087+
}
1088+
1089+
/**
1090+
* Use the provided {@link OidcIdToken} when constructing the authenticated user
1091+
*
1092+
* @param idTokenBuilderConsumer a {@link Consumer} of a {@link OidcIdToken.Builder}
1093+
* @return the {@link OidcLoginRequestPostProcessor} for further configuration
1094+
*/
1095+
public OidcLoginRequestPostProcessor idToken(Consumer<OidcIdToken.Builder> idTokenBuilderConsumer) {
1096+
OidcIdToken.Builder builder = OidcIdToken.withTokenValue("id-token");
1097+
builder.subject("test-subject");
1098+
idTokenBuilderConsumer.accept(builder);
1099+
this.idToken = builder.build();
1100+
return this;
1101+
}
1102+
1103+
/**
1104+
* Use the provided {@link OidcUserInfo} when constructing the authenticated user
1105+
*
1106+
* @param userInfoBuilderConsumer a {@link Consumer} of a {@link OidcUserInfo.Builder}
1107+
* @return the {@link OidcLoginRequestPostProcessor} for further configuration
1108+
*/
1109+
public OidcLoginRequestPostProcessor userInfoToken(Consumer<OidcUserInfo.Builder> userInfoBuilderConsumer) {
1110+
OidcUserInfo.Builder builder = OidcUserInfo.builder();
1111+
userInfoBuilderConsumer.accept(builder);
1112+
this.userInfo = builder.build();
1113+
return this;
1114+
}
1115+
1116+
/**
1117+
* Use the provided {@link OidcUser} as the authenticated user.
1118+
*
1119+
* Supplying an {@link OidcUser} will take precedence over {@link #idToken}, {@link #userInfo},
1120+
* and list of {@link GrantedAuthority}s to use.
1121+
*
1122+
* @param oidcUser the {@link OidcUser} to use
1123+
* @return the {@link OidcLoginRequestPostProcessor} for further configuration
1124+
*/
1125+
public OidcLoginRequestPostProcessor oidcUser(OidcUser oidcUser) {
1126+
this.oidcUser = oidcUser;
1127+
return this;
1128+
}
1129+
1130+
/**
1131+
* Use the provided {@link ClientRegistration} as the client to authorize.
1132+
*
1133+
* The supplied {@link ClientRegistration} will be registered into an
1134+
* {@link HttpSessionOAuth2AuthorizedClientRepository}. Tests relying on
1135+
* {@link org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient}
1136+
* annotations should register a {@link HttpSessionOAuth2AuthorizedClientRepository} bean
1137+
* to the application context.
1138+
*
1139+
* The client registration must be a valid {@link ClientRegistration} from the
1140+
* {@link org.springframework.security.oauth2.client.registration.ClientRegistrationRepository}
1141+
* in the application context.
1142+
*
1143+
* @param clientRegistration the {@link ClientRegistration} to use
1144+
* @return the {@link OidcLoginRequestPostProcessor} for further configuration
1145+
*/
1146+
public OidcLoginRequestPostProcessor clientRegistration(ClientRegistration clientRegistration) {
1147+
this.clientRegistration = clientRegistration;
1148+
return this;
1149+
}
1150+
1151+
@Override
1152+
public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) {
1153+
OidcUser oidcUser = getOidcUser();
1154+
OAuth2AuthenticationToken token = new OAuth2AuthenticationToken(oidcUser, oidcUser.getAuthorities(), this.clientRegistration.getRegistrationId());
1155+
OAuth2AuthorizedClient client = new OAuth2AuthorizedClient(this.clientRegistration, token.getName(), this.accessToken);
1156+
OAuth2AuthorizedClientRepository authorizedClientRepository = new HttpSessionOAuth2AuthorizedClientRepository();
1157+
authorizedClientRepository.saveAuthorizedClient(client, token, request, new MockHttpServletResponse());
1158+
1159+
return new AuthenticationRequestPostProcessor(token).postProcessRequest(request);
1160+
}
1161+
1162+
private ClientRegistration.Builder clientRegistrationBuilder() {
1163+
return ClientRegistration.withRegistrationId("test")
1164+
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
1165+
.clientId("test-client")
1166+
.tokenUri("https://token-uri.example.org");
1167+
}
1168+
1169+
private Collection<GrantedAuthority> getAuthorities() {
1170+
if (this.authorities == null) {
1171+
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
1172+
authorities.add(new OidcUserAuthority(getOidcIdToken(), getOidcUserInfo()));
1173+
for (String authority : this.accessToken.getScopes()) {
1174+
authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
1175+
}
1176+
return authorities;
1177+
} else {
1178+
return this.authorities;
1179+
}
1180+
}
1181+
1182+
private OidcIdToken getOidcIdToken() {
1183+
if (this.idToken == null) {
1184+
return new OidcIdToken("id-token", null, null, Collections.singletonMap(IdTokenClaimNames.SUB, "test-subject"));
1185+
} else {
1186+
return this.idToken;
1187+
}
1188+
}
1189+
1190+
private OidcUserInfo getOidcUserInfo() {
1191+
return this.userInfo;
1192+
}
1193+
1194+
private OidcUser getOidcUser() {
1195+
if (this.oidcUser == null) {
1196+
return new DefaultOidcUser(getAuthorities(), getOidcIdToken(), this.userInfo);
1197+
} else {
1198+
return this.oidcUser;
1199+
}
1200+
}
1201+
}
1202+
10271203
private SecurityMockMvcRequestPostProcessors() {
10281204
}
10291205
}

0 commit comments

Comments
 (0)