Java tutorial
/* * 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); } } }