知識庫 / Spring / Spring Security RSS 訂閱

Spring Security 登錄頁面與 Angular

Spring Security
HongKong
10
01:42 PM · Dec 06 ,2025

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 – systemjs 配置(版本 2)
  • package.json – node 模塊依賴(版本 2 及更高版本)
  • tsconfig.json – 根級別的 TypeScript 配置(版本 2 及更高版本)
  • tsconfig.app.json – 應用級別的 TypeScript 配置(版本 4 及更高版本)
  • .angular-cli.json – Angular CLI 配置(版本 4 和 5)
  • angular.json – Angular CLI 配置(版本 6 及更高版本)

4. 登錄頁面

4.1. 使用 AngularJS

讓我們創建 index.html 文件並添加相關的依賴項:

<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>

由於這是一個單頁應用程序,所有子組件都將根據路由邏輯添加到具有 屬性的 div 元素中。

現在,讓我們創建 ,它定義了組件與 URL 的映射關係:

(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');
            }
        });
    }
})();

登錄組件由兩個文件組成,即 login.controller.jslogin.view.html

我們先來看第一個:

<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>

以及第二項:

(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': vm.username,
                    'password': vm.password
                }
            }).then(function (response) {
                if (response.data) {
                    var token
                      = $window.btoa(vm.username + ':' + vm.password);
                    var userData = {
                        userName: vm.username,
                        authData: token
                    }
                    $window.sessionStorage.setItem(
                      'userData', JSON.stringify(userData)
                    );
                    $http.defaults.headers.common['Authorization']
                      = 'Basic ' + token;
                    $location.path('/');
                } else {
                    alert("Authentication failed.")
                }
            });
        };
    }
})();

控制器將通過傳遞用户名和密碼來調用 REST 服務。在成功認證後,它會對用户名和密碼進行編碼,並將編碼後的令牌存儲在會話存儲中,以便後續使用。

類似於登錄組件,主頁組件也包含兩個文件,即 home.view.html

<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>

以及 home.controller.js:

(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';
        }
    }
})();

主控制器將通過傳遞 Authorization 標頭來請求用户數據。 我們的 REST 服務僅在令牌有效時才會返回用户數據。

現在,讓我們安裝 http-server 以運行 Angular 應用程序:

npm install http-server --save

一旦安裝完成,您可以在命令提示符下打開項目根目錄,並執行以下命令:

http-server -onpx http-server -o

4.2. 使用 Angular 版本 2、4、5

<em >index.html</em> 在版本 2 中與 AngularJS 版本略有不同:

<!DOCTYPE html>
<html>
<head>
    <base href="/" />
    <script src="node_modules/core-js/client/shim.min.js"></script>
    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>

    <script src="systemjs.config.js"></script>
    <script>
        System.import('app').catch(function (err) { console.error(err); });
    </script>
</head>
<body>
    <app>Loading...</app>
</body>
</html>

main.ts 是應用程序的主入口點。它啓動應用程序模塊,因此瀏覽器加載登錄頁面:

platformBrowserDynamic().bootstrapModule(AppModule);

app.routing.ts 負責應用程序的路由。

const appRoutes: Routes = [
    { path: '', component: HomeComponent },
    { path: 'login', component: LoginComponent },
    { path: '**', redirectTo: '' }
];

export const routing = RouterModule.forRoot(appRoutes);

<em/>app.module.ts</em>聲明瞭組件並導入相關模塊:

@NgModule({
    imports: [
        BrowserModule,
        FormsModule,
        HttpModule,
        routing
    ],
    declarations: [
        AppComponent,
        HomeComponent,
        LoginComponent
    ],
    bootstrap: [AppComponent]
})

export class AppModule { }

由於我們正在構建一個單頁應用程序,所以讓我們創建一個根組件,它將添加所有子組件:

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html'
})

export class AppComponent { }

app.component.html 僅包含一個 <router-outlet> 標籤。Angular 使用該標籤來實現其位置路由機制。

現在,讓我們創建登錄組件及其對應的模板,在login.component.ts 中:

@Component({
    selector: 'login',
    templateUrl: './app/login/login.component.html'
})

export class LoginComponent implements OnInit {
    model: any = {};

    constructor(
        private route: ActivatedRoute,
        private router: Router,
        private http: Http
    ) { }

    ngOnInit() {
        sessionStorage.setItem('token', '');
    }

    login() {
        let url = 'http://localhost:8082/login';
        let result = this.http.post(url, {
            userName: this.model.username,
            password: this.model.password
        }).map(res => res.json()).subscribe(isValid => {
            if (isValid) {
                sessionStorage.setItem(
                  'token',
                  btoa(this.model.username + ':' + this.model.password)
                );
                this.router.navigate(['']);
            } else {
                alert("Authentication failed.");
            }
        });
    }
}

最後,讓我們來查看一下 login.component.html

<form name="form" (ngSubmit)="f.form.valid && login()" #f="ngForm" novalidate>
    <div [ngClass]="{ 'has-error': f.submitted && !username.valid }">
        <label for="username">Username</label>
        <input type="text"
          name="username" [(ngModel)]="model.username"
            #username="ngModel" required />
        <div *ngIf="f.submitted
          && !username.valid">Username is required</div>
    </div>
    <div [ngClass]="{ 'has-error': f.submitted && !password.valid }">
        <label for="password">Password</label>
        <input type="password"
          name="password" [(ngModel)]="model.password"
            #password="ngModel" required />
        <div *ngIf="f.submitted
          && !password.valid">Password is required</div>
    </div>
    <div>
        <button [disabled]="loading">Login</button>
    </div>
</form>

現在我們來安裝 node_modules(所有本地依賴項,將在 npm 中下載)。

npm install.

在從 package.json 下載所有依賴項後,使用以下命令運行項目:

npm run lite — 用於運行 Angular 項目的輕量級服務器。

4.3. 使用 Angular 6

Angular 團隊在版本 6 中進行了一些增強。由於這些更改,我們的示例也會與其它版本略有不同。我們示例中與版本 6 唯一的變化在於服務調用部分。

不再使用 <em >HttpModule</em >》,而是從HttpClientModule>中導入,該模塊位於@angular/common/http>` 中。

服務調用部分也會與舊版本有所不同:

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 登錄頁面。從 Angular 4 版本開始,我們可以利用 Angular CLI 項目進行便捷的開發和測試。

本文檔的目的僅僅是為了展示如何使用 Angular 與 Spring Security 結合使用。請勿將其直接應用於生產環境。

user avatar
0 位用戶收藏了這個故事!
收藏

發佈 評論

Some HTML is okay.