/* ==================================================================== * * Copyright (C) 2015 GeoSolutions S.A.S. * http://www.geo-solutions.it * * GPLv3 + Classpath exception * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. * * ==================================================================== * * This software consists of voluntary contributions made by developers * of GeoSolutions. For more information on GeoSolutions, please see * <http://www.geo-solutions.it/>. * */ package it.geosolutions.geostore.services.rest.security; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import javax.servlet.http.HttpServletRequest; import org.apache.log4j.Logger; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import com.google.common.base.Optional; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; /** * Base Token based authentication filter. * * Authenticates a user reading an authentication token from a configured header * (defaults to Authorization). * * The token can have a prefix that needs to be present in the header value (defaults to * Bearer, to be compatible with OAuth 2.0 tokens). * * Each implementation can verify the validity of a token (and the user bounded to it) * using a different methodology. * * A cache is internally used to avoid continuous token testing. * * Cache expiration time and size can be configured. * * @author Mauro Bartolomeoli * */ public abstract class TokenAuthenticationFilter extends GeoStoreAuthenticationFilter { private final static Logger LOGGER = Logger.getLogger(TokenAuthenticationFilter.class); protected LoadingCache<String, Optional<Authentication>> cache; private String tokenHeader = "Authorization"; private String tokenPrefix = "Bearer "; private int cacheSize = 1000; private int cacheExpiration = 60; /** * Header to check for token (defaults to Authorization). * * @param tokenHeader */ public void setTokenHeader(String tokenHeader) { this.tokenHeader = tokenHeader; } /** * Static prefix to look for in the header value. * Only if the prefix is found, the rest of the header is checked as a Token. * * Defaults to Bearer (OAuth 2.0 compatible). * @param tokenPrefix */ public void setTokenPrefix(String tokenPrefix) { this.tokenPrefix = tokenPrefix; } public void setCache(LoadingCache<String, Optional<Authentication>> cache) { this.cache = cache; } /** * Max number of cached entries (defaults to 1000). * * @param cacheSize */ public void setCacheSize(int cacheSize) { this.cacheSize = cacheSize; } /** * Cached entries expiration time, in seconds (defaults to 60s). * * @param cacheExpiration */ public void setCacheExpiration(int cacheExpiration) { this.cacheExpiration = cacheExpiration; } protected LoadingCache<String, Optional<Authentication>> getCache() { if(cache == null) { cache = CacheBuilder.newBuilder() .maximumSize(cacheSize) .refreshAfterWrite(cacheExpiration, TimeUnit.SECONDS) .build(new CacheLoader<String, Optional<Authentication>>() { public Optional<Authentication> load(String token) { return Optional.fromNullable(checkToken(token)); } }); } return cache; } protected void authenticate(HttpServletRequest req) { String authHeader = req.getHeader(tokenHeader); if (authHeader != null && authHeader.trim().toUpperCase().startsWith(tokenPrefix.toUpperCase())) { String token = authHeader.substring(tokenPrefix.length()).trim(); Authentication auth; try { auth = getCache().get(token).orNull(); if (auth != null) { LOGGER.info("User authenticated using token: " + auth.getName()); SecurityContextHolder.getContext().setAuthentication(auth); } } catch (ExecutionException e) { LOGGER.error("Error authenticating token", e); } } } /** * Phisically checks the validity of the given token and * returns an Authentication object for the corresponding principal. * * @param token * @return */ protected abstract Authentication checkToken(String token); }