ОБНОВЛЕНИЕ: перейти к официальному и рекомендованному способу делать это вместо hacky и неофициальный подход для предотвращения / предотвращения неизвестных проблем. Из моего ответа здесь .
На самом деле в документации есть часть
:
Получение сообщений от нескольких отправителей
FCM позволяет нескольким сторонам отправлять сообщения в одно и то же клиентское приложение. Например, предположим, что клиентское приложение является агрегатором статьи с несколькими вкладчиками, и каждый из них должен иметь возможность отправлять сообщение при публикации новой статьи. Это сообщение может содержать URL-адрес, чтобы клиентское приложение могло загрузить статью. Вместо того, чтобы централизовать всю операцию отправки в одном месте, FCM дает вам возможность разрешать каждому из этих авторов отправлять свои собственные сообщения.
Чтобы сделать это возможным, убедитесь, что каждый отправитель генерирует свой собственный идентификатор отправителя . Информацию о том, как получить идентификатор отправителя FCM, см. В документации клиента для вашей платформы. При запросе регистрации клиентское приложение извлекает токен несколько раз, каждый раз с другим идентификатором отправителя в поле аудитории.
Наконец, поделитесь регистрационным маркером с соответствующими серверами приложений (чтобы завершить работу с клиентом регистрации / серверное рукопожатие), и они смогут отправлять сообщения в клиентское приложение, используя свои собственные ключи аутентификации.
Обратите внимание, что существует ограничение на 100 отправителей.
blockquote>Я думаю, что запутанная, но важная часть здесь:
При запросе регистрации клиентское приложение извлекает токен несколько раз, каждый раз с другим идентификатором отправителя в поле аудитории.
Другими словами, вам нужно будет вызвать
getToken()
, передавая идентификатор отправителя, и просто"FCM"
(например,getToken("2xxxxx3344", "FCM")
) в качестве параметров. Вам нужно будет убедиться, что вы вызываете это для каждого отправителя (проекта), который вам нужен.Также обратите внимание на
getToken()
документы:Это блокирующая функция, поэтому не вызывайте ее в основном потоке.
blockquote>Некоторые дополнительные преимущества:
- Он не выполняет автоматическое повторное попытку, если он не работает как и по умолчанию.
- Он возвращает исключение IOException, когда он терпит неудачу.
Я делаю более приятную демонстрацию, а также очищаю некоторые из этих сервисов в полезном модуле, но вот что я придумал. Это сложный процесс, чтобы обойти некоторые предостережения, поэтому повесьте там. Вы должны разбить это на несколько частей.
Сначала вам нужна служба для хранения пользовательских идентичность. Я называю это principal
. Он может быть проверен, чтобы узнать, вошел ли пользователь в систему и по запросу он может разрешить объект, представляющий существенную информацию о личности пользователя. Это может быть все, что вам нужно, но основное - это отображаемое имя, имя пользователя, возможно, электронное письмо и роли, к которым принадлежит пользователь (если это относится к вашему приложению). Принципал также имеет методы проверки ролей.
.factory('principal', ['$q', '$http', '$timeout',
function($q, $http, $timeout) {
var _identity = undefined,
_authenticated = false;
return {
isIdentityResolved: function() {
return angular.isDefined(_identity);
},
isAuthenticated: function() {
return _authenticated;
},
isInRole: function(role) {
if (!_authenticated || !_identity.roles) return false;
return _identity.roles.indexOf(role) != -1;
},
isInAnyRole: function(roles) {
if (!_authenticated || !_identity.roles) return false;
for (var i = 0; i < roles.length; i++) {
if (this.isInRole(roles[i])) return true;
}
return false;
},
authenticate: function(identity) {
_identity = identity;
_authenticated = identity != null;
},
identity: function(force) {
var deferred = $q.defer();
if (force === true) _identity = undefined;
// check and see if we have retrieved the
// identity data from the server. if we have,
// reuse it by immediately resolving
if (angular.isDefined(_identity)) {
deferred.resolve(_identity);
return deferred.promise;
}
// otherwise, retrieve the identity data from the
// server, update the identity object, and then
// resolve.
// $http.get('/svc/account/identity',
// { ignoreErrors: true })
// .success(function(data) {
// _identity = data;
// _authenticated = true;
// deferred.resolve(_identity);
// })
// .error(function () {
// _identity = null;
// _authenticated = false;
// deferred.resolve(_identity);
// });
// for the sake of the demo, fake the lookup
// by using a timeout to create a valid
// fake identity. in reality, you'll want
// something more like the $http request
// commented out above. in this example, we fake
// looking up to find the user is
// not logged in
var self = this;
$timeout(function() {
self.authenticate(null);
deferred.resolve(_identity);
}, 1000);
return deferred.promise;
}
};
}
])
Во-вторых, вам нужна служба, которая проверяет состояние, к которому пользователь хочет перейти, удостоверяется, что они вошли в систему (при необходимости, не требуется signin, сброс пароля и т. д.), а затем выполняет проверку роли (если ваше приложение нуждается в этом). Если они не аутентифицированы, отправьте их на страницу входа. Если они аутентифицированы, но не выполняют проверку роли, отправьте их на страницу с запретом доступа. Я вызываю эту службу authorization
.
.factory('authorization', ['$rootScope', '$state', 'principal',
function($rootScope, $state, principal) {
return {
authorize: function() {
return principal.identity()
.then(function() {
var isAuthenticated = principal.isAuthenticated();
if ($rootScope.toState.data.roles
&& $rootScope.toState
.data.roles.length > 0
&& !principal.isInAnyRole(
$rootScope.toState.data.roles))
{
if (isAuthenticated) {
// user is signed in but not
// authorized for desired state
$state.go('accessdenied');
} else {
// user is not authenticated. Stow
// the state they wanted before you
// send them to the sign-in state, so
// you can return them when you're done
$rootScope.returnToState
= $rootScope.toState;
$rootScope.returnToStateParams
= $rootScope.toStateParams;
// now, send them to the signin state
// so they can log in
$state.go('signin');
}
}
});
}
};
}
])
Теперь вам нужно только прослушать ui-router
$stateChangeStart
. Это дает вам возможность изучить текущее состояние, состояние, в котором они хотят перейти, и вставить свою проверку авторизации. Если он терпит неудачу, вы можете отменить переход по маршруту или перейти на другой маршрут.
.run(['$rootScope', '$state', '$stateParams',
'authorization', 'principal',
function($rootScope, $state, $stateParams,
authorization, principal)
{
$rootScope.$on('$stateChangeStart',
function(event, toState, toStateParams)
{
// track the state the user wants to go to;
// authorization service needs this
$rootScope.toState = toState;
$rootScope.toStateParams = toStateParams;
// if the principal is resolved, do an
// authorization check immediately. otherwise,
// it'll be done when the state it resolved.
if (principal.isIdentityResolved())
authorization.authorize();
});
}
]);
Сложная часть отслеживания личности пользователя ищет ее, если вы уже прошли аутентификацию (скажем, вы «Посещение страницы после предыдущего сеанса и сохранение токена авторизации в файле cookie, или, может быть, вы сильно обновили страницу или удалили URL-адрес из ссылки). Из-за того, как ui-router
работает, вам нужно сделать свое удостоверение личности раз, прежде чем ваши проверки подлинности. Вы можете сделать это, используя опцию resolve
в вашей конфигурации состояния. У меня есть одно родительское состояние для сайта, на которое наследуются все государства, что заставляет принципала быть разрешенным, прежде чем что-либо еще произойдет.
$stateProvider.state('site', {
'abstract': true,
resolve: {
authorize: ['authorization',
function(authorization) {
return authorization.authorize();
}
]
},
template: '<div ui-view />'
})
Здесь есть еще одна проблема ... resolve
вызывается только один раз. После того, как ваше обещание для поиска идентичности завершится, он не будет снова выполнять делегат разрешения. Таким образом, мы должны выполнить ваши проверки подлинности в двух местах: один раз в соответствии с вашим обещанием по идентификации, разрешенным в resolve
, который распространяется при первом загрузке приложения, и один раз в $stateChangeStart
, если разрешение было выполнено, которое охватывает любое время вы перемещаетесь вокруг состояний.
ОК, так что мы сделали до сих пор?
Куда мы идем отсюда? Ну, вы можете организовать свои штаты в регионах, требующих входа. Вы можете потребовать аутентифицированных / авторизованных пользователей, добавив data
с roles
в эти состояния (или родительский элемент, если вы хотите использовать наследование). Здесь мы ограничиваем ресурс Admins:
.state('restricted', {
parent: 'site',
url: '/restricted',
data: {
roles: ['Admin']
},
views: {
'content@': {
templateUrl: 'restricted.html'
}
}
})
Теперь вы можете контролировать состояние в зависимости от того, что пользователи могут получить доступ к маршруту. Любые другие проблемы? Может быть, меняется только часть представления на основе того, вошли ли они в систему? Нет проблем. Используйте principal.isAuthenticated()
или даже principal.isInRole()
с любым из многочисленных способов условного отображения шаблона или элемента.
Сначала вставьте principal
в контроллер или что-то еще, и привяжите его к чтобы вы могли легко использовать его в своем представлении:
.scope('HomeCtrl', ['$scope', 'principal',
function($scope, principal)
{
$scope.principal = principal;
});
Показать или скрыть элемент:
<div ng-show="principal.isAuthenticated()">
I'm logged in
</div>
<div ng-hide="principal.isAuthenticated()">
I'm not logged in
</div>
и т. д., и так далее. В любом случае, в вашем примере приложения, у вас будет состояние для домашней страницы, которое позволит пропускать не прошедших проверку пользователей. Они могут иметь ссылки на состояния входа или регистрации или иметь эти формы, встроенные в эту страницу. Независимо от того, что вам подходит.
Страницы на панели инструментов могли наследоваться от состояния, которое требует, чтобы пользователи вошли в систему и, скажем, были членом роли User
. Все материалы авторизации, которые мы обсуждали, будут поступать оттуда.
Мне кажется, вам нужен service
, который обрабатывает процесс аутентификации (и его хранение).
В этой службе вам понадобятся некоторые основные методы:
isAuthenticated()
login()
logout()
Эта услуга должна быть введена в ваших контроллерах каждого модуля:
service.isAuthenticated()
). если нет, перенаправляйте / login service.login()
. Хороший и надежный пример для этого поведения является проект angular-app и, в частности, его модуль безопасности , который основан на удивительном HTTP-аут-перехватчике
Надеюсь, это поможет
У меня есть другое решение: это решение отлично работает, когда у вас есть только контент, который вы хотите показать при входе в систему. Определите правило, в котором вы проверяете, если вы вошли в систему, а не путь к белым спискам.
$urlRouterProvider.rule(function ($injector, $location) {
var UserService = $injector.get('UserService');
var path = $location.path(), normalized = path.toLowerCase();
if (!UserService.isLoggedIn() && path.indexOf('login') === -1) {
$location.path('/login/signin');
}
});
В моем примере я спрашиваю, не входит ли я в систему, а текущий маршрут, который я хочу использовать, не является частью `/ login ', потому что мои маршруты в белом списке следующие
/login/signup // registering new user
/login/signin // login to app
, поэтому у меня есть мгновенный доступ к этим двум маршрутам, и каждый другой маршрут будет проверяться, если вы в сети.
Вот мой весь файл маршрутизации для модуля входа в систему
export default (
$stateProvider,
$locationProvider,
$urlRouterProvider
) => {
$stateProvider.state('login', {
parent: 'app',
url: '/login',
abstract: true,
template: '<ui-view></ui-view>'
})
$stateProvider.state('signin', {
parent: 'login',
url: '/signin',
template: '<login-signin-directive></login-signin-directive>'
});
$stateProvider.state('lock', {
parent: 'login',
url: '/lock',
template: '<login-lock-directive></login-lock-directive>'
});
$stateProvider.state('signup', {
parent: 'login',
url: '/signup',
template: '<login-signup-directive></login-signup-directive>'
});
$urlRouterProvider.rule(function ($injector, $location) {
var UserService = $injector.get('UserService');
var path = $location.path();
if (!UserService.isLoggedIn() && path.indexOf('login') === -1) {
$location.path('/login/signin');
}
});
$urlRouterProvider.otherwise('/error/not-found');
}
() => { /* code */ }
является синтаксисом ES6, используйте вместо этого function() { /* code */ }
Сначала вам понадобится услуга, которую вы можете ввести в свои контроллеры, которые имеют некоторое представление о состоянии аутентификации приложения. Сохранение данных аутентификации с помощью локального хранилища - достойный способ приблизиться к нему.
Затем вам нужно будет проверить состояние auth перед изменениями состояния. Поскольку у вашего приложения есть несколько страниц, которые должны быть аутентифицированы, а другие - нет, создайте родительский маршрут, который проверяет auth, и сделает все остальные страницы, которые требуют того же самого, быть дочерним элементом этого родителя.
Наконец, вам нужно каким-то образом определить, может ли ваш пользователь в настоящий момент выполнять определенные операции. Этого можно достичь, добавив функцию «can» к вашей службе auth. Может принимать два параметра: - действие - требуется - (например, «manage_dashboards» или «create_new_dashboard») - объект - необязательный - объект, который работает. Например, если у вас есть объект панели мониторинга, вы можете проверить, есть ли dashboard.ownerId === loggedInUser.id. (Конечно, информация, переданная от клиента, никогда не должна быть доверенной, и вы всегда должны ее проверять на сервере, прежде чем записывать ее в свою базу данных).
angular.module('myApp', ['ngStorage']).config([
'$stateProvider',
function(
$stateProvider
) {
$stateProvider
.state('home', {...}) //not authed
.state('sign-up', {...}) //not authed
.state('login', {...}) //not authed
.state('authed', {...}) //authed, make all authed states children
.state('authed.dashboard', {...})
}])
.service('context', [
'$localStorage',
function(
$localStorage
) {
var _user = $localStorage.get('user');
return {
getUser: function() {
return _user;
},
authed: function() {
return (_user !== null);
},
// server should return some kind of token so the app
// can continue to load authenticated content without having to
// re-authenticate each time
login: function() {
return $http.post('/login.json').then(function(reply) {
if (reply.authenticated === true) {
$localStorage.set(_userKey, reply.user);
}
});
},
// this request should expire that token, rendering it useless
// for requests outside of this session
logout: function() {
return $http.post('logout.json').then(function(reply) {
if (reply.authenticated === true) {
$localStorage.set(_userKey, reply.user);
}
});
},
can: function(action, object) {
if (!this.authed()) {
return false;
}
var user = this.getUser();
if (user && user.type === 'admin') {
return true;
}
switch(action) {
case 'manage_dashboards':
return (user.type === 'manager');
}
return false;
}
}
}])
.controller('AuthCtrl', [
'context',
'$scope',
function(
context,
$scope
) {
$scope.$root.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) {
//only require auth if we're moving to another authed page
if (toState && toState.name.indexOf('authed') > -1) {
requireAuth();
}
});
function requireAuth() {
if (!context.authed()) {
$state.go('login');
}
}
}]
** ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: вышеуказанный код является псевдокодом и не содержит гарантий **
Решения, опубликованные до сих пор, на мой взгляд, излишне сложны. Есть более простой способ. Документация ui-router
говорит, что прослушивает $locationChangeSuccess
и использует $urlRouter.sync()
, чтобы проверить переход состояния, остановить его или возобновить. Но даже это на самом деле не работает.
Однако, вот две простые альтернативы. Выберите один:
$locationChangeSuccess
Вы можете прослушать $locationChangeSuccess
, и вы можете выполнить некоторую логику, даже асинхронную логику. Исходя из этой логики, вы можете позволить функции вернуть неопределенное, что приведет к продолжению перехода состояния как обычно, или вы можете сделать $state.go('logInPage')
, если пользователь должен пройти аутентификацию. Вот пример:
angular.module('App', ['ui.router'])
// In the run phase of your Angular application
.run(function($rootScope, user, $state) {
// Listen to '$locationChangeSuccess', not '$stateChangeStart'
$rootScope.$on('$locationChangeSuccess', function() {
user
.logIn()
.catch(function() {
// log-in promise failed. Redirect to log-in page.
$state.go('logInPage')
})
})
})
Имейте в виду, что это фактически не предотвращает загрузку целевого состояния, но оно перенаправляется на страницу входа в систему, если пользователь несанкционирован.
resolve
state A
. Они нажимают ссылку, чтобы перейти к protected state B
, но вы хотите перенаправить их на logInPage
. Если нет $timeout
, ui-router
просто остановит все переходы состояния, поэтому пользователь застрял бы в state A
. $timeout
позволяет ui-router
сначала предотвратить первоначальный переход на protected state B
, потому что решение было отклонено, и после этого оно перенаправляется на logInPage
.
– M.K. Safi
21 April 2015 в 17:53
authenticate
передается как параметр в ui-router
. Вам не нужно называть это самостоятельно. ui-router
вызывает его.
– M.K. Safi
9 June 2015 в 16:05
I Создал этот модуль, чтобы помочь сделать этот фрагмент процесса
. Вы можете делать такие вещи, как:
$routeProvider
.state('secret',
{
...
permissions: {
only: ['admin', 'god']
}
});
Или также
$routeProvider
.state('userpanel',
{
...
permissions: {
except: ['not-logged-in']
}
});
Это новенький, но стоит проверить!
Я хотел поделиться другим решением, работающим с ui router 1.0.0.X
Как вы знаете, stateChangeStart и stateChangeSuccess теперь устарели. https://github.com/angular-ui/ui-router/issues/2655
Вместо этого вы должны использовать $ transitions http: // angular-ui. github.io/ui-router/1.0.0-alpha.1/interfaces/transition.ihookregistry.html
Вот как я это достиг:
Сначала я have и AuthService с некоторыми полезными функциями
angular.module('myApp')
.factory('AuthService',
['$http', '$cookies', '$rootScope',
function ($http, $cookies, $rootScope) {
var service = {};
// Authenticates throug a rest service
service.authenticate = function (username, password, callback) {
$http.post('api/login', {username: username, password: password})
.success(function (response) {
callback(response);
});
};
// Creates a cookie and set the Authorization header
service.setCredentials = function (response) {
$rootScope.globals = response.token;
$http.defaults.headers.common['Authorization'] = 'Bearer ' + response.token;
$cookies.put('globals', $rootScope.globals);
};
// Checks if it's authenticated
service.isAuthenticated = function() {
return !($cookies.get('globals') === undefined);
};
// Clear credentials when logout
service.clearCredentials = function () {
$rootScope.globals = undefined;
$cookies.remove('globals');
$http.defaults.headers.common.Authorization = 'Bearer ';
};
return service;
}]);
Тогда у меня есть эта конфигурация:
angular.module('myApp', [
'ui.router',
'ngCookies'
])
.config(['$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/resumen');
$stateProvider
.state("dashboard", {
url: "/dashboard",
templateUrl: "partials/dashboard.html",
controller: "dashCtrl",
data: {
authRequired: true
}
})
.state("login", {
url: "/login",
templateUrl: "partials/login.html",
controller: "loginController"
})
}])
.run(['$rootScope', '$transitions', '$state', '$cookies', '$http', 'AuthService',
function ($rootScope, $transitions, $state, $cookies, $http, AuthService) {
// keep user logged in after page refresh
$rootScope.globals = $cookies.get('globals') || {};
$http.defaults.headers.common['Authorization'] = 'Bearer ' + $rootScope.globals;
$transitions.onStart({
to: function (state) {
return state.data != null && state.data.authRequired === true;
}
}, function () {
if (!AuthService.isAuthenticated()) {
return $state.target("login");
}
});
}]);
Вы можете видеть, что я использую
data: {
authRequired: true
}
для отметьте состояние доступным только в том случае, если оно аутентифицировано.
, затем на .run я использую переходы для проверки состояния, прошедшего проверку.
$transitions.onStart({
to: function (state) {
return state.data != null && state.data.authRequired === true;
}
}, function () {
if (!AuthService.isAuthenticated()) {
return $state.target("login");
}
});
Я построю этот пример, используя некоторый код, найденный в документация по переходу $. Я довольно новичок в маршрутизаторе ui, но он работает.
Надеюсь, это может помочь кому угодно.
Вот как мы вышли из бесконечного цикла маршрутизации и все еще использовали $state.go
вместо $location.path
if('401' !== toState.name) {
if (principal.isIdentityResolved()) authorization.authorize();
}
Использовать $ http Interceptor
С помощью перехватчика $ http вы можете отправлять заголовки в конец или наоборот, а также выполнять свои проверки.
Отличная статья о перехватчиках $ http
Пример:
$httpProvider.interceptors.push(function ($q) {
return {
'response': function (response) {
// TODO Create check for user authentication. With every request send "headers" or do some other check
return response;
},
'responseError': function (reject) {
// Forbidden
if(reject.status == 403) {
console.log('This page is forbidden.');
window.location = '/';
// Unauthorized
} else if(reject.status == 401) {
console.log("You're not authorized to view this page.");
window.location = '/';
}
return $q.reject(reject);
}
};
});
Поместите это в свою .config или .run функцию.
$location.path
вместо$state.go
. – jvannistelrooy 23 April 2014 в 12:11$scope.user
в вашей функцииthen
. Вы по-прежнему можете ссылаться наuser
в своих представлениях; когда он будет разрешен, представление будет обновлено. – HackedByChinese 21 June 2014 в 22:46$q.when(angular.noop).then(function(){$state.go('myState')
, все работает так, как ожидалось. Если я назову$state.go
, пока другой переход состояния не завершен, то это не сработает (я думаю, что это причина, по которой он не будет работать). – Sebastian 3 July 2014 в 12:29