Commit d9bfd829260df7e313e92472e83a4f87e95d93b9

Authored by Igor Kulikov
1 parent 9b18c408

Introduce OAuth failure handling

@@ -79,11 +79,18 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt @@ -79,11 +79,18 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
79 @Qualifier("oauth2AuthenticationSuccessHandler") 79 @Qualifier("oauth2AuthenticationSuccessHandler")
80 private AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler; 80 private AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler;
81 81
  82 + @Autowired(required = false)
  83 + @Qualifier("oauth2AuthenticationFailureHandler")
  84 + private AuthenticationFailureHandler oauth2AuthenticationFailureHandler;
  85 +
82 @Autowired 86 @Autowired
83 @Qualifier("defaultAuthenticationSuccessHandler") 87 @Qualifier("defaultAuthenticationSuccessHandler")
84 private AuthenticationSuccessHandler successHandler; 88 private AuthenticationSuccessHandler successHandler;
85 89
86 - @Autowired private AuthenticationFailureHandler failureHandler; 90 + @Autowired
  91 + @Qualifier("defaultAuthenticationFailureHandler")
  92 + private AuthenticationFailureHandler failureHandler;
  93 +
87 @Autowired private RestAuthenticationProvider restAuthenticationProvider; 94 @Autowired private RestAuthenticationProvider restAuthenticationProvider;
88 @Autowired private JwtAuthenticationProvider jwtAuthenticationProvider; 95 @Autowired private JwtAuthenticationProvider jwtAuthenticationProvider;
89 @Autowired private RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider; 96 @Autowired private RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider;
@@ -205,7 +212,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt @@ -205,7 +212,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
205 .loginPage("/oauth2Login") 212 .loginPage("/oauth2Login")
206 .loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl()) 213 .loginProcessingUrl(oauth2Configuration.getLoginProcessingUrl())
207 .successHandler(oauth2AuthenticationSuccessHandler) 214 .successHandler(oauth2AuthenticationSuccessHandler)
208 - .failureHandler(failureHandler); 215 + .failureHandler(oauth2AuthenticationFailureHandler);
209 } 216 }
210 } 217 }
211 218
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.security.auth.oauth2;
  17 +
  18 +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  19 +import org.springframework.security.core.AuthenticationException;
  20 +import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
  21 +import org.springframework.stereotype.Component;
  22 +import org.thingsboard.server.utils.MiscUtils;
  23 +
  24 +import javax.servlet.ServletException;
  25 +import javax.servlet.http.HttpServletRequest;
  26 +import javax.servlet.http.HttpServletResponse;
  27 +import java.io.IOException;
  28 +import java.net.URLEncoder;
  29 +import java.nio.charset.StandardCharsets;
  30 +
  31 +@Component(value = "oauth2AuthenticationFailureHandler")
  32 +@ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true")
  33 +public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
  34 +
  35 + @Override
  36 + public void onAuthenticationFailure(HttpServletRequest request,
  37 + HttpServletResponse response, AuthenticationException exception)
  38 + throws IOException, ServletException {
  39 + String baseUrl = MiscUtils.constructBaseUrl(request);
  40 + getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" +
  41 + URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString()));
  42 + }
  43 +}
@@ -26,7 +26,7 @@ import javax.servlet.http.HttpServletRequest; @@ -26,7 +26,7 @@ import javax.servlet.http.HttpServletRequest;
26 import javax.servlet.http.HttpServletResponse; 26 import javax.servlet.http.HttpServletResponse;
27 import java.io.IOException; 27 import java.io.IOException;
28 28
29 -@Component 29 +@Component(value = "defaultAuthenticationFailureHandler")
30 public class RestAwareAuthenticationFailureHandler implements AuthenticationFailureHandler { 30 public class RestAwareAuthenticationFailureHandler implements AuthenticationFailureHandler {
31 31
32 private final ThingsboardErrorResponseHandler errorResponseHandler; 32 private final ThingsboardErrorResponseHandler errorResponseHandler;
@@ -22,7 +22,7 @@ export default angular.module('thingsboard.api.user', [thingsboardApiLogin, @@ -22,7 +22,7 @@ export default angular.module('thingsboard.api.user', [thingsboardApiLogin,
22 .name; 22 .name;
23 23
24 /*@ngInject*/ 24 /*@ngInject*/
25 -function UserService($http, $q, $rootScope, adminService, dashboardService, timeService, loginService, toast, store, jwtHelper, $translate, $state, $location) { 25 +function UserService($http, $q, $rootScope, adminService, dashboardService, timeService, loginService, toast, store, jwtHelper, $translate, $state, $location, $mdDialog) {
26 var currentUser = null, 26 var currentUser = null,
27 currentUserDetails = null, 27 currentUserDetails = null,
28 lastPublicDashboardId = null, 28 lastPublicDashboardId = null,
@@ -406,6 +406,10 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, time @@ -406,6 +406,10 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, time
406 }, function fail() { 406 }, function fail() {
407 deferred.reject(); 407 deferred.reject();
408 }); 408 });
  409 + } else if (locationSearch.loginError) {
  410 + showLoginErrorDialog(locationSearch.loginError);
  411 + $location.search('loginError', null);
  412 + deferred.reject();
409 } else { 413 } else {
410 procceedJwtTokenValidate(); 414 procceedJwtTokenValidate();
411 } 415 }
@@ -415,6 +419,17 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, time @@ -415,6 +419,17 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, time
415 return deferred.promise; 419 return deferred.promise;
416 } 420 }
417 421
  422 + function showLoginErrorDialog(loginError) {
  423 + $translate(['login.error',
  424 + 'action.close']).then(function (translations) {
  425 + var alert = $mdDialog.alert()
  426 + .title(translations['login.error'])
  427 + .htmlContent(loginError)
  428 + .ok(translations['action.close']);
  429 + $mdDialog.show(alert);
  430 + });
  431 + }
  432 +
418 function loadIsUserTokenAccessEnabled() { 433 function loadIsUserTokenAccessEnabled() {
419 var deferred = $q.defer(); 434 var deferred = $q.defer();
420 if (currentUser.authority === 'SYS_ADMIN' || currentUser.authority === 'TENANT_ADMIN') { 435 if (currentUser.authority === 'SYS_ADMIN' || currentUser.authority === 'TENANT_ADMIN') {
@@ -1334,7 +1334,8 @@ @@ -1334,7 +1334,8 @@
1334 "password-link-sent-message": "Password reset link was successfully sent!", 1334 "password-link-sent-message": "Password reset link was successfully sent!",
1335 "email": "Email", 1335 "email": "Email",
1336 "login-with": "Login with {{name}}", 1336 "login-with": "Login with {{name}}",
1337 - "or": "or" 1337 + "or": "or",
  1338 + "error": "Login error"
1338 }, 1339 },
1339 "position": { 1340 "position": {
1340 "top": "Top", 1341 "top": "Top",