Skip to content

Commit

Permalink
Merge pull request #2580 from eclipse/fix-restApiRefresh
Browse files Browse the repository at this point in the history
Fixed KapuaSession data management in REST API
  • Loading branch information
Coduz committed May 10, 2019
2 parents 2a53e15 + 3cc10d9 commit 2b745eb
Show file tree
Hide file tree
Showing 6 changed files with 199 additions and 107 deletions.
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2011, 2016 Eurotech and/or its affiliates and others
* Copyright (c) 2016, 2019 Eurotech and/or its affiliates and others
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
Expand All @@ -11,11 +11,6 @@
*******************************************************************************/
package org.eclipse.kapua.app.api.core.auth;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
Expand All @@ -25,6 +20,11 @@
import org.eclipse.kapua.service.authentication.AccessTokenCredentials;
import org.eclipse.kapua.service.authentication.CredentialsFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class KapuaTokenAuthenticationFilter extends AuthenticatingFilter {

private static final String OPTIONS = "OPTIONS";
Expand Down Expand Up @@ -58,6 +58,7 @@ protected AuthenticationToken createToken(ServletRequest request, ServletRespons
if (authorizationHeader != null) {
tokenId = httpRequest.getHeader(AUTHORIZATION_HEADER).replace(BEARER + " ", "");
}

//
// Build AccessToken for Shiro Auth
KapuaLocator locator = KapuaLocator.getInstance();
Expand Down
@@ -0,0 +1,80 @@
/*******************************************************************************
* Copyright (c) 2019 Eurotech and/or its affiliates and others
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Eurotech - initial API and implementation
*******************************************************************************/
package org.eclipse.kapua.app.api.core.filter;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.eclipse.kapua.commons.security.KapuaSecurityUtils;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;


/**
* This {@link Filter} cleans up the {@link Subject#getSession()} state and the {@link KapuaSecurityUtils#getSession()} after a request.
* <p>
* The processing of the request can leave some information on the Shiro {@link Subject} or the {@link org.eclipse.kapua.commons.security.KapuaSession} and we must clean it to avoid that
* a subsequent request uses a {@link Thread} with dirty data inside.
* <p>
* Apache Shiro it is possible to define {@code noSessionCreation} on the urls mappings in the shiro.ini.
* Unfortunately using the {@code noSessionCreation} does not have any effect because our {@link org.eclipse.kapua.app.api.core.auth.KapuaTokenAuthenticationFilter} is invoked before the {@link org.apache.shiro.web.filter.session.NoSessionCreationFilter}
* so it has no effect (see {@link org.apache.shiro.web.filter.session.NoSessionCreationFilter javadoc}.
*
* @since 1.1.0
*/
public class KapuaSessionCleanupFilter implements Filter {

@Override
public void init(FilterConfig filterConfig) throws ServletException {
// No init required
}

@Override
public void destroy() {
// No destroy required
}

/**
* After the invokation of {@link FilterChain#doFilter(ServletRequest, ServletResponse)} the {@link Subject} and the {@link org.eclipse.kapua.commons.security.KapuaSession}
* are checked and cleaned accordingly.
* <p>
* See also {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)} javadoc.
*
* @param request See {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)} javadoc.
* @param response See {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)} javadoc.
* @param chain See {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)} javadoc.
* @throws IOException See {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)} javadoc.
* @throws ServletException See {@link Filter#doFilter(ServletRequest, ServletResponse, FilterChain)} javadoc.
* @since 1.1.0
*/
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

try {
chain.doFilter(request, response);
} finally {
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated()) {
subject.logout();
}

if (KapuaSecurityUtils.getSession() != null) {
KapuaSecurityUtils.clearSession();
}
}
}
}
Expand Up @@ -74,6 +74,7 @@ public AccountListResult simpleQuery( //
@ApiParam(value = "The account name to filter results.") @QueryParam("name") String name, //
@ApiParam(value = "The result set offset.", defaultValue = "0") @QueryParam("offset") @DefaultValue("0") int offset, //
@ApiParam(value = "The result set limit.", defaultValue = "50") @QueryParam("limit") @DefaultValue("50") int limit) throws Exception {

AccountQuery query = accountFactory.newQuery(scopeId);

AndPredicate andPredicate = query.andPredicate();
Expand Down
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2011, 2017 Eurotech and/or its affiliates and others
* Copyright (c) 2016, 2019 Eurotech and/or its affiliates and others
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
Expand All @@ -11,25 +11,25 @@
*******************************************************************************/
package org.eclipse.kapua.app.api.resources.v1.resources;

import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import org.eclipse.kapua.locator.KapuaLocator;
import org.eclipse.kapua.service.KapuaService;
import org.eclipse.kapua.service.authentication.ApiKeyCredentials;
import org.eclipse.kapua.service.authentication.AuthenticationService;
import org.eclipse.kapua.service.authentication.JwtCredentials;
import org.eclipse.kapua.service.authentication.LoginCredentials;
import org.eclipse.kapua.service.authentication.RefreshTokenCredentials;
import org.eclipse.kapua.service.authentication.UsernamePasswordCredentials;
import org.eclipse.kapua.service.authentication.token.AccessToken;

import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;

@Api("Authentication")
@Path("/authentication")
Expand All @@ -42,82 +42,73 @@ public class Authentication extends AbstractKapuaResource {
* Authenticates an user with username and password and returns
* the authentication token to be used in subsequent REST API calls.
*
* @param authenticationCredentials
* The username and password authentication credential of a user.
* @param authenticationCredentials The username and password authentication credential of a user.
* @return The authentication token
* @throws Exception
* Whenever something bad happens. See specific {@link KapuaService} exceptions.
* @throws Exception Whenever something bad happens. See specific {@link KapuaService} exceptions.
* @since 1.0.0
*/
@ApiOperation(nickname = "authenticationUser", value = "Authenticate an user", notes = "Authenticates an user with username and password and returns " +
"the authentication token to be used in subsequent REST API calls.", response = AccessToken.class)
@POST
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("user")
public AccessToken loginUsernamePassword(
@ApiParam(value = "The username and password authentication credential of a user.", required = true) UsernamePasswordCredentials authenticationCredentials) throws Exception {
return authenticationService.login(authenticationCredentials);
public AccessToken loginUsernamePassword(@ApiParam(value = "The username and password authentication credential of a user.", required = true) UsernamePasswordCredentials authenticationCredentials) throws Exception {
return login(authenticationCredentials);
}

/**
* Authenticates an user with a api key and returns
* the authentication token to be used in subsequent REST API calls.
*
* @param authenticationCredentials
* The API KEY authentication credential of a user.
* @param authenticationCredentials The API KEY authentication credential of a user.
* @return The authentication token
* @throws Exception
* Whenever something bad happens. See specific {@link KapuaService} exceptions.
* @throws Exception Whenever something bad happens. See specific {@link KapuaService} exceptions.
* @since 1.0.0
*/
@ApiOperation(nickname = "authenticationApiKey", value = "Authenticate an user", notes = "Authenticates an user with API KEY and returns " +
"the authentication token to be used in subsequent REST API calls.", response = AccessToken.class)
@POST
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("apikey")
public AccessToken loginApiKey(
@ApiParam(value = "The API KEY authentication credential of a user.", required = true) ApiKeyCredentials authenticationCredentials) throws Exception {
return authenticationService.login(authenticationCredentials);
public AccessToken loginApiKey(@ApiParam(value = "The API KEY authentication credential of a user.", required = true) ApiKeyCredentials authenticationCredentials) throws Exception {
return login(authenticationCredentials);
}

/**
* Authenticates an user with JWT and returns
* the authentication token to be used in subsequent REST API calls.
*
* @param authenticationCredentials
* The JWT authentication credential of a user.
* @param authenticationCredentials The JWT authentication credential of a user.
* @return The authentication token
* @throws Exception
* Whenever something bad happens. See specific {@link KapuaService} exceptions.
* @throws Exception Whenever something bad happens. See specific {@link KapuaService} exceptions.
* @since 1.0.0
*/
@ApiOperation(nickname = "authenticationJwt", value = "Authenticate an user", notes = "Authenticates an user with a JWT and returns " +
"the authentication token to be used in subsequent REST API calls.", response = AccessToken.class)
@POST
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("jwt")
public AccessToken loginJwt(
@ApiParam(value = "The JWT authentication credential of a user.", required = true) JwtCredentials authenticationCredentials) throws Exception {
return authenticationService.login(authenticationCredentials);
public AccessToken loginJwt(@ApiParam(value = "The JWT authentication credential of a user.", required = true) JwtCredentials authenticationCredentials) throws Exception {
return login(authenticationCredentials);
}

/**
* Invalidates the AccessToken related to this session.
* All subsequent calls will end up with a HTTP 401.
* A new login is required after this call to make other requests.
*
* @throws Exception
* Whenever something bad happens. See specific {@link KapuaService} exceptions.
*
* @throws Exception Whenever something bad happens. See specific {@link KapuaService} exceptions.
* @since 1.0.0
*/
@ApiOperation(nickname = "authenticationLogout", value = "Logs out an user", notes = "Terminates the current session and invalidates the access token")
@POST
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("logout")

public Response logout() throws Exception {
authenticationService.logout();

Expand All @@ -127,18 +118,29 @@ public Response logout() throws Exception {
/**
* Refreshes an expired {@link AccessToken}. Both the current AccessToken and the Refresh token will be invalidated.
* If also the Refresh token is expired, the user will have to restart with a new login.
*
* @throws Exception
* Whenever something bad happens. See specific {@link KapuaService} exceptions.
*
* @throws Exception Whenever something bad happens. See specific {@link KapuaService} exceptions.
* @since 1.0.0
*/
@ApiOperation(nickname = "authenticationRefresh", value = "Refreshes an AccessToken", notes = "Both the current AccessToken and the Refresh token will be invalidated. "
+ "If also the Refresh token is expired, the user will have to restart with a new login.")
@POST
@Consumes({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
@Consumes({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Produces({MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML})
@Path("refresh")
public AccessToken refresh(@ApiParam(value = "The current AccessToken's tokenId and refreshToken", required = true) RefreshTokenCredentials refreshTokenCredentials) throws Exception {
return authenticationService.refreshAccessToken(refreshTokenCredentials.getTokenId(), refreshTokenCredentials.getRefreshToken());
}

/**
* Authenticates the given {@link LoginCredentials}.
*
* @param loginCredentials The {@link LoginCredentials} to validate.
* @return The Authentication token
* @throws Exception Whenever something bad happens. See specific {@link KapuaService} exceptions.
* @since 1.1.0
*/
private AccessToken login(LoginCredentials loginCredentials) throws Exception {
return authenticationService.login(loginCredentials);
}
}

0 comments on commit 2b745eb

Please sign in to comment.