Add password reset and profile edit policy (#557)

* Add policies as oauth2 client registration.
* Make profile-edit and password-reset optional.

Signed-off-by: Pan Li <panli@microsoft.com>
This commit is contained in:
Pan Li 2019-02-11 01:39:45 -06:00 коммит произвёл GitHub
Родитель b9779a7ee0
Коммит da7e856996
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 99 добавлений и 95 удалений

Просмотреть файл

@ -5,10 +5,15 @@
*/
package com.microsoft.azure.spring.autoconfigure.b2c;
import lombok.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.util.Assert;
import javax.servlet.http.HttpServletRequest;
@ -17,42 +22,69 @@ import java.util.Map;
public class AADB2CAuthorizationRequestResolver implements OAuth2AuthorizationRequestResolver {
private final AADB2CProperties properties;
private static final String REQUEST_BASE_URI =
OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI;
private static final String REGISTRATION_ID_NAME = "registrationId";
private static final String MATCHER_PATTERN = String.format("%s/{%s}", REQUEST_BASE_URI, REGISTRATION_ID_NAME);
private static final AntPathRequestMatcher requestMatcher = new AntPathRequestMatcher(MATCHER_PATTERN);
private final OAuth2AuthorizationRequestResolver defaultResolver;
public AADB2CAuthorizationRequestResolver(ClientRegistrationRepository registrationRepository,
AADB2CProperties properties) {
this.defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(registrationRepository,
"/oauth2/authorization");
// TODO(pan): Investigate why should be below string.
this.properties = properties;
public AADB2CAuthorizationRequestResolver(@NonNull ClientRegistrationRepository repository) {
this.defaultResolver = new DefaultOAuth2AuthorizationRequestResolver(repository, REQUEST_BASE_URI);
}
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request) {
Assert.notNull(request, "request cannot be null.");
public OAuth2AuthorizationRequest resolve(@NonNull HttpServletRequest request) {
if (requestMatcher.matches(request)) {
return getB2CAuthorizationRequest(defaultResolver.resolve(request), getRegistrationId(request));
}
return getAADB2CAuthorizationRequest(defaultResolver.resolve(request));
// Return null may not be the good practice, but we need to align with oauth2.client.web
// DefaultOAuth2AuthorizationRequestResolver.
return null;
}
@Override
public OAuth2AuthorizationRequest resolve(HttpServletRequest request, String registrationId) {
Assert.notNull(request, "request cannot be null.");
Assert.hasText(registrationId, "registrationId should have text.");
return getAADB2CAuthorizationRequest(defaultResolver.resolve(request, registrationId));
private void cleanupSecurityContextAuthentication() {
SecurityContextHolder.getContext().setAuthentication(null);
}
private OAuth2AuthorizationRequest getAADB2CAuthorizationRequest(OAuth2AuthorizationRequest request) {
private OAuth2AuthorizationRequest getB2CAuthorizationRequest(@Nullable OAuth2AuthorizationRequest request,
String policyValue) {
Assert.hasText(policyValue, "Policy value should contain text.");
if (request == null) {
return null;
}
cleanupSecurityContextAuthentication();
final Map<String, Object> parameters = new HashMap<>(request.getAdditionalParameters());
parameters.putIfAbsent("p", properties.getPolicies().getSignUpOrSignIn().getName());
parameters.put("p", policyValue);
return OAuth2AuthorizationRequest.from(request).additionalParameters(parameters).build();
}
@Override
public OAuth2AuthorizationRequest resolve(@NonNull HttpServletRequest request, String registrationId) {
Assert.hasText(registrationId, "registrationId should have text.");
if (requestMatcher.matches(request)) {
return getB2CAuthorizationRequest(defaultResolver.resolve(request), registrationId);
}
return null;
}
private String getRegistrationId(HttpServletRequest request) {
if (requestMatcher.matches(request)) {
return requestMatcher.extractUriTemplateVariables(request).get(REGISTRATION_ID_NAME);
}
return null;
}
}

Просмотреть файл

@ -15,11 +15,13 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.util.UUID;
import java.util.ArrayList;
import java.util.List;
import static com.microsoft.azure.spring.autoconfigure.b2c.AADB2CProperties.*;
@ -31,17 +33,13 @@ import static com.microsoft.azure.spring.autoconfigure.b2c.AADB2CProperties.*;
"tenant",
"client-id",
"client-secret",
POLICY_SIGN_UP_OR_SIGN_IN_NAME,
POLICY_SIGN_UP_OR_SIGN_IN_REPLY_URL
"reply-url",
POLICY_SIGN_UP_OR_SIGN_IN
}
)
@EnableConfigurationProperties(AADB2CProperties.class)
public class AADB2CAutoConfiguration {
private static final String CLIENT_NAME = "aad-b2c";
private static final String REGISTRATION_ID = "aad-b2c-" + UUID.randomUUID();
private final ClientRegistrationRepository repository;
private final AADB2CProperties properties;
@ -54,8 +52,8 @@ public class AADB2CAutoConfiguration {
@Bean
@ConditionalOnMissingBean
public OAuth2AuthorizationRequestResolver b2cOAuth2AuthorizationRequestResolver() {
return new AADB2CAuthorizationRequestResolver(repository, properties);
public AADB2CAuthorizationRequestResolver b2cOAuth2AuthorizationRequestResolver() {
return new AADB2CAuthorizationRequestResolver(repository);
}
@Bean
@ -74,27 +72,39 @@ public class AADB2CAutoConfiguration {
this.properties = properties;
}
private void addB2CClientRegistration(@NonNull List<ClientRegistration> registrations, String policeValue) {
if (StringUtils.hasText(policeValue)) {
registrations.add(b2cClientRegistration(policeValue));
}
}
@Bean
@ConditionalOnMissingBean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(this.b2cOIDCClientRegistration());
final List<ClientRegistration> registrations = new ArrayList<>();
addB2CClientRegistration(registrations, properties.getPolicies().getSignUpOrSignIn());
addB2CClientRegistration(registrations, properties.getPolicies().getProfileEdit());
addB2CClientRegistration(registrations, properties.getPolicies().getPasswordReset());
return new InMemoryClientRegistrationRepository(registrations);
}
private ClientRegistration b2cOIDCClientRegistration() {
final AADB2CProperties.Policy policy = properties.getPolicies().getSignUpOrSignIn();
private ClientRegistration b2cClientRegistration(String policyValue) {
Assert.hasText(policyValue, "value should contains text.");
return ClientRegistration.withRegistrationId(REGISTRATION_ID)
return ClientRegistration.withRegistrationId(policyValue) // Use policy value as registration Id.
.clientId(properties.getClientId())
.clientSecret(properties.getClientSecret())
.clientAuthenticationMethod(ClientAuthenticationMethod.POST)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUriTemplate(policy.getReplyURL())
.redirectUriTemplate(properties.getReplyUrl())
.scope(properties.getClientId(), "openid")
.authorizationUri(AADB2CURL.getAuthorizationUrl(properties.getTenant()))
.tokenUri(AADB2CURL.getTokenUrl(properties.getTenant(), policy.getName()))
.jwkSetUri(AADB2CURL.getJwkSetUrl(properties.getTenant(), policy.getName()))
.tokenUri(AADB2CURL.getTokenUrl(properties.getTenant(), policyValue))
.jwkSetUri(AADB2CURL.getJwkSetUrl(properties.getTenant(), policyValue))
.userNameAttributeName("name")
.clientName(CLIENT_NAME)
.clientName(policyValue)
.build();
}
}

Просмотреть файл

@ -25,10 +25,10 @@ public class AADB2CLogoutSuccessHandler extends SimpleUrlLogoutSuccessHandler {
}
private String getAADB2CEndSessionUrl() {
final AADB2CProperties.Policy policy = properties.getPolicies().getSignUpOrSignIn();
final String policyValue = properties.getPolicies().getSignUpOrSignIn();
final String logoutSuccessUrl = properties.getLogoutSuccessUrl();
return AADB2CURL.getEndSessionUrl(properties.getTenant(), logoutSuccessUrl, policy.getName());
return AADB2CURL.getEndSessionUrl(properties.getTenant(), logoutSuccessUrl, policyValue);
}
@Override

Просмотреть файл

@ -28,34 +28,16 @@ public class AADB2CProperties {
* We do not use ${@link String#format(String, Object...)}
* as it's not real constant, which cannot be referenced in annotation.
*/
private static final String POLICY_PASSWORD_RESET = POLICIES + ".password-reset";
public static final String POLICY_PASSWORD_RESET = POLICIES + ".password-reset";
private static final String POLICY_PROFILE_EDIT = POLICIES + ".profile-edit";
public static final String POLICY_PROFILE_EDIT = POLICIES + ".profile-edit";
private static final String POLICY_SIGN_UP_OR_SIGN_IN = POLICIES + ".sign-up-or-sign-in";
public static final String POLICY_SIGN_UP_OR_SIGN_IN = POLICIES + ".sign-up-or-sign-in";
public static final String DEFAULT_LOGOUT_SUCCESS_URL = "http://localhost:8080/login?logout";
public static final String DEFAULT_LOGOUT_SUCCESS_URL = "http://localhost:8080/login";
public static final String PREFIX = "azure.activedirectory.b2c";
public static final String LOGOUT_SUCCESS_URL = "logout-success-url";
public static final String POLICY_SIGN_UP_OR_SIGN_IN_NAME = POLICY_SIGN_UP_OR_SIGN_IN + ".name";
public static final String POLICY_SIGN_UP_OR_SIGN_IN_REPLY_URL = POLICY_SIGN_UP_OR_SIGN_IN + ".reply-url";
public static final String POLICY_PASSWORD_RESET_NAME = POLICY_PASSWORD_RESET + ".name";
public static final String POLICY_PASSWORD_RESET_REPLY_URL = POLICY_PASSWORD_RESET + ".reply-url";
public static final String POLICY_PROFILE_EDIT_NAME = POLICY_PROFILE_EDIT + ".name";
public static final String POLICY_PROFILE_EDIT_REPLY_URL = POLICY_PROFILE_EDIT + ".reply-url";
public static final String PASSWORD_RESET_PROCESS_URL = "password-reset-process-url";
public static final String PROFILE_EDIT_PROCESS_URL = "profile-edit-process-url";
/**
* The name of the b2c tenant.
*/
@ -75,20 +57,17 @@ public class AADB2CProperties {
private String clientId;
/**
* The application ID that registered under b2c tenant.
* The application secret that registered under b2c tenant.
*/
@NotBlank(message = "client secret should not be blank")
private String clientSecret;
@URL(message = "reply URL should not be blank")
@URL(message = "reply URL should be valid URL")
private String replyUrl;
@URL(message = "logout success should be valid URL")
private String logoutSuccessUrl = DEFAULT_LOGOUT_SUCCESS_URL;
@URL(message = "reply URL should not be blank")
private String passwordResetProcessUrl;
@URL(message = "reply URL should not be blank")
private String profileEditProcessUrl;
/**
* The all polices which is created under b2c tenant.
*/
@ -104,10 +83,11 @@ public class AADB2CProperties {
@NonNull
public String getLoginProcessingUrl() {
return getReplyURLPath(policies.getSignUpOrSignIn().replyURL);
return getReplyURLPath(replyUrl);
}
@Getter
@Setter
@Validated
@NoArgsConstructor(access = AccessLevel.PROTECTED)
protected static class Policies {
@ -115,35 +95,17 @@ public class AADB2CProperties {
/**
* The sign-up-or-sign-in policy which is created under b2c tenant.
*/
private Policy signUpOrSignIn = new Policy();
@NotBlank(message = "sign-up-or-in value should not be blank")
private String signUpOrSignIn;
/**
* The profile-edit policy which is created under b2c tenant.
*/
private String profileEdit;
/**
* The password-reset policy which is created under b2c tenant.
*/
private Policy passwordReset = new Policy();
/**
* The password-reset policy which is created under b2c tenant.
*/
private Policy profileEdit = new Policy();
}
@Getter
@Setter
@Validated
@NoArgsConstructor(access = AccessLevel.PROTECTED)
protected static class Policy {
/**
* The name of policy which is created under b2c tenant.
*/
@NotBlank(message = "policy name should not be blank")
private String name;
/**
* The redirect URI which is configured under b2c tenant.
*/
@URL(message = "reply URL should not be blank")
private String replyURL;
private String passwordReset;
}
}