All Downloads are FREE. Search and download functionalities are using the official Maven repository.

no.nav.brukerdialog.security.jaspic.OidcAuthModule Maven / Gradle / Ivy

There is a newer version: 1.2020.02.21_14.01-cdaccec525a6
Show newest version
package no.nav.brukerdialog.security.jaspic;

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import no.nav.brukerdialog.security.jwks.CacheMissAction;
import no.nav.brukerdialog.security.oidc.OidcTokenValidator;
import no.nav.brukerdialog.security.oidc.OidcTokenValidatorResult;
import no.nav.brukerdialog.security.oidc.provider.OidcProvider;
import no.nav.brukerdialog.tools.HostUtils;
import no.nav.common.auth.LoginProvider;
import no.nav.common.auth.SsoToken;
import no.nav.common.auth.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;

import static java.util.Optional.empty;
import static java.util.Optional.of;
import static no.nav.brukerdialog.security.Constants.*;
import static no.nav.brukerdialog.security.jwks.CacheMissAction.NO_REFRESH;
import static no.nav.brukerdialog.security.jwks.CacheMissAction.REFRESH;
import static no.nav.brukerdialog.tools.Utils.getRelativePath;

@Slf4j
public class OidcAuthModule implements LoginProvider {

    private static final Logger log = LoggerFactory.getLogger(OidcAuthModule.class);
    private static final boolean sslOnlyCookies = !Boolean.valueOf(System.getProperty("develop-local", "false"));

    private final List providers;
    private final OidcTokenValidator oidcTokenValidator;

    public OidcAuthModule(List providers) {
        this(providers, new OidcTokenValidator());
    }

    OidcAuthModule(List providers, OidcTokenValidator oidcTokenValidator) {
        this.providers = providers;
        this.oidcTokenValidator = oidcTokenValidator;
    }

    @Override
    public Optional authenticate(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
        return Stream.of(NO_REFRESH, REFRESH)
                .flatMap(cacheControl -> authenticate(httpServletRequest, httpServletResponse, cacheControl))
                .findFirst();
    }

    private Stream authenticate(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, CacheMissAction cacheMissAction) {
        return providers.stream().flatMap(catchAndLogErrors(oidcProviderHandler -> doValidateRequest(httpServletRequest, httpServletResponse, oidcProviderHandler, cacheMissAction)));
    }

    private Function> catchAndLogErrors(Function> providerStreamFunction) {
        return (oidcProvider) -> {
            try {
                return providerStreamFunction.apply(oidcProvider);
            } catch (Throwable t) {
                log.warn(t.getMessage(), t);
                return Stream.empty();
            }
        };
    }

    private Stream doValidateRequest(
            HttpServletRequest httpServletRequest,
            HttpServletResponse httpServletResponse,
            OidcProvider oidcProvider,
            CacheMissAction cacheMissAction
    ) {
        Optional optionalRequestToken = oidcProvider.getToken(httpServletRequest);
        if (!optionalRequestToken.isPresent()) {
            return Stream.empty();
        }

        String requestToken = optionalRequestToken.get();
        OidcTokenValidatorResult requestTokenValidatorResult = oidcTokenValidator.validate(requestToken, oidcProvider, cacheMissAction);
        Optional optionalRefreshToken = oidcProvider.getRefreshToken(httpServletRequest);
        boolean needToRefreshToken = needToRefreshToken(requestTokenValidatorResult);
        if (optionalRefreshToken.isPresent() && needToRefreshToken) {
            String refreshToken = optionalRefreshToken.get();
            Optional optionalRefreshedToken = fetchUpdatedToken(refreshToken, requestToken, oidcProvider);
            if (optionalRefreshedToken.isPresent()) {
                String refreshedToken = optionalRefreshedToken.get();
                OidcTokenValidatorResult refreshedTokenValidatorResult = oidcTokenValidator.validate(refreshedToken, oidcProvider, cacheMissAction);
                if (refreshedTokenValidatorResult.isValid()) {
                    addHttpOnlyCookie(httpServletRequest, httpServletResponse, ID_TOKEN_COOKIE_NAME, refreshedToken);
                    return handleValidatedToken(refreshedTokenValidatorResult, refreshedToken, refreshedTokenValidatorResult.getSubject(), oidcProvider);
                }
            }
        }

        if (requestTokenValidatorResult.isValid()) {
            return handleValidatedToken(requestTokenValidatorResult, requestToken, requestTokenValidatorResult.getSubject(), oidcProvider);
        }
        return Stream.empty();
    }

    private boolean needToRefreshToken(OidcTokenValidatorResult validateResult) {
        return !validateResult.isValid() || tokenIsSoonExpired(validateResult);
    }

    private boolean tokenIsSoonExpired(OidcTokenValidatorResult validateResult) {
        return validateResult.getExpSeconds() * 1000 - Instant.now().toEpochMilli() < getMinimumTimeToExpireBeforeRefresh();
    }

    private int getMinimumTimeToExpireBeforeRefresh() {
        return Integer.parseInt(System.getProperty(REFRESH_TIME, "60")) * 1000;
    }

    private Optional fetchUpdatedToken(String refreshToken, String requestToken, OidcProvider oidcProvider) {
        log.debug("Refreshing token"); //Do not log token
        try {
            return of(oidcProvider.getFreshToken(refreshToken, requestToken).getToken());
        } catch (Exception e) {
            log.error("Could not refresh token", e);
            return empty();
        }
    }

    private void addHttpOnlyCookie(HttpServletRequest request, HttpServletResponse response, String name, String value) {
        Cookie cookie = new Cookie(name, value);
        cookie.setSecure(sslOnlyCookies);
        cookie.setHttpOnly(true);
        cookie.setPath("/");
        String domain = HostUtils.cookieDomain(request);
        if (domain != null) { //null for localhost
            cookie.setDomain(domain);
        }
        response.addCookie(cookie);
    }

    private void addApplicationCallbackSpecificHttpOnlyCookie(HttpServletResponse response, String name, String value) {
        Cookie cookie = new Cookie(name, value);
        cookie.setSecure(sslOnlyCookies);
        cookie.setHttpOnly(true);
        cookie.setPath(getRelativePath(getOidcRedirectUrl()));
        //to work on app.adeo.no and modapp.adeo.no
        cookie.setDomain(".adeo.no");
        response.addCookie(cookie);
    }

    @Override
    @SneakyThrows
    public Optional redirectUrl(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) {
        if (hasRedirectUrl()) {
            AuthorizationRequestBuilder builder = new AuthorizationRequestBuilder();
            //TODO CSRF attack protection. See RFC-6749 section 10.12 (the state-cookie containing redirectURL shold be encrypted to avoid tampering)
            addApplicationCallbackSpecificHttpOnlyCookie(httpServletResponse, builder.getStateIndex(), encode(getOriginalUrl(httpServletRequest)));
            return of(builder.buildRedirectString());
        } else {
            return empty();
        }
    }

    private String encode(String redirectLocation) throws UnsupportedEncodingException {
        return URLEncoder.encode(redirectLocation, "UTF-8");
    }

    private String getOriginalUrl(HttpServletRequest req) {
        return req.getQueryString() == null
                ? req.getRequestURL().toString()
                : req.getRequestURL().toString() + "?" + req.getQueryString();
    }

    private Stream handleValidatedToken(OidcTokenValidatorResult requestTokenValidatorResult, String token, String username, OidcProvider oidcProvider) {
        SsoToken ssoToken = SsoToken.oidcToken(token, requestTokenValidatorResult.getAttributes());
        Subject subject = new Subject(username, oidcProvider.getIdentType(token), ssoToken);
        return Stream.of(subject);
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy