Página de login do Spring Security com Angular
1. Visão geral
Neste tutorial, vamos criar umlogin page using Spring Security with:
-
AngularJS
-
Angular 2, 4, 5 e 6
O aplicativo de exemplo que vamos discutir aqui consiste em um aplicativo cliente que se comunica com o serviço REST, protegido por autenticação HTTP básica.
2. Configuração de segurança da primavera
Em primeiro lugar, vamos configurar a API REST com Spring Security e Basic Auth:
Aqui está 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();
}
}
Agora vamos criar os endpoints. Nosso serviço REST terá dois - um para login e outro para buscar os dados do usuário:
@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];
}
}
Da mesma forma, você pode verificar nosso outro tutorial sobreSpring Security OAuth2 também se estiver interessado em implementar um servidor OAuth2 para autorização.
3. Configurando o Cliente Angular
Agora que criamos o serviço REST, vamos configurar a página de login com diferentes versões do cliente Angular.
Os exemplos que veremos aqui usamnpm para gerenciamento de dependências enodejs para executar o aplicativo.
O Angular usa uma arquitetura de página única em que todos os componentes filho (em nosso caso, são componentes de login e home) são injetados em um DOM pai comum.
Ao contrário do AngularJS, que usa JavaScript, a versão 2 do Angular em diante usa o TypeScript como idioma principal. Portanto, o aplicativo também requer certos arquivos de suporte necessários para que ele funcione corretamente.
Devido aos aprimoramentos incrementais do Angular, os arquivos necessários diferem de versão para versão.
Vamos nos familiarizar com cada um deles:
-
systemjs.config.js - configurações do sistema (versão 2)
-
package.json - dependências do módulo do nó (versão 2 em diante)
-
tsconfig.json - configurações de tipo de script de nível raiz (versão 2 em diante)
-
tsconfig.app.json - configurações de texto do tipo de aplicativo (versão 4 em diante)
-
.angular-cli_.json_ - Configurações CLI Angular (versão 4 e 5)
-
angular.json - Configurações CLI angulares (versão 6 em diante)
4. Página de login
4.1. Usando AngularJS
Vamos criar o arquivoindex.html e adicionar as dependências relevantes a ele:
Como este é um aplicativo de página única, todos os componentes filhos serão adicionados ao elemento div com o atributong-view baseado na lógica de roteamento.
Agora vamos criar oapp.js que define o URL para o mapeamento do componente:
(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');
}
});
}
})();
O componente de login consiste em dois arquivos, ologin.controller.js e ologin.view.html.
Vejamos o primeiro:
Login
e o 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: '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.")
}
});
};
}
})();
O controlador chamará o serviço REST passando o nome de usuário e a senha. Após a autenticação bem-sucedida, ele codificará o nome de usuário e a senha e armazenará o token codificado no armazenamento da sessão para uso futuro.
Semelhante ao componente de login, o componente inicial também consiste em dois arquivos, ohome.view.html:
Hi {{vm.user}}!
You're logged in!!
e ohome.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';
}
}
})();
O controlador doméstico solicitará os dados do usuário passando o cabeçalhoAuthorization. Nosso serviço REST retornará os dados do usuário apenas se o token for válido.
Agora vamos instalarhttp-server para executar o aplicativo Angular:
npm install http-server --save
Uma vez instalado, podemos abrir a pasta raiz do projeto no prompt de comando e executar o comando:
http-server -o
4.2. Usando a versão Angular 2, 4, 5
Oindex.html na versão 2 difere ligeiramente da versão AngularJS:
Loading...
Omain.ts é o principal ponto de entrada do aplicativo. Ele inicializa o módulo do aplicativo e, como resultado, o navegador carrega a página de login:
platformBrowserDynamic().bootstrapModule(AppModule);
Oapp.routing.ts é responsável pelo roteamento do aplicativo:
const appRoutes: Routes = [
{ path: '', component: HomeComponent },
{ path: 'login', component: LoginComponent },
{ path: '**', redirectTo: '' }
];
export const routing = RouterModule.forRoot(appRoutes);
Oapp.module.ts declara os componentes e importa os módulos relevantes:
@NgModule({
imports: [
BrowserModule,
FormsModule,
HttpModule,
routing
],
declarations: [
AppComponent,
HomeComponent,
LoginComponent
],
bootstrap: [AppComponent]
})
export class AppModule { }
Como estamos criando um aplicativo de página única, vamos criar um componente raiz que adiciona todos os componentes filhos a ele:
@Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent { }
Oapp.component.html terá apenas uma tag<router-outlet>. O Angular usa essa tag para seu mecanismo de roteamento de localização.
Agora vamos criar o componente de login e seu modelo correspondente emlogin.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.");
}
});
}
}
Finalmente, vamos dar uma olhada emlogin.component.html:
4.3. Usando Angular 6
A equipe angular fez alguns aprimoramentos na versão 6. Devido a essas alterações, nosso exemplo também será um pouco diferente em comparação com outras versões. A única mudança que fizemos em nosso exemplo com relação à versão 6 está na parte de chamada de serviço.
Em vez deHttpModule, a versão 6 importaHttpClientModule de @angular/common/http.
A parte de chamada de serviço também será um pouco diferente das versões 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. Conclusão
Aprendemos como implementar uma página de login do Spring Security com Angular. A partir da versão 4, podemos usar o projeto CLI Angular para facilitar o desenvolvimento e o teste.
Como sempre, todos os exemplos que discutimos aqui podem ser encontrados emGithub project.