Página de inicio de sesión de Spring Security con Angular

1. Información general

En este tutorial, crearemos una página de inicio de sesión usando Spring Security con:

  • AngularJS
  • Angular 2, 4, 5 y 6

La aplicación de ejemplo que vamos a discutir aquí consiste en una aplicación cliente que se comunica con el servicio REST, asegurada con autenticación HTTP básica.

2. Configuración de seguridad de primavera

Primero que nada, configuremos la API REST con Spring Security y Basic Auth:

Así es como está configurado:

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

Ahora creemos los puntos finales. Nuestro servicio REST tendrá dos: uno para iniciar sesión y otro para obtener los datos del usuario:

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

Del mismo modo, también puede consultar nuestro otro tutorial sobre Spring Security OAuth2 si está interesado en implementar un servidor OAuth2 para la autorización.

3. Configuración del cliente angular

Ahora que hemos creado el servicio REST, configuremos la página de inicio de sesión con diferentes versiones del cliente Angular.

Los ejemplos que veremos aquí usan npm para la gestión de dependencias y nodejs para ejecutar la aplicación.

Angular usa una arquitectura de una sola página donde todos los componentes secundarios (en nuestro caso son componentes de inicio de sesión y de inicio) se inyectan en un DOM principal común.

A diferencia de AngularJS, que usa JavaScript, la versión 2 de Angular en adelante usa TypeScript como su lenguaje principal. Por lo tanto, la aplicación también requiere ciertos archivos de soporte que son necesarios para que funcione correctamente.

Debido a las mejoras incrementales de Angular, los archivos necesarios difieren de una versión a otra.

Familiaricémonos con cada uno de estos:

  • systemjs.config.js - configuraciones del sistema (versión 2)
  • package.json - dependencias del módulo de nodo (versión 2 en adelante)
  • tsconfig.json : configuraciones de TypeScript de nivel raíz (versión 2 en adelante)
  • tsconfig.app.json : configuraciones de TypeScript a nivel de aplicación (versión 4 en adelante)
  • .angular- cli .json - Configuraciones CLI angular (versión 4 y 5)
  • angular.json - Configuraciones de CLI angular (versión 6 en adelante)

4. Página de inicio de sesión

4.1. Usando AngularJS

Vamos a crear el index.html archivo y añadir las dependencias correspondientes a la misma:

Dado que se trata de una aplicación de una sola página, todos los componentes secundarios se agregarán al elemento div con el atributo ng-view según la lógica de enrutamiento.

Ahora creemos el archivo app.js que define la URL al mapeo de componentes:

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

El componente de inicio de sesión consta de dos archivos, login.controller.js y login.view.html.

Veamos el primero:

Login

Username Username is required Password Password is required Login

y el segundo:

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

El controlador invocará el servicio REST pasando el nombre de usuario y la contraseña. Después de la autenticación exitosa, codificará el nombre de usuario y la contraseña y almacenará el token codificado en el almacenamiento de la sesión para uso futuro.

Similar al componente de inicio de sesión, el componente de inicio también consta de dos archivos, el home.view.html :

You're logged in!!

Logout

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

El controlador doméstico solicitará los datos del usuario pasando el encabezado de Autorización . Nuestro servicio REST devolverá los datos del usuario solo si el token es válido.

Ahora instalemos el servidor http para ejecutar la aplicación Angular:

npm install http-server --save

Una vez que esté instalado, podemos abrir la carpeta raíz del proyecto en el símbolo del sistema y ejecutar el comando:

http-server -o

4.2. Usando la versión angular 2, 4, 5

El index.html en la versión 2 difiere ligeramente de la versión de AngularJS:

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

Los main.ts es el principal punto de entrada de la aplicación. Inicia el módulo de la aplicación y, como resultado, el navegador carga la página de inicio de sesión:

platformBrowserDynamic().bootstrapModule(AppModule);

Los app.routing.ts es responsable de la aplicación de enrutamiento:

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.

En lugar de HttpModule , la versión 6 importa HttpClientModule de @ angular / common / http.

La parte de llamada de servicio también será un poco diferente de las versiones anteriores:

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. Conclusión

Hemos aprendido cómo implementar una página de inicio de sesión de Spring Security con Angular. A partir de la versión 4, podemos hacer uso del proyecto CLI de Angular para facilitar el desarrollo y las pruebas.

Como siempre, todos los ejemplos que hemos discutido aquí se pueden encontrar en el proyecto GitHub.