I am writing IEF policy integrating with Federated Identity Provider. IDP returns claims in id_token as JSON. when I use claims mapping custom_attributes in output claims, I am getting AAD Exception as
An unexpected type "System.Collections.Generic.List1[System.Collections.Generic.KeyValuePair2[System.String,System.Object]]" was encountered of the claim with claim type id "custom_attributes"
here is my claim mapping:
<OutputClaim ClaimTypeReferenceId="custom_attributes" PartnerClaimType="custom_attributes"/>
claim Schema as:
<ClaimType Id="custom_attributes">
<DisplayName>custom_attributes</DisplayName>
<DataType>string</DataType>
<UserHelpText>Add help text here</UserHelpText>
</ClaimType>
id_token looks like below:
{
"custom_attributes":{
"emailAddress": "someone#example.com",
"displayName": "Someone",
"id" : 6353399
}
}
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName" />
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName" />
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId" />
<OutputClaimsTransformation ReferenceId="GetRequestorIdClaimFromJsonClaimsTransformation" />
</OutputClaimsTransformations>
claim Schema as
<ClaimsTransformation Id="GetRequestorIdClaimFromJsonClaimsTransformation" TransformationMethod="GetClaimFromJson">
<InputClaims>
<InputClaim ClaimTypeReferenceId="custom_attributes" TransformationClaimType="inputJson" />
</InputClaims>
<InputParameters>
<InputParameter Id="claimToExtract" DataType="string" Value="id"/>
</InputParameters>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="requestorid" TransformationClaimType="extractedClaim" />
</OutputClaims>
</ClaimsTransformation>
I found a solution to the problem and added it here
https://stackoverflow.com/questions/68086538/why-i-am-gettting-error-in-outputclaim-for-json-object-key-value-pair
Related
Is it possible to get the id_token returned by B2C after authentication inside of ClaimsTransformation.cs class?
I have tried to inject IHttpContextAccessor into the class, but it gets stuck in an infinite loop, calling the same method again and again, resulting in Stack Overflow error.
Try to add the following to the Azure AD OpenID Connect technical profile:
<ClaimsTransformations>
<ClaimsTransformation Id="CreateOtherMailsFromEmail" TransformationMethod="AddItemToStringCollection">
<InputClaims>
<InputClaim ClaimTypeReferenceId="email" TransformationClaimType="item" />
<InputClaim ClaimTypeReferenceId="otherMails" TransformationClaimType="collection" />
</InputClaims>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="email" TransformationClaimType="collection" />
</OutputClaims>
</ClaimsTransformation>
Reference Link : https://github.com/Azure-Samples/active-directory-b2c-custom-policy-starterpack/blob/main/LocalAccounts/TrustFrameworkBase.xml
In my environment there are an ADFS 4.0 and asp.net project with IdentityServer4 + WsFederation package as the Claim Provider. All RPs that using a Ws-Fed protocol work fine. But SAMLP RP, does not receive claims in the response.
Sequencing:
The RP initiates a SAML signin request in ADFS. ADFS makes a Ws-Fed singin request to IdentityServer4. ADFS Get Ws-Fed response with a propper claims. But on the next step when ADFS generate SAML response from Ws-Fed response, ADFS issues the SAML response for RP without a claims ...
There is an error in event log:
EventID: 303:
Additional Data
Exception details:
System.IdentityModel.Tokens.SecurityTokenException: MSIS7099: SubjectConfirmationData element was missing in received token.
at Microsoft.IdentityServer.Web.Protocols.Saml.SamlProtocolManager.ValidateRequestProperties(Saml2SecurityToken token)
at Microsoft.IdentityServer.Web.Protocols.Saml.SamlProtocolManager.Issue(HttpSamlRequestMessage httpSamlRequestMessage, SecurityTokenElement onBehalfOf, String sessionState, String relayState, String& newSamlSession, String& samlpAuthenticationProvider, Boolean isUrlTranslationNeeded, WrappedHttpListenerContext context, Boolean isKmsiRequested)
Ws-Fed message with UPN and E-mail claims:
<t:RequestSecurityTokenResponse xmlns:t="http://schemas.xmlsoap.org/ws/2005/02/trust"
Context="RPUrl=http://xxxxxx.com%2fadfs%2fservices%2ftrust\BaseUrl=http%3a%2f%2fxxxxxxxx.com%2fadfs%2fservices%2ftrust\ProtocolID=Saml\Id=id-8cbbc2e6-70f5-4804-9e61-99fd13781978\SAMLRequest=xxxxxxxx%3d\ProtocolBinding=urn%3aoasis%3anames%3atc%3aSAML%3a2.0%3abindings%3aHTTP-Redirect\RelayState=8e27a614-61b2-47f0-9583-f9be1d9ee75a"
>
<wsp:AppliesTo xmlns:wsp="http://schemas.xmlsoap.org/ws/2004/09/policy">
<wsa:EndpointReference xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsa:Address>http://xxxxxxxx.xxxxxxxxx.com/adfs/services/trust</wsa:Address>
</wsa:EndpointReference>
</wsp:AppliesTo>
<t:RequestedSecurityToken>
<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion"
ID="_aac09822-5db4-4791-b677-690ef5bf273e"
IssueInstant="2020-07-01T14:10:26.357Z"
Version="2.0"
>
<Issuer>https://xxxxxxxxxx.xxxxxxxxxxx.com</Issuer>
<Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
<SignedInfo>
<CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<Reference URI="#_aac09822-5db4-4791-b677-690ef5bf273e">
<Transforms>
<Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</Transforms>
<DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
<DigestValue>/2MHUXKcVJHSzV70wbFJVFrnYawDdFYPuekSh1/zEC4=</DigestValue>
</Reference>
</SignedInfo>
<SignatureValue>xxxxxxxxxx</SignatureValue>
<KeyInfo>
<X509Data>
<X509Certificate>xxxxxxxxx</X509Certificate>
</X509Data>
</KeyInfo>
</Signature>
<Subject>
<NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified">xxxxxxxx</NameID>
<SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer" />
</Subject>
<Conditions NotBefore="2020-07-01T14:10:26.357Z"
NotOnOrAfter="2020-07-02T00:10:26.357Z"
>
<AudienceRestriction>
<Audience>http://xxxxxxx.xxxxxxxx.com/adfs/services/trust</Audience>
</AudienceRestriction>
</Conditions>
<AttributeStatement>
<Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn">
<AttributeValue>xxxxxxxxx</AttributeValue>
</Attribute>
<Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress">
<AttributeValue>xxxxxxxxxx</AttributeValue>
</Attribute>
</AttributeStatement>
<AuthnStatement AuthnInstant="2020-07-01T14:10:26.342Z">
<AuthnContext>
<AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef>
</AuthnContext>
</AuthnStatement>
</Assertion>
</t:RequestedSecurityToken>
</t:RequestSecurityTokenResponse>
WS-Fed -> SAML (UPN and E-mail are missing):
<samlp:Response xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="_59eef1fa-81c5-4ede-8538-6caf4dbdf480"
Version="2.0"
IssueInstant="2020-07-01T14:10:26.698Z"
Destination="xxxxxxxxx/adfs/ls/"
Consent="urn:oasis:names:tc:SAML:2.0:consent:unspecified"
InResponseTo="id-7052d47f-3df0-4d49-8ddf-673603bccb8e"
>
<Issuer xmlns="urn:oasis:names:tc:SAML:2.0:assertion">xxxxxxxx/adfs/services/trust</Issuer>
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha256" />
<ds:Reference URI="#_59eef1fa-81c5-4ede-8538-6caf4dbdf480">
<ds:Transforms>
<ds:Transform Algorithm="http://www.w3.org/2000/09/xmldsig#enveloped-signature" />
<ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
</ds:Transforms>
<ds:DigestMethod Algorithm="http://www.w3.org/2001/04/xmlenc#sha256" />
<ds:DigestValue>iia3AYaxxx8UoxILqjhsxkgeO4rXqPk9Jil1t0jUbLU=</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>xxxxxxxx</ds:SignatureValue>
<KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>xxxxxxxxxxxxxxxxxx</ds:X509Certificate>
</ds:X509Data>
</KeyInfo>
</ds:Signature>
<samlp:Status>
<samlp:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Responder" />
</samlp:Status>
</samlp:Response>
Has anyone encountered a similar one?
The "urn:oasis:names:tc:SAML:2.0:status:Responder" is an error status. The SAML response will contain a SAML assertion with the various claims as the SAML Subject and SAML attributes only if successful.
Check the Windows event log on the ADFS server. There will be one or more error events associated with this error. It's most likely some sort of configuration error.
It sounds like you are not passing through the claims.
You need pass-through rules on the WS-Fed CP side and the same set of pass-through rules on the SAML RP side.
Since WS-Fed doesn't use NameID, you may also have to contruct a NameID claim on the SAML side using e.g. email or UPN.
The error was that IdentityServer4 generates a SAML 2.0 token inside the WsFederation message by default instead of SAML 1.1. Everything worked well until the applications initiated the WsFederation SignIn request, and extract SAML token by itself, but when the application sent the SAML SingIn request the ADFS must extract the SAML token but ADFS works with SAML 1.1 version only. So as a solution I switched a token version from SAML 2.0 to SAML 1.1 in IdentityServer4 and all started work.
namespace IdentityServer4.WsFederation
{
public class WsFederationOptions
{
//public string DefaultTokenType { get; set; } = WsFederationConstants.TokenTypes.Saml2TokenProfile11; // was
public string DefaultTokenType { get; set; } = WsFederationConstants.TokenTypes.OasisWssSaml11TokenProfile11; // working way
...
}
}
TechnicalProfile looks like this:
<TechnicalProfile Id="AAD-Common">
<DisplayName>Multi-Tenant AAD</DisplayName>
<Description>Login with your Contoso account</Description>
<Protocol Name="OpenIdConnect"/>
<Metadata>
<Item Key="METADATA">https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration</Item>
<!-- Update the Client ID below to the Application ID -->
<Item Key="client_id">[the client id]</Item>
<Item Key="response_types">id_token</Item>
<Item Key="scope">openid</Item>
<Item Key="response_mode">form_post</Item>
<Item Key="HttpBinding">POST</Item>
<Item Key="UsePolicyInRedirectUri">false</Item>
<Item Key="DiscoverMetadataByTokenIssuer">true</Item>
<!-- The commented key below specifies that users from any tenant can sign-in. Uncomment if you would like anyone with an Azure AD account to be able to sign in. -->
<Item Key="ValidTokenIssuerPrefixes">https://login.microsoftonline.com/</Item>
</Metadata>
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_AADAppSecret"/>
</CryptographicKeys>
<OutputClaims>
<OutputClaim ClaimTypeReferenceId="issuerUserId" PartnerClaimType="oid"/>
<OutputClaim ClaimTypeReferenceId="tenantId" PartnerClaimType="tid"/>
<OutputClaim ClaimTypeReferenceId="givenName" PartnerClaimType="given_name" />
<OutputClaim ClaimTypeReferenceId="surName" PartnerClaimType="family_name" />
<OutputClaim ClaimTypeReferenceId="displayName" PartnerClaimType="name" />
<OutputClaim ClaimTypeReferenceId="authenticationSource" DefaultValue="socialIdpAuthentication" AlwaysUseDefaultValue="true" />
<OutputClaim ClaimTypeReferenceId="identityProvider" PartnerClaimType="iss" />
</OutputClaims>
<OutputClaimsTransformations>
<OutputClaimsTransformation ReferenceId="CreateRandomUPNUserName"/>
<OutputClaimsTransformation ReferenceId="CreateUserPrincipalName"/>
<OutputClaimsTransformation ReferenceId="CreateAlternativeSecurityId"/>
<OutputClaimsTransformation ReferenceId="CreateSubjectClaimFromAlternativeSecurityId"/>
</OutputClaimsTransformations>
<UseTechnicalProfileForSessionManagement ReferenceId="SM-SocialLogin"/>
</TechnicalProfile>
When I validate this bearer token using Microsoft.AspNetCore.Authentication.JwtBearer AddJwtBearer I get this error:
token: '{"alg":"HS256","typ":"JWT","kid":"TwMsJMU3i_L7zOBKeOw7nWYHov3-eY70IsPfs1gT3aU"}.
{"exp":1588321433,"nbf":1588317833,"ver":"1.0","iss":"https://[mydomain].b2clogin.com/b3d62253-3d6e-453e-bb55-26d87c104a42/v2.0/","sub":"00000000-0000-0000-cdea-6895a0b0757c","aud":"dc3eeaae-c30a-4312-b346-b0919f7d4da1","acr":"b2c_1a_signup_signin","nonce":"defaultNonce","iat":1588317833,"auth_time":1588317833,"tid":"9188040d-6c67-4c5b-b112-36a304b66dad","name":"[my name]","idp":"https://login.microsoftonline.com/[my guid]/v2.0"}'.
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler: 2020-05-01 17:24:14,518 INFO Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler - JwtB2CBearer was not authenticated. Failure message: IDX10501: Signature validation failed. Unable to match key:
kid: 'TwMsJMU3i_L7zOBKeOw7nWYHov3-eY70IsPfs1gT3aU'.
Exceptions caught:
'System.NotSupportedException: IDX10634: Unable to create the SignatureProvider.
Algorithm: 'HS256', SecurityKey: 'Microsoft.IdentityModel.Tokens.RsaSecurityKey, KeyId: 'X5eXk4xyojNFum1kl2Ytv8dlNP4-c57dO6QGTVBwaNk', InternalId: 'e4c64ce7-9e7d-40e2-936a-6658d8f92f07'.'
is not supported. The list of supported algorithms is available here: https://aka.ms/IdentityModel/supported-algorithms
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateSignatureProvider(SecurityKey key, String algorithm, Boolean willCreateSignatures)
at Microsoft.IdentityModel.Tokens.CryptoProviderFactory.CreateForVerifying(SecurityKey key, String algorithm)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(Byte[] encodedBytes, Byte[] signature, SecurityKey key, String algorithm, TokenValidationParameters validationParameters)
at System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler.ValidateSignature(String token, TokenValidationParameters validationParameters)
'.
The Policy Key in Identity Experience Framework is setup using "Manual" configuration and the secret key is something I generated within the Azure AD B2C App.
I regenerated my tokens as guided here: https://aka.ms/ief
Ensured my cryptographic key from original post was set to:
<CryptographicKeys>
<Key Id="client_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer"/>
</CryptographicKeys>
I also ensured my JwtIssuer within TrustFrameworkBase.xml has its keys set to:
<CryptographicKeys>
<Key Id="issuer_secret" StorageReferenceId="B2C_1A_TokenSigningKeyContainer" />
<Key Id="issuer_refresh_token_key" StorageReferenceId="B2C_1A_TokenEncryptionKeyContainer" />
</CryptographicKeys>
It still wouldn't validate, I would get this error:
IDX10501: Signature validation failed. Unable to match keys
Through the guidance of this post: IDX10501: Signature validation failed. Unable to match keys I managed to get it working by setting the IssuerSigningKeys when configuring the JwtBearerOptions:
internal class JwtBearerOptionsConfiguration : IConfigureNamedOptions<JwtBearerOptions>
{
private readonly AzureAdB2COptions b2cOptions;
private readonly IMetaDataFromClaimsPrincipalService metaDataFromClaimsService;
public JwtBearerOptionsConfiguration(IOptions<AzureAdB2COptions> b2cOptions, IMetaDataFromClaimsPrincipalService metaDataFromClaims)
{
this.b2cOptions = b2cOptions.Value;
this.metaDataFromClaimsService = metaDataFromClaims;
}
public void Configure(JwtBearerOptions options)
{
this.Configure(Options.DefaultName, options);
}
public void Configure(string name, JwtBearerOptions options)
{
AzureAdB2COptions currentOptions = this.b2cOptions;
options.Audience = currentOptions.ClientId;
options.Authority = currentOptions.BuildAuthority(currentOptions.SignUpSignInPolicyIds.Last());
options.IncludeErrorDetails = true;
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{options.Authority}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
var openidconfig = configManager.GetConfigurationAsync().Result;
options.TokenValidationParameters = new TokenValidationParameters
{
IssuerSigningKeys = openidconfig.SigningKeys,
ValidateIssuerSigningKey = true
};
options.Events = new JwtBearerEvents
{
OnTokenValidated = this.OnTokenValidated,
OnAuthenticationFailed = this.OnAuthenticationFailed
};
}
private Task OnAuthenticationFailed(AuthenticationFailedContext arg)
{
return Task.CompletedTask;
}
private Task OnTokenValidated(TokenValidatedContext arg)
{
// Check if the authenticated principal has a valid Trust Framework Policy, otherwise do not grant access
string tfp = this.metaDataFromClaimsService.GetTrustFrameworkPolicy(arg.Principal);
if (!this.b2cOptions.SignUpSignInPolicyIds.Contains(tfp))
arg.Fail("Could not validate the Trust Framework Policy");
return Task.CompletedTask;
}
}
Thanks #Jas Suri
I'm working with spring security 3.2, JSF2 , Hibernate4.
I'have done 3/4 of the work :) but my authentication system doesn't work yet.
I have a UserService who implements UserDetailsService, a domain Class User who implements UserDetails.
THe login system never stop user to access secured pages, i tried user name and password who doesn't exist in my database...
Thanks for the help.
I have a loginBean who is trying to authenticate the user when he connects via login form :
public String login() {
try {
Authentication request = new UsernamePasswordAuthenticationToken(this.getUsername(), this.getPassword());
Authentication result = authenticationManager.authenticate(request);
SecurityContextHolder.getContext().setAuthentication(result);
} catch (AuthenticationException e) { e.printStackTrace();}
return "secured";
}
My spring security looks like this :
`<security:global-method-security jsr250-annotations="enabled" pre-post-annotations="enabled" secured-annotations="enabled" />
<security:http auto-config="true" use-expressions="true">
<security:intercept-url pattern="/Admin" access="isAuthenticated()" />
<security:form-login login-page="/login.xhtml" authentication-failure-url="/" > </security:form-login>
</security:http>
<!-- User Data Access Object -->
<beans:bean id="userDao" class="com.clb.genomic.lyon.dao.UserDaoImpl" >
<beans:property name="sessionFactory" ref="sessionFactory"></beans:property>
</beans:bean>
<!-- User Business Object -->
<beans:bean id="userBo" class="com.clb.genomic.lyon.bo.UserBoImpl" >
<beans:property name="userDao" ref="userDao" />
</beans:bean>
<beans:bean id="login" class="com.clb.genomic.lyon.beans.LoginBean" scope ="request">
<beans:property name="authenticationManager" ref="authenticationManager" />
</beans:bean>
<beans:bean id="standardPasswordEncoder" class="org.springframework.security.crypto.password.StandardPasswordEncoder"/>
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider user-service-ref="userBo" >
<security:password-encoder ref="standardPasswordEncoder"/>
</security:authentication-provider>
</security:authentication-manager>`
This is the error who show up...
org.springframework.security.authentication.AuthenticationServiceException: 1
at org.springframework.security.authentication.dao.DaoAuthenticationProvider.retrieveUser(DaoAuthenticationProvider.java:109)
at org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider.authenticate(AbstractUserDetailsAuthenticationProvider.java:132)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:156)
at com.clb.genomic.lyon.beans.LoginBean.login(LoginBean.java:47).....
Caused by: java.lang.ArrayIndexOutOfBoundsException: 1
at com.clb.genomic.lyon.dao.UserDaoImpl.loadUserByUsername(UserDaoImpl.java:59)
at com.clb.genomic.lyon.bo.UserBoImpl.loadUserByUsername(UserBoImpl.java:68)
at com.clb.genomic.lyon.bo.UserBoImpl$$FastClassByCGLIB$$9ea98abf.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204).....
The exception from stack trace shows you are getting ArrayIndexOutOfBoundsException and it seems that you are reading from an empty array.
You should also check what value is being passed to loadUserByUsername() method, and if that user exists.
I am using Spring Security 3.1 for Active Directory authentication and a local db for loading the authorities. I have seen similar examples but it is still not clear for me what exactly I should use. My current settings in spring-security.xml is:
<!-- LDAP server details -->
<security:authentication-manager>
<security:authentication-provider ref="ldapActiveDirectoryAuthProvider" />
</security:authentication-manager>
<beans:bean id="ldapActiveDirectoryAuthProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<beans:constructor-arg value="${ldap.domain}" />
<beans:constructor-arg value="${ldap.url}" />
<beans:property name="useAuthenticationRequestCredentials" value="true" />
<beans:property name="convertSubErrorCodesToExceptions" value="true" />
</beans:bean>
I have a class let's call it: "BookStoreDbAuthPopulator.java". Inside this class, I am calling this method:
// Load additional authorities and create an Authentication object
final List<GrantedAuthority> authorities = loadRolesFromDatabaseHere();
What is not still clear for me: Which interface should "BookStoreDbAuthPopulator.java" implements in order to add the loaded authorities from db to the UserDetails? "UserDetailsContextMapper" or "GrantedAuthoritiesMapper" or "AuthenticationProvider"?
Based on this solution: Spring Security 3 Active Directory Authentication, Database Authorization
"BookStoreDbAuthPopulator.java" should implement "AuthenticationProvider". My doubt is if I should use "BookStoreDbAuthPopulator.java" as a property for "ldapActiveDirectoryAuthProvider" bean?
Many thanks in advance.
My final solution is "BookStoreDbAuthPopulator.java" implements "UserDetailsContextMapper".
public class BookStoreDbAuthPopulator implements UserDetailsContextMapper {
// populating roles assigned to the user from AUTHORITIES table in DB
private List<SimpleGrantedAuthority> loadRolesFromDatabase(String username) {
//"SELECT ROLE FROM AUTHORITIES WHERE LCASE(USERNAME) LIKE ?"
...
}
#Override
public UserDetails mapUserFromContext(DirContextOperations ctx, String username, Collection<? extends GrantedAuthority> authorities) {
List<SimpleGrantedAuthority> allAuthorities = new ArrayList<SimpleGrantedAuthority>();
for (GrantedAuthority auth : authorities) {
if (auth != null && !auth.getAuthority().isEmpty()) {
allAuthorities.add((SimpleGrantedAuthority) auth);
}
}
// add additional roles from the database table
allAuthorities.addAll(loadRolesFromDatabase(username));
return new User(username, "", true, true, true, true, allAuthorities);
}
#Override
public void mapUserToContext(UserDetails user, DirContextAdapter ctx) {
}
}
Then in spring-security.xml
<!-- AuthenticationManager: AuthenticationProvider, LDAP server details -->
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="ldapActiveDirectoryAuthProvider" />
</security:authentication-manager>
<beans:bean id="ldapActiveDirectoryAuthProvider" class="org.springframework.security.ldap.authentication.ad.ActiveDirectoryLdapAuthenticationProvider">
<!-- the domain name (may be null or empty). If no domain name is configured, it is assumed that the username will always contain the domain name. -->
<beans:constructor-arg value="${ldap.domain}" />
<!-- an LDAP url (or multiple URLs) -->
<beans:constructor-arg value="${ldap.url}" />
<!-- Determines whether the supplied password will be used as the credentials in the successful authentication token. -->
<beans:property name="useAuthenticationRequestCredentials" value="true" />
<!-- by setting this property to true, when the authentication fails the error codes will also be used to control the exception raised. -->
<beans:property name="convertSubErrorCodesToExceptions" value="true" />
<!-- for customizing user authorities -->
<beans:property name="userDetailsContextMapper" ref="myUserDetailsContextMapper" />
</beans:bean>
<!-- Customizing UserDetail -->
<beans:bean id="myUserDetailsContextMapper" class="com.mybookstore.mywebcomp.w.BookStoreDbAuthPopulator">
</beans:bean>