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:
Родитель
b9779a7ee0
Коммит
da7e856996
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче