Create JWT access token from a SAMLAuthenticationToken
Asked Answered
C

2

5

In my application I have so far used the OAuth2 password grant flow to generate a JWT access token to clients providing their username and password using Spring Security and Spring OAuth. They then use that token in all requests to my Spring Boot REST API.

Some of my customers now want to use SAML authentication instead. My idea is to create a separate endpoint /saml/accessToken and secure it with Spring SAML. Once the SAML authentication is complete the user is redirected back to /saml/accessToken, now with a valid authentication, and is given a JWT which the client can use to further communicate with my REST API.

I need a controller method that accepts an authenticated SAMLAuthenticationToken, generates a JWT using it's credentials, and returns it to the client:

@RequestMapping(value = "/saml/accessToken")
public String getAccessToken(SAMLAuthenticationToken authentication) {
    return accessTokenFactory.create(authentication);
}

It is the accessTokenFactory in the above example I need help with. I would like to follow the Spring coding ecosystem as much as possible and avoid using a "hack" solution, so that I can make use of the already existing TokenEnhancers and so forth.


What is the best way to create a JWT access token from a SAMLAuthenticationToken?

Charmain answered 23/10, 2018 at 10:47 Comment(0)
C
5

As it turns out, the Authentication object for the SAML cookie resulting from a successful SAML authentication flow is actually an ExpiringUsernameAuthenticationToken, not a SAMLAuthenticationToken. The ExpiringUsernameAuthenticationToken#principal is the User implementation (lets call it CustomerUser) I set during the SAML authentication in my SAMLUserDetailsService implementation, which is the same type of User that I use in the OAuth2 password grant flow.

Since I didn't find any way of using the default Spring OAuth way of creating a JWT for the ExpiringUsernameAuthenticationToken, I ended up writing a separate JwtFactory#create(ExpiringUsernameAuthenticationToken) using jjwt. This lead to a clean and easy solution.

The main drawback of doing it this way is that the JwtFactory cannot make use of my TokenEnhancer beans, that are responsible for adding additional parameters to the JWT. Therefore there exists som level of code and logic duplication for adding additional JWT parameters, both in the TokenEnhancers (used by Spring OAuth) and the JwtFactory (used manually after SAML authentication). This is a code smell that should be avoided. But hacking Spring OAuth specific functionality into my custom JwtFactory seems even worse, so this is something I'll have to live with.

Charmain answered 27/11, 2018 at 9:19 Comment(1)
Could you share some details of your implementation? Specifically how you handled the token creation and redirect and how you receive the token on the client. Is the request to /saml/accessToken user initiated or a request by some kind of HTTP library?Conant
M
1

I have tried below code and it's works for me.

After successful SAML (Okta) Login user redirect to below method which we have configured in SAML Configuration Class

  @Bean
@Qualifier("saml")
public SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler() {
    SavedRequestAwareAuthenticationSuccessHandler successRedirectHandler = new SavedRequestAwareAuthenticationSuccessHandler();
    successRedirectHandler.setDefaultTargetUrl("/oauth/saml/token");
    return successRedirectHandler;
}

 @RequestMapping("/oauth/saml/token")
    public String home(ExpiringUsernameAuthenticationToken userToken) throws JsonProcessingException {
        SAMLCredential credential = (SAMLCredential) userToken.getCredentials();
        Map<String, Object> map = new HashMap<>();
        map.put("group", credential.getAttributeAsStringArray("group"));
        Assertion authenticationAssertion = credential.getAuthenticationAssertion();
        String access_token = getSamlJWTToken(authenticationAssertion, map);
        map.put("access_token", access_token);
        ObjectMapper mapper = new ObjectMapper();
        String token = mapper.writeValueAsString(map);
        return token;
    }

private String getSamlJWTToken(Assertion authenticationAssertion, Map<String, Object> map) {
        String SECRET_KEY = Base64.getUrlEncoder().encodeToString(samlKeystorePassword.getBytes());
        String id = authenticationAssertion.getID();
        String issuer = authenticationAssertion.getIssuer().getValue();
        String subject = authenticationAssertion.getSubject().getNameID().getValue();
        DateTime notBefore = authenticationAssertion.getConditions().getNotBefore();
        DateTime notOnOrAfter = authenticationAssertion.getConditions().getNotOnOrAfter();
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary(SECRET_KEY);
        Key signingKey = new SecretKeySpec(apiKeySecretBytes, signatureAlgorithm.getJcaName());
        JwtBuilder builder = Jwts.builder()
                .setClaims(map)
                .setId(id)
                .setIssuedAt(notBefore.toDate())
                .setSubject(subject)
                .setIssuer(issuer)
                .signWith(signatureAlgorithm, signingKey)
                .setExpiration(notOnOrAfter.toDate());
        return builder.compact();
    }
Malinger answered 15/7, 2021 at 13:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.