当前位置: 首页 > news >正文

基于SAML的ADFS认证集成方案

why ADFS

之所以和ADFS 'sayhello'是公司要求,实现内网项目在外网下的SSO登录访问

当第一次看到ADFS时,第一想到是公司内部哪个工程师搞得一个架构,取英文缩写ADFS,大概用于身份认证,提到到认证方式,想到目前市面主流的oauth2,Jwt,OpenID等,基于SAML2.0的ADFS服务器集成方案是啥,如果是内部框架也没有太详细的部署方案啊,去搜索saml概念,发现是网上有介绍,看一些saml介绍发现ADFS也是有介绍的.那么正式进入学习状态:

搞懂需求上写的啥至少前提要搞懂三种概念,

1.LDAP协议与AD域概念

2.SAML概念

3.ADFS概念

如果用了多年windows系统对windows域与工作组概念还模糊的查看windows域与工作组概念

学习新东西啃专业名词理解概念及原理是不可缺少的一步,以上概念如果你读了一遍还是蒙圈状态,那就多读几遍,书读百遍,其义自见.要不然不知道怎么动手,下面就大概记录下web端大概的集成思路

集成方案说明

系统使用ADFS主要的目的:目前系统使用LDAP协议的AD域模式登录,是在内网下运行,如果实现系统的外网访问,保证用户身份验证的安全性,选择采用基于SAML的ADFS联合身份验证方案,具体的集成方案说明如下:

这张图取自:  https://www.oasis-open.org/committees/download.php/11511/sstc-saml-tech-overview-2.0-draft-03.pdf

以上是用户认证的简单流程图,图中共有三个角色:

  1. Server Provider(SP):服务提供者,提供服务和资源
  2. Identity Provider(IDP):身份鉴别服务器,认证用户并生成saml断言,即ADFS服务器
  3. Client:用户访问服务的客户端,比如浏览器

此图片说明了以下步骤:

  1. 用户尝试访问SP提供的服务。
  2. SP根据配置生成一个 SAML格式的身份验证请求数据包。配置包括ADFS服务器地址,证书,IDP返回SP消息数据的URL地址,身份认证协议等信息,通过浏览器重定向到ADFS服务器进行SAML身份认证.
  3. IDP解码SAML请求,并对用户进行身份验证,IDP要求提供有效登录凭据以验证用户身份。
  4. IDP生成一个 SAML 响应,其中包含经过验证的用户的用户名等信息。按照 SAML 2.0 规范,此响应将使用IDP的 DSA/RSA 公钥和私钥进行数字签名。
  5. IDP将信息返回到用户的浏览器。根据配置的返回路径,浏览器将该信息转发到 SP。
  6. SP使用IDP的公钥验证 SAML 响应。如果成功验证该响应,SP则会将用户重定向到目标网址。
  7. 用户成功登陆系统。

集成配置说明

web端启用基于SAML的SSO,使用OneLogin的开源SAML Java工具包,提供SAML身份验证,应用程序启用单点登录(SSO)。

1.引入依赖

核心包(com.onelogin:JavaSAML-core)

用于处理AuthNRequest、SAMLResponse、LogoutRequest、LogoutResponse和元数据的类和方法。此外,它还包含类来加载工具箱和HttpRequest类的设置,HttpRequest类是一个框架无关的HTTP请求表示。

工具包(com.onelogin:JavaSAML)

包含一个带有Auth类的,用于处理javax.servlet.http对象,javax.servlet.http对象。

github地址:https://github.com/onelogin/java-saml

Java-SAML具有以下依赖关系:

org.apache.santuario:xmlsec
joda-time:joda-time
org.apache.commons:commons-lang3
commons-codec:commons-codec
testing:org.hamcrest:hamcrest-core and org.hamcrest:hamcrest-libraryjunit:junitorg.mockito:mockito-core
logging:org.slf4j:slf4j-apich.qos.logback:logback-classic
For CI:org.jacoco:jacoco-maven-plugin

具体请参看官网说明,官网地址:

https://developers.onelogin.com/saml/java

2.onelogin.saml.properties配置

所有设置都在此文件中定义。

首先,我们需要配置SP的信息,IDP的信息,在某些情况下,还需要高级安全问题的配置,如签名和加密。

SAML JAVA Toolkit已经封装的相对比较完善,所以关键点在于我们对自己的Java项目进行正确的配置就可以接入

实际项目配置的onelogin.saml.properties如下:注解项除外保持默认值即可#  If 'strict' is True, then the Java Toolkit will reject unsigned
#  or unencrypted messages if it expects them signed or encrypted
#  Also will reject the messages if not strictly follow the SAML
onelogin.saml2.strict =  false# Enable debug mode (to print errors)
onelogin.saml2.debug =  false#  Service Provider Data that we are deploying
##  Identifier of the SP entity  (must be a URI)
onelogin.saml2.sp.entityid = https://cat-dev.###.com/cat/jsp/sso/metadata.jsp# Specifies info about where and how the <AuthnResponse> message MUST be
#  returned to the requester, in this case our SP.
# URL Location where the <Response> from the IDP will be returned
onelogin.saml2.sp.assertion_consumer_service.url = https://cat-dev.###.com/cat/r# SAML protocol binding to be used when returning the <Response>
# message.  Onelogin Toolkit supports for this endpoint the
# HTTP-POST binding only
onelogin.saml2.sp.assertion_consumer_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST# Specifies info about where and how the <Logout Response> message MUST be
# returned to the requester, in this case our SP.
onelogin.saml2.sp.single_logout_service.url =# SAML protocol binding to be used when returning the <LogoutResponse> or sending the <LogoutRequest>
# message.  Onelogin Toolkit supports for this endpoint the
# HTTP-Redirect binding only
onelogin.saml2.sp.single_logout_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect# Specifies constraints on the name identifier to be used to
# represent the requested subject.
# Take a look on lib/Saml2/Constants.php to see the NameIdFormat supported
onelogin.saml2.sp.nameidformat = urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified# Usually x509cert and privateKey of the SP are provided by files placed at
# the certs folder. But we can also provide them with the following parameters
# test-----------------------------------------------------------
onelogin.saml2.sp.x509cert = -----BEGIN CERTIFICATE-----\nMIICpDCCAYwCCQCaHqptRwiRkDANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwHhcNMTgwNjEzMDYwNjE4WhcNMjgwNjEwMDYwNjE4WjAUMRIwEAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQChL1RZp0wr7OgaAUk/n4woZgIv0i3ub7nLwgulK6TFYUUo66gKy/Pvpafwn4UklW0xv0Yfr2inyMjbTvmVAtUKWloPrEwvCw1N4w1Fo9KiPUkUt9mrAkiBgp7tbXE34wDT2qJZpx+3ne70nfIJcY3GUYx/FksKvo6s9yJ+GW4nZkMZEkAHD3AZwIF0OZwRicezbSsPOukE6Poc9q7bwoAjDrW8Ab3ll7U4F14ErKU/eesZ5lmTOKaEwZRkuq/1XwVQdFHySdUQuxdDsSNKbVOVBGL6r6ZAcyxX8KBunU6A3E55UWA2I4mg+rFRAEuXm6BAUOdkkS8yPyMkf+IeoIA7AgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFR7Zmoug2EBjRtgF8GjpNAg94rZEBB6vWkNJPsFE6Bc9v7jOULNUuJUF+jvVntSrvs024ONHbAqD/2j/uKpO6Np7LUDEy/lY6t3IpZAmymrWjoVnm1/VZYm2VRZmOSlrkIuZLwu3LWwd39zxL4mDLbZTTQHz3SdiZP6MGIUgZy5NERCmJnLwPrrgHu0JJqZtXiwqAQUlyT1cZXwiOulwQd/3TDwxPPY/zTbclZw3u9WskLgjO0M0mKlg20rZxLpiTSUBDHMGsETKW5k6Q2fwwRreKROUG0zL62Jlj4DIIvW6vDNL8NQSnhohLOfXQMaSZeiH1XUuxC1ZR4fq+yCl7o=\n-----END CERTIFICATE-----# Requires Format PKCS#8   BEGIN PRIVATE KEY
# If you have     PKCS#1   BEGIN RSA PRIVATE KEY  convert it by   openssl pkcs8 -topk8 -inform pem -nocrypt -in sp.rsa_key -outform pem -out sp.pem
# test-----------------------------------------------------------
onelogin.saml2.sp.privatekey = -----BEGIN PRIVATE KEY-----\nMIIEowIBAAKCAQEAoS9UWadMK+zoGgFJP5+MKGYCL9It7m+5y8ILpSukxWFFKOuoCsvz76Wn8J+FJJVtMb9GH69op8jI2075lQLVClpaD6xMLwsNTeMNRaPSoj1JFLfZqwJIgYKe7W1xN+MA09qiWacft53u9J3yCXGNxlGMfxZLCr6OrPcifhluJ2ZDGRJABw9wGcCBdDmcEYnHs20rDzrpBOj6HPau28KAIw61vAG95Ze1OBdeBKylP3nrGeZZkzimhMGUZLqv9V8FUHRR8knVELsXQ7EjSm1TlQRi+q+mQHMsV/Cgbp1OgNxOeVFgNiOJoPqxUQBLl5ugQFDnZJEvMj8jJH/iHqCAOwIDAQABAoIBADXOK9UlsJq1IaGUrlPruYi+zJoUCjse1qG669I+KGmvF7waNmUsQgjMfqwnQ/W7X9EMbackEcZ4kvwKd+wTHvSuxoOW23OUt+M5GPQXRLfdx2iAGswoHfYFmXHeZ73lLCCMSketLzxHHz5O/z3BxzbdgA3objJu/AenE7+OU6QYykjCn1zMi5yuYLPV8NytZCga4iGw8XS+H5KBfno4OQYPjyow+Q4itSlOJXdc6KFcbqzZXb5tZ+Q82J8qRUjQBgAqyQgOXTfDj/1IdwEn0yHlnjcT9tr+dNPAgeu8cknd9zcxl6xvYE1eYg620X0q5H3q0JJ2k+LRtxI6+Hr3BYkCgYEAzj/N1k53QfcOyCXq8afIGrFw51zeo3rv32AfkKSn6feN2Gy+9I3n9iOK2KTzemAjA3WbEboKK7l1rjUbU+3Svr3b7X1AnonyfBq2yvKWB4TVWkIWlpCy5pLRduY8OiJRtg0SJT8DPJMxESnkyZkqtZJL8LmT8Dw/eYVVY3ZakXcCgYEAyBC8G+8yMb+sgBfl7TAfTcLXk5n8F8nkpWKAcW4VFXtodrAXcLBClrJ+/BUkt/sqZA1IMjbUR094owGEyopJ1cnghRhmZx9dpJfwzgVPb53TOHTLrxm4A18jARaoLekfZ/PBhQOQiTE8akbPHbuwixcQEBS5jwgYJN2YM9A9mF0CgYBciUv1ByeCtTIworKS0dB6CXq6k3RgrNvKwPnoj7e2xZcir0fNuY2FZdT59qg3E8Mh3jZA8dN2YrNmAfXM5jtT0SNHnpbLiuD8xY+V5tlhbju7T0OLMkjSIrVQP2RuQM+geqTViTwOhYvSQ5WezdXXuVfRHbI+awmfoC77fTKNaQKBgQDCb6kyCOUifmMa1p8KRoOV4m/7LmNXh0qlBTdJhjANgbOD7h3J0jPVG8LYIYBfIkYPmOz6iFkEuRLIcThqU73wfdOr5ovXWx96UISi5XxPQPa/3pr6ISe6dyKg8zEd9XwlXjxMlqtI+kX6D7lI71ljxFVDG7E/diFo6sf6Sz8hrQKBgFyrZqsBgz0R6d9V0R0JWNpdt+WShl5Hb0HrG844ZJQZKZbEwJFDxi0I5GomCydfCEnyBS2+iLYdaNkAYPVjDynb5CZVPzy6shCE6T2GLG9Cc/IXhPMvNQz6RHx1HyM7TegIgQY4hh9ZHtDe8ROZrAucpOG0NHaT+gRuXe4bnedd\n-----END PRIVATE KEY-----# Identity Provider Data that we want connect with our SP
## Identifier of the IDP entity  (must be a URI)
# test-----------------------------------------------------------
onelogin.saml2.IDP.entityid = https://sso.###.com/FederationMetadata/2007-06/FederationMetadata.xml# SSO endpoint info of the IDP. (Authentication Request protocol)
# URL Target of the IDP where the SP will send the Authentication Request Message
# test-------------------------------------------------------------
onelogin.saml2.IDP.single_sign_on_service.url = https://sso.###.com/adfs/ls# SAML protocol binding to be used when returning the <Response>
# message.  Onelogin Toolkit supports for this endpoint the
# HTTP-Redirect binding only
onelogin.saml2.IDP.single_sign_on_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect# SLO endpoint info of the IDP.
# URL Location of the IDP where the SP will send the SLO Request
# test-----------------------------------------------------------
onelogin.saml2.IDP.single_logout_service.url =# Optional SLO Response endpoint info of the IDP.
# URL Location of the IDP where the SP will send the SLO Response. If left blank, same URL as onelogin.saml2.IDP.single_logout_service.url will be used.
# Some IDPs use a separate URL for sending a logout request and response, use this property to set the separate response url
# test-----------------------------------------------------------
onelogin.saml2.IDP.single_logout_service.response.url =# SAML protocol binding to be used when returning the <Response>
# message.  Onelogin Toolkit supports for this endpoint the
# HTTP-Redirect binding only
onelogin.saml2.IDP.single_logout_service.binding = urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect# Public x509 certificate of the IDP
# test-----------------------------------------------------------
onelogin.saml2.IDP.x509cert = MIIC6DCCAdCgAwIBAgIQZiRU+RFa9rRJO57iE1ib9DANBgkqhkiG9w0BAQsFADAwMS4wLAYDVQQDEyVBREZTIFNpZ25pbmcgLSBkZXZvcHMueGlhb2hvbmdzaHUuY29tMB4XDTE3MDcyNTEzMDIxMFoXDTE4MDcyNTEzMDIxMFowMDEuMCwGA1UEAxMlQURGUyBTaWduaW5nIC0gZGV2b3BzLnhpYW9ob25nc2h1LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALfwBsXi/9Hlor/Q8W9ljtRk/Pqv91OPdS5K9f4SyQKs1idFi1fR94uCnbC3HxZeCo4/3iCYoiwpG7PZnuC/EIDIQ0Pdu2b7jOcgmTww6k2CsyZPMZxBWaFPWofwL7kJ+SEUxz0Vd93TV2J/V09Gy6QR6lTBtz2CJZXpkTSYuLnQMfkDiHJKeFsrBkiH5bqVyEriv7rgiFajb8gZdTiEEbWEqGgps++17oCpmz9dMi0Vjz31ij2YHWPh3Y7EJ/aVGBHcFD1KgIqfWeDxJ0e2EjIqM8bdDAq6MsAWll1++BIkKBMUdoFvu7S8jT5aszUnScQzEPCXJL0Cs6rmBhEDoCkCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAhjhQbeaMJEYPC3ZwhfFAZMuuCOcFg3MgSh1fMDQ1iqtJ1uX7EpkX9NG0bJfFmyvU/5L/wjV41byg3N0HdHuO1VsFTV4q2XzFZJRBPb7Ta6OIQYw+qIX4RH18TSObguv3GgT7AbWHS8HDAmivmTWoL2fJL9sgIMnv/iL/EyG8iXf1fyZBP6Kr60d3xmPFyhlJ76/hBzqRGW6WrdAFunD1vIWr0u/zIs2GbY6A9CcLmpCTAgVTtytGmt+2iTm7trck1l8qOCQCkqX6iH6swHULLLZTUo6STQdTdsbtymf9GE396l6Pc3JwNXKG0Eq89/3l8o1vmyv7ikW1FdQUWTOs7w==
# Instead of use the whole x509cert you can use a fingerprint
# (openssl x509 -noout -fingerprint -in "IDP.crt" to generate it,
# or add for example the -sha256 , -sha384 or -sha512 parameter)
#
# If a fingerprint is provided, then the certFingerprintAlgorithm is required in order to
# let the toolkit know which Algorithm was used. Possible values: sha1, sha256, sha384 or sha512
# 'sha1' is the default value.
# onelogin.saml2.IDP.certfingerprint = 
# onelogin.saml2.IDP.certfingerprint_algorithm = sha1# Security settings
## Indicates that the nameID of the <samlp:logoutRequest> sent by this SP
# will be encrypted.
onelogin.saml2.security.nameid_encrypted = false# Indicates whether the <samlp:AuthnRequest> messages sent by this SP
# will be signed.              [The Metadata of the SP will offer this info]
onelogin.saml2.security.authnrequest_signed = false# Indicates whether the <samlp:logoutRequest> messages sent by this SP
# will be signed.
onelogin.saml2.security.logoutrequest_signed = false# Indicates whether the <samlp:logoutResponse> messages sent by this SP
# will be signed.
onelogin.saml2.security.logoutresponse_signed = false# Sign the Metadata
# Empty means no signature, or comma separate the keyFileName and the certFileName
onelogin.saml2.security.want_messages_signed =# Indicates a requirement for the <samlp:Response>, <samlp:LogoutRequest> and
# <samlp:LogoutResponse> elements received by this SP to be signed.
onelogin.saml2.security.want_assertions_signed = false# Indicates a requirement for the Metadata of this SP to be signed.
# Right now supported null (in order to not sign) or true (sign using SP private key) 
onelogin.saml2.security.sign_metadata =# Indicates a requirement for the Assertions received by this SP to be encrypted
onelogin.saml2.security.want_assertions_encrypted = false# Indicates a requirement for the NameID received by this SP to be encrypted
onelogin.saml2.security.want_nameid = false# Indicates a requirement for the NameID received by this SP to be encrypted
onelogin.saml2.security.want_nameid_encrypted = false# Authentication context.
# Set Empty and no AuthContext will be sent in the AuthNRequest
# You can set multiple values (comma separated them)
onelogin.saml2.security.requested_authncontext = urn:oasis:names:tc:SAML:2.0:ac:classes:Password# Allows the authn comparison parameter to be set, defaults to 'exact'
onelogin.saml2.security.onelogin.saml2.security.requested_authncontextcomparison = exact# Indicates if the SP will validate all received xmls.
# (In order to validate the xml, 'strict' and 'wantXMLValidation' must be true).
onelogin.saml2.security.want_xml_validation = true# Algorithm that the toolkit will use on signing process. Options:
#  'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
#  'http://www.w3.org/2000/09/xmldsig#dsa-sha1'
#  'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'
#  'http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'
#  'http://www.w3.org/2001/04/xmldsig-more#rsa-sha512'
onelogin.saml2.security.signature_algorithm = http://www.w3.org/2000/09/xmldsig#rsa-sha1

3.启动SSO

Auth auth = new Auth(request, response);auth.login();

根据安全设置‘onelogin.saml2.security.authnrequest_signed’发送已签名或无签名的AuthNRequest。

IDP然后将SAML响应返回给用户的客户端。然后,使用此信息转发到SP。

4.SP元数据

此代码将根据我们在配置文件中提供的信息提供我们的SP的XML元数据。

Auth auth = new Auth();
Saml2Settings settings = auth.getSettings();
settings.setSPValidationOnly(true);
String metadata = settings.getSPMetadata();
List<String> errors = Saml2Settings.validateMetadata(metadata);
if (errors.isEmpty()) {out.println(metadata);
} else {response.setContentType("text/html; charset=UTF-8");for (String error : errors) {out.println("<p>"+error+"</p>");}
}

getSPMetdata将根据设置的安全参数返回签名或不签名的元数据。onelogin.saml2.security.sign_metadata。

5.属性消费者服务

此代码处理IDP通过用户客户端转发给SP的SAML响应。

Auth auth = new Auth(request, response);
auth.processResponse();
if (!auth.isAuthenticated()) {out.println("Not authenticated");
}List<String> errors = auth.getErrors();
if (!errors.isEmpty()) {out.println(StringUtils.join(errors, ", "));if (auth.isDebugActive()) {String errorReason = auth.getLastErrorReason();if (errorReason != null && !errorReason.isEmpty()) {out.println(auth.getLastErrorReason());}}
} else {Map<String, List<String>> attributes = auth.getAttributes();String nameId = auth.getNameId();String nameIdFormat = auth.getNameIdFormat();String sessionIndex = auth.getSessionIndex();String nameidNameQualifier = auth.getNameIdNameQualifier();String nameidSPNameQualifier = auth.getNameIdSPNameQualifier();session.setAttribute("attributes", attributes);session.setAttribute("nameId", nameId);session.setAttribute("nameIdFormat", nameIdFormat);session.setAttribute("sessionIndex", sessionIndex);session.setAttribute("nameidNameQualifier", nameidNameQualifier);session.setAttribute("nameidSPNameQualifier", nameidSPNameQualifier);String relayState = request.getParameter("RelayState");if (relayState != null && relayState != ServletUtils.getSelfRoutedURLNoQuery(request)) {response.sendRedirect(request.getParameter("RelayState"));} else {if (attributes.isEmpty()) {out.println("You don't have any attributes");}else {Collection<String> keys = attributes.keySet();for(String name :keys){out.println(name);List<String> values = attributes.get(name);for(String value :values) {out.println(" - " + value);}}}}
}

SAML响应被处理,然后检查以确保没有错误。它还验证用户是否经过身份验证,然后将用户数据存储在会话中。在这一点上,有两种可能的选择:

1.如果没有提供RelayState,我们可以在这个视图中显示用户数据,或者以我们想要的方式显示用户数据。

2.如果提供RelayState,则会发生重定向。注意,在重定向之前将用户数据保存在会话中,以便在RelayState视图中获得可用的用户数据。

 

为了检索属性,使用:

Map<String, List<String>> attributes = auth.getAttributes();

通过这种方法,获得了一个Map,其中包含了IDP在SAML响应断言中提供的所有用户数据。

每个属性名都可以用作获取值的键。在尝试获取属性之前,请检查用户是否经过身份验证。如果未对用户进行身份验证,则将返回一个空Map。例如,如果我们在auth.processResponse之前调用getAttributes,getAttributes()将返回一个空Map。

6.注销服务

此代码处理注销请求和注销响应。

Auth auth = new Auth(request, response);
auth.processSLO();
List<String> errors = auth.getErrors();
if (errors.isEmpty()) {out.println("Sucessfully logged out");
} else {for(String error : errors) {out.println(error);}
}

如果sp接收到注销请求,则验证请求,关闭会话,并将注销响应发送到IDP。

 

参考:

https://developers.onelogin.com/saml/java

http://www.cnblogs.com/qiuhk/p/9387772.html

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


http://www.taodudu.cc/news/show-7665378.html

相关文章:

  • adfs 登录逻辑
  • 集成adfs
  • 设计模式 Design Parttern ——桥模式Bridge
  • JavaScript Promise详解 resolve()、reject()、then()、catch()、finally()(包含习题)
  • ABAP 逻辑数据库的循环REJECT
  • IJCAI2023 Summary Reject公布
  • return和reject、resolve的配合使用
  • Linux常用命令——reject命令
  • alpine linux默认账号密码
  • Alpine 安装依赖包
  • vmware安装alpine linux
  • alpine linux nginx,Docker alpine构建nginx
  • alpine安装启动mysql_Alpine Linux常用命令
  • alpine 编译c语言,Alpine Linux配置使用技巧
  • alpine 笔记
  • Alpine基本操作
  • Alpine VMware
  • C++编程之 std::forward
  • C++11 move和forward实现原理
  • Linux ip_forward
  • linux ip forward不起作用,linux-ipforward实现
  • association标签传多参
  • MyBatis之association和collection标签
  • MyBatis_延迟加载association和lazyLoadingEnabled详解
  • Mybatis 关联查寻 association 和 collection
  • resultMap中association的各个属性
  • mybaties association使用
  • mybatis的association
  • hexchat(IRC聊天客户端)
  • irc客户端 linux,IRC使用入门