this.service.event()
.pipe(
debounceTime(1000),
distinctUntilChanged(),
tap(() => {
this.isLoading = true;
})
)
.subscribe((data: any) => {
this.isLoading = false;
if (data != null) {
}
})
Dans ce cas, l'event à binder n'est pas (submit) mais (ngSubmit) :
<div class="container">
<div class="logo">Create a new account!</div>
<div class="register-item">
<form #myform="ngForm" (ngSubmit)="register(myform)" class="form form-register">
<div class="form-field">
<label class="user" for="register-username"><span class="hidden">Name</span></label>
<input name="name" id="register-username" type="email" class="form-input" placeholder="Name" ngModel required>
</div>
<div class="form-field">
<label class="user" for="register-email"><span class="hidden">Email</span></label>
<input name="email" id="register-email" type="email" class="form-input" placeholder="Email" ngModel required>
</div>
<div class="form-field">
<label class="lock" for="register-password"><span class="hidden">Password</span></label>
<input name="password" id="register-password" type="password" class="form-input" placeholder="Password" ngModel required>
</div>
<div class="form-field">
<input type="submit" value="Register">
</div>
</form>
</div>
</div>
Plutôt que PhantomJS, passer à Puppeter.
Ca marche mieux... En fait ça télécharge chromium lol
Hello Antichesse, tu m'as dit que tu allais peut-être t'intéresser à la remontée du coverage côté Front vers Sonar, donc je te met des infos sur ce que j'ai fait, pour t'aider.
Infos préalables (mais je sais que ça marche avec Angular et depuis Karma 4.1.0) :
Package.json
"devDependencies": {
"@angular-devkit/build-angular": "0.901.0",
"@angular/cli": "9.1.0",
"@angular/compiler-cli": "9.1.0",
"@angular/language-service": "9.1.0",
"@types/jasmine": "3.5.10",
"@types/jasminewd2": "2.0.8",
"@types/node": "12.12.34",
"codelyzer": "5.2.2",
"jasmine-core": "3.5.0",
"jasmine-spec-reporter": "4.2.1",
"karma": "4.4.1",
"karma-coverage-istanbul-reporter": "2.1.1",
"karma-firefox-launcher": "1.3.0",
"karma-jasmine": "3.0.3",
"karma-jasmine-html-reporter": "1.5.3",
"protractor": "5.4.3",
"ts-node": "8.3.0",
"tslint": "6.1.1",
"typescript": "3.8.3"
},
Alors déjà, je lance mes tests en Firefox Headless.
Extrait de mon fichier Karma.conf.js :
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-firefox-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
...
browsers: ['FirefoxHeadless'],
captureTimeout: 600000,
browserDisconnectTolerance: 3,
browserDisconnectTimeout: 600000,
browserNoActivityTimeout: 600000,
customLaunchers: {
FirefoxHeadless: {
base: 'Firefox',
flags: ['-headless', '--no-sandbox']
}
}
...
});
};
Mais surtout, dans le fichier Karma, il y a un élément super important, l'emplacement de stockage de ton coverage :
Karma.conf.js
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/project-name'), **<---- ICI**
reports: ['html', 'lcovonly', 'text-summary'], <--- Lcov doit être inclu aussi !
fixWebpackSourcePaths: true
},
Le dir indique ou le coverage sera stocké. Tu en auras besoin dans ton fichier JenkinsFile. Pour moi, à la racine de mon WORKSPACE, j'aurais un dossier "coverage", qui contient un autre dossier "project-name" contenant le résultat de l'analyse Istanbul-reporter, incluant le coverage.
Voici quelques extraits de mon fichier JenkinsFile, pour montrer les modifs que j'ai du faire :
pipeline {
agent { label 'trololo-pouetpouet' }
stages {
stage('Clone source code') {
steps {
git branch:'${BRANCH_NAME}', credentialsId: gitCredentialsId, url: gitSshUrl
}
}
stage('Test') {
steps {
println " ========== START PLAYING TESTS ========== "
configFileProvider([configFile(fileId: 'npm-npmrc', targetLocation: '/root/.npmrc')]) {
sh "export SASS_BINARY_PATH=${sassBinaryLocation}/${bindingNodeFileName} ; npm run test-prod"
}
stash includes: 'coverage/**/*', name: 'COVERAGE' **<---- Ici, c'est parce que je change d'agent, donc je garde l'output de mon coverage **
}
}
stage('Build') {
steps {
println " ========== START BUILDING DELIVERABLE ========== "
...
}
}
stage('Sonarqube analysis') {
agent {
label 'sonar-scanner-agent-de-ouf'
}
steps {
git branch:'${BRANCH_NAME}', credentialsId: gitCredentialsId, url: gitSshUrl
script {
configFileProvider([configFile(fileId: 'npm-npmrc', targetLocation: '/root/.npmrc')]) {
...
unstash 'COVERAGE' ** <----- je récupère mon output d'analyse incluant mon coverage **
withSonarQubeEnv(sonarEnvName) {
sh "sonar-scanner "+
"-Dsonar.verbose=true "+
"-Dsonar.projectBaseDir=${WORKSPACE} "+
"-Dsonar.projectVersion=${taggedVersion} "+
"-Dsonar.sources=. "+
"-Dsonar.exclusions='**/*.spec.ts, **/*.js, src/test/*'"+ **<---- Ici, j'exclue les tests et le js du coverage et de l'analyse **
"-Dsonar.projectName='${binaryName}' "+
"-Dsonar.projectKey=${binaryName} "+
"-Dsonar.typescript.lcov.reportPaths=${env.WORKSPACE}/coverage/project-name/lcov.info "+ <---- Ici, il faut indiquer le path récupéré depuis le fichier Karma.conf.js, et pointer vers le fichier lcov.info
(env.BRANCH_NAME != 'master' ? "-Dsonar.branch.name=${env.BRANCH_NAME} -X " : "")
}
}
}
}
}
...
}
...
}
Et last, but not least, une petite indication supplémentaire à préciser quand on fait de l'angular :
package.json
"scripts": {
"ng": "ng",
...
"test": "ng test --watch --code-coverage", ** <---- Indiquer que l'on veut l'emission du coverage !**
"test-prod": "npm install && ng test --code-coverage",
"lint": "ng lint",
"e2e": "ng e2e"
},
Note : l'option de script "--code-coverage" peut également être remplacée par l'option de build:
"test": { ** <---- En phase test **
"builder": ...
"options": {
....
"codeCoverage": true, ** <---- Indiquer codeCoverage true **
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss",
....
}
},
Et voilà, ton coverage est dispo sur SONAR !
Si quelque n'est pas clair, n'hésite pas à me le dire !
Aujourd'hui j'ai appris comment combiner des observables pour attendre que tous les traitements s'exécutent.
Et c'est surtout grâce au lien.
En même temps, la doc RXJS est pas mal non plus : https://www.learnrxjs.io/learn-rxjs/operators/combination/combinelatest
Une propal de micro-front. On découpe le front avec les micro services et on les insère dans un "portail".
Premier essai via web-components :
Les pours :
- Look and feel consistant
- Plusieurs composants sur la même page
- Indépendant au niveau déploiement entre les composants
Les contres :
- Pas une SPA (on recharge tout à chaque fois)
- Mauvaises performances (notamment duplication librairies)
- Dépendances entre SPA qui rentrent en conflit.
- Mécanisme de routing (basé sur les #) : A redéfinir totalement pour éviter les conflits : conflit entre apps
- Installation de la stack lourd (surtout en local pour du test)
Second essai via les React Fragments (avec Tailor pour résoudre les temps différents de réponse entre les différentes applications) :
Les pours :
- Possibilité de partager des composants réact entiers
- Système de routage centralisé
Les contres :
- Dépendances entre libs : toujours un pb
- Dépendances de Réact encore plus problématiques
Troisième essai avec Layout-as-lib (c'est Layout-as-lib qui est la librairie qui s'inscrit dans les applications). Layout-as-lib est masqué par défaut pour la version "legacy" et affichée en cas de besoin dans les différentes SPA.
Les pours :
- Plus facile d'utiliser une lib dans une SPA que l'inverse : Intégration en 1,5 jour
- Plus rapide
- Indépendance dans le développement et le déploiement : on release chaque SPA distinctement
- Plus de conflit de librairie
- On ne peut continuer à tester que son composant
Les contres :
- Pas de SPA du tout (mais c'est pas grave dans leur cas)
- Pas de possibilité d'aggregation de composants (mais c'est pas grave dans leur cas)
- Expérience utilisateur peut être incohérente : Une SPA n'a pas l'appel à la lib à la même version que l'autre SPA
- Des changements majeurs dans la navigation doivent être reportés partout
L'idée est hyper intéressante : On garde le concept de SPA, mais on rajoute des éléments de navigation entre SPA via une logique de lib.
J'aime bien ! Je valide à 100% l'idée ! C'est super génial !
!! Points d'attention !! :
- Bien réfléchir son usecase d'abord
- Attention au design (style - Css)
A tester !
Bon bah apparemment on peut... ma foi... sigh...
This guy demonstrate how to override a component behavior through directives.
Usefull.
C'est un article de pub ?
Je reprend les points :
1) No build step required
Hum... Le cli te setup un build complet en mode boîte noire, comme tous les concurrents.
Apparemment, tu n'es pas obligé d'en dépendre, ca peut être intéressant. Pas vraiment d'avis ! Mais Cest un aspect intéressant.
2) World-class CLI
Donc la, le gars dit que le cli vue à rattrapé la concurrence. Ok, cest cool.
3) Friendly learning curve
Je trouve vue tres proche de Angular par rapport au paradigme, donc je dirai que la différence vient des habitudes de langage de l'utilisateur. Peut être que le gars qui a écrit l'article n'est pas à l'aise avec le typescript et la programme orientée objet ?
À mon sens, tu sais faire l'un, tu sais faire l'autre.
4) A solid écosystem
Alors la, le gars m'a perdue. Dans tous les frameworks SPA, le routing et compagnie est inclus.
Le seul ou Ca n'était pas le cas, c'était angular 1.5 où il fallait passer par angular-Universal.
Mais Ca fait quelques années que ca n'est plus le cas.
On dirait le discours Scala vs Java 5 XD
5) the vue instance
Pour créer un un composant angular, Cest une annotation au dessus de ta classe pour linker le html et le css et ton composant est déclaré.
Même features des deux côtés.
6) vue3 lands this year
Les correctifs vue.js rejoignent ceux de Aurélie avec le binding des listes. Good for vue.
Je vais m'arrêter la, le reste ce sont des arguments tres subjectifs.
À mon avis, le gars exprime exactement la raison pour laquelle vue.js ne m'intéresse pas : il propose la même chose que les leaders.
La seule distinction c'est la gestion par module pour Angular.
Aurélia, toujours premier dans mon cœur <3
Tandis que je regarde un peu ce que propose VueJs, je vais mettre ici mes notes :
Pb de set-up avec VueJS:
- Nécessite une connexion avec GitHub pour récupérer le format de templating :/
- Notion de templating à approfondir ??? On ne sait pas trop quel template propose quoi...
Contournement sur Windows (sigh) :
- Récuperer le template sur Github (téléchargement direct)
- Le mettre dans un dossier C:/Users/****/.vue-template/NOMDUTEMPLATE
- Relancer la création de l'application : vue init NOMDUTEMPLATE NOMDUPROJET --offline
- Entrer dans le dossier contenant l'application
- npm install
- npm run dev
Du côté du build, on a deux commandes disponibles :
- dev : pour tester en live
- build : pour builder le tintouin.
Ca a l'avantage d'être déjà configuré. Le reload est rapide. Je pouce en l'air !
<a href="?addtag=Gestion" title="Hashtag Gestion">#Gestion</a> des composants :
Coupler les trois couches : js - css - html
Ou les séparer et les importer au besoin :
<template>
<div>This will be pre-compiled</div>
</template>
<script src="./my-component.js"></script>
<style src="./my-component.css"></style>
C'est bien de laisser le choix. La gestion des composants est très proche d'Aurélia : on peut importer n'importe quel composant dans n'importe quel autre sauf que l'import a lieu coté model, là ou Aurélia le met par défaut côté template (pas d'avis sur le sujet pour le moment).
Ça créé des problèmes si la personne n'est pas très propre sur son code (on ne sait pas quel composant est appelé où). Mais c'est plus un pb côté éditeur.
Je ne suis pas fan du côté module d'Angular : a voir si cette façon de faire est encore présente dans Angular 6 (Je connais très bien Angular 4 mais pas encore bien le 6 ni le 7)
Pré-requis : Component template should contain exactly one root element : un seul div racine <= pourquoi pas ! C'est pas plus mal :)
Le binding de class css est un peu foireux (plus proche de la logique angular) :
VueJS :
v-bind:class="{ active: isActive, 'text-danger': hasError }"
Aurelia :
class="${isActive ? 'active' : ''}"
Angular :
[class.active]="isActive"
Le binding de style est tout aussi dégueulasse :
VueJs :
<div v-bind:style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
Aurelia :
<div style="width: ${width}px; height: ${height}px;"></div>
Angular :
[style.color]="isSpecial ? 'red' : 'green'"
Conditional rendering : Pareil que la concurrence
VueJS :
<h1 v-if="ok">Yes</h1>
<h1 v-else>No</h1>
Aurelia :
<div if.bind="greet">
Hello, World!
</div>
Angular :
<div *ngIf="show">
Text to show
</div>
C'est la même merde que les autres. Tout va bien :)
CELA DIT : SI TU FOIRES TA GESTION DU IF, T'AS QUAND MÊME GRAVE L'AIR D'UN CON !
V-if et v-for marchent pas ensemble. Comme partout ailleurs. RAS.
Et ils ont tellement merdé la conditionnelle de class et style qu'ils proposent v-show, qui ressemblerait globalement à
<div style="display:${mustbeshown}"></div>
chez Aurelia
Boucle dans le template : RAS
VueJS :
<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
Aurelia :
<template>
<template repeat.for="friend of friends">
<p>Hello, ${friend}!</p>
</template>
</template>
Angular :
<li *ngFor="let user of userObservable">
{{user}}
</li>
Pas de choses fofolles côté event management ou forms.
Côté binding, on retrouve les bonnes idées Aurelia :
<blog-post
v-for="post in posts"
v-bind:key="post.id"
v-bind:title="post.title"
></blog-post>
Aurelia :
<blog-post
repeat.for="post in posts"
key.bind="post.id"
title.bind="post.title"
></blog-post>
Quand on aborde la question des events, là, je grince un peu des dents...
Appeler une méthode du model depuis la vue (via un boutton par exemple) :
VUEJS :
<template>
<div>
<button v-on:click="clickTest()">trololo pouet pouet click me</button>
</div>
</template>
<script>
module.exports= {
data: function () {
return {
clickTest: function () {
console.log("THIS SIS DGKMGHKGLKLK");
}
}
},
//OU :
methods: {
clickTest: function () {
console.log("THIS SIS DGKMGHKGLKLK");
toto="texte4"
}
}
}
</script>
AURELIA :
<template>
<button click.trigger="clickTest()">Cancel</button>
</template>
export class MYCOMPONENT {
public void clickTest() {
console.log("THIS SIS DGKMGHKGLKLK");
}
}
Angular :
<template>
<button (click)="clickTest()">Cancel</button>
</template>
export class MYCOMPONENT {
public void clickTest() {
console.log("THIS SIS DGKMGHKGLKLK");
}
}
On peut considérer la fonction comme étant une data... OK. Ou alors,y a un petit espace privatif pour les méthodes....
Cétait pas possible de définir un endroit pour tout mettre ? Il faut mettre les pommes dans le tiroir à pommes et les couteaux dans le bac à couteaux...
OUI MAIS ! pourquoi ? On distingue de manière physique les constantes, les variables bindées et les méthodes... on ne sait pas le faire sans distinguer ? Je ne veux pas être tributaire de votre synthaxe alambiquée, je veux pouvoir choisir d'encapsuler mes objets, de faire contenir mes méthodes dans des classes extérieures... et votre framework n'encourage pas du tout à penser le code de manière testable et autonome sans le framework...
Je ne comprend pas l'expérience utilisateur que vous avez pensée....
Le summum du cocobongo : Le data binding entre composants
Alors là, les gars de vueJs nous l'ont bien géré à l'antipattern angular :
To update the parent property, what you should do is $emit the updated value and listen for the change in the parent.
Vue.component("navigation-form",{
template: '#navigation-form',
props: ['propRoomSelected'],
data: function () {
return {
roomSelected: this.propRoomSelected,
}
},
methods:{
updateCoachStatus: function(event){
this.$emit("update-room-selected", 67) ;
}
}
})
Nan les gars, là je vous suis pas... Pourquoi ne pas faire comme Aurelia :
<component toto.bind="tata"></component>
export class Toto {
public toto: String;
public void changeToto() {
this.toto = "titi";
}
}
LE DOUBLE DATA BINDING DOIT ÊTRE NATIF ! PAS REIMPLEMENTE A CHAQUE VARIABLE AVEC DES RE-EMISSIONS A GOGO !
C'est chiant et lourd à lire, sans déconner...
CONCLUSION
Mon avis pour le moment :
1) La synthaxe VueJS est quand même très invasive côté "corporate".
ON SAIT QUE C'EST VUEJS !!! TOUS LES FICHIERS S'APPELLENT VUE BON SANG ! POURQUOI VOUS NOUS METTEZ DES PETITS V PARTOUT, BON SANG DE BOIS !
Note à postériori : Bon, ok, plutôt que v-bind:toto, on peut écrire :toto (OUF !)
2) Une vision très orienté JS
3) Un usage qui n'est pas très instinctif pour un dev
Un dev, pour déclencher l'usage d'une class de style, va naturellement penser conditionnelle et
class="{{isOK ? 'class1': 'class2'}}"
me semble plus instinctif à écrire que
v-bind:class="{ class1: isOK, class2: !isOk}"
4) La gestion du double data binding, ça m'a tuer (sans faute de frappe). C'est super moche.
Aurélia, tu as toujours la première place dans mon coeur <3 (udabest)
Unit testing routes configuration with angular.
Enfin une explication claire sur le double data-binding angular
Exemple upload côté client :
fichier html
<input type="file" #package (change)="prepareFile(package.files)" class="is-small fileupload"/>
<button type="button" class="button is-small" (click)="importPackage()" [disabled]="!this.validPackage">Valider</button>
fichier ts :
selectedPackage: any;
prepareFile(files: any) {
this.selectedPackage = files;
//exemple pour bloquer l'upload si on a pas affaire à un zip :
if(files != null){
let extension = files[0].name.split('.').pop();
if(extension == "zip" || extension == "ZIP"){
this.validPackage = true;
}else{
this.validPackage= false;
}
}
}
importPackage(): void {
let file: File = this.selectedPackage[0];
this.packageService.uploadFile(file).subscribe(
result => this.onFileUploaded(result)
);
}
onFileUploaded(result: any): void {
this.selectedPackage = null;
}
packageService.ts
uploadFile(file: File): Observable<Entity[]> {
let formData: FormData = new FormData();
formData.append('file', file, file.name);
const url = `${this.getUrl()}/upload`;
return this.http.post(url, formData)
.map(res => this.extractListFromResponse(res))
.catch(res => this.handleError(res))
}
Dans notre cas, le retour de la requete est une liste d'item. A vous de voir de votre côté.
Controller côté java:
@RequestMapping(value = "/upload", method = RequestMethod.POST)
public ResponseEntity uploadFile(@RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
try {
return new ResponseEntity<>(this.packageService.uploadPackage(file.getBytes()), HttpStatus.OK);
} catch (GenericBELException | IOException e) {
return super.forwardException("Impossible de récupérer le package " + file.getOriginalFilename(), e);
}
} else {
return this.createStructuredResponse("Impossible de récupérer le package " + file.getOriginalFilename() + ". Il était vide à la réception.", HttpStatus.NO_CONTENT);
}
}
A partir du file, vous pouvez extraire le contenu avec des BufferReader.
Je rajoute aussi ce header, qui permet de garder la connexion vivante derrière un proxy :
Angular :
headers.append('Proxy-Connection', 'Keep-alive');
http.service.ts
import {Injectable} from '@angular/core';
import {Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers} from '@angular/http';
import {Observable} from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
@Injectable()
export class HttpService extends Http {
constructor (backend: XHRBackend, options: RequestOptions) {
let token = localStorage.getItem('auth_token'); // your custom token getter function here
options.headers.set('Authorization', `Bearer ${token}`);
super(backend, options);
}
request(url: string|Request, options?: RequestOptionsArgs): Observable<Response> {
let token = localStorage.getItem('auth_token');
if (typeof url === 'string') { // meaning we have to add the token to the options, not in url
if (!options) {
// let's make option object
options = {headers: new Headers()};
}
options.headers.set('Authorization', `Bearer ${token}`);
} else {
// we have to add the token to the url object
url.headers.set('Authorization', `Bearer ${token}`);
}
return super.request(url, options).catch(this.catchAuthError(this));
}
private catchAuthError (self: HttpService) {
// we have to pass HttpService's own instance here as `self`
return (res: Response) => {
console.log(res);
if (res.status === 401 || res.status === 403) {
// if not authenticated
console.log(res);
}
return Observable.throw(res);
};
}
}
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
/**
* The authentication entry point is called when the client makes a call to a resource without proper authentication. In
* other words, the client has not logged in.
* <p>
* If this class is not present in the code, then Spring security opens a default pop-up. We want a 401 error.
*/
@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class);
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
LOGGER.info("intercepted request : 401 error code.");
response.getWriter().append("Access Denied : " + authException.getMessage());
response.setStatus(401);
}
}
Cette classe est incluse dans la couche sécurité via la configuration du http :
//Override the default login pop-up with a 401 response.
http.exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint);
Ne reste plus qu'à configurer la gestion des erreurs 401 avec Angular :
import { Injectable } from '@angular/core';
import { Request, XHRBackend, RequestOptions, Response, Http, RequestOptionsArgs, Headers } from '@angular/http';
import { Router, NavigationEnd, Event } from '@angular/router';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/observable/throw';
/**
* This class handles generically the error on authentication.
*/
@Injectable()
export class AuthenticatedHttpService extends Http {
private router: Router;
constructor(backend: XHRBackend, defaultOptions: RequestOptions, router: Router) {
super(backend, defaultOptions);
this.router = router;
}
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
return super.request(url, options).catch((error: Response) => {
if ((error.status === 401 || error.status === 403) && (window.location.href.match(/\?/g) || []).length < 2) {
if(window.location.href != '/login') {
this.router.navigate(['/login']);
}
else {
return Observable.throw("authentication met an exception");
}
}
return Observable.throw("request authentication");
});
}
}
Dépendances nécessaire dans Maven :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<scope>compile</scope>
</dependency>
<!-- Pas sure que celle-ci soit nécessaire : -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-core</artifactId>
<scope>compile</scope>
</dependency>
Configuration de la sécurité côté sécurité :
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
@ComponentScan("com.pacakge.security")
public class MyWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(MyWebSecurityConfigurerAdapter.class);
@Autowired
@Qualifier("authenticationProvider")
private AuthenticationProvider authenticationProvider;
@Autowired
@Qualifier("isSecurityDisabled")
private Boolean isSecurityDisabled;
@Autowired
public void configureGlobal(AuthenticationManagerBuilder authenticationProvider) throws Exception {
authenticationProvider.authenticationProvider(this.authenticationProvider);
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
LOGGER.debug("configure HttpSecurity");
LOGGER.info("Security is disabled : {}", isSecurityDisabled);
if (isSecurityDisabled) {
http.httpBasic().and()
.authorizeRequests().antMatchers("/index.html", "/", "/logout.html").permitAll().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/user").permitAll()
.anyRequest().authenticated();
}
else {
http.httpBasic().and()
.authorizeRequests().antMatchers("/index.html", "/", "/logout.html").permitAll().and()
// configure the csrfTokenRepository
.csrf().csrfTokenRepository(csrfTokenRepository()).and()
.authorizeRequests()
.antMatchers("/user").permitAll()
.antMatchers("/contact/**").access("hasRole('ADMIN') and hasRole('DBA')")
.anyRequest().authenticated();
// Add the csrf filter
http.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
}
http.exceptionHandling().accessDeniedPage("/403");
}
/**
* Csrf Token Repository that contains the header named "X-XSRF-TOKEN".
* Angular adds CRSF token headers to keep sure server and client are not talking to someone else.
*
* @return CsrfTokenRepository
*/
private static CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
@Override
public void configure(WebSecurity web) throws Exception {
LOGGER.info("Security is disabled : {}", isSecurityDisabled);
web.ignoring().antMatchers(HttpMethod.OPTIONS, "/**");
// Spring Security should completely ignore URLs starting with
// /static/assets/
web.ignoring().antMatchers("/static/assets/**").and();
web.ignoring().antMatchers("/static/app/**").and();
// Spring Security should completely ignore URLs for the js / images /
// font
web.ignoring().antMatchers("/static/*.ico").and();
web.ignoring().antMatchers("/static/*.eot").and();
web.ignoring().antMatchers("/static/*.svg").and();
web.ignoring().antMatchers("/static/*.ttf").and();
web.ignoring().antMatchers("/static/*.woff").and();
web.ignoring().antMatchers("/static/*.woff2").and();
web.ignoring().antMatchers("/static/*.js").and();
web.ignoring().antMatchers("/static/*.css").and();
web.ignoring().antMatchers("/static/*.map").and();
web.ignoring().antMatchers("/static/*.png").and();
web.ignoring().antMatchers("/static/*.jpg").and();
}
}
Exemple d'Authentication provider :
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
/**
* A mock for authentication provider.
*/
@Component
public class MockAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
private static final Logger CLASS_LOGGER = LoggerFactory.getLogger(MockAuthenticationProvider.class);
//Your own service to manage authentication.
private final AuthentiService authentiService;
private final LDAPUserToSecurityUserConverter lDAPUserToSecurityUserConverter;
/**
* Initialize the Mock authentication provider.
*
* @param authentiService the authent service.
* @param lDAPUserToSecurityUserConverter the converter from LDAP user to security user.
*/
public MockAuthenticationProvider(LDAPUserToSecurityUserConverter lDAPUserToSecurityUserConverter, AuthentiService authentiService) {
CLASS_LOGGER.trace("Set up of mocked authentication provider.");
this.lDAPUserToSecurityUserConverter = lDAPUserToSecurityUserConverter;
this.authentiService = authentiService;
}
@Override
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}
@Override
public Authentication authenticate(Authentication authentication) {
CLASS_LOGGER.info("requesting authentication");
String name = authentication.getName();
String password = authentication.getCredentials().toString();
StopWatch stopWatch = new StopWatch();
stopWatch.start();
SecurityUser authenticatedUser = (SecurityUser) this.retrieveUser(name, (UsernamePasswordAuthenticationToken) authentication);
stopWatch.stop();
authenticatedUser.setTimeToSpentOnAuthentication(stopWatch.getTotalTimeMillis());
return new UsernamePasswordAuthenticationToken(authenticatedUser, password, authenticatedUser.getAuthorities());
}
@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) {
String password = (String) authentication.getCredentials();
try {
// Login User
String token = this.authentService.login(username, password);
CLASS_LOGGER.debug("Username : {} got token : {}", username, token);
// Retrieve User Identity + roles + permissions
LDAPUser user = this.authentService.getUserInformation(token);
// Build the AuthenticatedUser object based on all the data
return this.lDAPUserToSecurityUserConverter.convert(user, password, token);
}
catch (Exception e) {
CLASS_LOGGER.debug(e.getMessage(), e);
throw new AuthenticationServiceException("Authentication error", e);
}
}
@Override
protected void additionalAuthenticationChecks(UserDetails arg0, UsernamePasswordAuthenticationToken arg1) throws AuthenticationException {
// Nothing to do.
}
}
Controller d'authentication :
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class AuthenticationController {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthenticationController.class);
@RequestMapping(value = "/user", method = RequestMethod.GET)
public SecurityUser user() {
LOGGER.debug("user controller");
SecurityUser authenticationUser = null;
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null) {
LOGGER.info("Authentication provider is {}", auth.getClass().getSimpleName());
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = (UsernamePasswordAuthenticationToken) auth;
authenticationUser = (SecurityUser) usernamePasswordAuthenticationToken.getPrincipal();
}
else {
LOGGER.warn("No Authentication found");
}
return authenticationUser;
}
}
Service Angular d'authentication :
import { Injectable } from '@angular/core';
import { Http, Response, Headers, RequestOptions, URLSearchParams } from '@angular/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import { Observable } from 'rxjs/Observable';
/**
* This class can request the server to get contact information.
*/
@Injectable()
export class AuthenticationService {
constructor(private http: Http) {
}
authenticate(username: string, password: string): Observable<any> {
//Authentication goes by using headers
const headers = new Headers();
if (username != null && password != null) {
const authorizationValue: string = 'Basic ' + btoa(username + ':' + password);
headers.append('authorization', authorizationValue);
}
return this.http.get('user', {headers: headers})
.map((res: Response) => res.json())
.catch( error => this.handleError(error) );
}
}
Cettte technique géniale permet de distinguer deux applications autonomes au sein d'un seul et même repo angular2 (ou 4).
Super option.
Comment gérer le découpage de votre projet en sous-projets via la gestion des modules.