Page de connexion Spring Security avec Angular

1. Vue d'ensemble

Dans ce didacticiel, nous allons créer une page de connexion à l'aide de Spring Security avec:

  • AngularJS
  • Angulaire 2, 4, 5 et 6

L'exemple d'application dont nous allons parler ici consiste en une application cliente qui communique avec le service REST, sécurisée avec une authentification HTTP de base.

2. Configuration de la sécurité Spring

Tout d'abord, configurons l'API REST avec Spring Security et Basic Auth:

Voici comment il est configuré:

@Configuration @EnableWebSecurity public class BasicAuthConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user") .password("password") .roles("USER"); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests() .antMatchers("/login").permitAll() .anyRequest() .authenticated() .and() .httpBasic(); } }

Créons maintenant les points de terminaison. Notre service REST en aura deux - un pour la connexion et l'autre pour récupérer les données utilisateur:

@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]; } }

De même, vous pouvez consulter notre autre tutoriel sur Spring Security OAuth2 si vous souhaitez implémenter un serveur OAuth2 pour l'autorisation.

3. Configuration du client angulaire

Maintenant que nous avons créé le service REST, configurons la page de connexion avec différentes versions du client Angular.

Les exemples que nous allons voir ici utilisent npm pour la gestion des dépendances et nodejs pour exécuter l'application.

Angular utilise une architecture à page unique où tous les composants enfants (dans notre cas, ce sont des composants de connexion et d'accueil) sont injectés dans un DOM parent commun.

Contrairement à AngularJS, qui utilise JavaScript, Angular version 2 et suivantes utilise TypeScript comme langage principal. Par conséquent, l'application nécessite également certains fichiers de support nécessaires pour son bon fonctionnement.

En raison des améliorations incrémentielles d'Angular, les fichiers nécessaires diffèrent d'une version à l'autre.

Familiarisons-nous avec chacun de ces éléments:

  • systemjs.config.js - configurations système (version 2)
  • package.json - dépendances du module de nœud (à partir de la version 2)
  • tsconfig.json - configurations Typescript au niveau racine (à partir de la version 2)
  • tsconfig.app.json - configurations Typescript au niveau de l'application (à partir de la version 4)
  • .angular- cli .json - Configurations CLI angulaire (versions 4 et 5)
  • angular.json - Configurations CLI angulaires (à partir de la version 6)

4. Page de connexion

4.1. Utilisation d'AngularJS

Créons le fichier index.html et ajoutons-y les dépendances pertinentes:

Puisqu'il s'agit d'une application à page unique, tous les composants enfants seront ajoutés à l'élément div avec l' attribut ng-view basé sur la logique de routage.

Créons maintenant le fichier app.js qui définit le mappage de l'URL vers le composant:

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

Le composant de connexion se compose de deux fichiers, le login.controller.js et le login.view.html.

Regardons le premier:

Login

Username Username is required Password Password is required Login

et le second:

(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: '//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.") } }); }; } })();

Le contrôleur appellera le service REST en transmettant le nom d'utilisateur et le mot de passe. Une fois l'authentification réussie, il codera le nom d'utilisateur et le mot de passe et stockera le jeton codé dans le stockage de session pour une utilisation future.

Semblable au composant de connexion, le composant home se compose également de deux fichiers, le home.view.html :

You're logged in!!

Logout

et le 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: '//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'; } } })();

Le contrôleur domestique demandera les données utilisateur en passant l'en- tête d' autorisation . Notre service REST renverra les données utilisateur uniquement si le jeton est valide.

Maintenant, installons le serveur http pour exécuter l'application Angular:

npm install http-server --save

Une fois que cela est installé, nous pouvons ouvrir le dossier racine du projet dans l'invite de commande et exécuter la commande:

http-server -o

4.2. Utilisation des versions angulaires 2, 4, 5

L' index.html de la version 2 diffère légèrement de la version AngularJS:

         System.import('app').catch(function (err) { console.error(err); });    Loading...  

Le main.ts est le principal point d'entrée de l'application. Il démarre le module d'application et, par conséquent, le navigateur charge la page de connexion:

platformBrowserDynamic().bootstrapModule(AppModule);

Le app.routing.ts est responsable du routage de l'application:

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:

@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 tag. The Angular uses this tag for its location routing mechanism.

Now let's create the login component and its corresponding template in 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 = '//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:

 Username Username is required Password Password is required Login 

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

Au lieu de HttpModule , la version 6 importe HttpClientModule depuis @ angular / common / http.

La partie appel de service sera également un peu différente des anciennes versions:

this.http.post
    
     (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

Nous avons appris comment implémenter une page de connexion Spring Security avec Angular. À partir de la version 4, nous pouvons utiliser le projet Angular CLI pour un développement et des tests faciles.

Comme toujours, tous les exemples dont nous avons discuté ici peuvent être trouvés sur le projet GitHub.