Spring Security Login Page avec Angular

Page de connexion Spring Security avec Angular

1. Vue d'ensemble

Dans ce tutoriel, nous allons créer unlogin page using Spring Security with:

  • 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 sécurité de printemps

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 aura deux - un pour la connexion et l'autre pour récupérer les données de l'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 également consulter notre autre didacticiel surSpring Security OAuth2 si vous souhaitez mettre en œuvre 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 utilisentnpm pour la gestion des dépendances etnodejs 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 ultérieure utilise TypeScript comme langage principal. Par conséquent, l'application nécessite également certains fichiers de support nécessaires au bon fonctionnement de l'application.

En raison des améliorations incrémentielles apportées à 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 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 fichierindex.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'attributng-view en fonction de la logique de routage.

Créons maintenant lesapp.js qui définissent 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, leslogin.controller.js et leslogin.view.html.

Examinons le premier:

Login

Username is required
Password is required

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

Le contrôleur appelle 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 ultérieure.

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

Hi {{vm.user}}!

You're logged in!!

Logout

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

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

Maintenant, installonshttp-server pour exécuter l'application Angular:

npm install http-server --save

Une fois celui-ci installé, vous pouvez ouvrir le dossier racine du projet à l’invite de commande et exécuter la commande suivante:

http-server -o

4.2. Utilisation de la version angulaire 2, 4, 5

Leindex.html de la version 2 diffère légèrement de la version AngularJS:




    
    
    
    

    
    


    Loading...

Lemain.ts est le point d'entrée principal de l'application. Il amorce le module d’application et le navigateur charge donc la page de connexion:

platformBrowserDynamic().bootstrapModule(AppModule);

Leapp.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);

Leapp.module.ts déclare les composants et importe les modules concernés:

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

export class AppModule { }

Puisque nous créons une application à page unique, créons un composant racine qui y ajoute tous les composants enfants:

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

export class AppComponent { }

Lesapp.component.html n'auront qu'une balise<router-outlet>. L'Angular utilise cette balise pour son mécanisme de routage de localisation.

Créons maintenant le composant de connexion et son modèle correspondant danslogin.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.");
            }
        });
    }
}

Enfin, jetons un œil auxlogin.component.html:

Username is required
Password is required

4.3. Utilisation de Angular 6

L'équipe angulaire a apporté quelques améliorations à la version 6. En raison de ces changements, notre exemple sera également un peu différent des autres versions. Le seul changement que nous avons dans notre exemple par rapport à la version 6 concerne la partie appel de service.

Au lieu deHttpModule, la version 6 importeHttpClientModule depuis  @angular/common/http.

La partie appelant au service sera également un peu différente des versions antérieures:

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 à implémenter une page de connexion Spring Security avec Angular. À partir de la version 4, nous pouvons utiliser le projet Angular CLI pour faciliter le développement et les tests.

Comme toujours, tous les exemples dont nous avons parlé ici peuvent être trouvés surGithub project.