在上周,我创建了一个完整的示例Web应用程序,以演示使用AngularJS的自定义登录表单实现。这对我来说并不容易,因为我是一名Java开发人员,而且我没有太多的JavaScript经验。但最后我的代码工作正常。华友世纪: - )
我想与您分享我的解决方案,如果您能看一下并建议我进行任何简化,我会很高兴。请随意批评我的JavaScript代码。
在我的演示应用程序中,服务器端资源(图像,html页面和RESTFul api)受OpenAM保护。
为了避免OpenAm中受保护网址的错误和复杂配置,我在我的网络应用程序中创建了两个文件夹,一个用于公共内容,另一个用于受保护内容。
我的war文件(web-app)的结构如下所示:
web-ui.war
|-app
| |-components
| |-protected <-- protected web folder by OpenAM
| | |-welcome
| | |-welcome.html
| |-public
| | |-help
| | | |-help-general.html
| | | |-help-howItWorks.html
| | |-home
| | | |-home.html
| | |-log-in
| | | |-logIn.html
| | | |-logIn-controller.js
| | | |-logIn-factory.js
| | |-sign-up
| | | sign-up.html
| | |-app-directive.js
| | |-app-module.js
| | |-app-route.js
|- assets
| |-css
| | |-app.css
| |-img
| |-libs
| | |-angular
| | | |-1.4.7
| | | |...
| | |-angular-ui
| | | |-0.2.15
| | | |...
| | |-bootstrap
| | | |-3.3.5
| | | |...
| | |-ui-bootstrap
| | | |-0.14.2
| | | |...
|-WEB-INF
|-index.html
由于我的文件夹结构合适,OpenAM配置非常简单。 “未强制URI(没有任何保护的公共网址)”的值:
前三个网址位于我的演示网络应用程序(war文件)中。我有另一个公共网址。它是一个身份验证java servlet,它接收用户名/密码并将它们传递给OpenAM。 OpenAM将生成一个正确的令牌,我的servlet将它发送回客户端的浏览器。
我使用AngularJS v1.4.7,ui-bootstrap v0.14.2和angular-ui v0.2.15进行路由。
我的index.html页面非常简单:
<!DOCTYPE html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="utf-8">
<title>MyApp</title>
<!-- Bootstrap Core CSS -->
<!--<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" type="text/css">-->
<link rel="stylesheet" href="assets/libs/bootstrap/3.3.5/css/bootstrap.min.css" type="text/css">
<!-- Custom CSS -->
<link rel="stylesheet" href="assets/css/app.css" type="text/css">
</head>
<body>
<!-- Page Header -->
<header id="navigation-bar">
<div class="..." ng-click="navCollapsed = true">
<ul class="nav navbar-nav navbar-right">
<li><a ui-sref="protected/welcome">private page</a></li>
<li><a ui-sref="help-general">Help</a></li>
<li><a ui-sref="sign-up">Register</a></li>
<li><a ui-sref="log-in">Login</a></li>
</ul>
</div>
</nav>
</header>
<!-- Page Body -->
<div ui-view></div>
<!-- AngularJS -->
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.15/angular-ui-router.js"></script>
<!-- Angular UI Bootstrap -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-bootstrap/0.14.2/ui-bootstrap-tpls.min.js"></script>
<!-- Application JavaScript files -->
<script src="app/components/public/app-module.js"></script>
<script src="app/components/public/app-directive.js"></script>
<script src="app/components/public/app-routes.js"></script>
<!-- JavaScripts for Login page -->
<script src="app/components/public/log-in/logIn-controller.js"></script>
<script src="app/components/public/log-in/logIn-factory.js"></script>
</body>
</html>
app-module.js文件:
var app = angular.module("myApp", ['ui.bootstrap', 'ui.router']);
我创建了两个指令来处理文本框中的onEnter事件,并将焦点设置为(显示闪烁的光标)文本元素。
app-directive.js文件:
app.directive('myEnter', function () {
return function (scope, element, attrs) {
element.bind("keydown keypress", function (event) {
if(event.which === 13) {
scope.$apply(function (){
scope.$eval(attrs.myEnter);
});
event.preventDefault();
}
});
};
});
app.directive('autoFocus', function($timeout) {
return {
restrict: 'AC',
link: function(_scope, _element) {
$timeout(function(){
_element[0].focus();
}, 0);
}
};
});
app-routes.js文件的重要部分是' data:{requiresLogin:true} '。该行告诉客户端用户想要访问受保护内容的页面路由逻辑。如果用户未登录,则需要显示登录页面,登录成功后,需要显示原始请求页面。
此js文件中的另一个重要行与'$ stateChangeStart'事件有关。每次更改URL时都会触发(模板html路由开始工作)。在这种情况下,如果需要显示登录页面,则将请求的URL存储在loginFactory中。在登录过程之后,需要显示原始请求页面,因此我们需要此信息以供进一步使用。
app-routes.js文件:
app.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/home');
$stateProvider
// public pages
.state('home', {
url: '/home',
templateUrl: 'app/components/public/home/home.html'
})
.state('log-in', {
url: '/log-in',
templateUrl: 'app/components/public/log-in/logIn.html'
})
.state('sign-up', {
url: '/sign-up',
templateUrl: 'app/components/public/sign-up/signUp.html'//,
})
.state('help-general', {
url: '/help-general',
templateUrl: 'app/components/public/help/help-general.html'
})
.state('help-howItWorks', {
url: '/help-howItWorks',
templateUrl: 'app/components/public/help/help-howItWorks.html'
})
// private pages
.state('private/welcome', {
url: '/private/welcome',
templateUrl: 'app/components/protected/welcome/welcome.html',
data : { requiresLogin: true }
});
});
app.run(function($rootScope, $state, loginFactory) {
$rootScope.$on('$stateChangeStart', function(event, toState) {
var isAuthenticationRequired = toState.data && toState.data.requiresLogin && !loginFactory.isLoggedIn();
if (isAuthenticationRequired == true) {
loginFactory.setToState(toState);
event.preventDefault();
$state.go('log-in');
}
});
});
在我的login.html文件中,autoFocus指令用于用户名文本框,my-enter指令用于密码按钮。如果用户在键入密码后按Enter键,则调用控制器的login()方法。当用户单击登录按钮时,将应用相同的行为。
在显示login.html之前,我们调用控制器的reset()方法。它会清除以前密码文本框的值,并隐藏“无效的用户名或密码”标签。
login.html文件,没有任何设计元素:
<div class="..." ng-controller="loginController" data-ng-init="reset()">
<h3>Login or <a ui-sref="user-registration">Sign up</a></h3>
<div class="...">
<div class="col-xs-12 col-sm-6">
<input type="text" class="..." placeholder="email address" ng-model="user.username" auto-focus>
<input type="password" class="..." placeholder="Password" ng-model="user.password" my-enter="login()">
<span class="..." ng-if="!firstAttempt">Invalid username or password</span>
<span class="..." ng-if="firstAttempt"></span>
<button class="..." ng-click="login()">Login</button>
</div>
</div>
login-factory.js将用户名和密码发送到auth servlet,并将用户重定向到下一个请求的页面。 login.factory.js文件:
app.factory('loginFactory', function($http) {
var loggedIn = false;
var toState;
return {
isLoggedIn: function() {
return loggedIn;
},
setToState: function(state) {
toState = state;
},
callAuthService: function($location, $scope) {
var formData = 'username=' + $scope.user.username + '&password=' + $scope.user.password;
var response = $http({
method: 'POST',
url: 'http://...:8080/loginservlet/public/auth',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
data: formData})
.success(function (data, status, headers, config) {
loggedIn = true;
if (toState) {
$location.path(toState.url);
toState = undefined;
} else
$location.path('private/welcome');
})
.error(function (data, status, headers, config) {
loggedIn = false;
});
}
};
});
login-controller.js文件:
app.controller('loginController', function($scope, loginFactory, $location) {
$scope.loggedIn = false;
$scope.user = {};
$scope.firstAttempt = true;
$scope.login = function() {
loginFactory.callAuthService($location, $scope);
$scope.firstAttempt = false;
};
$scope.reset = function() {
$scope.firstAttempt = true;
$scope.user.password = '';
}
});
就是这样。
我有一些问题:
从/ app / components / protected区域延迟加载js文件:I 想保留模板相关的js文件(例如:控制器) 与html文件相同的目录,并与它们一起加载它们 模板html文件。有没有现成的最佳做法?
我的auth servlet将带有令牌的cookie发送回浏览器和 当新网址出现时,浏览器会将令牌cookie返回给服务器 请求。如果cookie中的令牌是正确的,那么OpenAM给出 访问受保护的资源。我读到cookie不是安全的 存储令牌信息的方法。而不是我想使用HTML5 功能:将其存储在浏览器的语言环境存储中并发送 每次请求都自动到服务器。你能帮我实现吗?
这个问题与指令有关:如果我将'myEnter'指令重命名为'onEnter'或'customEnter'o'caEnter',那么它将不再起作用。 为什么?我是否只能使用'my'prefx befote指令的名称?
我尝试遵循建议的AngularJS命名约定 相关的JavaScript文件,现在我有很多。就是这样 将所有这些内容都包含在index.html文件中我感到很不舒服 认为它对Web应用程序的性能不利。是 有任何最佳实践来合并/组合“数百万”分开 在应用程序进入生产服务器之前将JavaScript文件合并为一个文件?