Reputation: 103
UserServiceImpl :
@Override
public Object loadUserBySAML(SAMLCredential credential) throws UsernameNotFoundException{
String userID = credential.getNameID().getValue();
logger.info(userID + " is logged in");
List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_USER");
authorities.add(authority);
List<Attribute> userAttributes = credential.getAttributes();
logger.info("Credential attributes: " + userAttributes);
for (int attrIndex = 0; attrIndex < userAttributes.size(); attrIndex++) {
Attribute attr = userAttributes.get(attrIndex);
List<XMLObject> attrValues = attr.getAttributeValues();
StringBuilder strBuilder = new StringBuilder();
for (int attrValueIndex = 0; attrValueIndex < attrValues.size(); attrValueIndex++) {
XMLObject currObj = attrValues.get(attrValueIndex);
strBuilder.append(currObj.toString()).append(",");
}
strBuilder.deleteCharAt(strBuilder.length() - 1);
logger.info(attr.getFriendlyName() + ", " + strBuilder.toString());
String samlAttrValue = strBuilder.toString();
switch (attr.getFriendlyName()) {
case "userName":
samlUserAttribute.setUserName(samlAttrValue);
case "email":
samlUserAttribute.setEmail(samlAttrValue);
break;
case "firstName":
samlUserAttribute.setFirstName(samlAttrValue);
break;
case "lastName":
samlUserAttribute.setLastName(samlAttrValue);
break;
case "userType":
samlUserAttribute.setUserType(samlAttrValue);
break;
case "accountName":
samlUserAttribute.setAccountName(samlAttrValue);
break;
case "contactId":
samlUserAttribute.setContactId(samlAttrValue);
break;
default:
logger.info("invalid attribute name" + attr.getFriendlyName());
}
}
logger.info("User details obtained: " + samlUserAttribute);
return new SamlUserDTO(userID, "<abc123>", authorities, samlUserAttribute);
}
UserDetails Implementation:
private SamlUserAttribute currentUserAttribute;
private String password;
private final String username;
private final Set<GrantedAuthority> authorities;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final boolean enabled;
.. setter/getter/contructors for userDetails...
3.Security-config details
<security:http entry-point-ref="samlEntryPoint" access-decision-manager-ref="accessDecisionManager" authentication-manager-ref="authenticationManager"
use-expressions="true">
<security:intercept-url pattern="/login" access="permitAll"/>
<security:intercept-url pattern="/**"
access="isFullyAuthenticated()" />
<security:custom-filter after="BASIC_AUTH_FILTER"
ref="samlFilter" />
<security:csrf disabled="true"/>
</security:http>
<bean id="samlFilter" class="org.springframework.security.web.FilterChainProxy">
<security:filter-chain-map request-matcher="ant">
<security:filter-chain pattern="/**"
filters="samlEntryPoint" />
</security:filter-chain-map>
</bean>
<bean id="samlAuthenticationProvider"
class="org.springframework.security.saml.SAMLAuthenticationProvider">
<property name="userDetails" ref="UserServiceImpl" />
<property name="forcePrincipalAsString" value="false" />
</bean>
<bean id="UserServiceImpl"
class="com.akamai.marketplace.service.impl.common.UserServiceImpl">
</bean>
On hitting the required URL, I can see on the browser that IDP redirection happens, IDP replies with the required Assertions, and the landing URL is /login But in my /login controller, Authentication Object's UserDetail has not been populated with the custom data.
/login controller :
@RequestMapping(path="/login",method = RequestMethod.POST)
public ResponseEntity<SamlUserDTO> login() {
logger.info("login API reached through IdP.");
Authentication userAuthentication = SecurityContextHolder.getContext().getAuthentication();
logger.info("user details: "+userAuthentication.getDetails());
logger.info("user credentials: "+userAuthentication.getCredentials());
logger.info("principal " + userAuthentication.getPrincipal());
SamlUserDTO samlUserDTO1 = (SamlUserDTO) userAuthentication.getPrincipal();
return ResponseEntity.ok(samlUserDTO1);
}
logs :
2016-08-11 17:22:16 DEBUG BaseMessageEncoder:56 - Successfully encoded message. 2016-08-11 17:22:16 DEBUG HttpSessionStorage:93 - Storing message a399ehchh04afi304hih7e49fd791g2 to session someValue
2016-08-11 17:22:16 INFO SAMLDefaultLogger:127 - AuthNRequest;SUCCESS;someIP;SP-entityId;Idp-entityId;;;
2016-08-11 17:22:37 INFO UserController:18 - login API reached through IdP.
2016-08-11 17:22:37 INFO UserController:20 - user details: org.springframework.security.web.authentication.WebAuthenticationDetails@ffffa64e: RemoteIpAddress: Idp-IpAddress; SessionId: someValue
2016-08-11 17:22:37 INFO UserController:21 - user credentials:
2016-08-11 17:22:37 INFO UserController:22 - principal anonymousUser 2016-08-11 17:22:37 DEBUG ExceptionHandlerExceptionResolver:133 - Resolving exception from handler [public org.springframework.http.ResponseEntity com.akamai.marketplace.controller.UserController.login()]: java.lang.ClassCastException: java.lang.String cannot be cast to controller.model.SamlUserDTO
2016-08-11 17:22:37 DEBUG ExceptionHandlerExceptionResolver:361 - Invoking @ExceptionHandler method: public org.springframework.http.ResponseEntity RESTExceptionHandler.handleExeption(java.lang.Exception) 2016-08-11 17:22:37 ERROR RESTExceptionHandler:60 - Exception caught by exception handler: java.lang.ClassCastException: java.lang.String cannot be cast to package.SamlUserDTO
Upvotes: 0
Views: 1039
Reputation: 103
this issue got resolved. For people facing similar issue where authNResponses are not being decoded even though you have override the loadBySAML() method, please look at this xml tag in your security config file :
<bean id="samlWebSSOProcessingFilter" class="org.springframework.security.saml.SAMLProcessingFilter">
The constructor arg to this filter should be the relative URL where authNResponse will be posted by IdP. i.e the end point exposed by SP to IdP. example in my case :
<constructor-arg>
<value type="java.lang.String">/login</value>
</constructor-arg>
If this path is not explicitly mentioned, the default value will be
public static final String FILTER_URL = "/saml/SSO";
So the SAMLProcessorImpl will listen on path /saml/SSO for SAML messages.
Hope this helps!
Upvotes: 1