Optimierung der SAML2-Integration und Verbesserung der Zertifikats- und Fehlerbehandlung (#2105)

* certificate processing

* Hides dialog when provider list is empty

* removed: unused
This commit is contained in:
Ludy 2024-10-27 23:17:36 +01:00 committed by GitHub
parent 1b88d89191
commit d2046c64d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 38 additions and 53 deletions

View File

@ -1,48 +1,42 @@
package stirling.software.SPDF.config.security.saml2; package stirling.software.SPDF.config.security.saml2;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.KeyFactory; import java.security.KeyFactory;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPrivateKey;
import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.util.FileCopyUtils;
public class CertificateUtils { public class CertificateUtils {
public static X509Certificate readCertificate(Resource certificateResource) throws Exception { public static X509Certificate readCertificate(Resource certificateResource) throws Exception {
String certificateString = try (PemReader pemReader =
new String( new PemReader(
FileCopyUtils.copyToByteArray(certificateResource.getInputStream()), new InputStreamReader(
StandardCharsets.UTF_8); certificateResource.getInputStream(), StandardCharsets.UTF_8))) {
String certContent = PemObject pemObject = pemReader.readPemObject();
certificateString byte[] decodedCert = pemObject.getContent();
.replace("-----BEGIN CERTIFICATE-----", "") CertificateFactory cf = CertificateFactory.getInstance("X.509");
.replace("-----END CERTIFICATE-----", "") return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(decodedCert));
.replaceAll("\\R", "") }
.replaceAll("\\s+", "");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
byte[] decodedCert = Base64.getDecoder().decode(certContent);
return (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(decodedCert));
} }
public static RSAPrivateKey readPrivateKey(Resource privateKeyResource) throws Exception { public static RSAPrivateKey readPrivateKey(Resource privateKeyResource) throws Exception {
String privateKeyString = try (PemReader pemReader =
new String( new PemReader(
FileCopyUtils.copyToByteArray(privateKeyResource.getInputStream()), new InputStreamReader(
StandardCharsets.UTF_8); privateKeyResource.getInputStream(), StandardCharsets.UTF_8))) {
String privateKeyContent = PemObject pemObject = pemReader.readPemObject();
privateKeyString byte[] decodedKey = pemObject.getContent();
.replace("-----BEGIN PRIVATE KEY-----", "") return (RSAPrivateKey)
.replace("-----END PRIVATE KEY-----", "") KeyFactory.getInstance("RSA")
.replaceAll("\\R", "") .generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
.replaceAll("\\s+", ""); }
KeyFactory kf = KeyFactory.getInstance("RSA");
byte[] decodedKey = Base64.getDecoder().decode(privateKeyContent);
return (RSAPrivateKey) kf.generatePrivate(new PKCS8EncodedKeySpec(decodedKey));
} }
} }

View File

@ -89,10 +89,9 @@ public class AccountWebController {
} }
SAML2 saml2 = securityProps.getSaml2(); SAML2 saml2 = securityProps.getSaml2();
if (saml2 != null) { if (securityProps.isSaml2Activ()
if (saml2.getEnabled()) { && applicationProperties.getSystem().getEnableAlphaFunctionality()) {
providerList.put("/saml2/authenticate/" + saml2.getRegistrationId(), "SAML 2"); providerList.put("/saml2/authenticate/" + saml2.getRegistrationId(), "SAML 2");
}
} }
// Remove any null keys/values from the providerList // Remove any null keys/values from the providerList
providerList providerList
@ -101,7 +100,8 @@ public class AccountWebController {
model.addAttribute("providerlist", providerList); model.addAttribute("providerlist", providerList);
model.addAttribute("loginMethod", securityProps.getLoginMethod()); model.addAttribute("loginMethod", securityProps.getLoginMethod());
model.addAttribute("altLogin", securityProps.isAltLogin()); boolean altLogin = providerList.size() > 0 ? securityProps.isAltLogin() : false;
model.addAttribute("altLogin", altLogin);
model.addAttribute("currentPage", "login"); model.addAttribute("currentPage", "login");
@ -164,6 +164,17 @@ public class AccountWebController {
case "userIsDisabled": case "userIsDisabled":
erroroauth = "login.userIsDisabled"; erroroauth = "login.userIsDisabled";
break; break;
case "invalid_destination":
erroroauth = "login.invalid_destination";
break;
// Valid InResponseTo was not available from the validation context, unable to
// evaluate
case "invalid_in_response_to":
erroroauth = "login.invalid_in_response_to";
break;
case "not_authentication_provider_found":
erroroauth = "login.not_authentication_provider_found";
break;
default: default:
break; break;
} }

View File

@ -283,25 +283,5 @@
</script> </script>
<th:block th:insert="~{fragments/footer.html :: footer}"></th:block> <th:block th:insert="~{fragments/footer.html :: footer}"></th:block>
</div> </div>
<div th:if="${altLogin}" class="modal fade" id="editUserModal" tabindex="-1" role="dialog" aria-labelledby="editUserModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editUserModalLabel" th:text="#{login.ssoSignIn}"></h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close">
<span class="material-symbols-rounded">close</span>
</button>
</div>
<div class="modal-body">
<div class="mb-3" th:each="provider : ${providerlist}">
<a th:href="@{|/oauth2/authorization/${provider.key}|}" th:text="${provider.value}" class="w-100 btn btn-lg btn-primary">OpenID Connect</a>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" th:text="#{close}"></button>
</div>
</div>
</div>
</div>
</body> </body>
</html> </html>