1. 概述
在本教程中,我們將使用 Spring Security 創建一個 登錄頁面,內容包括:
- AngularJS
- Angular 2, 4, 5, 和 6
我們將討論的示例應用程序包含一個與 REST 服務通信的客户端應用程序,該應用程序使用基本 HTTP 身份驗證進行保護。
2. Spring Security 配置
首先,讓我們使用 Spring Security 和 Basic Auth 設置 REST API:
配置如下:
@Configuration
@EnableWebSecurity
public class BasicAuthConfiguration {
@Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails user = User.withUsername("user")
.password("{noop}password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.cors(withDefaults())
.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.requestMatchers("/login").permitAll()
.anyRequest().authenticated())
.httpBasic(withDefaults());
return http.build();
}
}
現在,讓我們創建端點。 我們的 REST 服務將有兩個 – 一個用於登錄,另一個用於獲取用户數據:
@RestController
@CrossOrigin
public class UserController {
@RequestMapping("/login")
public boolean login(@RequestBody User user) {
return
user.getUserName().equals("user") && user.getPassword().equals("password");
}
@RequestMapping("/user")
public Principal user(HttpServletRequest request) {
String authToken = request.getHeader("Authorization")
.substring("Basic".length()).trim();
return () -> new String(Base64.getDecoder()
.decode(authToken)).split(":")[0];
}
}
同樣,如果您對實施 OAuth2 服務器進行授權,可以查看我們的其他 Spring Security OAuth2 教程。
3. 設置 Angular 客户端
現在我們已經創建了 REST 服務,接下來將設置帶有不同版本的 Angular 客户端的登錄頁面。
這裏我們將使用的示例使用 npm 進行依賴管理,使用 nodejs 運行應用程序。
Angular 使用單頁面架構,其中所有子組件(在本例中為登錄和主頁組件)都注入到公共父 DOM 中。
與 AngularJS 相比,Angular 從版本 2 開始使用 TypeScript 作為主要語言。因此,應用程序還需要某些支持文件才能正確運行。
由於 Angular 的增量增強,不同版本的所需文件各不相同。
讓我們熟悉一下這些文件:
- systemjs.config.js – 系統配置(版本 2)
- package.json – 節點模塊依賴(版本 2 及更高版本)
- tsconfig.json – 根級別 TypeScript 配置(版本 2 及更高版本)
- tsconfig.app.json – 應用程序級別 TypeScript 配置(版本 4 及更高版本)
- .angular-cli.json – Angular CLI 配置(版本 4 和 5)
- angular.json – Angular CLI 配置(版本 6 及更高版本)
4. Login Page
4.1. Using AngularJSLet’s create the index.html file and add the relevant dependencies to it:
<html ng-app="app">
<body>
<div ng-view></div>
<script src="//code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="//code.angularjs.org/1.6.0/angular.min.js"></script>
<script src="//code.angularjs.org/1.6.0/angular-route.min.js"></script>
<script src="app.js"></script>
<script src="home/home.controller.js"></script>
<script src="login/login.controller.js"></script>
</body>
</html>
Since this is a single page application, all the child components will be added to the div element with ng-view attribute based on the routing logic.
Now let’s create the app.js which defines the URL to component mapping:
(function () {
'use strict';
angular
.module('app', ['ngRoute'])
.config(config)
.run(run);
config.$inject = ['$routeProvider', '$locationProvider'];
function config($routeProvider, $locationProvider) {
$routeProvider.when('/', {
controller: 'HomeController',
templateUrl: 'home/home.view.html',
controllerAs: 'vm'
}).when('/login', {
controller: 'LoginController',
templateUrl: 'login/login.view.html',
controllerAs: 'vm'
}).otherwise({ redirectTo: '/login' });
}
run.$inject = ['$rootScope', '$location', '$http', '$window'];
function run($rootScope, $location, $http, $window) {
var userData = $window.sessionStorage.getItem('userData');
if (userData) {
$http.defaults.headers.common['Authorization']
= 'Basic ' + JSON.parse(userData).authData;
}
$rootScope
.$on('$locationChangeStart', function (event, next, current) {
var restrictedPage
= $.inArray($location.path(), ['/login']) === -1;
var loggedIn
= $window.sessionStorage.getItem('userData');
if (restrictedPage && !loggedIn) {
$location.path('/login');
}
});
}
})();
The login component consists of two files, the login.controller.js, and the login.view.html.
Let’s look at the first one:
<h2>Login</h2>
<form name="form" ng-submit="vm.login()" role="form">
<div>
<label for="username">Username</label>
<input type="text" name="username" id="username" ng-model="vm.username" required />
<span ng-show="form.username.$dirty && form.username.$error.required">Username is required</span>
</div>
<div>
<label for="password">Password</label>
<input type="password" name="password" id="password" ng-model="vm.password" required />
<span ng-show="form.password.$dirty && form.password.$error.required">Password is required</span>
</div>
<div class="form-actions">
<button type="submit" ng-disabled="form.$invalid || vm.dataLoading">Login</button>
</div>
</form>
and the second one:
(function () {
'use strict';
angular
.module('app')
.controller('LoginController', LoginController);
LoginController.$inject = ['$location', '$window', '$http'];
function LoginController($location, $window, $http) {
var vm = this;
vm.login = login;
(function initController() {
$window.localStorage.setItem('token', '');
})();
function login() {
$http({
url: 'http://localhost:8082/login',
method: "POST",
data: {
'userName': this.username,
'password': this.password
}
}).then(function (response) {
if (response.data) {
var token
= $window.btoa(this.username + ':' + this.password);
var userData = {
userName: this.username,
authData: token
}
$window.sessionStorage.setItem(
'userData', JSON.stringify(userData)
);
$http.defaults.headers.common['Authorization']
= 'Basic ' + token;
$location.path('/');
} else {
alert("Authentication failed.");
}
});
};
}
})();
The controller will invoke the REST service by passing the username and password. After the successful authentication, it’ll encode the username and password and store the encoded token in session storage for future use.
Similar to the login component, the home component also consists of two files, the home.view.html and home.controller.js
Let’s look at the first one:
<h1>Hi {{vm.user}}!</h1>
<p>You're logged in!!</p>
<p><a href="#!/login" class="btn btn-primary" ng-click="logout()">Logout</a></p>
and the second one:
(function () {
'use strict';
angular
.module('app')
.controller('HomeController', HomeController);
HomeController.$inject = ['$window', '$http', '$scope'];
function HomeController($window, $http, $scope) {
var vm = this;
vm.user = null;
initController();
function initController() {
$http({
url: 'http://localhost:8082/user',
method: "GET"
}).then(function (response) {
vm.user = response.data.name;
}, function (error) {
console.log(error);
});
};
$scope.logout = function () {
$window.sessionStorage.setItem('userData', '');
$http.defaults.headers.common['Authorization'] = 'Basic';
}
}
})();
The home controller will request the user data by passing the Authorization header. Our REST service will return the user data only if the token is valid.
Now let’s install http-server for running the Angular application:
npm install http-server --save
After the instalation of all dependencies from package.json was downloaded run the project with this command:
npm run lite
— running a lite server for angular project.
4.2. Using Angular 6
Angular team has made some enhancements in version 6. Due to these changes, our example will also be a little different compared to other versions. The only change we’ve in our example with respect to version 6 is in the service calling part.
Instead of HttpModule, the version 6 imports HttpClientModule from @angular/common/http.
The service calling part will also be a little different from older versions:
this.http.post<Observable<boolean>>(url, {
userName: this.model.username,
password: this.model.password
}).subscribe(isValid => {
if (isValid) {
sessionStorage.setItem(
'token',
btoa(this.model.username + ':' + this.model.password)
);
this.router.navigate(['']);
} else {
alert("Authentication failed.");
}
});
5. 結論
我們學習瞭如何使用Angular實現Spring Security登錄頁面。從版本4開始,我們可以利用Angular CLI項目進行輕鬆的開發和測試。
本文檔的目的僅僅是為了展示如何使用Angular與Spring Security結合。請勿在生產環境中直接使用它。