mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-10-23 02:38:48 +00:00
Prepopulate Username When Known
Closes gh-17935
This commit is contained in:
parent
e813aad82b
commit
42376e2eee
@ -68,6 +68,7 @@ public final class DefaultLoginPageConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
|
||||
@Override
|
||||
public void init(H http) {
|
||||
this.loginPageGeneratingFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
|
||||
this.loginPageGeneratingFilter.setResolveHiddenInputs(DefaultLoginPageConfigurer.this::hiddenInputs);
|
||||
this.logoutPageGeneratingFilter.setResolveHiddenInputs(DefaultLoginPageConfigurer.this::hiddenInputs);
|
||||
http.setSharedObject(DefaultLoginPageGeneratingFilter.class, this.loginPageGeneratingFilter);
|
||||
|
@ -59,6 +59,9 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
|
||||
public static final String ERROR_PARAMETER_NAME = "error";
|
||||
|
||||
private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder
|
||||
.getContextHolderStrategy();
|
||||
|
||||
private @Nullable String loginPageUrl;
|
||||
|
||||
private @Nullable String logoutSuccessUrl;
|
||||
@ -118,6 +121,18 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@link SecurityContextHolderStrategy} to retrieve authenticated users.
|
||||
* <p>
|
||||
* Uses {@link SecurityContextHolder#getContextHolderStrategy()} by default.
|
||||
* @param securityContextHolderStrategy the strategy to use
|
||||
* @since 7.0
|
||||
*/
|
||||
public void setSecurityContextHolderStrategy(SecurityContextHolderStrategy securityContextHolderStrategy) {
|
||||
Assert.notNull(securityContextHolderStrategy, "securityContextHolderStrategy cannot be null");
|
||||
this.securityContextHolderStrategy = securityContextHolderStrategy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a Function used to resolve a Map of the hidden inputs where the key is the
|
||||
* name of the input and the value is the value of the input. Typically this is used
|
||||
@ -307,6 +322,13 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
return "";
|
||||
}
|
||||
|
||||
String username = getUsername();
|
||||
String usernameInput = ((username != null)
|
||||
? HtmlTemplates.fromTemplate(FORM_READONLY_USERNAME_INPUT).withValue("username", username)
|
||||
: HtmlTemplates.fromTemplate(FORM_USERNAME_INPUT))
|
||||
.withValue("usernameParameter", this.usernameParameter)
|
||||
.render();
|
||||
|
||||
String hiddenInputs = this.resolveHiddenInputs.apply(request)
|
||||
.entrySet()
|
||||
.stream()
|
||||
@ -317,7 +339,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
.withValue("loginUrl", contextPath + this.authenticationUrl)
|
||||
.withRawHtml("errorMessage", renderError(loginError, errorMsg))
|
||||
.withRawHtml("logoutMessage", renderSuccess(logoutSuccess))
|
||||
.withValue("usernameParameter", this.usernameParameter)
|
||||
.withRawHtml("usernameInput", usernameInput)
|
||||
.withValue("passwordParameter", this.passwordParameter)
|
||||
.withRawHtml("rememberMeInput", renderRememberMe(this.rememberMeParameter))
|
||||
.withRawHtml("hiddenInputs", hiddenInputs)
|
||||
@ -337,11 +359,17 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
.map((inputKeyValue) -> renderHiddenInput(inputKeyValue.getKey(), inputKeyValue.getValue()))
|
||||
.collect(Collectors.joining("\n"));
|
||||
|
||||
String username = getUsername();
|
||||
String usernameInput = (username != null)
|
||||
? HtmlTemplates.fromTemplate(ONE_TIME_READONLY_USERNAME_INPUT).withValue("username", username).render()
|
||||
: ONE_TIME_USERNAME_INPUT;
|
||||
|
||||
return HtmlTemplates.fromTemplate(ONE_TIME_TEMPLATE)
|
||||
.withValue("generateOneTimeTokenUrl", contextPath + this.generateOneTimeTokenUrl)
|
||||
.withRawHtml("errorMessage", renderError(loginError, errorMsg))
|
||||
.withRawHtml("logoutMessage", renderSuccess(logoutSuccess))
|
||||
.withRawHtml("hiddenInputs", hiddenInputs)
|
||||
.withRawHtml("usernameInput", usernameInput)
|
||||
.render();
|
||||
}
|
||||
|
||||
@ -410,6 +438,14 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
.render();
|
||||
}
|
||||
|
||||
private @Nullable String getUsername() {
|
||||
Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();
|
||||
if (authentication != null && authentication.isAuthenticated()) {
|
||||
return authentication.getName();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private boolean isLogoutSuccess(HttpServletRequest request) {
|
||||
return this.logoutSuccessUrl != null && matches(request, this.logoutSuccessUrl);
|
||||
}
|
||||
@ -511,7 +547,7 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
{{errorMessage}}{{logoutMessage}}
|
||||
<p>
|
||||
<label for="username" class="screenreader">Username</label>
|
||||
<input type="text" id="username" name="{{usernameParameter}}" placeholder="Username" required autofocus>
|
||||
{{usernameInput}}
|
||||
</p>
|
||||
<p>
|
||||
<label for="password" class="screenreader">Password</label>
|
||||
@ -522,6 +558,14 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
<button type="submit" class="primary">Sign in</button>
|
||||
</form>""";
|
||||
|
||||
private static final String FORM_READONLY_USERNAME_INPUT = """
|
||||
<input type="text" id="username" name="{{usernameParameter}}" value="{{username}}" placeholder="Username" required readonly>
|
||||
""";
|
||||
|
||||
private static final String FORM_USERNAME_INPUT = """
|
||||
<input type="text" id="username" name="{{usernameParameter}}" placeholder="Username" required autofocus>
|
||||
""";
|
||||
|
||||
private static final String HIDDEN_HTML_INPUT_TEMPLATE = """
|
||||
<input name="{{name}}" type="hidden" value="{{value}}" />
|
||||
""";
|
||||
@ -554,11 +598,19 @@ public class DefaultLoginPageGeneratingFilter extends GenericFilterBean {
|
||||
{{errorMessage}}{{logoutMessage}}
|
||||
<p>
|
||||
<label for="ott-username" class="screenreader">Username</label>
|
||||
<input type="text" id="ott-username" name="username" placeholder="Username" required>
|
||||
{{usernameInput}}
|
||||
</p>
|
||||
{{hiddenInputs}}
|
||||
<button class="primary" type="submit" form="ott-form">Send Token</button>
|
||||
</form>
|
||||
""";
|
||||
|
||||
private static final String ONE_TIME_READONLY_USERNAME_INPUT = """
|
||||
<input type="text" id="ott-username" name="username" value="{{username}}" placeholder="Username" required readonly>
|
||||
""";
|
||||
|
||||
private static final String ONE_TIME_USERNAME_INPUT = """
|
||||
<input type="text" id="ott-username" name="username" placeholder="Username" required>
|
||||
""";
|
||||
|
||||
}
|
||||
|
@ -26,11 +26,15 @@ import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.authentication.BadCredentialsException;
|
||||
import org.springframework.security.authentication.TestAuthentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolderStrategy;
|
||||
import org.springframework.security.core.context.SecurityContextImpl;
|
||||
import org.springframework.security.web.WebAttributes;
|
||||
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
|
||||
import org.springframework.security.web.servlet.TestMockHttpServletRequests;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
@ -246,6 +250,30 @@ public class DefaultLoginPageGeneratingFilterTests {
|
||||
assertThat(response.getContentAsString()).contains("Password");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void generateWhenAuthenticatedThenReadOnlyUsername() throws Exception {
|
||||
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
|
||||
DefaultLoginPageGeneratingFilter filter = new DefaultLoginPageGeneratingFilter();
|
||||
filter.setLoginPageUrl(DefaultLoginPageGeneratingFilter.DEFAULT_LOGIN_PAGE_URL);
|
||||
filter.setFormLoginEnabled(true);
|
||||
filter.setUsernameParameter("username");
|
||||
filter.setPasswordParameter("password");
|
||||
filter.setOneTimeTokenEnabled(true);
|
||||
filter.setOneTimeTokenGenerationUrl("/ott/authenticate");
|
||||
filter.setSecurityContextHolderStrategy(strategy);
|
||||
given(strategy.getContext()).willReturn(new SecurityContextImpl(TestAuthentication.authenticatedUser()));
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
filter.doFilter(TestMockHttpServletRequests.get("/login").build(), response, this.chain);
|
||||
assertThat(response.getContentAsString()).contains("Request a One-Time Token");
|
||||
assertThat(response.getContentAsString()).contains(
|
||||
"""
|
||||
<input type="text" id="ott-username" name="username" value="user" placeholder="Username" required readonly>
|
||||
""");
|
||||
assertThat(response.getContentAsString()).contains("""
|
||||
<input type="text" id="username" name="username" value="user" placeholder="Username" required readonly>
|
||||
""");
|
||||
}
|
||||
|
||||
@Test
|
||||
void generatesThenRenders() throws ServletException, IOException {
|
||||
DefaultLoginPageGeneratingFilter filter = new DefaultLoginPageGeneratingFilter(
|
||||
|
Loading…
x
Reference in New Issue
Block a user