1. Overview
1.概述
In this tutorial, we’ll be creating a login page using Spring Security with:
在本教程中,我们将使用Spring Security创建一个登录页面,包括:。
- AngularJS
- Angular 2, 4, 5, and 6
The example application which we’re going to discuss here consists of a client application that communicates with the REST service, secured with basic HTTP authentication.
我们将在这里讨论的示例应用程序包括一个与REST服务进行通信的客户端应用程序,该应用程序使用基本的HTTP认证。
2. Spring Security Configuration
2.Spring安全配置
First of all, let’s set up the REST API with Spring Security and Basic Auth:
首先,让我们用Spring Security和Basic Auth设置REST API。
Here is how it’s configured:
以下是它的配置方式。
@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()
.disable()
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**")
.permitAll()
.antMatchers("/login")
.permitAll()
.anyRequest()
.authenticated()
.and()
.httpBasic();
return http.build();
}
}
Now let’s create the endpoints. Our REST service will have two – one for login and the other for fetching the user data:
现在让我们来创建端点。我们的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];
}
}
Likewise, you can check out our other tutorial about Spring Security OAuth2 as well if you’re interested in implementing an OAuth2 server for authorization.
同样,如果您对实施用于授权的OAuth2服务器感兴趣,您也可以查看我们关于Spring Security OAuth2的其他教程。
3. Setting Up the Angular Client
3.设置Angular客户端
Now that we have created the REST service, let’s set up the login page with different versions of the Angular client.
现在我们已经创建了REST服务,让我们用不同版本的Angular客户端来设置登录页面。
The examples which we’re going to see here use npm for dependency management and nodejs for running the application.
我们将在这里看到的例子使用npm进行依赖性管理,nodejs运行应用程序。
Angular uses a single page architecture where all the child components (in our case these are login and home components) are injected into a common parent DOM.
Angular使用了一个单页架构,其中所有的子组件(在我们的案例中,这些是登录和主页组件)都被注入到一个共同的父DOM。
Unlike AngularJS, which uses JavaScript, Angular version 2 onwards uses TypeScript as its main language. Hence the application also requires certain supporting files that are necessary for it to work correctly.
与使用JavaScript的AngularJS不同,Angular第二版以后使用TypeScript作为其主要语言。因此,该应用程序还需要某些支持文件,这些文件是它正常工作所必需的。
Due to the incremental enhancements of Angular, the files needed differs from version to version.
由于Angular的增量增强,所需的文件在不同的版本中有所不同。
Let’s get familiar with each of these:
让我们分别熟悉一下。
- systemjs.config.js – system configurations (version 2)
- package.json – node module dependencies (version 2 onwards)
- tsconfig.json – root level Typescript configurations (version 2 onwards)
- tsconfig.app.json – application level Typescript configurations (version 4 onwards)
- .angular-cli.json – Angular CLI configurations (version 4 and 5)
- angular.json – Angular CLI configurations (version 6 onwards)
4. Login Page
4.登录页面
4.1. Using AngularJS
4.1.使用AngularJS
Let’s create the index.html file and add the relevant dependencies to it:
让我们创建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>
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.
由于这是一个单页应用程序,所有的子组件将根据路由逻辑被添加到带有ng-view属性的div元素。
Now let’s create the app.js which defines the URL to component mapping:
现在让我们创建app.js,它定义了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');
}
});
}
})();
The login component consists of two files, the login.controller.js, and the login.view.html.
登录组件由两个文件组成,login.controller.js,和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': 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.")
}
});
};
}
})();
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.
该控制器将通过传递用户名和密码来调用REST服务。认证成功后,它将对用户名和密码进行编码,并将编码后的令牌存储在会话存储中,以供将来使用。
Similar to the login component, the home component also consists of two files, the home.view.html:
与登录组件类似,主页组件也由两个文件组成,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>
and the home.controller.js:
和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';
}
}
})();
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.
主控制器将通过传递Authorization头来请求用户数据。我们的REST服务只有在令牌有效时才会返回用户数据。
Now let’s install http-server for running the Angular application:
现在让我们安装http-server以运行Angular应用程序。
npm install http-server --save
Once this is installed, we can open the project root folder in command prompt and execute the command:
一旦安装完毕,我们就可以在命令提示符下打开项目根目录并执行命令。
http-server -o
4.2. Using Angular Version 2, 4, 5
4.2.使用Angular版本2、4、5
The index.html in version 2 differs slightly from the AngularJS version:
版本2中的index.html与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>
The main.ts is the main entry point of the application. It bootstraps the application module and as a result, the browser loads the login page:
main.ts是应用程序的主要入口点。它引导应用模块,结果是浏览器加载登录页面。
platformBrowserDynamic().bootstrapModule(AppModule);
The app.routing.ts is responsible for the application routing:
app.routing.ts负责应用程序的路由。
const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'login', component: LoginComponent },
{ path: '**', redirectTo: '' }
];
export const routing = RouterModule.forRoot(appRoutes);
The app.module.ts declares the components and imports the relevant modules:
app.module.ts声明组件并导入相关模块。
@NgModule({
imports: [
BrowserModule,
FormsModule,
HttpModule,
routing
],
declarations: [
AppComponent,
HomeComponent,
LoginComponent
],
bootstrap: [AppComponent]
})
export class AppModule { }
Since we’re creating a single page application, let’s create a root component which adds all the child components to it:
由于我们正在创建一个单页应用程序,让我们创建一个根组件,将所有的子组件添加到它上面。
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent { }
The app.component.html will have only a <router-outlet> tag. The Angular uses this tag for its location routing mechanism.
app.component.html将只有一个<router-outlet>/em>标签。Angular使用这个标签作为其位置路由机制。
Now let’s create the login component and its corresponding template in login.component.ts:
现在让我们在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.");
}
});
}
}
Finally, let’s have a look at the login.component.html:
最后,让我们看一下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>
4.3. Using Angular 6
4.3.使用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.
Angular团队在第6版中做了一些改进。由于这些变化,我们的例子与其他版本相比也会有一些不同。在我们的例子中,与第6版相比,唯一的变化是在服务调用部分。
Instead of HttpModule, the version 6 imports HttpClientModule from @angular/common/http.
代替HttpModule,版本6从 @angular/common/http.导入HttpClientModule。
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. Conclusion
5.结论
We’ve learned how to implement a Spring Security login page with Angular. From version 4 onwards, we can make use of the Angular CLI project for easy development and testing.
我们已经学会了如何用Angular实现一个Spring Security的登录页面。从第4版开始,我们可以利用Angular CLI项目来方便开发和测试。
As always all the example we’ve discussed here can be found over GitHub project.
一如既往,我们在这里讨论的所有例子都可以在GitHub项目上找到。