Browse Source

Polish authorization consent

Issue gh-340 gh-280
pull/348/head
Joe Grandja 5 years ago
parent
commit
4517022f36
  1. 8
      oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java
  2. 22
      oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java
  3. 2
      oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java
  4. 4
      oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilterTests.java
  5. 7
      samples/boot/oauth2-integration/authorizationserver-custom-consent-page/src/main/resources/templates/consent.html

8
oauth2-authorization-server/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationServerConfigurer.java

@ -158,7 +158,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
* *
* <ul> * <ul>
* <li>{@code client_id} - the client identifier</li> * <li>{@code client_id} - the client identifier</li>
* <li>{@code scope} - the space separated list of scopes present in the authorization request</li> * <li>{@code scope} - a space-delimited list of scopes present in the authorization request</li>
* <li>{@code state} - a CSRF protection token</li> * <li>{@code state} - a CSRF protection token</li>
* </ul> * </ul>
* *
@ -172,11 +172,9 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
* <li>It must include the received {@code state} as an HTTP parameter</li> * <li>It must include the received {@code state} as an HTTP parameter</li>
* <li>It must include the list of {@code scope}s the {@code Resource Owner} * <li>It must include the list of {@code scope}s the {@code Resource Owner}
* consented to as an HTTP parameter</li> * consented to as an HTTP parameter</li>
* <li>It must include the {@code consent_action} parameter, with a value either
* {@code approve} or {@code cancel} as an HTTP parameter</li>
* </ul> * </ul>
* *
* @param consentPage the consent page to redirect to if consent is required (e.g. "/oauth2/consent") * @param consentPage the URI of the custom consent page to redirect to if consent is required (e.g. "/oauth2/consent")
* @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration * @return the {@link OAuth2AuthorizationServerConfigurer} for further configuration
*/ */
public OAuth2AuthorizationServerConfigurer<B> consentPage(String consentPage) { public OAuth2AuthorizationServerConfigurer<B> consentPage(String consentPage) {
@ -296,7 +294,7 @@ public final class OAuth2AuthorizationServerConfigurer<B extends HttpSecurityBui
authenticationManager, authenticationManager,
providerSettings.authorizationEndpoint()); providerSettings.authorizationEndpoint());
if (StringUtils.hasText(this.consentPage)) { if (StringUtils.hasText(this.consentPage)) {
authorizationEndpointFilter.setUserConsentUri(this.consentPage); authorizationEndpointFilter.setConsentPage(this.consentPage);
} }
builder.addFilterBefore(postProcess(authorizationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class); builder.addFilterBefore(postProcess(authorizationEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);

22
oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilter.java

@ -90,7 +90,7 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
private final RequestMatcher authorizationEndpointMatcher; private final RequestMatcher authorizationEndpointMatcher;
private final AuthenticationConverter authenticationConverter; private final AuthenticationConverter authenticationConverter;
private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy(); private final RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
private String userConsentUri; private String consentPage;
/** /**
* Constructs an {@code OAuth2AuthorizationEndpointFilter} using the provided parameters. * Constructs an {@code OAuth2AuthorizationEndpointFilter} using the provided parameters.
@ -168,11 +168,11 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
* Specify the URI to redirect Resource Owners to if consent is required. A default consent * Specify the URI to redirect Resource Owners to if consent is required. A default consent
* page will be generated when this attribute is not specified. * page will be generated when this attribute is not specified.
* *
* @param userConsentUri the URI of the custom consent page to redirect to if consent is required (e.g. "/oauth2/consent") * @param consentPage the URI of the custom consent page to redirect to if consent is required (e.g. "/oauth2/consent")
* @see org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer#consentPage(String) * @see org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer#consentPage(String)
*/ */
public final void setUserConsentUri(String userConsentUri) { public final void setConsentPage(String consentPage) {
this.userConsentUri = userConsentUri; this.consentPage = consentPage;
} }
@Override @Override
@ -230,24 +230,24 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
.toUriString(); .toUriString();
this.redirectStrategy.sendRedirect(request, response, redirectUri); this.redirectStrategy.sendRedirect(request, response, redirectUri);
} else { } else {
UserConsentPage.displayConsent(request, response, clientId, principal, requestedScopes, authorizedScopes, state); DefaultConsentPage.displayConsent(request, response, clientId, principal, requestedScopes, authorizedScopes, state);
} }
} }
private boolean hasConsentUri() { private boolean hasConsentUri() {
return StringUtils.hasText(this.userConsentUri); return StringUtils.hasText(this.consentPage);
} }
private String resolveConsentUri(HttpServletRequest request) { private String resolveConsentUri(HttpServletRequest request) {
if (UrlUtils.isAbsoluteUrl(this.userConsentUri)) { if (UrlUtils.isAbsoluteUrl(this.consentPage)) {
return this.userConsentUri; return this.consentPage;
} }
RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder(); RedirectUrlBuilder urlBuilder = new RedirectUrlBuilder();
urlBuilder.setScheme(request.getScheme()); urlBuilder.setScheme(request.getScheme());
urlBuilder.setServerName(request.getServerName()); urlBuilder.setServerName(request.getServerName());
urlBuilder.setPort(request.getServerPort()); urlBuilder.setPort(request.getServerPort());
urlBuilder.setContextPath(request.getContextPath()); urlBuilder.setContextPath(request.getContextPath());
urlBuilder.setPathInfo(this.userConsentUri); urlBuilder.setPathInfo(this.consentPage);
return urlBuilder.getUrl(); return urlBuilder.getUrl();
} }
@ -427,7 +427,7 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
/** /**
* For internal use only. * For internal use only.
*/ */
private static class UserConsentPage { private static class DefaultConsentPage {
private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8); private static final MediaType TEXT_HTML_UTF8 = new MediaType("text", "html", StandardCharsets.UTF_8);
private static void displayConsent(HttpServletRequest request, HttpServletResponse response, private static void displayConsent(HttpServletRequest request, HttpServletResponse response,
@ -485,7 +485,7 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
for (String scope : scopesToAuthorize) { for (String scope : scopesToAuthorize) {
builder.append(" <div class=\"form-group form-check py-1\">"); builder.append(" <div class=\"form-group form-check py-1\">");
builder.append(" <input class=\"form-check-input\" type=\"checkbox\" name=\"scope\" value=\"" + scope + "\" id=\"" + scope + "\" checked>"); builder.append(" <input class=\"form-check-input\" type=\"checkbox\" name=\"scope\" value=\"" + scope + "\" id=\"" + scope + "\">");
builder.append(" <label class=\"form-check-label\" for=\"" + scope + "\">" + scope + "</label>"); builder.append(" <label class=\"form-check-label\" for=\"" + scope + "\">" + scope + "</label>");
builder.append(" </div>"); builder.append(" </div>");
} }

2
oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OAuth2AuthorizationCodeGrantTests.java

@ -501,7 +501,7 @@ public class OAuth2AuthorizationCodeGrantTests {
private static String scopeCheckbox(String scope) { private static String scopeCheckbox(String scope) {
return MessageFormat.format( return MessageFormat.format(
"<input class=\"form-check-input\" type=\"checkbox\" name=\"scope\" value=\"{0}\" id=\"{0}\" checked>", "<input class=\"form-check-input\" type=\"checkbox\" name=\"scope\" value=\"{0}\" id=\"{0}\">",
scope scope
); );
} }

4
oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/web/OAuth2AuthorizationEndpointFilterTests.java

@ -285,7 +285,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
FilterChain filterChain = mock(FilterChain.class); FilterChain filterChain = mock(FilterChain.class);
this.filter.setUserConsentUri("/oauth2/custom-consent"); this.filter.setConsentPage("/oauth2/custom-consent");
this.filter.doFilter(request, response, filterChain); this.filter.doFilter(request, response, filterChain);
verify(this.authenticationManager).authenticate(any()); verify(this.authenticationManager).authenticate(any());
@ -471,7 +471,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
private static String scopeCheckbox(String scope) { private static String scopeCheckbox(String scope) {
return MessageFormat.format( return MessageFormat.format(
"<input class=\"form-check-input\" type=\"checkbox\" name=\"scope\" value=\"{0}\" id=\"{0}\" checked>", "<input class=\"form-check-input\" type=\"checkbox\" name=\"scope\" value=\"{0}\" id=\"{0}\">",
scope scope
); );
} }

7
samples/boot/oauth2-integration/authorizationserver-custom-consent-page/src/main/resources/templates/consent.html

@ -42,8 +42,7 @@
type="checkbox" type="checkbox"
name="scope" name="scope"
th:value="${scope.scope}" th:value="${scope.scope}"
th:id="${scope.scope}" th:id="${scope.scope}">
checked>
<label class="form-check-label font-weight-bold" th:for="${scope.scope}" th:text="${scope.scope}"></label> <label class="form-check-label font-weight-bold" th:for="${scope.scope}" th:text="${scope.scope}"></label>
<p class="text-primary" th:text="${scope.description}"></p> <p class="text-primary" th:text="${scope.description}"></p>
</div> </div>
@ -60,12 +59,12 @@
</div> </div>
<div class="form-group pt-3"> <div class="form-group pt-3">
<button class="btn btn-primary btn-lg" type="submit" name="consent_action" value="approve"> <button class="btn btn-primary btn-lg" type="submit">
Submit Consent Submit Consent
</button> </button>
</div> </div>
<div class="form-group"> <div class="form-group">
<button class="btn btn-link regular" type="submit" name="consent_action" value="cancel"> <button class="btn btn-link regular" type="submit">
Cancel Cancel
</button> </button>
</div> </div>

Loading…
Cancel
Save