Spring Security-Anmeldeseite mit Angular

Anmeldeseite für Spring Security mit Angular

1. Überblick

In diesem Tutorial erstellen wir einlogin page using Spring Security with:

  • AngularJS

  • Winkel 2, 4, 5 und 6

Die Beispielanwendung, die wir hier diskutieren werden, besteht aus einer Clientanwendung, die mit dem REST-Service kommuniziert und mit einer grundlegenden HTTP-Authentifizierung gesichert ist.

2. Spring-Sicherheitskonfiguration

Lassen Sie uns zunächst die REST-API mit Spring Security und Basic Auth einrichten:

So ist es konfiguriert:

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

Jetzt erstellen wir die Endpunkte. Unser REST-Service verfügt über zwei Funktionen - eine zum Anmelden und eine zum Abrufen der Benutzerdaten:

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

Ebenso können Sie unser anderes Tutorial zuSpring Security OAuth2lesen, wenn Sie einen OAuth2-Server zur Autorisierung implementieren möchten.

3. Angular Client einrichten

Nachdem wir den REST-Service erstellt haben, richten wir die Anmeldeseite mit verschiedenen Versionen des Angular-Clients ein.

In den Beispielen, die wir hier sehen werden, werdennpm für das Abhängigkeitsmanagement undnodejs für die Ausführung der Anwendung verwendet.

Angular verwendet eine einzelne Seitenarchitektur, bei der alle untergeordneten Komponenten (in unserem Fall Login- und Home-Komponenten) in ein gemeinsames übergeordnetes DOM eingefügt werden.

Im Gegensatz zu AngularJS, das JavaScript verwendet, verwendet Angular ab Version 2 TypeScript als Hauptsprache. Daher benötigt die Anwendung auch bestimmte unterstützende Dateien, die für das ordnungsgemäße Funktionieren erforderlich sind.

Aufgrund der inkrementellen Verbesserungen von Angular unterscheiden sich die benötigten Dateien von Version zu Version.

Machen wir uns mit jedem dieser Punkte vertraut:

  • systemjs.config.js - Systemkonfigurationen (Version 2)

  • package.json - Knotenmodulabhängigkeiten (ab Version 2)

  • tsconfig.json - Typescript-Konfigurationen auf Stammebene (ab Version 2)

  • tsconfig.app.json - Typescript-Konfigurationen auf Anwendungsebene (ab Version 4)

  • .angular-cli_.json_ - Angular CLI-Konfigurationen (Version 4 und 5)

  • angular.json - Angular CLI-Konfigurationen (ab Version 6)

4. Loginseite

4.1. Verwenden von AngularJS

Erstellen wir dieindex.html-Datei und fügen die entsprechenden Abhängigkeiten hinzu:



    

Da es sich um eine Einzelseitenanwendung handelt, werden alle untergeordneten Komponenten mit dem Attributng-viewbasierend auf der Routing-Logik zum div-Element hinzugefügt.

Erstellen wir nun dieapp.js, die die URL zur Komponentenzuordnung definieren:

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

Die Anmeldekomponente besteht aus zwei Dateien,login.controller.js undlogin.view.html.

Schauen wir uns den ersten an:

Login

Username is required
Password is required

und der zweite:

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

Der Controller ruft den REST-Service auf, indem er den Benutzernamen und das Kennwort übergibt. Nach der erfolgreichen Authentifizierung werden der Benutzername und das Kennwort verschlüsselt und das verschlüsselte Token zur späteren Verwendung im Sitzungsspeicher gespeichert.

Ähnlich wie die Anmeldekomponente besteht auch die Home-Komponente aus zwei Dateien,home.view.html:

Hi {{vm.user}}!

You're logged in!!

Logout

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

Der Heimcontroller fordert die Benutzerdaten an, indem er den HeaderAuthorizationübergibt. Unser REST-Service gibt die Benutzerdaten nur zurück, wenn der Token gültig ist.

Installieren wir nunhttp-server, um die Angular-Anwendung auszuführen:

npm install http-server --save

Sobald dies installiert ist, können wir den Projektstammordner in der Eingabeaufforderung öffnen und den Befehl ausführen:

http-server -o

4.2. Verwenden von Angular Version 2, 4, 5

Dieindex.html in Version 2 unterscheiden sich geringfügig von der AngularJS-Version:




    
    
    
    

    
    


    Loading...

Dasmain.ts ist der Haupteinstiegspunkt der Anwendung. Es bootet das Anwendungsmodul und als Ergebnis lädt der Browser die Anmeldeseite:

platformBrowserDynamic().bootstrapModule(AppModule);

Dasapp.routing.ts ist für das Anwendungsrouting verantwortlich:

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

export const routing = RouterModule.forRoot(appRoutes);

Dasapp.module.ts deklariert die Komponenten und importiert die relevanten Module:

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

export class AppModule { }

Da wir eine Einzelseitenanwendung erstellen, erstellen wir eine Stammkomponente, die alle untergeordneten Komponenten hinzufügt:

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

export class AppComponent { }

Dieapp.component.html haben nur ein<router-outlet>-Tag. Der Angular verwendet dieses Tag für seinen Standort-Routing-Mechanismus.

Jetzt erstellen wir die Anmeldekomponente und die entsprechende Vorlage inlogin.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.");
            }
        });
    }
}

Schauen wir uns zum Schluss dielogin.component.html an:

Username is required
Password is required

4.3. Verwenden von Angular 6

Angular Team hat einige Verbesserungen in Version 6 vorgenommen. Aufgrund dieser Änderungen unterscheidet sich unser Beispiel auch ein wenig von anderen Versionen. Die einzige Änderung, die wir in unserem Beispiel in Bezug auf Version 6 vorgenommen haben, betrifft den Dienstaufrufteil.

Anstelle vonHttpModule importiert die Version 6HttpClientModule aus  @angular/common/http.

Der Service Calling Part unterscheidet sich auch ein wenig von älteren Versionen:

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

Wir haben gelernt, wie Sie eine Spring Security-Anmeldeseite mit Angular implementieren. Ab Version 4 können wir das Angular CLI-Projekt zum einfachen Entwickeln und Testen verwenden.

Wie immer finden Sie alle hier diskutierten Beispiele überGithub project.