Spring Boot OAuth2 with encrypted JWT access token
Asked Answered
S

1

8

In my Spring Bott application I have configured own OAuth2 with Authorization/Resource servers.

I have implemented following JwtAccessTokenConverter:

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    JwtAccessTokenConverter converter = new JwtAccessTokenConverter() {

        @Override
        public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
            DBUserDetails user = (DBUserDetails) authentication.getUserAuthentication().getPrincipal();
            final Map<String, Object> additionalInfo = new HashMap<>();
            additionalInfo.put("user_id", user.getUser().getId());
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication);
            return enhancedToken;
        }

    };

    converter.setSigningKey("123");

    DefaultAccessTokenConverter accessTokenConverter = new DefaultAccessTokenConverter();
    DefaultUserAuthenticationConverter userTokenConverter = new DefaultUserAuthenticationConverter();
    userTokenConverter.setUserDetailsService(userDetailsService);
    accessTokenConverter.setUserTokenConverter(userTokenConverter);

    converter.setAccessTokenConverter(accessTokenConverter);

    return converter;
}

Right now my application produces following tokens, for example:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxNzgzNTEsInVzZXJfbmFtZSI6ImFkbWluIiwic2NvcGUiOlsicmVhZCIsIndyaXRlIl0sImV4cCI6MTQ3NTA5NDA4NSwiYXV0aG9yaXRpZXMiOlsiUEVSTUlTU0lPTl9ERUxFVEVfT1dOX0NSSVRFUklPTiIsIlBFUk1JU1NJT05fREVMRVRFX09XTl9DT01NRU5UIiwiUEVSTUlTU0lPTl9VUERBVEVfT1dOX0NSSVRFUklPTiIsIlBFUk1JU1NJT05fVVBEQVRFX0FOWV9DUklURVJJT04iLCJQRVJNSVNTSU9OX0RFTEVURV9PV05fQ1JJVEVSSU9OX0dST1VQIiwiUEVSTUlTU0lPTl9ERUxFVEVfQU5ZX0RFQ0lTSU9OIiwiUEVSTUlTU0lPTl9DUkVBVEVfVk9URSIsIlBFUk1JU1NJT05fREVMRVRFX0FOWV9DUklURVJJT04iLCJQRVJNSVNTSU9OX0NSRUFURV9DUklURVJJT05fR1JPVVAiLCJQRVJNSVNTSU9OX0RFTEVURV9PV05fREVDSVNJT04iLCJQRVJNSVNTSU9OX0NSRUFURV9ERUNJU0lPTiIsIlBFUk1JU1NJT05fREVMRVRFX0FOWV9DUklURVJJT05fR1JPVVAiLCJQRVJNSVNTSU9OX0RFTEVURV9BTllfVk9URSIsIlBFUk1JU1NJT05fREVMRVRFX09XTl9WT1RFIiwiUEVSTUlTU0lPTl9VUERBVEVfT1dOX0NSSVRFUklPTl9HUk9VUCIsIlBFUk1JU1NJT05fVVBEQVRFX0FOWV9DUklURVJJT05fR1JPVVAiLCJQRVJNSVNTSU9OX1VQREFURV9BTllfQ09NTUVOVCIsIlBFUk1JU1NJT05fVVBEQVRFX09XTl9DT01NRU5UIiwiUEVSTUlTU0lPTl9VUERBVEVfQU5ZX0RFQ0lTSU9OIiwiUEVSTUlTU0lPTl9BUFBFTkRfREVDSVNJT04iLCJQRVJNSVNTSU9OX1VQREFURV9PV05fVk9URSIsIlBFUk1JU1NJT05fVVBEQVRFX0FOWV9WT1RFIiwiUEVSTUlTU0lPTl9ERUxFVEVfQU5ZX0NPTU1FTlQiLCJQRVJNSVNTSU9OX1VQREFURV9PV05fREVDSVNJT04iLCJQRVJNSVNTSU9OX0NSRUFURV9DT01NRU5UIiwiUEVSTUlTU0lPTl9DUkVBVEVfQ1JJVEVSSU9OIiwiUEVSTUlTU0lPTl9SRUFEX0FDVFVBVE9SX0RBVEEiXSwianRpIjoiMWU3OGMzMGYtNTY0ZS00NjliLWE1MmMtODlhOGM4YzFiZmY2IiwiY2xpZW50X2lkIjoiZGVjaXNpb253YW50ZWRfY2xpZW50X2lkIn0.Cnj_7b3FAanmL0Y-_kxcH2f4yjLFHOw-4NOVr67WZ88

This token can be decoded with JWT debugger here https://jwt.io/

I don't want to expose the internals of this token to external world and would like to encode this token in a some way.

How it can be implemented with Spring Boot, OAuth2, JWT ?

Soelch answered 28/9, 2016 at 8:39 Comment(5)
The source code of this jwt oauth2 spring security implementation might help: github.com/absolutegalaber/jwt-oauth2-examplePrincedom
Why do you want to hide the internals from the user, wouldn't they already know all that about themselves? If I see your token, then I have already hijacked your session (even if the internals are encoded/encrypted). Anyway checkout JWE (JSON Web Encryption) tools.ietf.org/html/rfc7516 P.S. Have you considered masking the permissions to drastically shrink that token down?Gosse
@Alex, thanks for your answer. Right now I'm thinking about shrinking of permissions. And also, I have created another ticket about JWE - #39769169Soelch
@Soelch did you find any proper solutionSelf
Honestly, I don’t remember right now, because it was many years ago (Soelch
S
6

i try this and it is working for me:https://gist.github.com/salgmachine/352799a6052b02901982dcbf85d30346

Create Custom JwtAccessTokenConverter

public class JwtJweAccessTokenConverter extends JwtAccessTokenConverter {

    RSAKey recipientJWK, recipientPublicJWK;

    public JwtJweAccessTokenConverter() {
        try {
            recipientJWK = new RSAKeyGenerator(2048).keyID("456").keyUse(KeyUse.ENCRYPTION).generate();
            recipientPublicJWK = recipientJWK.toPublicJWK();
        } catch (JOSEException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    @Override
    protected String encode(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        String jwt = super.encode(accessToken, authentication);

        try {
            // jwt is already signed at this point (by JwtAccessTokenConverter)
            SignedJWT parsed = SignedJWT.parse(jwt);

            // Create JWE object with signed JWT as payload
            JWEObject jweObject = new JWEObject(
                    new JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_256, EncryptionMethod.A256GCM).contentType("JWT") // required
                                                                                                                    // to
                                                                                                                    // indicate
                                                                                                                    // nested
                                                                                                                    // JWT
                            .build(),
                    new Payload(parsed));

            // Encrypt with the recipient's public key
            jweObject.encrypt(new RSAEncrypter(recipientPublicJWK));

            // Serialise to JWE compact form
            String jweString = jweObject.serialize();

            return jweString;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return jwt;
    }

    @Override
    protected Map<String, Object> decode(String token) {
        try {
            // basically treat the incoming token as an encrypted JWT
            EncryptedJWT parse = EncryptedJWT.parse(token);
            // decrypt it
            RSADecrypter dec = new RSADecrypter(recipientJWK);
            parse.decrypt(dec);
            // content of the encrypted token is a signed JWT (signed by
            // JwtAccessTokenConverter)
            SignedJWT signedJWT = parse.getPayload().toSignedJWT();
            // pass on the serialized, signed JWT to JwtAccessTokenConverter
            return super.decode(signedJWT.serialize());

        } catch (ParseException e) {
            e.printStackTrace();
        } catch (JOSEException e) {
            e.printStackTrace();
        }

        return super.decode(token);
    }
}

And Config your Oauth2 Auth server and resource to use your Custom JwtAccessTokenConverter

@Bean
public TokenStore tokenStore() {

    return new JwtTokenStore(accessTokenConverter());
}

@Bean
public JwtAccessTokenConverter accessTokenConverter() {
    final JwtAccessTokenConverter converter = new JwtJweAccessTokenConverter();
    final KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("mytest.jks"),
            "mypass".toCharArray());
    converter.setKeyPair(keyStoreKeyFactory.getKeyPair("mytest"));
    return converter;
}

Check github link for completed code example

Self answered 29/8, 2020 at 16:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.