Pagina di accesso Spring Security con Angular

1. Panoramica

In questo tutorial, creeremo una pagina di accesso utilizzando Spring Security con:

  • AngularJS
  • Angolare 2, 4, 5 e 6

L'applicazione di esempio di cui parleremo qui è costituita da un'applicazione client che comunica con il servizio REST, protetta dall'autenticazione HTTP di base.

2. Configurazione Spring Security

Prima di tutto, configuriamo l'API REST con Spring Security e Basic Auth:

Ecco come è configurato:

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

Ora creiamo gli endpoint. Il nostro servizio REST ne avrà due: uno per il login e l'altro per il recupero dei dati dell'utente:

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

Allo stesso modo, puoi anche consultare il nostro altro tutorial su Spring Security OAuth2 se sei interessato a implementare un server OAuth2 per l'autorizzazione.

3. Configurazione del client angolare

Ora che abbiamo creato il servizio REST, configuriamo la pagina di accesso con diverse versioni del client Angular.

Gli esempi che vedremo qui usano npm per la gestione delle dipendenze e nodejs per eseguire l'applicazione.

Angular utilizza un'architettura a pagina singola in cui tutti i componenti figlio (nel nostro caso sono componenti di login e home) vengono iniettati in un DOM genitore comune.

A differenza di AngularJS, che utilizza JavaScript, Angular dalla versione 2 in poi utilizza TypeScript come linguaggio principale. Pertanto l'applicazione richiede anche alcuni file di supporto necessari per il corretto funzionamento.

A causa dei miglioramenti incrementali di Angular, i file necessari differiscono da versione a versione.

Familiarizziamo con ciascuno di questi:

  • systemjs.config.js - configurazioni di sistema (versione 2)
  • package.json - dipendenze del modulo del nodo (dalla versione 2 in poi)
  • tsconfig.json - configurazioni di Typescript a livello di root (dalla versione 2 in poi)
  • tsconfig.app.json - configurazioni dei dattiloscritti a livello di applicazione (dalla versione 4 in poi)
  • .angular- cli .json - configurazioni della CLI angolare (versione 4 e 5)
  • angular.json : configurazioni della CLI angolare (dalla versione 6 in poi)

4. Pagina di accesso

4.1. Utilizzando AngularJS

Creiamo il file index.html e aggiungiamo le relative dipendenze:

Poiché si tratta di un'applicazione a pagina singola, tutti i componenti figlio verranno aggiunti all'elemento div con l' attributo ng-view in base alla logica di routing.

Ora creiamo l' app.js che definisce l'URL per la mappatura dei componenti:

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

Il componente di accesso è costituito da due file, login.controller.js e login.view.html.

Diamo un'occhiata al primo:

Login

Username Username is required Password Password is required Login

e il secondo:

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

Il controller richiamerà il servizio REST passando il nome utente e la password. Dopo l'autenticazione riuscita, codificherà il nome utente e la password e memorizzerà il token codificato nella memoria della sessione per un utilizzo futuro.

Simile al componente di accesso, anche il componente home è costituito da due file, home.view.html :

You're logged in!!

Logout

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

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.

Now let's install http-server for running the Angular application:

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

The index.html in version 2 differs slightly from the AngularJS version:

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

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:

platformBrowserDynamic().bootstrapModule(AppModule);

The app.routing.ts is responsible for the application routing:

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.

Invece di HttpModule , la versione 6 importa HttpClientModule da @ angular / common / http.

Anche la parte di chiamata del servizio sarà leggermente diversa dalle versioni precedenti:

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

Abbiamo imparato come implementare una pagina di accesso di Spring Security con Angular. Dalla versione 4 in poi, possiamo utilizzare il progetto Angular CLI per un facile sviluppo e test.

Come sempre tutti gli esempi che abbiamo discusso qui possono essere trovati nel progetto GitHub.