2

So I am just moving from the InMemoryTokenStore to the JdbcTokenStore. As usual, one seemingly simple change is followed by a handful of side effects including swallowed exceptions - sorry for the rant.

This is how I used to access the users' principal:

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = (String) authentication.getPrincipal();

First

For some reason, getPrincipal() always returned just the username instead of the UserDetails object. This was good enough for me so I went with it.

Now that I changed the token store, getPrincipal() does indeed return the UserDetails object. I can live with that but I would like to know why this changed all of a sudden - I have to refactor some code because of that since I always expected the username from getPrincipal() up to now. I also would like to know whether I can change that.

Second

From what I can see, the JdbcTokenStore seems to serialize Java objects. It tries to serialize a Token object and a UserDetails object. The columns token and authentication seem to represent those serialized objects and I would like to understand why this is actually necessary. After all this is information which could be recoverd during startup/runtime from the database. Except the Token of course but I don't get why they don't just store the token (the String) but instead the object.

Most of all: The slightest change to any of those classes and they won't be deserializable! Every user will be forced to a new login if one of those classes gets changed which kind of defies the reason why I want to use the JdbcTokenStore in the first place - so there must be something fishy or I am not getting it.

enter image description here


Maybe somebody could shed more light on this.

Stefan Falk
  • 23,898
  • 50
  • 191
  • 378

1 Answers1

0

Looking at your rant and then looking at your code, I was a little bit surprised too! What it's doing under the covers is actually serializing the objects (like you suggest!) into a very unfriendly looking object string:

protected byte[] serializeAccessToken(OAuth2AccessToken token) {
    return SerializationUtils.serialize(token);
}

It looks like what one should do (and I'm not sure why they don't point this out in the documentation), is to override the *serialize methods in the JdbcTokenStore:

protected OAuth2AccessToken deserializeAccessToken(byte[] token) 
protected OAuth2Authentication  deserializeAuthentication(byte[] authentication) 
protected OAuth2RefreshToken    deserializeRefreshToken(byte[] token) 
protected byte[] serializeAccessToken(OAuth2AccessToken token) 
protected byte[] serializeAuthentication(OAuth2Authentication authentication) 
protected byte[] serializeRefreshToken(OAuth2RefreshToken token)

Which is probably why they're all protected. The implementation would probably look like:

class JacksonJdbcTokenStore extends JdbcTokenStore {

    private ObjectMapper mapper;

    public JdbcTokenStore(ObjectMapper mapper, DataSource dataSource) {
        this.mapper = mapper;
        super(dataSource);
    }

    protected byte[] serializeAuthentication(OAuth2Authentication authentication) 
    {
        return mapper.writeValueAsBytes(authentication);
    }

    protected OAuth2Authentication deserializeAuthentication(byte[] authentication) {
        return mapper.read(authentication, OAuth2Authentication.class);
    }

    // Same type of thing for the other serialize/deserialize operations...

}

I haven't actually tried or tested the above code, and you might have to fiddle around with the serialization rules, but that's all I can tell you.

I can commiserate with you about the strangeness about this strange default behavior, but I don't see why you'd be doing something wrong here. That's the best I can say!

FYI, for the first issue, I'm not surprised that these different implementations might return different Principal's, and that is normal for Authentication implementations and that's just the way Spring's done it.

Dovmo
  • 8,121
  • 3
  • 30
  • 44
  • Hmm.. overwriting the default implementation is not something I'd want to do but it's a good idea. However, I think this might complicate a lot of things because when I deserialize (the username), I'll have to create an `OAuth2Authentication` object from this which, I guess, will require additional code. I'm not sure if this can be done just like that but I will try it. – Stefan Falk Sep 08 '18 at 16:42
  • Ah, and I will have to change the behavior too.. `getPrincipal()` will have to return just the username as well.. – Stefan Falk Sep 08 '18 at 16:44
  • Okay I might see the difficulty here and maybe also the reason why the `OAuth2Authentication` gets serialized for the `authentication` column of the `oauth_access_token` table. This object also holds a `OAuth2Request` object which seems to carry some essential information about the authentication of the user. It seems like this is actually the main reason why they're doing that I would believe. I have to say this is very weird - mainly because of the question "What happens if I change my UserDetails class?". So I think to fix all this I think it would be possible to .. – Stefan Falk Sep 08 '18 at 17:01
  • just let `getPrincipal()` return just the username. That way `OAuth2Authentication` does not hold e.g. `MyUserDetailsImpl` but instead just a `String`. Now things will only break if *they* make a change to *anything* inside the `OAuth2Authentication` class (I guess). ^^ – Stefan Falk Sep 08 '18 at 17:02
  • I wouldn't necessarily worry yet about their changes. If they're doing it the way they are, they'll likely factor in backwards compatibility. If you're worried they won't do that (possible!), you could use this serialization solution and version it with the Spring version and use that as a hint when you do the deserialization – Dovmo Sep 09 '18 at 01:45