
Masalah otorisasi dan otentikasi dan, secara umum, aspek keamanan informasi semakin sering muncul dalam proses pengembangan aplikasi, dan masing-masing dengan tingkat fanatisme yang berbeda mendekati masalah ini. Mengingat fakta bahwa beberapa tahun terakhir bidang kegiatan saya telah mengembangkan perangkat lunak di sektor keuangan, khususnya, sistem perhitungan risiko, saya tidak bisa melewati ini, terutama mengingat pendidikan yang relevan. Oleh karena itu, dalam kerangka artikel ini saya memutuskan untuk membahas topik ini dan memberi tahu apa yang harus saya hadapi dalam proses mengatur aplikasi kami.
pengantar
. , - - nginx apache, dmz. . , -. - , - , GUI . - , LDAP , .
RADIUS, Kerberos OAuth/OpenID . - , base64, - JsonWebToken, . , , , , , , .
SSL- โ UAT . Java JKS, .
, โ , . , , โ .
, , , .
PKCS 12 , Java, JKS (Java KeyStore). JDK keytool.
, keystore, :
keytool -genkey -alias example.com -keyalg RSA -keystore keystore.jks  -keysize 2048
, JKS Java
keytool- (CSR) Java keystore
 keytool -certreq -alias example.com -keystore keystore.jks -file example.com.csr
 
- CA
 keytool -import -trustcacerts -alias root -file Thawte.crt -keystore keystore.jks
 
 - keytool -import -trustcacerts -alias example.com -file example.com.crt -keystore keystore.jks
 
- keystore
 keytool -genkey -keyalg RSA -alias selfsigned -keystore keystore.jks -storepass password -validity 360 -keysize 2048
 
 - keytool -printcert -v -file example.com.crt
 
- keystore
 keytool -list -v -keystore keystore.jks
 
- keystore
 keytool -list -v -keystore keystore.jks -alias example.com
 
- keystore
 keytool -delete -alias example.com -keystore keystore.jks
 
- keystore
 keytool -storepasswd -new new_storepass -keystore keystore.jks
 
- keystore
 keytool -export -alias example.com -file example.com.crt -keystore keystore.jks
 
 - keytool -list -v -keystore $JAVA_HOME/jre/lib/security/cacerts
 
- trustStore
 keytool -import -trustcacerts -file /path/to/ca/ca.pem -alias CA_ALIAS -keystore $JAVA_HOME/jre/lib/security/cacerts
 
 KeyStore & TrustStore
JKS, , KeyStore TrustStore. , JKS . (KeyStore) , . TrustStore , . , Linux /usr/local/share/ca-certificates/
Java cacerts, java.home\lib\security changeit.
, JDK/JRE , prod/uat .
KeyStore & TrustStore.
SSL NettyServer
, (protobuf) (wss), SSL netty . , , .withSslContext(), .
NettyServer.builder()
        .addHttpListener(
        NetworkInterfaceBuilder.forPort(serviceUri.getPort())
            .withSslContext(sslContextFactory.getSslContext()),
        PathHandler.path()
            .addExactPath(serviceUri.getPath(), createServiceHandler()
    )
    .build();
SSL- โ SslContextBuilder โ forServer forClient. , trustManager keyManager. โ TrustManagerFactory KeyManagerFactory.
 SslContextBuilder.forServer(getKeyManagerFactory())
        .trustManager(getTrustManagerFactory())
        .build();
, trustManager- jks ,
private TrustManagerFactory getTrustManagerFactory() throws Exception {
    final TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    final KeyStore keyStore = KeyStore.getInstance("JKS");
    final InputStream trustStoreFile = getTrustStoreFile();
    keyStore.load(trustStoreFile, trustStorePassword.toCharArray());
    tmFactory.init(keyStore);
    return tmFactory;
}
KeyStore .
private KeyManagerFactory getKeyManagerFactory() throws Exception {
    final KeyManagerFactory kmFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
    final KeyStore keyStore = KeyStore.getInstance("JKS");
    final InputStream keyStoreFile = getKeyStoreFile();
    keyStore.load(keyStoreFile, keyStorePassword.toCharArray());
    kmFactory.init(keyStore, keyPassword.toCharArray());
    return kmFactory;
}
, SSL NettyServer.
SSL gRPC / RSocket
, , gRPC RSocket . Netty , . .
SSL Spring Boot RestController
Java Spring DI. , Spring Boot , . , , . RestController, , . , , Spring Boot c .
server.ssl.key-store-type=JKS
server.ssl.key-store=classpath:cert.jks
server.ssl.key-store-password=changeit
server.ssl.key-alias=key
trust.store=classpath:cert.jks
trust.store.password=changeit
. , , , WebServerFactoryCustomizer, Jetty, Tomcat Undertow.
, lambda c Connector. setSecure(true), ProtocolHandler-, jks c keystore trustStore . , tomcat :
@Bean
WebServerFactoryCustomizer<ConfigurableWebServerFactory> containerCustomizer() throws Exception {
    TomcatConnectorCustomizer customizer;
    String absoluteKeystoreFile = keystoreFile.getAbsolutePath();
    String absoluteTruststoreFile = truststoreFile.getAbsolutePath();
    boolean sslEnabled = checkSslSettings(absoluteKeystoreFile, absoluteTruststoreFile, keystorePass);
    if (sslEnabled) {
        customizer = (connector) -> {
            connector.setPort(port);
            connector.setSecure(true);
            connector.setScheme("https");
            Http11NioProtocol proto = (Http11NioProtocol) connector.getProtocolHandler();
            proto.setSSLEnabled(true);
            proto.setClientAuth(clientAuth);
            proto.setKeystoreFile(absoluteKeystoreFile);
            proto.setKeystorePass(keystorePass);
            proto.setTruststoreFile(absoluteTruststoreFile);
            proto.setTruststorePass(truststorePass);
            proto.setKeystoreType(keyStoreType);
            proto.setKeyAlias(keyAlias);
            proto.setCiphers(chiphers);
        };
    } else {
        customizer = (connector) -> {
            connector.setPort(port);
            connector.setSecure(false);
            if (sslEnabled) {
                log.error("Key- or Trust-store file โ {} or {} โ is not found, or keystore password is missing, REST service on port {} is disabled", absoluteKeystoreFile, absoluteTruststoreFile, port);
                ((Http11NioProtocol) connector.getProtocolHandler()).setMaxConnections(0);
            }
        };
    }
    return (ConfigurableWebServerFactory factory) -> {
        ConfigurableTomcatWebServerFactory tomcatWebServerFactory = (ConfigurableTomcatWebServerFactory) factory;
        tomcatWebServerFactory.addConnectorCustomizers(customizer);
    };
ยซยป - , .
TLS/SSL
keystore , java.security.* , .
jks KeyStore KeyPair, X509Certificate, , .
bouncyastle, Java, Java Cryptography Architecture (JCA) Java Cryptography Extension (JCE).
kotlin Java github (https://github.com/bcgit/bc-java https://github.com/bcgit/bc-kotlin).
keyStore :
KeyStore generateKeyStore(String password) { 
    X509Certificate2 ca = X509Certificate2Builder()
        .setSubject("CN=CA")
        .build()
     X509Certificate2 int = X509Certificate2Builder()
        .setIssuer(ca)
        .setSubject("CN=Intermediate")
        .build()
    X509Certificate2 cert = X509Certificate2Builder()
        .setIssuer(int)
        .setSubject("CN=Child")
        .setIntermediate(false)
        .build()
    return KeyStoreBuilder()
        .addTrustedCertificate("test-ca", ca)
        .addPrivateKey("test-pk", cert.keyPair, password, asList(cert, int, ca))
        .build()
}
, , CA, cert, , ( ), KeyPair keyPair X509Certificate2, X509Certificate.
, .
, โ X509Certificate2Builder KeyStoreBuilder. java java.security.KeyStore.Builder, โ newInstance, - . .
setEntry KeyStore , Entry (TrustedCertificateEntry PrivateKeyEntry).
KeyStore#setEntry setEntry(String alias, Entry entry, ProtectionParameter protParam) 3 , item KeyStoreBuilder#build() :
public KeyStore build() {
    try {
        KeyStore keyStore = KeyStore.getInstance("JKS", "SUN");
        keyStore.load(null, null);
        for (Item it : entries.values()) {
            keyStore.setEntry(it.alias, it.entry, it.parameter);
        }
        return keyStore;
    } catch (IOException | GeneralSecurityException e) {
        throw new RuntimeException(e.getMessage(), e);
    }
entries KeyStore#setEntry, , setEntry addPrivateKey
public KeyStoreBuilder addPrivateKey(String alias, KeyPair pair, String password, List<X509Certificate> chain) {
    addEntry(alias,
             new KeyStore.PrivateKeyEntry(pair.getPrivate(), chain),
             new KeyStore.PasswordProtection(password.toCharArray()));
    return this;
 }
addTrustedCertificate,
public KeyStoreBuilder addTrustedCertificate(String alias, X509Certificate cert) {
    addEntry(alias, new KeyStore.TrustedCertificateEntry(cert), null);
    return this;
}
keyStore.
C X509 , build(). , , build() X509Certificate2, , , :
public X509Certificate2 build() {
    if (Security.getProvider("BC") == null) {
        Security.addProvider(new BouncyCastleProvider());
    }
    final X500Name subject = new X500Name(this.subject);
    final KeyPair subjectKeyPair = newKeyPair(subjectKeyStrength);
    final boolean selfSigned = this.issuer == null;
    final X500Name issuer = selfSigned ? subject : new X500Name(this.issuer.getSubjectDN().getName());
    final KeyPair issuerKeyPair = selfSigned ? subjectKeyPair : this.issuer.getKeyPair();
    
    final Date notBefore = new Date();
    final Date notAfter = new Date(notBefore.getTime() + 20L * 365 * 24 * 60 * 60 * 1000);
    final BigInteger serialNumber = BigInteger.valueOf(SERIALS.incrementAndGet());
    final SubjectPublicKeyInfo subjectKeyInfo = SubjectPublicKeyInfo.getInstance(subjectKeyPair.getPublic().getEncoded());
    final X509v3CertificateBuilder builder = new X509v3CertificateBuilder(
        issuer, serialNumber, notBefore, notAfter, subject, subjectKeyInfo);
    
    final AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA256withRSA");
    final AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
    try {
        final BcX509ExtensionUtils extensionUtils = new BcX509ExtensionUtils();
        builder.addExtension(
            new ASN1ObjectIdentifier("2.5.29.14"), 
            false,
            extensionUtils.createSubjectKeyIdentifier(
                SubjectPublicKeyInfo.getInstance(subjectKeyPair.getPublic().getEncoded()))
        );
        builder.addExtension(
            new ASN1ObjectIdentifier("2.5.29.35"), 
            false,
            extensionUtils.createAuthorityKeyIdentifier(
                SubjectPublicKeyInfo.getInstance(issuerKeyPair.getPublic().getEncoded()))
        );
        builder.addExtension(
            new ASN1ObjectIdentifier("2.5.29.19"), 
            false,
            new BasicConstraints(intermediate)); 
        AsymmetricKeyParameter privateKey = PrivateKeyFactory.createKey(issuerKeyPair.getPrivate().getEncoded());
        ContentSigner signer = new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
        .build(privateKey);
        X509Certificate cert = new JcaX509CertificateConverter()
            .setProvider("BC")
            .getCertificate(builder.build(signer));
        return new X509Certificate2(cert, subjectKeyPair);
    } catch (IOException | OperatorCreationException | CertificateException e) {
        throw new RuntimeException(e.getMessage(), e);
    }
}
private static KeyPair newKeyPair(int subjectKeyStrength) {
    try {
        if (subjectKeyStrength <= 0) {
            subjectKeyStrength = DEFAULT_KEY_STRENGTH; 
        }
        
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA", "BC");
        keyPairGen.initialize(subjectKeyStrength, SecureRandom.getInstance("SHA1PRNG"));
        return keyPairGen.generateKeyPair();
    } catch (GeneralSecurityException e) {
        throw new RuntimeException(e.getMessage(), e);
    }
}
bouncycastle.
bouncycastle, .
java.security.KeyPairGenerator, -. RSA c SHA1PRNG.
, , , .
, , . , .
, , .
, , .
, , Spring boot , , Spring Initializr
@SpringBootApplication
@RestController
public class Server {
    static String certFile = System.getProperty("user.home") + "/cert.jks";
    static String defaultPassword = "changeit";
    @GetMapping("/hello")
    public String hello() {
        System.out.println("request /hello");
        return "hello";
    }
    @Bean
    WebServerFactoryCustomizer<ConfigurableWebServerFactory> containerCustomizer() {
        TomcatConnectorCustomizer customizer = (connector) -> {
            connector.setPort(8080);
            connector.setSecure(true);
            connector.setScheme("https");
            Http11NioProtocol proto = (Http11NioProtocol) connector.getProtocolHandler();
            proto.setSSLEnabled(true);
            proto.setClientAuth("true");
            proto.setKeystoreFile(certFile);
            proto.setKeystorePass(defaultPassword);
            proto.setTruststoreFile(certFile);
            proto.setTruststorePass(defaultPassword);
            proto.setKeystoreType("JKS");
        };
        return (ConfigurableWebServerFactory factory) -> {
            ConfigurableTomcatWebServerFactory tomcatWebServerFactory = (ConfigurableTomcatWebServerFactory) factory;
            tomcatWebServerFactory.addConnectorCustomizers(customizer);
        };
    }
    public static void main(String[] args) {
        SpringApplication.run(Server.class, args);
    }
}
class Client {
    RestTemplate restTemplate() throws Exception {
        SSLContext sslContext = new SSLContextBuilder()
                .loadTrustMaterial(new URL(Server.certFile), Server.defaultPassword.toCharArray())
                .build();
        SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext);
        HttpClient httpClient = HttpClients.custom()
                .setSSLSocketFactory(socketFactory)
                .build();
        HttpComponentsClientHttpRequestFactory factory =
                new HttpComponentsClientHttpRequestFactory(httpClient);
        return new RestTemplate(factory);
    }
    public static void main(String[] args) {
        String response = new RestTemplate().getForObject("https://localhost:8080/hello", String.class);
        System.out.println("received " + response);
    }
}
cert.jks , , .