org.wso2.carbon.mss.security.oauth2.OAuth2SecurityInterceptor.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.mss.security.oauth2.OAuth2SecurityInterceptor.java

Source

/*
*  Copyright (c) 2015, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
*  WSO2 Inc. licenses this file to you under the Apache License,
*  Version 2.0 (the "License"); you may not use this file except
*  in compliance with the License.
*  You may obtain a copy of the License at
*
*    http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing,
*  software distributed under the License is distributed on an
*  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
*  KIND, either express or implied.  See the License for the
*  specific language governing permissions and limitations
*  under the License.
*/
package org.wso2.carbon.mss.security.oauth2;

import com.google.common.base.Charsets;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Multimap;
import com.google.common.io.ByteStreams;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.mss.HttpResponder;
import org.wso2.carbon.mss.Interceptor;
import org.wso2.carbon.mss.ServiceMethodInfo;
import org.wso2.carbon.mss.security.MSSSecurityException;
import org.wso2.carbon.mss.security.SecurityErrorCode;
import org.wso2.carbon.mss.util.SystemVariableUtil;

import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;

/**
 * Act as a security gateway for resources secured with Oauth2.
 * <p>
 * Verify Oauth2 access token in Authorization Bearer HTTP header and allow access to the resource accordingly.
 *
 * @since 1.0.0
 */
public class OAuth2SecurityInterceptor implements Interceptor {
    private final Log log = LogFactory.getLog(OAuth2SecurityInterceptor.class);

    private static final String AUTHORIZATION_HTTP_HEADER = "Authorization";
    private static final String AUTH_TYPE_OAUTH2 = "OAuth2";
    private static final String BEARER_PREFIX = "bearer";
    private static final String AUTH_SERVER_URL_KEY = "AUTH_SERVER_URL";
    private static final String AUTH_SERVER_URL;
    private static final String TRUST_STORE = "TRUST_STORE";
    private static final String TRUST_STORE_PASSWORD = "TRUST_STORE_PASSWORD";

    static {
        AUTH_SERVER_URL = SystemVariableUtil.getValue(AUTH_SERVER_URL_KEY, null);
        if (AUTH_SERVER_URL == null) {
            throw new RuntimeException(AUTH_SERVER_URL_KEY + " is not specified.");
        }
        String trustStore = SystemVariableUtil.getValue(TRUST_STORE, null);
        String trustStorePassword = SystemVariableUtil.getValue(TRUST_STORE_PASSWORD, null);
        if (trustStore != null && !trustStore.isEmpty() && trustStorePassword != null
                && !trustStorePassword.isEmpty()) {
            System.setProperty("javax.net.ssl.trustStore", trustStore);
            System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword);
        }
    }

    @Override
    public boolean preCall(HttpRequest request, HttpResponder responder, ServiceMethodInfo serviceMethodInfo) {
        SecurityErrorCode errorCode;

        try {
            HttpHeaders headers = request.headers();
            if (headers != null && headers.contains(AUTHORIZATION_HTTP_HEADER)) {
                String authHeader = headers.get(AUTHORIZATION_HTTP_HEADER);
                return validateToken(authHeader);
            } else {
                throw new MSSSecurityException(SecurityErrorCode.AUTHENTICATION_FAILURE,
                        "Missing Authorization header is the request.`");
            }
        } catch (MSSSecurityException e) {
            errorCode = e.getErrorCode();
            log.error(e.getMessage() + " Requested Path: " + request.getUri());
        }

        handleSecurityError(errorCode, responder);
        return false;
    }

    @Override
    public void postCall(HttpRequest request, HttpResponseStatus status, ServiceMethodInfo serviceMethodInfo) {

    }

    /**
     * Extract the accessToken from the give Authorization header value and validates the accessToken
     * with an external key manager.
     *
     * @param authHeader Authorization Bearer header which contains the access token
     * @return true if the token is a valid token
     */
    private boolean validateToken(String authHeader) throws MSSSecurityException {
        // 1. Check whether this token is bearer token, if not return false
        String accessToken = extractAccessToken(authHeader);

        // 2. Send a request to key server's introspect endpoint to validate this token
        String responseStr = getValidatedTokenResponse(accessToken);
        Map<String, String> responseData = getResponseDataMap(responseStr);

        //TODO handle NPE

        // 3. Process the response and return true if the token is valid.
        if (!Boolean.parseBoolean(responseData.get(IntrospectionResponse.ACTIVE))) {
            throw new MSSSecurityException(SecurityErrorCode.AUTHENTICATION_FAILURE, "Invalid Access token.");
        }

        // 4. TODO build User principal based on the claims available in the response.
        //        String claims = responseData.get(IntrospectionResponse.SUB);

        return true;
    }

    /**
     * @param authHeader Authorization Bearer header which contains the access token
     * @return access token
     */
    private String extractAccessToken(String authHeader) throws MSSSecurityException {
        authHeader = authHeader.trim();
        if (authHeader.toLowerCase().startsWith(BEARER_PREFIX)) {
            // Split the auth header to get the access token.
            // Value should be in this format ("Bearer" 1*SP b64token)
            String[] authHeaderParts = authHeader.split(" ");
            if (authHeaderParts.length == 2) {
                return authHeaderParts[1];
            }
        }

        throw new MSSSecurityException(SecurityErrorCode.INVALID_AUTHORIZATION_HEADER,
                "Invalid Authorization header: " + authHeader);
    }

    /**
     * Validated the given accessToken with an external key server.
     *
     * @param accessToken AccessToken to be validated.
     * @return the response from the key manager server.
     */
    private String getValidatedTokenResponse(String accessToken) throws MSSSecurityException {
        URL url;
        try {
            url = new URL(AUTH_SERVER_URL);
            HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();
            urlConn.setDoOutput(true);
            urlConn.setRequestMethod(HttpMethod.POST.name());
            urlConn.getOutputStream().write(("token=" + accessToken).getBytes(Charsets.UTF_8));
            return new String(ByteStreams.toByteArray(urlConn.getInputStream()), Charsets.UTF_8);
        } catch (java.io.IOException e) {
            log.error("Error invoking Authorization Server", e);
            throw new MSSSecurityException(SecurityErrorCode.GENERIC_ERROR, "Error invoking Authorization Server",
                    e);
        }
    }

    /**
     * @param responseStr validated token response string returned from the key server.
     * @return a Map of key, value pairs available the response String.
     */
    private Map<String, String> getResponseDataMap(String responseStr) {
        Gson gson = new Gson();
        Type typeOfMapOfStrings = new TypeToken<Map<String, String>>() {
        }.getType();
        return gson.fromJson(responseStr, typeOfMapOfStrings);
    }

    /**
     * @param errorCode Security error code
     * @param responder HttpResponder instance which is used send error messages back to the client
     */
    private void handleSecurityError(SecurityErrorCode errorCode, HttpResponder responder) {
        if (errorCode == SecurityErrorCode.AUTHENTICATION_FAILURE
                || errorCode == SecurityErrorCode.INVALID_AUTHORIZATION_HEADER) {
            Multimap<String, String> map = ArrayListMultimap.create();
            map.put(HttpHeaders.Names.WWW_AUTHENTICATE, AUTH_TYPE_OAUTH2);
            responder.sendStatus(HttpResponseStatus.UNAUTHORIZED, map);

        } else if (errorCode == SecurityErrorCode.AUTHORIZATION_FAILURE) {
            responder.sendStatus(HttpResponseStatus.FORBIDDEN);

        } else {
            responder.sendStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
        }

    }

}