NIFI-6280 - Broke out the matching for /access/knox/** and /access/oidc/** to allow the Jetty security filters to be applied in the /access/oidc/logout and /access/knox/logout cases.

NIFI-6280 - Updated terminology in JwtAuthenticationFilter to authentication instead of authorization. Added stricter token parsing using an explicit regex pattern. Added tests.
NIFI-6280 - Updated terminology from Authorization to Authentication.
NIFI-6280 - Updated the access logout method to use getNiFiUserIdentity(). Updated javascript logout method to handle errors.
NIFI-6280 - Fixing checkstyle issues.
NIFI-6280 - Added some javadoc comments and logging. Renamed some variables for clarity. Fixed handling of exception when JWT does not match expected format.
NIFI-6280 - Cleaned up checkstyle, increased log severity level for logout action, and cleaned up Groovy syntax in test.

This closes #3482.

Signed-off-by: Andy LoPresto <alopresto@apache.org>
This commit is contained in:
thenatog 2019-05-16 13:19:47 -04:00 committed by Andy LoPresto
parent 8a50cb10b2
commit fe68d43e1d
No known key found for this signature in database
GPG Key ID: 6EC293152D90B61D
12 changed files with 317 additions and 85 deletions

View File

@ -17,16 +17,9 @@
package org.apache.nifi.record.path;
import org.apache.nifi.record.path.exception.RecordPathException;
import org.apache.nifi.serialization.SimpleRecordSchema;
import org.apache.nifi.serialization.record.DataType;
import org.apache.nifi.serialization.record.MapRecord;
import org.apache.nifi.serialization.record.Record;
import org.apache.nifi.serialization.record.RecordField;
import org.apache.nifi.serialization.record.RecordFieldType;
import org.apache.nifi.serialization.record.RecordSchema;
import org.apache.nifi.serialization.record.util.DataTypeUtils;
import org.junit.Test;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.StandardCharsets;
@ -42,10 +35,16 @@ import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.apache.nifi.record.path.exception.RecordPathException;
import org.apache.nifi.serialization.SimpleRecordSchema;
import org.apache.nifi.serialization.record.DataType;
import org.apache.nifi.serialization.record.MapRecord;
import org.apache.nifi.serialization.record.Record;
import org.apache.nifi.serialization.record.RecordField;
import org.apache.nifi.serialization.record.RecordFieldType;
import org.apache.nifi.serialization.record.RecordSchema;
import org.apache.nifi.serialization.record.util.DataTypeUtils;
import org.junit.Test;
public class TestRecordPath {

View File

@ -140,6 +140,9 @@
<logger name="org.apache.nifi.web.filter.RequestLogger" level="INFO" additivity="false">
<appender-ref ref="USER_FILE"/>
</logger>
<logger name="org.apache.nifi.web.api.AccessResource" level="INFO" additivity="false">
<appender-ref ref="USER_FILE"/>
</logger>
<!--

View File

@ -50,7 +50,8 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
/**
* NiFi Web Api Spring security
* NiFi Web Api Spring security. Applies the various NiFiAuthenticationFilter servlet filters which will extract authentication
* credentials from API requests.
*/
@Configuration
@EnableWebSecurity
@ -88,7 +89,9 @@ public class NiFiWebApiSecurityConfiguration extends WebSecurityConfigurerAdapte
// the /access/download-token and /access/ui-extension-token endpoints
webSecurity
.ignoring()
.antMatchers("/access", "/access/config", "/access/token", "/access/kerberos", "/access/oidc/**", "/access/knox/**");
.antMatchers("/access", "/access/config", "/access/token", "/access/kerberos",
"/access/oidc/exchange", "/access/oidc/callback", "/access/oidc/request",
"/access/knox/callback", "/access/knox/request");
}
@Override

View File

@ -29,6 +29,25 @@ import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import io.swagger.annotations.ApiResponse;
import io.swagger.annotations.ApiResponses;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.admin.service.AdministrationException;
import org.apache.nifi.authentication.AuthenticationResponse;
@ -69,25 +88,6 @@ import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* RESTful endpoint for managing access.
*/
@ -103,6 +103,7 @@ public class AccessResource extends ApplicationResource {
private static final String OIDC_REQUEST_IDENTIFIER = "oidc-request-identifier";
private static final String OIDC_ERROR_TITLE = "Unable to continue login sequence";
private X509CertificateExtractor certificateExtractor;
private X509AuthenticationProvider x509AuthenticationProvider;
private X509PrincipalExtractor principalExtractor;
@ -344,9 +345,6 @@ public class AccessResource extends ApplicationResource {
.build();
httpServletResponse.sendRedirect(logoutUri.toString());
}
String authorizationHeader = httpServletRequest.getHeader(JwtAuthenticationFilter.AUTHORIZATION);
jwtService.logOut(authorizationHeader);
}
@GET
@ -375,7 +373,7 @@ public class AccessResource extends ApplicationResource {
// build the authorization uri
final URI authorizationUri = UriBuilder.fromUri(knoxService.getKnoxUrl())
.queryParam("originalUrl", originalUri.toString())
.queryParam("originalUrl", originalUri)
.build();
// generate the response
@ -416,7 +414,7 @@ public class AccessResource extends ApplicationResource {
)
public void knoxLogout(@Context HttpServletRequest httpServletRequest, @Context HttpServletResponse httpServletResponse) throws Exception {
String redirectPath = generateResourceUri("..", "nifi", "login");
httpServletResponse.sendRedirect(redirectPath.toString());
httpServletResponse.sendRedirect(redirectPath);
}
/**
@ -747,7 +745,7 @@ public class AccessResource extends ApplicationResource {
return generateCreatedResponse(uri, token).build();
}
@GET
@DELETE
@Consumes(MediaType.WILDCARD)
@Produces(MediaType.WILDCARD)
@Path("/logout")
@ -758,6 +756,7 @@ public class AccessResource extends ApplicationResource {
@ApiResponses(
value = {
@ApiResponse(code = 200, message = "User was logged out successfully."),
@ApiResponse(code = 401, message = "Authentication token provided was empty or not in the correct JWT format."),
@ApiResponse(code = 500, message = "Client failed to log out."),
}
)
@ -766,13 +765,19 @@ public class AccessResource extends ApplicationResource {
throw new IllegalStateException("User authentication/authorization is only supported when running over HTTPS.");
}
String authorizationHeader = httpServletRequest.getHeader(JwtAuthenticationFilter.AUTHORIZATION);
final String token = StringUtils.substringAfterLast(authorizationHeader, " ");
try {
jwtService.logOut(token);
return generateOkResponse().build();
} catch (final JwtException e) {
return Response.serverError().build();
String userIdentity = NiFiUserUtils.getNiFiUserIdentity();
if(userIdentity != null && !userIdentity.isEmpty()) {
try {
logger.info("Logging out user " + userIdentity);
jwtService.logOut(userIdentity);
return generateOkResponse().build();
} catch (final JwtException e) {
logger.error("Logout of user " + userIdentity + " failed due to: " + e.getMessage());
return Response.serverError().build();
}
} else {
return Response.status(401, "Authentication token provided was empty or not in the correct JWT format.").build();
}
}

View File

@ -410,7 +410,6 @@ public class ITAccessTokenEndpoint {
String logoutUrl = BASE_URL + "/access/logout";
Response response = TOKEN_USER.testCreateToken(accessTokenUrl, user, password);
Response responseA = TOKEN_USER.testCreateToken(accessTokenUrl, "jack", password);
// ensure the request is successful
Assert.assertEquals(201, response.getStatus());

View File

@ -38,7 +38,8 @@ import java.io.IOException;
import java.io.PrintWriter;
/**
*
* The NiFiAuthenticationFilter abstract class defines the base methods for NiFi's various existing and future
* authentication mechanisms. The subclassed filters are applied in NiFiWebApiSecurityConfiguration.
*/
public abstract class NiFiAuthenticationFilter extends GenericFilterBean {
@ -74,16 +75,16 @@ public abstract class NiFiAuthenticationFilter extends GenericFilterBean {
log.info(String.format("Attempting request for (%s) %s %s (source ip: %s)", authenticationRequest.toString(), request.getMethod(),
request.getRequestURL().toString(), request.getRemoteAddr()));
// attempt to authorize the user
// attempt to authenticate the user
final Authentication authenticated = authenticationManager.authenticate(authenticationRequest);
successfulAuthorization(request, response, authenticated);
successfulAuthentication(request, response, authenticated);
}
} catch (final AuthenticationException ae) {
// invalid authentication - always error out
unsuccessfulAuthorization(request, response, ae);
unsuccessfulAuthentication(request, response, ae);
return;
} catch (final Exception e) {
log.error(String.format("Unable to authorize: %s", e.getMessage()), e);
log.error(String.format("Unable to authenticate: %s", e.getMessage()), e);
// set the response status
response.setContentType("text/plain");
@ -91,7 +92,7 @@ public abstract class NiFiAuthenticationFilter extends GenericFilterBean {
// other exception - always error out
PrintWriter out = response.getWriter();
out.println(String.format("Failed to authorize request. Please contact the system administrator."));
out.println(String.format("Failed to authenticate request. Please contact the system administrator."));
return;
}
@ -107,16 +108,32 @@ public abstract class NiFiAuthenticationFilter extends GenericFilterBean {
*/
public abstract Authentication attemptAuthentication(HttpServletRequest request);
protected void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
/**
* If authentication was successful, apply the successful authentication result to the security context and add
* proxy headers to the response if the request was made via a proxy.
*
* @param request The original client request that was successfully authenticated.
* @param response Servlet response to the client containing the successful authentication details.
* @param authResult The Authentication 'token'/object created by one of the various NiFiAuthenticationFilter subclasses.
*/
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
log.info("Authentication success for " + authResult);
SecurityContextHolder.getContext().setAuthentication(authResult);
ProxiedEntitiesUtils.successfulAuthorization(request, response, authResult);
ProxiedEntitiesUtils.successfulAuthentication(request, response);
}
protected void unsuccessfulAuthorization(HttpServletRequest request, HttpServletResponse response, AuthenticationException ae) throws IOException {
/**
* If authentication was unsuccessful, update the response with the appropriate status and give the reason for why
* the user was not able to be authenticated. Update the response with proxy headers if the request was made via a proxy.
*
* @param request The original client request that failed to be authenticated.
* @param response Servlet response to the client containing the unsuccessful authentication attempt details.
* @param ae The related exception thrown and explanation for the unsuccessful authentication attempt.
*/
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException ae) throws IOException {
// populate the response
ProxiedEntitiesUtils.unsuccessfulAuthorization(request, response, ae);
ProxiedEntitiesUtils.unsuccessfulAuthentication(request, response, ae);
// set the response status
response.setContentType("text/plain");
@ -132,11 +149,11 @@ public abstract class NiFiAuthenticationFilter extends GenericFilterBean {
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
out.println(ae.getMessage());
} else if (ae instanceof AuthenticationServiceException) {
log.error(String.format("Unable to authorize: %s", ae.getMessage()), ae);
log.error(String.format("Unable to authenticate: %s", ae.getMessage()), ae);
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
out.println(String.format("Unable to authorize: %s", ae.getMessage()));
out.println(String.format("Unable to authenticate: %s", ae.getMessage()));
} else {
log.error(String.format("Unable to authorize: %s", ae.getMessage()), ae);
log.error(String.format("Unable to authenticate: %s", ae.getMessage()), ae);
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
out.println("Access is denied.");
}

View File

@ -27,7 +27,6 @@ import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
/**
@ -148,13 +147,26 @@ public class ProxiedEntitiesUtils {
return StringUtils.join(proxyChain, "");
}
public static void successfulAuthorization(HttpServletRequest request, HttpServletResponse response, Authentication authResult) {
/**
* If a successfully authenticated request was made via a proxy, relevant proxy headers will be added to the response.
*
* @param request The proxied client request that was successfully authenticated.
* @param response A servlet response to the client containing the successful authentication details.
*/
public static void successfulAuthentication(HttpServletRequest request, HttpServletResponse response) {
if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) {
response.setHeader(PROXY_ENTITIES_ACCEPTED, Boolean.TRUE.toString());
}
}
public static void unsuccessfulAuthorization(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
/**
* If an unauthenticated request was made via a proxy, add proxy headers to explain why authentication failed.
*
* @param request The original client request that failed to be authenticated.
* @param response Servlet response to the client containing the unsuccessful authentication attempt details.
* @param failed The related exception thrown and explanation for the unsuccessful authentication attempt.
*/
public static void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) {
if (StringUtils.isNotBlank(request.getHeader(PROXY_ENTITIES_CHAIN))) {
response.setHeader(PROXY_ENTITIES_DETAILS, failed.getMessage());
}

View File

@ -16,13 +16,15 @@
*/
package org.apache.nifi.web.security.jwt;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.web.security.InvalidAuthenticationException;
import org.apache.nifi.web.security.NiFiAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
*/
@ -30,8 +32,9 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class);
// The Authorization header contains authentication credentials
public static final String AUTHORIZATION = "Authorization";
public static final String BEARER = "Bearer ";
private static final Pattern tokenPattern = Pattern.compile("^Bearer (\\S*\\.\\S*\\.\\S*)$");
@Override
public Authentication attemptAuthentication(final HttpServletRequest request) {
@ -43,15 +46,30 @@ public class JwtAuthenticationFilter extends NiFiAuthenticationFilter {
// TODO: Refactor request header extraction logic to shared utility as it is duplicated in AccessResource
// get the principal out of the user token
final String authorization = request.getHeader(AUTHORIZATION);
final String authorizationHeader = request.getHeader(AUTHORIZATION);
// if there is no authorization header, we don't know the user
if (authorization == null || !StringUtils.startsWith(authorization, BEARER)) {
if (authorizationHeader == null || !validJwtFormat(authorizationHeader)) {
return null;
} else {
// Extract the Base64 encoded token from the Authorization header
final String token = StringUtils.substringAfterLast(authorization, " ");
final String token = getTokenFromHeader(authorizationHeader);
return new JwtAuthenticationRequestToken(token, request.getRemoteAddr());
}
}
private boolean validJwtFormat(String authenticationHeader) {
Matcher matcher = tokenPattern.matcher(authenticationHeader);
return matcher.matches();
}
private String getTokenFromHeader(String authenticationHeader) {
Matcher matcher = tokenPattern.matcher(authenticationHeader);
if(matcher.matches()) {
return matcher.group(1);
} else {
throw new InvalidAuthenticationException("JWT did not match expected pattern.");
}
}
}

View File

@ -32,7 +32,6 @@ import java.util.Calendar;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.admin.service.AdministrationException;
import org.apache.nifi.admin.service.KeyService;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.key.Key;
import org.apache.nifi.web.security.token.LoginAuthenticationToken;
import org.slf4j.LoggerFactory;
@ -170,17 +169,15 @@ public class JwtService {
}
}
public void logOut(String authorizationHeader) {
if (authorizationHeader == null || authorizationHeader.isEmpty()) {
throw new JwtException("Log out failed: The required Authorization header was not present in the request to log out user.");
public void logOut(String userIdentity) {
if (userIdentity == null || userIdentity.isEmpty()) {
throw new JwtException("Log out failed: The user identity was not present in the request token to log out user.");
}
String identity = NiFiUserUtils.getNiFiUserIdentity();
try {
keyService.deleteKey(identity);
keyService.deleteKey(userIdentity);
} catch (Exception e) {
logger.error("Unable to log out user: " + identity + ". Failed to remove their token from database.");
logger.error("Unable to log out user: " + userIdentity + ". Failed to remove their token from database.");
throw e;
}
}

View File

@ -0,0 +1,180 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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.apache.nifi.web.security.jwt
import groovy.json.JsonOutput
import org.apache.nifi.web.security.InvalidAuthenticationException
import org.junit.After
import org.junit.AfterClass
import org.junit.Before
import org.junit.BeforeClass
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4.class)
class JwtAuthenticationFilterTest extends GroovyTestCase {
public static String jwtString
@Rule
public ExpectedException expectedException = ExpectedException.none()
@BeforeClass
static void setUpOnce() throws Exception {
final String ALG_HEADER = "{\"alg\":\"HS256\"}"
final int EXPIRATION_SECONDS = 500
Calendar now = Calendar.getInstance()
final long currentTime = (long) (now.getTimeInMillis() / 1000.0)
final long TOKEN_ISSUED_AT = currentTime
final long TOKEN_EXPIRATION_SECONDS = currentTime + EXPIRATION_SECONDS
// Generate a token that we will add a valid signature from a different token
// Always use LinkedHashMap to enforce order of the keys because the signature depends on order
final String EXPECTED_PAYLOAD =
JsonOutput.toJson(
sub:'unknownuser',
iss:'MockIdentityProvider',
aud:'MockIdentityProvider',
preferred_username:'unknownuser',
kid:1,
exp:TOKEN_EXPIRATION_SECONDS,
iat:TOKEN_ISSUED_AT)
// Set up our JWT string with a test token
jwtString = JwtServiceTest.generateHS256Token(ALG_HEADER, EXPECTED_PAYLOAD, true, true)
}
@AfterClass
static void tearDownOnce() throws Exception {
}
@Before
void setUp() throws Exception {
}
@After
void tearDown() throws Exception {
}
@Test
void testValidAuthenticationHeaderString() {
// Arrange
String authenticationHeader = "Bearer " + jwtString
// Act
boolean isValidHeader = new JwtAuthenticationFilter().validJwtFormat(authenticationHeader)
// Assert
assertTrue(isValidHeader)
}
@Test
void testMissingBearer() {
// Arrange
// Act
boolean isValidHeader = new JwtAuthenticationFilter().validJwtFormat(jwtString)
// Assert
assertFalse(isValidHeader)
}
@Test
void testExtraCharactersAtBeginningOfToken() {
// Arrange
String authenticationHeader = "xBearer " + jwtString
// Act
boolean isValidToken = new JwtAuthenticationFilter().validJwtFormat(authenticationHeader)
// Assert
assertFalse(isValidToken)
}
@Test
void testBadTokenFormat() {
// Arrange
String[] tokenStrings = jwtString.split("\\.")
String badToken = "Bearer " + tokenStrings[1] + tokenStrings[2]
// Act
boolean isValidToken = new JwtAuthenticationFilter().validJwtFormat(badToken)
// Assert
assertFalse(isValidToken)
}
@Test
void testMultipleTokenInvalid() {
// Arrange
String authenticationHeader = "Bearer " + jwtString
authenticationHeader = authenticationHeader + " " + authenticationHeader
// Act
boolean isValidToken = new JwtAuthenticationFilter().validJwtFormat(authenticationHeader)
// Assert
assertFalse(isValidToken)
}
@Test
void testExtractToken() {
// Arrange
String authenticationHeader = "Bearer " + jwtString
// Act
String extractedToken = new JwtAuthenticationFilter().getTokenFromHeader(authenticationHeader)
// Assert
assertEquals(jwtString, extractedToken)
}
@Test
void testMultipleTokenDottedInvalid() {
// Arrange
String authenticationHeader = "Bearer " + jwtString
authenticationHeader = authenticationHeader + "." + authenticationHeader
// Act
boolean isValidToken = new JwtAuthenticationFilter().validJwtFormat(authenticationHeader)
// Assert
assertFalse(isValidToken)
}
@Test
void testMultipleTokenNotExtracted() {
// Arrange
expectedException.expect(InvalidAuthenticationException.class)
expectedException.expectMessage("JWT did not match expected pattern.")
String authenticationHeader = "Bearer " + jwtString
authenticationHeader = authenticationHeader + " " + authenticationHeader
// Act
String token = new JwtAuthenticationFilter().getTokenFromHeader(authenticationHeader)
// Assert
// Expect InvalidAuthenticationException
}
}

View File

@ -511,7 +511,7 @@ public class JwtServiceTest {
public void testLogoutWhenAuthTokenIsEmptyShouldThrowError() throws Exception {
// Arrange
expectedException.expect(JwtException.class);
expectedException.expectMessage("Log out failed: The required Authorization header was not present in the request to log out user.");
expectedException.expectMessage("Log out failed: The user identity was not present in the request token to log out user.");
// Act
jwtService.logOut(null);
@ -520,5 +520,4 @@ public class JwtServiceTest {
// Should throw exception when authorization header is null
}
}

View File

@ -118,12 +118,12 @@
this.logoutCtrl = {
logout: function () {
$.ajax({
type: 'GET',
type: 'DELETE',
url: '../nifi-api/access/logout',
dataType: 'json'
})
nfStorage.removeItem("jwt");
window.location = '../nifi/logout';
}).done(function () {
nfStorage.removeItem("jwt");
window.location = '../nifi/logout';
}).fail(nfErrorHandler.handleAjaxError);
}
};
}