总览
最新版本的WSO2 Identity Server(版本5.0.0)配备了“应用程序身份验证框架”,该框架提供了很大的灵活性,可以对来自使用异构协议的各种服务提供商的用户进行身份验证。 它具有多个扩展点,可用于满足企业系统中常见的几个自定义要求。 在这篇文章中,我将分享使用这样一个扩展点的细节。
功能扩展
在企业系统中使用SAML单一登录时,依赖方通过SAML响应来了解用户是否已通过身份验证。 在这一点上,依赖方尚不知道其为业务和授权目的可能需要的已认证用户的其他属性。 为了向依赖方提供这些属性详细信息,SAML规范允许在SAML响应中也发送属性。 WSO2 Identity Server通过为管理员提供的GUI开箱即用地支持此功能。 有关此功能和配置的详细信息,请参阅[1]。
当我们需要向SAML响应中添加除下划线用户存储中可用的属性之外的其他属性时,此特定扩展提供的灵活性会派上用场。 为了提供依赖方请求的所有属性,可能需要寻找外部数据源。
在这里我要描述的样本中,我们将研究一个场景,该系统需要提供一些存储在用户存储中的用户本地属性,以及一些我希望从外部数据源中检索到的其他属性。
遵循SAML响应是我们需要从WSO2 IS发送给依赖方的内容。
<saml2p:Response Destination="https://localhost:9444/acs" ID="faibaccbcepemkackalbbjkihlegenhhigcdjbjk" InResponseTo="kbedjkocfjdaaadgmjeipbegnclbelfffbpbophe" IssueInstant="2014-07-17T13:15:05.032Z" Version="2.0" xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">localhost </saml2:Issuer> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> .......... </ds:Signature> <saml2p:Status> <saml2p:StatusCode Value="urn:oasis:names:tc:SAML:2.0:status:Success"/> </saml2p:Status> <saml2:Assertion ID="phmbbieedpcfdhcignelnepkemobepgaaipbjjdk" IssueInstant="2014-07-17T13:15:05.032Z" Version="2.0" xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity">localhost</saml2:Issuer> <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> ......... </ds:Signature> <saml2:Subject> <saml2:NameID Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">Administrator</saml2:NameID> <saml2:SubjectConfirmation Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"> <saml2:SubjectConfirmationData InResponseTo="kbedjkocfjdaaadgmjeipbegnclbelfffbpbophe" NotOnOrAfter="2014-07-17T13:20:05.032Z" Recipient="https://localhost:9444/acs"/> </saml2:SubjectConfirmation> </saml2:Subject> <saml2:Conditions NotBefore="2014-07-17T13:15:05.032Z" NotOnOrAfter="2014-07-17T13:20:05.032Z"> <saml2:AudienceRestriction> <saml2:Audience>carbonServer2</saml2:Audience> </saml2:AudienceRestriction> </saml2:Conditions> <saml2:AuthnStatement AuthnInstant="2014-07-17T13:15:05.033Z"> <saml2:AuthnContext> <saml2:AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml2:AuthnContextClassRef> </saml2:AuthnContext> </saml2:AuthnStatement> <saml2:AttributeStatement> <saml2:Attribute Name="http://wso2.org/claims/role" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"> <saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string"> Internal/carbonServer2,Internal/everyone </saml2:AttributeValue> </saml2:Attribute> <saml2:AttributeStatement> <saml2:Attribute Name="http://pushpalanka.org/claims/keplerNumber" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"> <saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string"> E90836W19881010 </saml2:AttributeValue> </saml2:Attribute> <saml2:Attribute Name="http://pushpalanka.org/claims/status" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic"> <saml2:AttributeValue xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string"> active </saml2:AttributeValue> </saml2:Attribute> </saml2:AttributeStatement> </saml2:AttributeStatement> </saml2:Assertion> </saml2p:Response>
在此响应中,我们具有一个本地属性(即角色)和另外两个属性http://pushpalanka.org/claims/keplerNumber和http://pushpalanka.org/claims/status,这些属性已从其他方法中检索出在我们的扩展名中定义。
怎么样?
- 实施定制逻辑以获取外部声明。 我们仅需注意两个事实。
- 定制实现应实现接口“ org.wso2.carbon.identity.application.authentication.framework.handler.claims.ClaimHandler”,或扩展接口“ org.wso2.carbon.identity.application.authentication”的默认实现。 framework.handler.claims.impl.DefaultClaimHandler”。
- 在方法“ public Map <String,String> handleClaimMappings”处返回的映射应包含我们要添加到SAML响应中的所有属性。
以下是我按照上面编写的示例代码。 外部声明可能已从数据库中查询,从文件中读取或根据需要使用任何其他机制。
public class CustomClaimHandler implements ClaimHandler { private static Log log = LogFactory.getLog(CustomClaimHandler.class); private static volatile CustomClaimHandler instance; private String connectionURL = null; private String userName = null; private String password = null; private String jdbcDriver = null; private String sql = null; public static CustomClaimHandler getInstance() { if (instance == null) { synchronized (CustomClaimHandler.class) { if (instance == null) { instance = new CustomClaimHandler(); } } } return instance; } public Map<String, String> handleClaimMappings(StepConfig stepConfig, AuthenticationContext context, Map<String, String> remoteAttributes, boolean isFederatedClaims) throws FrameworkException { String authenticatedUser = null; if (stepConfig != null) { //calling from StepBasedSequenceHandler authenticatedUser = stepConfig.getAuthenticatedUser(); } else { //calling from RequestPathBasedSequenceHandler authenticatedUser = context.getSequenceConfig().getAuthenticatedUser(); } Map<String, String> claims = handleLocalClaims(authenticatedUser, context); claims.putAll(handleExternalClaims(authenticatedUser)); return claims; } /** * @param context * @return * @throws FrameworkException */ protected Map<String, String> handleLocalClaims(String authenticatedUser, AuthenticationContext context) throws FrameworkException { .... } private Map<String, String> getFilteredAttributes(Map<String, String> allAttributes, Map<String, String> requestedClaimMappings, boolean isStandardDialect) { .... } protected String getDialectUri(String clientType, boolean claimMappingDefined) { .... } /** * Added method to retrieve claims from external sources. This results will be merged to the local claims when * returning final claim list, to be added to the SAML response, that is sent back to the SP. * * @param authenticatedUser : The user for whom we require claim values * @return */ private Map<String, String> handleExternalClaims(String authenticatedUser) throws FrameworkException { Map<String, String> externalClaims = new HashMap<String, String>(); externalClaims.put("http://pushpalanka.org/claims/keplerNumber","E90836W19881010"); externalClaims.put("http://pushpalanka.org/claims/status","active"); return externalClaims; } }
- 将已编译的OSGI软件包放在IS_HOME / repository / components / dropins中。 (我们将其开发为OSGI捆绑软件,因为我们还需要使用RealmService获得本地声明。 您可以在此处找到完整的捆绑软件和源代码 )
- 使WSO2 Identity Server使用我们拥有的新的自定义实现。
在IS_HOME / repository / conf / security / applicationauthentication.xml中,配置新的处理程序名称。 (在“ ApplicationAuthentication.Extensions.ClaimHandler”元素中。)
<ClaimHandler>com.wso2.sample.claim.handler.CustomClaimHandler</ClaimHandler>
现在,如果查看生成的SAML响应,我们将看到添加的外部属性。
干杯!
[1] – https://docs.wso2.com/display/IS500/Adding+a+Service+Provider