Browse Source

JSY-42593 add:提供一个sp和idp进行sso的demo

main
codingXiaxw 6 months ago
commit
52d4c1c586
  1. 8
      .idea/.gitignore
  2. 30
      build.gradle
  3. 5
      gradle/wrapper/gradle-wrapper.properties
  4. 39
      idp_metadata.xml
  5. 21
      rp-certificate.crt
  6. 27
      rp-private.key
  7. 39
      sp-metadata.xml
  8. 142
      src/main/java/com/fanruan/sso/bean/SSOSamlBean.java
  9. 75
      src/main/java/com/fanruan/sso/bean/SSOSamlResultBean.java
  10. 59
      src/main/java/com/fanruan/sso/controller/SSOController.java
  11. 20
      src/main/java/com/fanruan/sso/filter/SAMLFilter.java
  12. 22
      src/main/java/com/fanruan/sso/service/SSOService.java
  13. 172
      src/main/java/com/fanruan/sso/service/impl/SSOServiceImpl.java
  14. 33
      src/main/java/com/fanruan/sso/utils/OpenSAMLUtils.java
  15. 104
      src/main/java/com/fanruan/sso/utils/XMLAnalysisUtils.java

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

30
build.gradle

@ -0,0 +1,30 @@
buildscript {
ext {
springBootVersion = '2.5.14'
}
}
plugins {
id 'java'
}
group 'org.example'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation 'com.onelogin:java-saml:2.9.0'
implementation(platform("org.springframework.boot:spring-boot-dependencies:" + springBootVersion))
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.2'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.2'
}
test {
useJUnitPlatform()
}

5
gradle/wrapper/gradle-wrapper.properties vendored

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

39
idp_metadata.xml

@ -0,0 +1,39 @@
<?xml version="1.0"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://app.onelogin.com/saml/metadata/0110a1e6-c349-45c4-9728-310cb79d843d">
<IDPSSODescriptor xmlns:ds="http://www.w3.org/2000/09/xmldsig#" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIID3DCCAsSgAwIBAgIUB2/7G0jrXZEpZqCOClAOw9tM0gswDQYJKoZIhvcNAQEF
BQAwRTEQMA4GA1UECgwHRmFucnVhbjEVMBMGA1UECwwMT25lTG9naW4gSWRQMRow
GAYDVQQDDBFPbmVMb2dpbiBBY2NvdW50IDAeFw0yNDA4MDEwMzM3MzZaFw0yOTA4
MDEwMzM3MzZaMEUxEDAOBgNVBAoMB0ZhbnJ1YW4xFTATBgNVBAsMDE9uZUxvZ2lu
IElkUDEaMBgGA1UEAwwRT25lTG9naW4gQWNjb3VudCAwggEiMA0GCSqGSIb3DQEB
AQUAA4IBDwAwggEKAoIBAQCWV9aev80yVah8cbhq3JYSn30GiJQFXPXX09zPzztO
FcvnKsskx5oRj0DXVuhGwPEaQ4b2wMahMNHoGVVuMFAs1xPa55lxcY4XirEhY/nW
i9CYb0SiLZnb+W382byr+nqYbYCvPASu5ifRtM1adwngfcd7w1JbkylzlzuXStFl
qpNGKVWPYVwb3I3mCmeppThYWakrvXQcy1VFHJ2LHehoVCQsaf2UxgZVazwV22wG
UF7e3grTc2+dsTTIUuF04jLir34N++PE5RufI1irADj4WhdaFI7st1YaWCBZSe5Z
UR298IlatrKQ088mfWQc4oHHznRO1ffoHUmmL31uh5O9AgMBAAGjgcMwgcAwDAYD
VR0TAQH/BAIwADAdBgNVHQ4EFgQU0Xtr2IIM7Kdw2priuuGKDM9G2WswgYAGA1Ud
IwR5MHeAFNF7a9iCDOyncNqa4rrhigzPRtlroUmkRzBFMRAwDgYDVQQKDAdGYW5y
dWFuMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxGjAYBgNVBAMMEU9uZUxvZ2luIEFj
Y291bnQgghQHb/sbSOtdkSlmoI4KUA7D20zSCzAOBgNVHQ8BAf8EBAMCB4AwDQYJ
KoZIhvcNAQEFBQADggEBAIJ6vM7/WZ4yELkC5ql3sQCE+NilPSXgksk44ZF+MqNA
Cc4KjYbU9eFCKhrzOxXIX4+rw2A/M1EOUpj1T88wKnQtzzwciglao3uvPLP9mUgT
KiBqHaHV42piwy9bdwf/yckgZmo0DnwOSO9mhHcrKBVdFMIfh6iojaC34Diex72O
bjY3NDw/Lky/+5KCvCX8L7rQJzdB6uksE3ei4gV6wSBxpP/4qOao8BJh9gCUAAUg
gSEFbbmB2CiL4p134uUpDfLt6F7aaOQ+K9CLCrVhHFc8+KT/ubotOe6L6REbfowC
bsCvmdbnq3JRPuI/jbT8EYk4PRrWnmRQ6G0zdxkFk7U=</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</KeyDescriptor>
<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://codingxiaxw.onelogin.com/trust/saml2/http-redirect/slo/3440964"/>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://codingxiaxw.onelogin.com/trust/saml2/http-redirect/sso/0110a1e6-c349-45c4-9728-310cb79d843d"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://codingxiaxw.onelogin.com/trust/saml2/http-post/sso/0110a1e6-c349-45c4-9728-310cb79d843d"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://codingxiaxw.onelogin.com/trust/saml2/soap/sso/0110a1e6-c349-45c4-9728-310cb79d843d"/>
</IDPSSODescriptor>
</EntityDescriptor>

21
rp-certificate.crt

@ -0,0 +1,21 @@
-----BEGIN CERTIFICATE-----
MIIDcDCCAlgCCQDVWvep1uXiejANBgkqhkiG9w0BAQsFADB6MUUwQwYDVQQIDDxv
cGVuc3NsIHJlcSAtbmV3IC1rZXkgcnAtcHJpdmF0ZS5rZXkgLW91dCBycC1jZXJ0
aWZpY2F0ZS5jc3IxDTALBgNVBAcMBHd1eGkxEDAOBgNVBAoMB2ZhbnJ1YW4xEDAO
BgNVBAsMB2ZhbnJ1YW4wHhcNMjQwODA4MTEyNjIzWhcNMjUwODA4MTEyNjIzWjB6
MUUwQwYDVQQIDDxvcGVuc3NsIHJlcSAtbmV3IC1rZXkgcnAtcHJpdmF0ZS5rZXkg
LW91dCBycC1jZXJ0aWZpY2F0ZS5jc3IxDTALBgNVBAcMBHd1eGkxEDAOBgNVBAoM
B2ZhbnJ1YW4xEDAOBgNVBAsMB2ZhbnJ1YW4wggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDS0iT7OyG3Y1+3mLfXeoAFZLgiwUel143W5gdlbXSHbuJ0xNrE
vaGRaUj0vZCdVPL6bAtQiJGQGAEgYpp9ZeaPexCrOE92LhHGZADbcVT2B954ni+f
LKG7J9FnZcRDOBEqYhyVqTvG6uN0TIIkRwKFTjsSPdRQyqV6uNW4y+r8RrjIOAMS
K5CWKZbqiiAcb5EqmeYQfWNLjVCn+E199R1LIlEnrMz4+kHEIOTTzNQht30utx5f
aiBlLQ9XVyVPWHV0iNzGs2qxHUA07bZyEuiG2t+79OAQWKzjqTK5GTvAWEbZ6IaO
FVi970pG7E14T+rivZGqJrLWvv8MvW4BE0v7AgMBAAEwDQYJKoZIhvcNAQELBQAD
ggEBAFAZg0TGNSpnIKR1MW4Y0K+2LyslBlMTirrQY21MobS/S+WM8QE+qZvyPUpp
ilaXnquCptM1MtX/9kPRlRA5v8bEWNZQA6bs3RkH5FC5j+TKcrPti7yAaTcMxw7h
S/e9e3HZ9ZeU2b1M87Gs1uGTWJ4LyH5vKfHRpNlhOj+rv4k8UeCce/ER0z4OBmwE
0OtY7xWEP5arF6iVyntpYPbxujuxui1orfsUl5DEOUvKA3VHG5fsgUkhP/KoxFTS
6ETrC5qy7HCk9J88HX9ovxY/bj/SWwAGx3wNaG+NZz2pQyD6NaBOSRvBC2ZwFlWr
TgtYc4URcnVH2DOkamR9hFecVA4=
-----END CERTIFICATE-----

27
rp-private.key

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA0tIk+zsht2Nft5i313qABWS4IsFHpdeN1uYHZW10h27idMTa
xL2hkWlI9L2QnVTy+mwLUIiRkBgBIGKafWXmj3sQqzhPdi4RxmQA23FU9gfeeJ4v
nyyhuyfRZ2XEQzgRKmIclak7xurjdEyCJEcChU47Ej3UUMqlerjVuMvq/Ea4yDgD
EiuQlimW6oogHG+RKpnmEH1jS41Qp/hNffUdSyJRJ6zM+PpBxCDk08zUIbd9Lrce
X2ogZS0PV1clT1h1dIjcxrNqsR1ANO22chLohtrfu/TgEFis46kyuRk7wFhG2eiG
jhVYve9KRuxNeE/q4r2Rqiay1r7/DL1uARNL+wIDAQABAoIBAFq4h6V8+rNaFhSB
qYsWcgTgJMT/+38IVUdG0aP6CA1H0DeDhsjm+aIDdpuq/5JUvgK5f9z2B/3D9qgj
Bmwz75WK1c94eelXRdrjqeLE0FTXagrpt4B9ylMpfVlLV7u9YtWkRry4iLq+1YZ0
bgSCcjk/QvhElxr1OWSoKYBOcB6C39o96Obzek6cgdUNRZpP1pnyNpYMAnrHUOnR
a+iFMFt3q7Pn815EG4Rg4Emgmd9v9c/xAonrqoXddZSsCfUTo1duqfoxJnU21O3q
spcofbX25sULYL1gpM4p1V92hHaSNZHdZhxMlUuSCwBk1PJEGLrFEDiVZXXDcH8m
B8ghZrECgYEA/H/gz0rkNW6zk1nMKT/ozJUb0XqTVZBrJuq7L0vmOUuNUMalbkzA
GlDUM7r852yeiwx4S+XgTRvWRsqau/cuX1B+Z4qaLjWi7RUrQqI9iZS3HFbdX21R
pXz03iCPONL+X4Mzh6Z7NIKf8ptn4syMXJan1M5XrsM8bPAS1oRyuf8CgYEA1b5Z
Pkzk6N6GqGVzfNQ8G3l4620rbJBvc1itNTK4TX1W9MOKIHj/zoYopgk+2snQQtUd
2Urg+3LzJ7vegBmt8Mlpjs6Y27Iz4gyurWHAYEkBK/weqExOz6QB1lYFaO3DPHcx
+SJc2tSmoBEvcLmEBfvJ0uIvN637BmyPg7umVgUCgYEAgwV8SzRqXMuXxTNIfHMc
QuRwre9z+mdZIrWU8gLpcPuiVbLubuDGoiElK76wswmq7y5GUePz0y9Jriw9xKGL
34uuO94xCR9t7qYYb5guZHDV34+3iWf5gOzpR0YP64WY10kGeTJLJkFN7B719jr7
7qOCbSuxVg8bENA2hjfuLFMCgYEAp60PrYP8/4Gx+WC83GxSSutcJLQboKseA0rJ
djY3xvJQyOqs7RR++LDeKoKOQGyZaBRvugq3vApNHhqPTcbXYVFf8Zu45oBBm09/
qJxKoj4jITJDiptyKAntNwt8avg6dLC9D0gZt8GihWd14+Rk4ZzIkxrFF9TwW/XG
D/2hW1ECgYBQtU5PYiaMb7zTVQVVS7aVbym6QInvyZ03O64Y3y+cLf+PFG3F6q/4
SRtsmILe+sBY8MdQj0gvGBguMRTgprahS38mbQLdyGCmJIsKdqk9IHv4xU9sHU+n
xoznawU1UDlxxWrfBpaYVb4CkxaDjL5FvWHv74ZgZ1+Zh3e0gCuIzQ==
-----END RSA PRIVATE KEY-----

39
sp-metadata.xml

@ -0,0 +1,39 @@
<?xml version="1.0"?><md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" validUntil="2024-08-11T07:38:28Z" cacheDuration="PT604800S" entityID="http://localhost:8081/decision/sso/saml/coding/iss" ID="ONELOGIN_82622fcf-63a5-4d42-83af-1358c5f5e6fa"><md:SPSSODescriptor AuthnRequestsSigned="true" WantAssertionsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"><md:KeyDescriptor use="signing"><ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:X509Data><ds:X509Certificate>MIIDcDCCAlgCCQDVWvep1uXiejANBgkqhkiG9w0BAQsFADB6MUUwQwYDVQQIDDxv
cGVuc3NsIHJlcSAtbmV3IC1rZXkgcnAtcHJpdmF0ZS5rZXkgLW91dCBycC1jZXJ0
aWZpY2F0ZS5jc3IxDTALBgNVBAcMBHd1eGkxEDAOBgNVBAoMB2ZhbnJ1YW4xEDAO
BgNVBAsMB2ZhbnJ1YW4wHhcNMjQwODA4MTEyNjIzWhcNMjUwODA4MTEyNjIzWjB6
MUUwQwYDVQQIDDxvcGVuc3NsIHJlcSAtbmV3IC1rZXkgcnAtcHJpdmF0ZS5rZXkg
LW91dCBycC1jZXJ0aWZpY2F0ZS5jc3IxDTALBgNVBAcMBHd1eGkxEDAOBgNVBAoM
B2ZhbnJ1YW4xEDAOBgNVBAsMB2ZhbnJ1YW4wggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDS0iT7OyG3Y1+3mLfXeoAFZLgiwUel143W5gdlbXSHbuJ0xNrE
vaGRaUj0vZCdVPL6bAtQiJGQGAEgYpp9ZeaPexCrOE92LhHGZADbcVT2B954ni+f
LKG7J9FnZcRDOBEqYhyVqTvG6uN0TIIkRwKFTjsSPdRQyqV6uNW4y+r8RrjIOAMS
K5CWKZbqiiAcb5EqmeYQfWNLjVCn+E199R1LIlEnrMz4+kHEIOTTzNQht30utx5f
aiBlLQ9XVyVPWHV0iNzGs2qxHUA07bZyEuiG2t+79OAQWKzjqTK5GTvAWEbZ6IaO
FVi970pG7E14T+rivZGqJrLWvv8MvW4BE0v7AgMBAAEwDQYJKoZIhvcNAQELBQAD
ggEBAFAZg0TGNSpnIKR1MW4Y0K+2LyslBlMTirrQY21MobS/S+WM8QE+qZvyPUpp
ilaXnquCptM1MtX/9kPRlRA5v8bEWNZQA6bs3RkH5FC5j+TKcrPti7yAaTcMxw7h
S/e9e3HZ9ZeU2b1M87Gs1uGTWJ4LyH5vKfHRpNlhOj+rv4k8UeCce/ER0z4OBmwE
0OtY7xWEP5arF6iVyntpYPbxujuxui1orfsUl5DEOUvKA3VHG5fsgUkhP/KoxFTS
6ETrC5qy7HCk9J88HX9ovxY/bj/SWwAGx3wNaG+NZz2pQyD6NaBOSRvBC2ZwFlWr
TgtYc4URcnVH2DOkamR9hFecVA4=
</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:KeyDescriptor use="encryption"><ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#"><ds:X509Data><ds:X509Certificate>MIIDcDCCAlgCCQDVWvep1uXiejANBgkqhkiG9w0BAQsFADB6MUUwQwYDVQQIDDxv
cGVuc3NsIHJlcSAtbmV3IC1rZXkgcnAtcHJpdmF0ZS5rZXkgLW91dCBycC1jZXJ0
aWZpY2F0ZS5jc3IxDTALBgNVBAcMBHd1eGkxEDAOBgNVBAoMB2ZhbnJ1YW4xEDAO
BgNVBAsMB2ZhbnJ1YW4wHhcNMjQwODA4MTEyNjIzWhcNMjUwODA4MTEyNjIzWjB6
MUUwQwYDVQQIDDxvcGVuc3NsIHJlcSAtbmV3IC1rZXkgcnAtcHJpdmF0ZS5rZXkg
LW91dCBycC1jZXJ0aWZpY2F0ZS5jc3IxDTALBgNVBAcMBHd1eGkxEDAOBgNVBAoM
B2ZhbnJ1YW4xEDAOBgNVBAsMB2ZhbnJ1YW4wggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDS0iT7OyG3Y1+3mLfXeoAFZLgiwUel143W5gdlbXSHbuJ0xNrE
vaGRaUj0vZCdVPL6bAtQiJGQGAEgYpp9ZeaPexCrOE92LhHGZADbcVT2B954ni+f
LKG7J9FnZcRDOBEqYhyVqTvG6uN0TIIkRwKFTjsSPdRQyqV6uNW4y+r8RrjIOAMS
K5CWKZbqiiAcb5EqmeYQfWNLjVCn+E199R1LIlEnrMz4+kHEIOTTzNQht30utx5f
aiBlLQ9XVyVPWHV0iNzGs2qxHUA07bZyEuiG2t+79OAQWKzjqTK5GTvAWEbZ6IaO
FVi970pG7E14T+rivZGqJrLWvv8MvW4BE0v7AgMBAAEwDQYJKoZIhvcNAQELBQAD
ggEBAFAZg0TGNSpnIKR1MW4Y0K+2LyslBlMTirrQY21MobS/S+WM8QE+qZvyPUpp
ilaXnquCptM1MtX/9kPRlRA5v8bEWNZQA6bs3RkH5FC5j+TKcrPti7yAaTcMxw7h
S/e9e3HZ9ZeU2b1M87Gs1uGTWJ4LyH5vKfHRpNlhOj+rv4k8UeCce/ER0z4OBmwE
0OtY7xWEP5arF6iVyntpYPbxujuxui1orfsUl5DEOUvKA3VHG5fsgUkhP/KoxFTS
6ETrC5qy7HCk9J88HX9ovxY/bj/SWwAGx3wNaG+NZz2pQyD6NaBOSRvBC2ZwFlWr
TgtYc4URcnVH2DOkamR9hFecVA4=
</ds:X509Certificate></ds:X509Data></ds:KeyInfo></md:KeyDescriptor><md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat><md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://localhost:8081/decision/sso/saml/coding/acs" index="1"/></md:SPSSODescriptor></md:EntityDescriptor>

142
src/main/java/com/fanruan/sso/bean/SSOSamlBean.java

@ -0,0 +1,142 @@
package com.fanruan.sso.bean;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SSOSamlBean {
/**
* idp endpoint
*/
private String ssoEndpoint;
/**
* idp算法
*/
private String signatureAlg;
/**
* idp公钥
*/
private String signatureCrt;
/**
* idp issuer
*/
private String issuer;
/**
* idp slo
*/
private String sloEndpoint;
/**
* 是否开启单点登录
*/
private boolean turnOn;
/**
* sp 证书
*/
private String spCrt;
/**
* sp 私钥
*/
private String spPrivateKey;
/**
* 唯一标识
* 如果是odm的sso则为odm的id
* 如果是企业设置的sso则为九数云的企业id
*/
private String registrationId;
public SSOSamlBean(String ssoEndpoint, String signatureAlg, String signatureCrt, String issuer, String sloEndpoint,
String registrationId, boolean turnOn, String spCrt, String spPrivateKey) {
this.ssoEndpoint = ssoEndpoint;
this.signatureAlg = signatureAlg;
this.signatureCrt = signatureCrt;
this.issuer = issuer;
this.sloEndpoint = sloEndpoint;
this.registrationId = registrationId;
this.turnOn = turnOn;
this.spCrt = spCrt;
this.spPrivateKey = spPrivateKey;
}
public String getSpCrt() {
return spCrt;
}
public void setSpCrt(String spCrt) {
this.spCrt = spCrt;
}
public String getSpPrivateKey() {
return spPrivateKey;
}
public void setSpPrivateKey(String spPrivateKey) {
this.spPrivateKey = spPrivateKey;
}
public boolean isTurnOn() {
return turnOn;
}
public void setTurnOn(boolean turnOn) {
this.turnOn = turnOn;
}
public String getRegistrationId() {
return registrationId;
}
public void setRegistrationId(String registrationId) {
this.registrationId = registrationId;
}
public String getSsoEndpoint() {
return ssoEndpoint;
}
public void setSsoEndpoint(String ssoEndpoint) {
this.ssoEndpoint = ssoEndpoint;
}
public String getSignatureAlg() {
return signatureAlg;
}
public void setSignatureAlg(String signatureAlg) {
this.signatureAlg = signatureAlg;
}
public String getSignatureCrt() {
return signatureCrt;
}
public void setSignatureCrt(String signatureCrt) {
this.signatureCrt = signatureCrt;
}
public String getIssuer() {
return issuer;
}
public void setIssuer(String issuer) {
this.issuer = issuer;
}
public String getSloEndpoint() {
return sloEndpoint;
}
public void setSloEndpoint(String sloEndpoint) {
this.sloEndpoint = sloEndpoint;
}
}

75
src/main/java/com/fanruan/sso/bean/SSOSamlResultBean.java

@ -0,0 +1,75 @@
package com.fanruan.sso.bean;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonInclude(JsonInclude.Include.NON_NULL)
public class SSOSamlResultBean {
/**
* sso endpoint
*/
private String ssoEndpoint;
/**
* sp metadata
*/
private String spMetadata;
/**
* sp sso地址
*/
private String spIss;
/**
* sp 断言消费地址
*/
private String spAcs;
/**
* sp 登录地址
*/
private String spSlo;
public String getSsoEndpoint() {
return ssoEndpoint;
}
public void setSsoEndpoint(String ssoEndpoint) {
this.ssoEndpoint = ssoEndpoint;
}
public String getSpMetadata() {
return spMetadata;
}
public void setSpMetadata(String spMetadata) {
this.spMetadata = spMetadata;
}
public String getSpIss() {
return spIss;
}
public void setSpIss(String spIss) {
this.spIss = spIss;
}
public String getSpAcs() {
return spAcs;
}
public void setSpAcs(String spAcs) {
this.spAcs = spAcs;
}
public String getSpSlo() {
return spSlo;
}
public void setSpSlo(String spSlo) {
this.spSlo = spSlo;
}
}

59
src/main/java/com/fanruan/sso/controller/SSOController.java

@ -0,0 +1,59 @@
package com.fanruan.sso.controller;
import com.fanruan.hihidata.action.aspect.PortalRoleCheck;
import com.fanruan.hihidata.action.aspect.RateLimit;
import com.fanruan.hihidata.action.aspect.Scope;
import com.fanruan.hihidata.action.reponse.HiRespond;
import com.fanruan.hihidata.config.role.CorpVersionRoleType;
import com.fanruan.hihidata.service.sso.SSOService;
import com.fanruan.hihidata.service.utils.OpenSAMLUtils;
import com.fr.decision.webservice.annotation.LoginStatusChecker;
import com.fr.third.org.apache.commons.lang3.StringUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@RestController
public class SSOController {
@Autowired
private SSOService ssoService;
@ResponseBody
@GetMapping(value = "/sso/saml/{registrationId}/sp/metadata", produces = MediaType.APPLICATION_XML_VALUE)
public void getSpMetadata(@PathVariable("registrationId") String registrationId, HttpServletRequest req, HttpServletResponse res) throws Exception {
String SpMetadata = ssoService.generateSpMetadata(registrationId);
OpenSAMLUtils.downloadByStringContent(req, res, "sp-metadata.xml", SpMetadata);
}
@ResponseBody
@GetMapping(value = "/sso/saml/{registrationId}/iss", produces = MediaType.TEXT_HTML_VALUE)
public String iss(@PathVariable("registrationId") String registrationId, HttpServletRequest req, HttpServletResponse res) throws Exception {
ssoService.iss(registrationId, req, res);
return StringUtils.EMPTY;
}
@ResponseBody
@PostMapping(value = "/sso/saml/{registrationId}/acs")
public String acs(@PathVariable("registrationId") String registrationId, HttpServletRequest req, HttpServletResponse res) throws Exception {
return ssoService.acs(registrationId, req, res);
}
@ResponseBody
@GetMapping(value = "/sso/saml/{registrationId}/slo")
public String slo(@PathVariable("registrationId") String registrationId, HttpServletRequest req, HttpServletResponse res) throws Exception {
ssoService.slo(registrationId, req, res);
return StringUtils.EMPTY;
}
}

20
src/main/java/com/fanruan/sso/filter/SAMLFilter.java

@ -0,0 +1,20 @@
package com.fanruan.sso.filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class SAMLFilter {
private final SamlIgnores ignores = new SamlIgnores();
private boolean initialized = false;
@Override
public void doFilter(HttpServletRequest req, HttpServletResponse res, FilterChain filterChain) throws IOException, ServletException {
// do something
filterChain.doFilter(req, res);
}
}

22
src/main/java/com/fanruan/sso/service/SSOService.java

@ -0,0 +1,22 @@
package com.fanruan.sso.service;
import com.fanruan.hihidata.bean.sso.SSOSamlBean;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public interface SSOService {
void iss(String registrationId, HttpServletRequest req, HttpServletResponse res) throws Exception;
String acs(String registrationId, HttpServletRequest req, HttpServletResponse res) throws Exception;
void slo(String registrationId, HttpServletRequest req, HttpServletResponse res) throws Exception;
String logout(String registrationId, HttpServletRequest req, HttpServletResponse res) throws Exception;
String generateSpMetadata(String registrationId) throws Exception;
SSOSamlBean getSamlSpConfig(String registrationId) throws Exception;
}

172
src/main/java/com/fanruan/sso/service/impl/SSOServiceImpl.java

@ -0,0 +1,172 @@
package com.fanruan.sso.service.impl;
import com.fanruan.sso.bean.SSOSamlBean;
import com.fanruan.sso.service.SSOService;
import com.fanruan.sso.utils.XMLAnalysisUtils;
import com.fasterxml.jackson.databind.JsonNode;
import com.onelogin.saml2.Auth;
import com.onelogin.saml2.authn.AuthnRequestParams;
import com.onelogin.saml2.logout.LogoutRequestParams;
import com.onelogin.saml2.settings.Saml2Settings;
import com.onelogin.saml2.settings.SettingsBuilder;
import com.onelogin.saml2.util.Util;
import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.web.util.WebUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@Service
public class SSOServiceImpl implements SSOService {
private static final String UPPERCASE_RULER = "uppercase";
private static final String LOWERCASE_RULER = "lowercase";
private static final String RELAY_STATE = "RelayState";
@Override
public String generateSpMetadata(String registrationId) throws Exception {
Saml2Settings settings = getConfig(getSamlSpConfig(registrationId));
settings.setSPValidationOnly(true);
String metadata = settings.getSPMetadata();
List<String> errors = Saml2Settings.validateMetadata(metadata);
if (errors.isEmpty()) {
return metadata;
} else {
StringBuilder errorsBuilder = new StringBuilder();
for (String error : errors) {
errorsBuilder.append(error);
}
return errorsBuilder.toString();
}
}
@Override
public void slo(String registrationId, HttpServletRequest req, HttpServletResponse res) throws Exception {
Saml2Settings settings = getConfig(getSamlSpConfig(registrationId));
Auth auth = new Auth(settings, req, res);
auth.processSLO();
LoginService.getInstance().crossDomainLogout(req, res, "callback").createCrossDomainResponse();
auth.logout();
}
@Override
public String logout(String registrationId, HttpServletRequest req, HttpServletResponse res) throws Exception {
Saml2Settings settings = getConfig(getSamlSpConfig(registrationId));
Auth auth = new Auth(settings, req, res);
return auth.logout(StringUtils.EMPTY, new LogoutRequestParams(), true);
}
@Override
public String acs(String registrationId, HttpServletRequest request, HttpServletResponse response) throws Exception {
Saml2Settings settings = getConfig(getSamlSpConfig(registrationId));
Auth auth = new Auth(settings, request, response);
auth.processResponse();
String lastResponseXML = auth.getLastResponseXML();
FineLoggerFactory.getLogger().info("registrationId is {}, The xml is: {}", registrationId, lastResponseXML);
// 先校验下responese的合法性
// String username = XMLAnalysisUtils.getUserName(lastResponseXML);
String memberName = XMLAnalysisUtils.getMemberName(lastResponseXML);
String corpId = XMLAnalysisUtils.getCorpId(lastResponseXML);
String relayState = WebUtils.getHTTPRequestParameter(request, RELAY_STATE);
if (Objects.isNull(user)) {
// 走注册逻辑
String newUserId = RandomUtils.generateUUIDString();
User newUser = corpCell.initDecisionMember(newUserId, mobile);
return ssoLogin(corpService, request, response, newUser, corpId, relayState, registrationId);
}
return ssoLogin(corpService, request, response, user, corpId, relayState, registrationId);
}
@NotNull
private String ssoLogin(CorpService corpService, HttpServletRequest request, HttpServletResponse response, User newUser, String corpId,
String relayState, String registrationId) throws Exception {
corpService.reLogin(request, response, newUser, corpId);
String serviceUrl = TemplateUtils.renderParameter4Tpl(HiCommonConstants.MAIN_PAGE_HOME, HiServletURLProvider.getServletUrlMap());
String loginUrl = TemplateUtils.renderParameter4Tpl(HiCommonConstants.LOCAL_LOGIN, HiServletURLProvider.getServletUrlMap());
request.getSession().setAttribute(IdApiConstants.SSO_LOGIN, registrationId);
if (StringUtils.isNotEmpty(relayState)) {
response.sendRedirect(serviceUrl);
return StringUtils.EMPTY;
}
response.sendRedirect(loginUrl);
return StringUtils.EMPTY;
}
public Saml2Settings getConfig(SSOSamlBean ssoSamlBean) throws Exception {
Map<String, Object> samlData = new HashMap<>();
String prefix = TemplateUtils.renderParameter4Tpl(HiCommonConstants.MAIN_PAGE_URL, HiServletURLProvider.getServletUrlMap());
String spAcsUrl = OemContext.getFullDomain() + prefix + "/sso/saml/" + ssoSamlBean.getRegistrationId() + "/acs";
String spEntityId = OemContext.getFullDomain() + prefix + "/sso/saml/" + ssoSamlBean.getRegistrationId() + "/iss";
samlData.put("onelogin.saml2.sp.entityid", spEntityId);
samlData.put("onelogin.saml2.sp.assertion_consumer_service.url", spAcsUrl);
//IDP配置
String idpEntityId = ssoSamlBean.getIssuer();
String idpSignInUrl = ssoSamlBean.getSsoEndpoint();
String idpPublicKey = ssoSamlBean.getSignatureCrt();
String idpLogOutUrl = ssoSamlBean.getSloEndpoint();
samlData.put("onelogin.saml2.idp.single_sign_on_service.url", idpSignInUrl);
samlData.put("onelogin.saml2.idp.entityid", idpEntityId);
X509Certificate idpX509CertInstance = Util.loadCert((idpPublicKey).trim());
samlData.put("onelogin.saml2.idp.x509cert", idpX509CertInstance);
String cert = ssoSamlBean.getSpCrt();
X509Certificate spX509CertInstance = Util.loadCert(cert.trim());
samlData.put("onelogin.saml2.sp.x509cert", spX509CertInstance);
String privateKey = ssoSamlBean.getSpPrivateKey();
PrivateKey spPrivateKey = Util.loadPrivateKey(privateKey.trim());
samlData.put("onelogin.saml2.sp.privatekey", spPrivateKey);
samlData.put("onelogin.saml2.security.authnrequest_signed", true);
samlData.put("onelogin.saml2.security.logoutrequest_signed", true);
//签名断言和加密断言的功能都默认开启了,增加安全性
samlData.put("onelogin.saml2.security.want_assertions_signed", true);
samlData.put("onelogin.saml2.security.want_assertions_encrypted", true);
samlData.put("onelogin.saml2.security.want_nameid_encrypted", true);
samlData.put("onelogin.saml2.idp.single_logout_service.url", idpLogOutUrl);
SettingsBuilder builder = new SettingsBuilder();
Saml2Settings settings = builder.fromValues(samlData).build();
return settings;
}
@Override
public SSOSamlBean getSamlSpConfig(String registrationId) throws Exception {
JsonNode jsonNode = oemService.find(registrationId);
String spCrt = OemContext.getValue(jsonNode, OemContext.CRT_TEXT);
String spKey = OemContext.getValue(jsonNode, OemContext.KEY_TEXT);
JsonNode ssoConfig = jsonNode.get(OemContext.SSO);
String ssoEndpoint = OemContext.getValue(ssoConfig, OemContext.SSO_ENDPOINT);
String signatureAlg = OemContext.getValue(ssoConfig, OemContext.SSO_SIG_ALG);
String signatureCrt = OemContext.getValue(ssoConfig, OemContext.SSO_SIG_CRT);
String issuer = OemContext.getValue(ssoConfig, OemContext.SSO_ISSUER);
String sloEndpoint = OemContext.getValue(ssoConfig, OemContext.SLO_ENDPOINT);
boolean turnOn = ssoConfig.get("turnOn").asBoolean();
return new SSOSamlBean(ssoEndpoint, signatureAlg, signatureCrt, issuer, sloEndpoint, registrationId, turnOn, spCrt, spKey);
}
@Override
public void iss(String registrationId, HttpServletRequest request, HttpServletResponse response) throws Exception {
Saml2Settings settings = getConfig(getSamlSpConfig(registrationId));
Auth auth = new Auth(settings, request, response);
// 获取IDP和 重定向内容
String url = auth.login(WebUtils.getOriginalURL(request), new AuthnRequestParams(false, false, false), true);
response.setStatus(302);
response.setHeader("Location", url);
}
}

33
src/main/java/com/fanruan/sso/utils/OpenSAMLUtils.java

@ -0,0 +1,33 @@
package com.fanruan.sso.utils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStream;
public class OpenSAMLUtils {
public static void downloadByStringContent(HttpServletRequest request,
HttpServletResponse response,
String fileName, String content)
throws IOException {
//设置向浏览器端传送的文件格式
response.setContentType("application/octet-stream;charset=utf-8");
response.setCharacterEncoding("utf-8");
Browser browser = Browser.resolve(request);
fileName = browser.getEncodedFileName4Download(fileName);
response.setHeader("Content-disposition", "attachment; filename=" + fileName);
try (OutputStream out = response.getOutputStream(); BufferedInputStream inp = new BufferedInputStream(new ByteArrayInputStream(content.getBytes("utf-8")));) {
int len = 0;
byte[] buf = new byte[1024];
while ((len = inp.read(buf)) > 0) {
out.write(buf, 0, len);
}
} catch (Exception e) {
// 输出log
}
}
}

104
src/main/java/com/fanruan/sso/utils/XMLAnalysisUtils.java

@ -0,0 +1,104 @@
package com.fanruan.sso.utils;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
import java.util.Objects;
public class XMLAnalysisUtils {
private static final String ASSERTION_NODE = "Assertion";
private static final String SUBJECT_NODE = "Subject";
private static final String NAME_ID_NODE = "NameID";
private static final String MEMBER_NAME_NODE = "MemberName";
private static final String MOBILE_NODE = "Mobile";
private static final String CORP_ID_NODE = "CorpId";
private static final String ATTRIBUTE_STATEMENT_NODE = "AttributeStatement";
private static final String ATTRIBUTE_VALUE_NODE = "AttributeValue";
private static final String MATCHING_PARAMETER_NAME = "Name";
private static final String AUTHN_STATEMENT_NODE = "AuthnStatement";
private static final String ATTRIBUTE_SESSION_INDEX = "SessionIndex";
public static final String CACHE_SUFFIX = "_@_";
public static String getMemberName(String xml) throws Exception {
if (StringUtils.isNotEmpty(xml)) {
Document doc = DocumentHelper.parseText(xml);
Element rootElement = doc.getRootElement();
Element assertionElement = rootElement.element(ASSERTION_NODE);
Element statementElement = assertionElement.element(ATTRIBUTE_STATEMENT_NODE);
if (Objects.nonNull(statementElement)) {
List list = statementElement.elements();
for (Object ele : list) {
Element element = (Element) ele;
if (StringUtils.equals(MEMBER_NAME_NODE.toLowerCase(), element.attributeValue(MATCHING_PARAMETER_NAME).toLowerCase())) {
String memberName = element.element(ATTRIBUTE_VALUE_NODE).getText();
FineLoggerFactory.getLogger().info("The parsed member name is[{}]", memberName);
return memberName;
}
}
}
}
return StringUtils.EMPTY;
}
public static String getMobile(String xml) throws Exception {
if (StringUtils.isNotEmpty(xml)) {
Document doc = DocumentHelper.parseText(xml);
Element rootElement = doc.getRootElement();
Element assertionElement = rootElement.element(ASSERTION_NODE);
Element statementElement = assertionElement.element(ATTRIBUTE_STATEMENT_NODE);
if (Objects.nonNull(statementElement)) {
List list = statementElement.elements();
for (Object ele : list) {
Element element = (Element) ele;
if (StringUtils.equals(MOBILE_NODE.toLowerCase(), element.attributeValue(MATCHING_PARAMETER_NAME).toLowerCase())) {
String mobile = element.element(ATTRIBUTE_VALUE_NODE).getText();
FineLoggerFactory.getLogger().info("The parsed mobile is[{}]", mobile);
return mobile;
}
}
}
}
return StringUtils.EMPTY;
}
public static String getCorpId(String xml) throws Exception {
if (StringUtils.isNotEmpty(xml)) {
Document doc = DocumentHelper.parseText(xml);
Element rootElement = doc.getRootElement();
Element assertionElement = rootElement.element(ASSERTION_NODE);
Element statementElement = assertionElement.element(ATTRIBUTE_STATEMENT_NODE);
if (Objects.nonNull(statementElement)) {
List list = statementElement.elements();
for (Object ele : list) {
Element element = (Element) ele;
if (StringUtils.equals(CORP_ID_NODE.toLowerCase(), element.attributeValue(MATCHING_PARAMETER_NAME).toLowerCase())) {
String corpId = element.element(ATTRIBUTE_VALUE_NODE).getText();
FineLoggerFactory.getLogger().info("The parsed corpId is[{}]", corpId);
return corpId;
}
}
}
}
return StringUtils.EMPTY;
}
public static String getUserName(String xml) throws DocumentException {
if (StringUtils.isNotEmpty(xml)) {
Document doc = DocumentHelper.parseText(xml);
Element rootElement = doc.getRootElement();
Element assertionElement = rootElement.element(ASSERTION_NODE);
Element subjectElement = assertionElement.element(SUBJECT_NODE);
Element nameElement = subjectElement.element(NAME_ID_NODE);
String nameID = nameElement.getText();
if (StringUtils.isNotEmpty(nameID)) {
FineLoggerFactory.getLogger().info("The parsed username is[{}]", nameID);
return nameID;
}
}
throw new UnsupportedOperationException("Can not get NameID!");
// return userName;
}
}
Loading…
Cancel
Save