Neue Passwort-Speicherung in Spring Security 5

Was hat sich geändert?

Wer bislang zur Speicherung der Passwörter in Spring Security eine Digest-basierte Speicherung nutzte (z.B. ShaPasswordEncoder oder MessageDigestPasswordEncoder), wird bei der Migration auf Spring Security 5 über das neue Speicherformat für Passwörter stolpern:

{id}encodedPassword

Die id gibt dabei den zu verwendenden PasswordEncoder an. Beispielsweise ist {bcrypt} mit dem BCryptPasswordEncoder kodiert und {noop} wäre ein Passwort im Klartext.

Eine schöne historische Übersicht und die Begründung warum die SHA basierten Kodierungen inzwischen unsicher sind, bietet die Dokumentation.

Digest-basierte Verfahren sind unsicher

Bislang galt die "Verschlüsselung" der Benutzer-Passwörter mit SHA-256 und einem zufälligen "Salt" als der Stand der Dinge. Der SHA Algorithmus soll schnell einen Hash ohne Kollision erstellen. Wegen der schnellen Operation ermöglicht dies aber auch Brute-Force-Attacken. Ein Algorithmus der diese Operation aufwändiger macht, ist daher zur Speicherung von Passwörtern besser geeignet. bcrypt wurde zu diesem Zweck entwickelt.

Migration SHA-256

Wer bislang den ShaPasswordEncoder nutzte, braucht bei der Migration lediglich ein Prefix vor das gespeicherte Passwort setzen, und den verwendenten Hash-Algorithmus angeben:

UPDATE "user" SET password = CONCAT('{SHA-256}', password);

Der DelegatingPasswordEncoder nutzt dieses Prefix und leitet das kodierte Passwort an den MessageDigestEncoder weiter.

Migration SHA-256 mit Salt

Eine übliche Kombination ist die Kodierung mit dem ShaPasswordEncoder und ein Salt, der mit dem Benutzer in der Datenbank gespeichert wird. Auf diesen greift der Encoder über die ReflectionSaltSource zu. Der neue MessageDigestPasswordEncoder ist in der Lage den Salt zu nutzen.

Dazu muss der Salt in geschweiften Klammern vor das Passwort gesetzt werden:

String s = salt == null ? null : "{" + salt + "}";
String migratedPassword = s + user.getPassword();

Damit wird das Passwort gemeinsam mit dem Salt nun im Passwort-Feld des Benutzers gespeichert. Um jetzt zusätzlich den eingesetzten Hash-Algorithmus austauschbar zu halten, muss auch noch die Information über den eingesetzten Hash-Algorithmus mit in dem Attribut gespeichert werden.

Beispiel:

String password = "thePassword";
String salt = "theSalt";
String migratedPassword = "{SHA-256}{theSalt}thePassword";

Eine Datenbankmigration könnte so aussehen. Die Spalten in der Tabelle user sollen password bzw. salt sein.

UPDATE "user" SET password = CONCAT('{SHA-256}{', salt, '}', password);

Der DelegatingPasswordEncoder wertet {SHA-256} aus und delegiert an den MessageDigestPasswordEncoder("SHA-256") weiter. Dieser wertet nun den Salt in den geschweiften Klammern aus und kann so das kodierte Passwort dekodieren.

Weitere Informationen

Lesenswerte JavaDocs

Quellen

Maven copy artifact to different directory than the local repository

The maven-jar-plugin creates the artifacts jar into ${project.build.directory} (by default target/). The maven-install-plugin copies this file to the local Maven repository (by default %user.home%/.m2/repository).

Sometimes it is wanted to additionally copy this artifact into another location. For example to deploy the artifact to a server. Two possibilities come to my mind to solve this problem:

For example, one could copy all *.jar files in the target directory like this:

<plugin>
  <groupId>com.github.goldin</groupId>
  <artifactId>copy-maven-plugin</artifactId>
  <version>0.2.5</version>
  <executions>
    <execution>
      <id>deploy-to-local-directory</id>
      <phase>install</phase>
      <goals>
        <goal>copy</goal>
      </goals>
      <configuration>
        <skipIdentical>false</skipIdentical>
        <failIfNotFound>false</failIfNotFound>
        <resources>
          <resource>
            <description>Copy artifact to another directory</description>
            <targetPath>your/local/path</targetPath>
            <directory>${project.build.directory}</directory>
            <includes>
              <include>*.jar</include>
            </includes>
          </resource>
        </resources>
      </configuration>
    </execution>
  </executions>
</plugin>

Suggested from me at stackoverflow.

Test-driven Configuration Management with Maven

As an agile software developer practicing test-driven development (TDD) it is normal to write the tests first. Unfortunately, switching roles and wearing the "configuration manager hat" it is sometimes hard to adhere to the same rules of TDD because there are tools missing which provides test infrastructure for build management. One solution might be to use the build server for such tasks.

This is an example of Test-driven dependency mangement with Maven:

We can use the versions-maven-plugin to list all available updates of plugins used in the pom.xml.

For example Maven might produce this information on an updated version of the release plugin:

mvn versions:display-property-updates
...
[08:01:04][de.timomeinen.maven:maven-parent] 
[INFO] The following version property updates are available:
[08:01:04][de.timomeinen.maven:maven-parent] 
[INFO] ${version.plugin.release}......... 2.4.1 -> 2.4.2

If we trigger a nightly build on the CI server and let the build fail on an update message, we have a nice test if there are any updates available for our Maven project.

This example uses TeamCity, but you can surely adopt it to other CI server as well.

Configure a new Maven Build Step:
teamcity-setup

And define a Build Failure Condition (Step 4):
teamcity-setup-build-failure-condition

Fail build if build log contains exact text "The following version property updates are available" with the Failure message "Updates available".

The result looks like this:
teamcity-01

This will let the build fail on a dependency update, gives you a nice message and TeamCity even produces a link to the part of the build log, where you can see which dependencies you can update.

That's a nice example of what I call "Test-driven System Administration (TDSA)".

Test for private constructor to get full code coverage

Imagine we have a simple helper class which only consists of public static methods.

public class Helper {
  public static void doSomething() {}
}

And the test:

import org.junit.Test;

public class HelperTest {
 @Test
 public void testDoSomething() {
   Helper.doSomething();
 }
}

Our code coverage tool won't show us a 100% coverage, because the (default) constructor is not invoked. For example Intellij IDEA only shows 50%.

One solution would be to simply invoke the default constructor with new Helper.doSomething(). But we don't want to have objects of this class and therefore override the default constructor with a private one:

public class Helper {
    private Helper() {}
    public static void doSomething() {}
}

The problem is, that now even the test cannot instantiate the class. And as we are testdriven, the question is: Should the decision to create a class without any objects be motivated by tests, too? In my opinion yes. I don't want some other programmer to remove the private constructor. At least a test shall break.

We reach this by the following test:

import static org.junit.Assert.assertTrue;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import org.junit.Test;

public class HelperTest {

    @Test
    public void testDoSomething() throws Exception {
        Helper.doSomething();
    }

    @Test
    public void testPrivateConstructor() throws Exception {
        Constructor constructor = Helper.class.getDeclaredConstructor();
        assertTrue("Constructor is not private", Modifier.isPrivate(constructor.getModifiers()));

        constructor.setAccessible(true);
        constructor.newInstance();
    }
}

Now we have a guard to the design decision of a private constructor and our tests are back on 100% coverage again.

List all properties used by Maven

List all properties (System properties, environment variables, properties introduced by plugins like ) used by Maven:

<plugin>
    <artifactId>maven-antrun-plugin</artifactId>
    <version>1.7</version>
    <executions>
        <execution>
            <phase>validate</phase>
            <configuration>
                <target>
                    <echoproperties />
                </target>
            </configuration>
            <goals>
                <goal>run</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Use JRebel to develop TeamCity plugins with more speed and fun

Regarding the TeamCity Documentation a plugin developer has to restart the server, to see code changes. JetBrains recommends to use Javas HotSwap. But as we all know (at least the JRebel fans), HotSwap has a lot of limitations. For example, it is not possible to change method parameters or introduce new methods.

JRebel is a great alternative and doesn't have these limitations. And as it turned out, it is very simple to use JRebel with TeamCity plugin development: Just create the rebel.xml and put in the plugins resource path. The file has to reside on the top most hierarchy of the jar.

To generate the rebel.xml, just use the IDE plugins. In IDEA for example, I right-clicked on the project name and select generate rebel.xml and then I stored the generated file in the resources-directory.

Now, I can develop the Piazza plugin without restarting the TeamCity over and over again. Thank you, ZeroTurnaround!

O2 Prepaid Surfstick unter Mac OS X installieren

Nachdem Alice von der Telefónica gekauft wurde, gab es für uns treue Alice-Kunden einen O2-Surfstick zur Begrüßung, da anscheinend auch O2 von dem spanischen Telekommunikationsriesen gekauft wurde. Eigentlich sollte der Surfstick gratis sein, aber die Nachfrage war sehr groß und sah gab es bereits am Abend keine kostenlosen Sticks mehr.

Inzwischen hat man sich bei O2 überlegt, dass die Leute mit Alice-Surfstick-Gutschein den Stick wenigstens günstiger erhalten sollen, nämlich für 20 € statt 40 €. Klingt fair, da auch noch 5 Tagesflatrate dabei sind. Also habe ich mir dieses Angebot Heute gekauft.

Anekdote 1:
Der Surfstick kostet normalerweise 39,99 €. Der Alice-Gutschein gibt 20 € Rabatt - macht: 19,99 €. Das Problem war, dass in der Kasse des O2 Shops der rabattierte Preis mit 19,90 € eingetragen war. Bei der Eingabe der Coupon-Nummer zeigte die Anwendung nur eine Fehlermeldung: Der Rabatt für diesen Rabattcoupon darf 20 € nicht überschreiten. Tjaaa, der Surfstick war einfach 9 Cent zu günstig. Ende vom Lied: Der freundliche O2-Verkäufer sprach mit seinem Teamleiter, der auch keine Lösung wußte. Nun buchen die beiden das am Montag nach, da Heute (Samstag) auch niemand mehr im Servicecenter sitzt. Egal - ich hab den Stick bekommen.

Ab nach Hause, den Mobile Partner installiert und gestartet. PIN eingetippt, Guthaben-Code eingetippt, Verbinden gedrückt - und? Nix passiert. Ich mach es kurz: Der O2 Support war zwar sehr freundlich und sehr schnell, hatte allerdings keine Ahnung von Macs. Die Infos, die ich bekam brachten mich aber zum Problem und nun kann ich stolz diesen Blog-Eintrag über UMTS via O2-Surfstick schreiben:

  • Mobile Partner vom Stick installieren
  • Systemeinstellungen -> Netzwerk
  • Hier bekommt man eine Informationsmeldung, dass ein neues Modem erkannt wurde: 'Found modem Huawei'. Tipp-Topp, da dies der Hersteller des Sticks ist
  • Auf das neue Modem klicken
  • Telefonnummer: *99# eingeben
  • Benutzer und Passwort leer lassen
  • Jetzt kommt der wichtige Teil: Weitere Optionen klicken
  • Hersteller: Andere
  • Modell: HUAWEI Mobile Connect auswählen (hier ist nämlich standardmäßig ein Apple-Modem ausgewählt)
  • Fehlerkorrektur und Komprimierung im Modem aktivieren deaktivieren

Falls das noch nicht funktioniert, muss als Passwort null eingegeben werden.

Netzwerkeinstellungen HUAWEI Modem

Modemauswahl

Nach diesem Hick-Hack funktionierte mein Stick und der riesengroße Vorteil ist, dass man jetzt über die Modem-Funktion von Mac OS X die An- und Abwahl machen kann.

Hier mal die Geschwindigkeit:
speed

Zum Schluss noch Anekdote 2:
Nachdem meine ganzen Verbindungsversuche scheiterten, bekam ich eine O2 SMS, dass meine Tagesflatrate nicht aktiviert werden konnte und ich deshalb eine 3,50 € Gutschrift erhalten solle. Keine 3 Minuten später bekam ich noch eine SMS von O2 mit der Warnung, dass mein Guthaben auf unter 4 € abgesunken sei! Irgendwie hat es O2 mit Cent-Beträgen und Warnmeldungen.

Gemischtes Encoding einer Datei reparieren

Gestern habe ich eine Stunde lang verzweifelt versucht eine SQL Datei in die Datenbank einzufahren, die verschiedene Zeichensätze in der Datei hatte.

Mit file lässt sich die Kodierung einer Datei überprüfen, allerdings liefert sie in meinem Fall nur:

hdl.sql: Non-ISO extended-ASCII English text, with very long lines

Non-ISO ist nicht das, was man lesen möchte, wenn man Datenbanken importiert. Zum Glück habe ich hier ein schönes Shell-Skript gefunden, was die Datei 1a nach UTF-8 kodiert. Dazu wird eine Mischung aus recode und sed verwendet.

Im Anschluss liefert file nun den korrekten Wert:

hdl-utf8.sql: UTF-8 Unicode English text, with very long lines

Vielen Dank dafür.