Préface
Les cadres d'autorisation générale populaires incluent désormais le Shiro d'Apache et la Spring Family Spring Security. En ce qui concerne l'authentification des microservices d'aujourd'hui, nous devons utiliser notre cadre d'autorisation pour créer nos propres services d'authentification. Aujourd'hui, le Premier ministre a été le Premier ministre.
Spring Security implémente principalement l'authentification (authentification, solution qui êtes-vous?) Et le contrôle d'accès (contrôle d'accès, c'est-à-dire que êtes-vous autorisé à faire?, Également connu sous le nom d'autorisation). Spring Security sépare l'authentification de l'architecture d'autorisation et fournit des points d'extension.
Objets de base
Le code principal est sous le package Spring-Security-Core. Pour comprendre la sécurité du printemps, vous devez faire attention aux objets principaux à l'intérieur.
SecurityContexTholder, SecurityContext et authentification
SecurityContexTholder est un conteneur de stockage pour SecurityContext. Le stockage threadLocal est utilisé par défaut, ce qui signifie que les méthodes SecurityContext sont disponibles dans le même thread.
SecurityContext stocke principalement les principales informations de l'application et est représentée par l'authentification dans Spring Security.
Obtenez le directeur:
Objet principal = SecurityContexTholder.getContext (). GetAuthentication (). GetPrincipal (); if (principale instance of UserDetails) {String username = ((userDetails) Principal) .getUsername ();} else {String username = Principal.Tostring ();}Dans Spring Security, vous pouvez jeter un œil à la définition d'authentification:
L'authentification de l'interface publique étend le principal, sérialisable {collection <? étend l'autorité accorde> getAuthorities (); / ** * Habituellement mot de passe * / objet getCredentials (); / ** * Stockant des détails supplémentaires sur la demande d'authentification. Il peut s'agir d'une adresse IP *, d'un numéro de série de certificat, etc. * / objet getDetails (); / ** * Utilisé pour identifier s'il est authentifié. Si vous vous connectez avec un nom d'utilisateur et un mot de passe, c'est généralement le nom d'utilisateur * / objet getPrincipal (); / ** * s'il est authentifié * / boolean isAuthenticated (); vide setAuthenticated (boolean isAuthenticated) lève illégalargumentException;}Dans les applications pratiques, UsernamePasswordAuthenticationToken est généralement utilisée:
Classe abstraite AbstractAuthenticationToken implémente l'authentification, les informations d'identification {} classe publique UsernamepasswordAuthenticationToken étend AbstractAuthenticationToken {}Un processus d'authentification commun est généralement comme ceci: créez un usernamepasswordAuthenticationToken, puis remettez-le à l'authentificationManager pour l'authentification (décrit en détail plus tard). Si l'authentification est adoptée, les informations d'authentification seront stockées via le SecurityContexTholder.
UserNamepasswordAuthenticationToken AuthenticationToken = new userNamepasswordAuthenticationToken (LoginvM.GetUserName (), loginvm.getPassword ()); Authentication Authentication = this.AuthenticationManager.AuthentiCate (AuthenticationToken); SecurityContexTholder.getContext (). SetAuthentication (authentification);
UserDetails et UserDetailsService
UserDetails est une interface clé de Spring Security, qui est utilisée pour représenter un principal.
Interface publique UserDetails étend sérialisable {/ ** * Les informations d'autorisation utilisateur peuvent être comprises comme un rôle * / collection <? étend l'autorité accorde> getAuthorities (); / ** * Mot de passe utilisateur * * @return le mot de passe * / String getPassword (); / ** * nom d'utilisateur * * / string getUserName (); Boolean IsAccountNonexpired (); Boolean IsAccountNonLocked (); Boolean isCredentialSnOnexpired (); Boolean iseNabled ();}UserDetails fournit les informations nécessaires requises pour l'authentification. En usage réel, vous pouvez implémenter UserDetails par vous-même et ajouter des informations supplémentaires, telles que les e-mails, les mobiles et autres informations.
Dans l'authentification, le principal est généralement le nom d'utilisateur. Nous pouvons obtenir UserDetails via le principal via UserDetailSService:
Interface publique UserDetailSService {UserDetails LoadUserByUserName (String Username) lève UserNameNotFoundException;}Autorisation de l'autorité
Comme mentionné dans UserDetails, l'autorisation accorde peut être comprise comme un rôle, comme Role_Administrator ou Role_HR_SuperVisor.
résumé
Certification d'authentification
AuthenticationManager
L'authentification est principalement réalisée via l'interface AuthenticationManager, qui ne contient qu'une seule méthode:
Interface publique AuthenticationManager {Authentification Authentication (authentification Authentication) lève AuthenticationException;}La méthode Authenticiate () fait principalement trois choses:
AuthenticationException est une exception d'exécution, qui est généralement gérée par l'application de manière courante. Le code utilisateur n'a généralement pas besoin d'être capturé et traité spécifiquement.
L'implémentation par défaut de AuthenticationManager est ProdiderManager, qui délégue un ensemble d'instances AuthenticationProvider pour implémenter l'authentification.
AuthenticationProvider et AuthenticationManager sont similaires à l'authentification, tous deux contiennent l'authentification, mais il a un support de méthode supplémentaire pour permettre à la demande de demande si l'appelant prend en charge un type d'authentification donné:
Interface publique AuthenticationProvider {Authentication Authentication (Authentication Authentication) lève AuthenticationException; Supports booléens (classe <?> Authentification);}ProviderManager contient un ensemble d'authentificationProviders. Lors de l'exécution de l'authentification, il traverse les fournisseurs puis appelle le support. S'il est pris en charge, il exécute la méthode d'authentification qui traverse le fournisseur actuel. Si un fournisseur est authentifié avec succès, cassez.
L'authentification d'authentification publique (authentification d'authentification) lève AuthenticationException {class <? étend l'authentification> TOTEST = Authentication.getClass (); AuthenticationException LastException = null; Résultat d'authentification = null; Boolean Debug = Logger.IsDebugeNabled (); pour (AuthenticationProvider Provider: getProviders ()) {if (! provider.supports (totest)) {continue; } if (debug) {logger.debug ("Tentative d'authentification en utilisant" + provider.getClass (). getName ()); } try {result = provider.authentiCate (authentification); if (result! = null) {copydetails (authentification, résultat); casser; }} catch (accountStAtUsexception e) {prepareException (e, authentification); // SEC-546: Évitez d'interroger les fournisseurs supplémentaires si la défaillance de l'automne est due à // le lancer d'état du compte non valide E; } catch (InternalAuthenticationsServiceException e) {prepareException (e, authentification); jeter e; } catch (AuthenticationException e) {LastException = E; }} if (result == null && parent! = null) {// permettre au parent d'essayer. try {result = parent.authentiCate (authentification); } catch (providernotFoundException e) {// ignorer comme nous lancerons ci-dessous si aucune autre exception ne s'est produite avant // appelant le parent et que le parent // peut lancer ProvidertotFound même si un fournisseur dans l'enfant déjà // a géré la demande} catch (AuthenticationException e) {LastException = E; }} if (result! = null) {if (erasecredentialsafterAuthentication && (result instanceof identsSontainer)) {// L'authentification est terminée. Supprimer les informations d'identification et autres données secrètes // de l'authentification (((CredentialSontainer) Résultat) .erAsecredentials (); } EventPublisher.PublishAuthenticationsUccess (résultat); Résultat de retour; } // Le parent était null ou ne s'authentifie pas (ou ne lance pas une exception). if (LastException == NULL) {LastException = new ProviderNotFoundException (Messages.GetMessage ("ProviderManager.ProviderNotFound", nouvel objet [] {Totest.GetName ()}, "Aucune authentificationProvider trouvée pour {0}")); } prepareException (LastException, authentification); lancer LastException; }Comme le montre le code ci-dessus, ProviderManager a un parent facultatif. Si le parent n'est pas vide, Parent.Authenticate (Authentication) est appelé
AuthenticationProvider
AuthenticationProvider a de nombreuses implémentations. Celui qui vous préoccupe le plus est généralement DaoAuthenticationProvider, hérité de AbstractUserDetailsAuthenticationProvider. Le noyau est d'implémenter l'authentification via UserDetails. DaoAuthenticationProvider sera automatiquement chargé par défaut et n'a pas besoin d'être configuré manuellement.
Examinons d'abord l'abstractUserDetailSauthenticationProvider et examinons l'authentification la plus principale:
L'authentification de l'authentification publique (authentification authentification) lève AuthenticationException {// doit être usernamepasswordAuthenticationToken assert.isinstanceof (usernamepasswordAuthenticationToken.class, authentication, messages.getMessage ("abstractUserDeTailsAuthenticationProvider.OnlySupports" pris en charge ")); // Get username String username = (authentication.getPrincipal () == null)? "Non_provided": authentication.getName (); booléen cachewasused = true; // Obtenez userDetails de Cache User = this.userCache.getUserFromCache (nom d'utilisateur); if (user == null) {cachewasused = false; Essayez la méthode abstraite {// RetrieveUser pour obtenir l'utilisateur utilisateur = rétriveUser (nom d'utilisateur, (userNamepasswordAuthenticationToken) Authentification); } catch (userNamenotFoundException notfound) {logger.debug ("utilisateur '" + nom d'utilisateur + "' non trouvé"); if (hideUserNotFoundExceptions) {Throw new BadCredentialSexception (messages.getMessage ("AbstractUserDetailsAuthenticationProvider.Badcredentials", "Bad Credentials")); } else {lancer notfound; }} Assert.notnull (utilisateur, "Retrieveuser a renvoyé null - une violation du contrat d'interface"); } essayez {// pré-vérifiez, defaultPreAuthenticationChecks, vérifiez si l'utilisateur est verrouillé ou si le compte est disponible pour pré-authenticationChecks.check (utilisateur); // Méthode abstraite, vérification personnalisée supplémentaire AuthenticationChecks (utilisateur, (userNamePasswordAuthenticationToken) Authentification); } catch (AuthenticationException Exception) {if (cachewasused) {// Il y a eu un problème, alors réessayez après la vérification // nous utilisons les dernières données (c'est-à-dire pas du cache) cachewasused = false; user = rétriveUser (nom d'utilisateur, (usernamepasswordAuthenticationToken) Authentication); pré-authenticationChecks.Check (utilisateur); Authentication-AuthenticationChecks (utilisateur, (userNamepasswordAuthenticationToken) Authentification); } else {lancer une exception; }} // Post-check DefaultPostAuthenticationChecks, vérifiez isCredentialSnOnexpired PostAuthenticationChecks.Check (utilisateur); if (! cachewasused) {this.usercache.putUserInCache (utilisateur); } Objet PrincipalToreTurn = User; if (forcePrincipalAsstring) {princialToreTurn = user.getUserName (); } RETOUR CREETESUCCESSAUTHENTICTION (PrincipalToreTurn, Authentication, User); }Le test ci-dessus est principalement basé sur l'implémentation de l'utilisateur, où la logique d'acquisition et de vérification des utilisateurs est implémentée par des classes spécifiques. L'implémentation par défaut est DaoAuthenticationProvider. Le cœur de cette classe consiste à permettre aux développeurs de fournir à UserDetailSService pour obtenir UserDetails et PasswordEncoder pour vérifier si le mot de passe est valide:
UserDetailSService privé UserDetailSService; PasswordEncoder privé PasswordEncoder;
Pour voir l'implémentation spécifique, RetrieveUser, appelez directement UserDetailSService pour obtenir l'utilisateur:
UserDetails Final Protected RetrieveUser (String Username, userNamePasswordAuthenticationToken Authentication) lève AuthenticationException {UserDetails LoadEDUser; essayez {loveDuser = this.getUserDetailsService (). LoadUserByUserName (nom d'utilisateur); } catch (userNamenotFoundException notFound) {if (authentication.getCredentials ()! = null) {String a présentépassword = authentication.getCredentials (). toString (); PasswordEncoder.ispasswordValid (usernotFoundEncodedPassword, présenté Password, null); } lancez notfound; } catch (exception RepositoryProblem) {Throw New InternalAuthenticationsViceException (RepositoryProblem.GetMessage (), RepositoryProblem); } if (chardeDuser == null) {lancez de nouveaux AuthenticationsServiceException ("UserDetailSService renvoyé null, qui est une violation du contrat d'interface"); } return chardeDuser; }Regardons la vérification:
Protected void supplémentaireAuthenticationChecks (userDetails userDetails, userNamepasswordAuthenticationToken Authentication) lève AuthenticationException {objet salt = null; if (this.saltsource! = null) {salt = this.saltsource.getsalt (userDetails); } if (authentication.getCredentials () == null) {logger.debug ("Authentification a échoué: aucune information fournie"); Jetez un nouveau BadCredentialSException (Messages.GetMessage ("AbstractUserDetailsAuthenticationProvider.BadCredentials", "Bad Identials")); } // Obtenez la chaîne de mot de passe utilisateur présentée à Password = Authentication.GetCredentials (). ToString (); // compare si le mot de passe après mot de passe encoder est le même que le mot de passe de userDetails if (! PasswordEncoder.ispasswordValid (userdetails.getPassword (), présentédpassword, sel)) {logger.debug ("Authentication a échoué: le mot de passe ne correspond pas à la valeur stockée"); Jetez un nouveau BadCredentialSException (Messages.GetMessage ("AbstractUserDetailsAuthenticationProvider.BadCredentials", "Bad Identials")); }}Résumé: Pour personnaliser l'authentification, utilisez DaoAuthenticationProvider, il vous suffit de lui fournir un mot de passe encodeur et un userDetailSservice.
Personnaliser les gestionnaires d'authentification
Spring Security fournit une authentification de classe BuilderManagerBuilder, qui vous permet d'implémenter rapidement l'authentification personnalisée.
Voir la description officielle du code source:
SecurityBuilder a utilisé pour créer un AuthenticationManager. Permet de construire facilement dans l'authentification de la mémoire, l'authentification LDAP, l'authentification basée sur JDBC, l'ajout de UserDetailSservice et l'ajout d'authentificationProvider.
AuthenticationManagerBuilder peut être utilisée pour créer un authenticationManager, qui peut créer une authentification basée sur la mémoire, l'authentification LDAP, l'authentification JDBC et ajouter UserDetailSService et AuthenticationProvider.
Utilisation simple:
@ Configuration @ activerwebsecurity @ activeglobalThodSecurity (pressenabled = true, sécurisé = true) de classe publique ApplicationsEcurity étend WebSecurityConfigurerAdapter {public SecurityConfiguration (AuthenticationManagerBuilder AuthenticationManagerBuilder, UserDetailSService UserDeTailSService, ToKenprovider TokenProvider, CorsFilter CorsFilter, SecurityProblemSupport ProblemSupport) {this.AuthenticationManagerBuilder = AuthenticationManagerBuilder; this.userDetailSService = userDetailSService; this.tokenprovider = tokenprovider; this.CorsFilter = corsFilter; this.problemsupport = ProblemSupport; } @PostConstruct public void init () {try {AuthenticationManagerBuilder .UserDetailSService (userDetailSService) .PasswordEncoder (PasswordEncoder ()); } catch (exception e) {lancez new BeaninitializationException ("Sécurité Échec de la configuration", E); }} @Override Protected void Configure (httpSecurity http) lève une exception {http .addfilterBefore (CorsFilter, usernamepasswordAuthenticationFilter.class) .ExceptionHandling () .AuthenticationEntryPoint (ProblemSrupport) .AcabledHandler (ProblemSUpporT) .and (). .heders () .frameOptions () .diable () .and () .SessionManagement () .SessionCreationPolicy (SessionCreationPolicy.Stateless) .and () .AuthorizeRestes () .Antmatchers ("/ api / regist .antmatchers ("/ api / authenticate"). permutall () .antmatchers ("/ api / compte / reset-password / init"). permutall () .antmatchers ("/ api / compte / reset-password / finition"). permutall () .antmatchers ("/ api / profil-info"). permis (). .antmatchers ("/ api / **"). authenticated () .antMatchers ("/ Management / Health"). permutall () .antmatchers ("/ Management / **"). Hasauthority (autorités Constants.Admin) .antmatchers ("/ v2 / api-docs / **"). permis (). .antMatchers ("/ Swagger-Resources / Configuration / UI"). PermitAll () .Antmatchers ("/ Swagger-Ui / index.html"). Hasauthority (AuthoritiesConstants.Admin) .And () .Apply (SecurityConfigurerAdapter ()); }}Autorisation et contrôle d'accès
Une fois l'authentification réussie, nous pouvons continuer à autoriser, qui est implémentée via AccessDecisionManager. Il existe trois implémentations du cadre, la valeur par défaut est basée sur Affirmative, qui est faite via AccessDecisionVoter, ce qui est un peu comme le ProviderManager est confié aux authentification Providers pour l'authentification.
Décision publique void (authentification Authentification, objet objet, collection <Icongattribute> configattributes) lève AccessEniedException {int deny = 0; // Traverse DecisionVoter pour (AccessDecisionVoter Voter: getDecisionVoters ()) {// Votting int result = vote.vote (authentification, objet, configattributes); if (logger.isdebugeNabled ()) {logger.debug ("électeur:" + électeur + ", renvoyé:" + résultat); } switch (result) {Case AccessDecisionVoter.Access_Granted: return; Case AccessDecisionVoter.Access_dened: Deny ++; casser; par défaut: pause; }} // veto if (deny> 0) {throw new AccessEniedException (messages.getMessage ("AbstractAccessDecisionManager.Accessdened", "l'accès est refusé")); } // Pour aller aussi loin, chaque AccessDecisionVoter s'est abstenu CheckAllowIfAllAbStainDecisions (); }Jetons un coup d'œil à AccessDecisionVoter:
Boolean Supports (configAttribute Attribut); Boolean Supports (Class <?> Clazz); int vote (authentification Authentification, objet S, collection <filmAttribute> Attributs);
L'objet est la ressource à laquelle l'utilisateur souhaite accéder et Configattribute est la condition que l'objet doit être rempli. Habituellement, la charge utile est une chaîne, comme Role_Admin. Jetons donc un coup d'œil à la mise en œuvre de RoleVoter. Le noyau consiste à extraire l'autorité accorde de l'authentification, puis à comparer avec Configattribut si les conditions sont remplies.
Public Boolean Supports (configAttribute Attribut) {if ((attribut.getAttribute ()! = null) && attribut.getAttribute (). startSwith (getRolePrefix ())) {return true; } else {return false; }} public booléen support (class <?> Clazz) {return true; } public int vote (authentification Authentification, objet Object, collection <ImgAttribute> Attributs) {if (authentication == null) {return Access_denied; } int result = access_abstain; // obtient des informations sur l'autorité accordées <? étend l'autorité accorde> autorités = extratauthorités (authentification); for (configAttribute Attribut: Attributes) {if (this.Supports (attribut)) {// Access visé par défaut result = Access_dened; // tenter de trouver une autorité accordée pour (Autorité de l'autorité accordé: autorités) {// Déterminez s'il existe une autorité de correspondance if (attribut.getAttribute (). Equals (autorité.getAuthority ())) {// vous pouvez accéder à return access_granted; }}}} Retour Résultat; }Ici, je dois demander, d'où vient la configattribute? En fait, il est dans la configuration de l'applications en matière de sécurité ci-dessus.
Comment implémenter la sécurité Web
Spring Security (pour les backends d'interface utilisateur et HTTP) dans la couche Web est basé sur des filtres de servlet, et la figure suivante montre la hiérarchie typique des gestionnaires pour une seule demande HTTP.
Spring Security est enregistré sur la couche Web via FilterChainProxy en tant que filtre unique, filtre à l'intérieur du proxy.
FilterChainProxy est équivalent à un conteneur de filtre. Grâce à VirtualFilterChain, chaque filtre interne est appelé en séquence.
public void dofilter (ServLetRequest Request, ServletResponse Response, FilterChain Chain) lève IOException, ServletException {booléen clearContext = request.getAttribute (filter_applied) == null; if (clearContext) {try {request.setAttribute (filter_applied, boolean.true); dofterinternal (demande, réponse, chaîne); } enfin {SecurityContexTholder.ClearContext (); request.RemoveAtTribute (filter_applied); }} else {dofilteRinternal (demande, réponse, chaîne); }} private void dofilterinternal (ServLetRequest Request, ServletResponse réponse, filterchain chaîne) lance ioexception, servlexception {FirewalElEquest fwRequest = foyer. HttpServletResponse fwResponse = Firewall .getFirewalledResponse ((httpServletResponse) Réponse); List <filter> filters = getFilters (fwRequest); if (filters == null || filters.size () == 0) {if (logger.isdebugeNabled ()) {logger.debug (urlutils.buildrequestUrl (fwrequest) + (filtres == null? "n'a pas de filtres correspondants": "a une liste de filtres vide")); } fwrequest.reset (); chain.dofilter (fwRequest, fwResponse); retour; } VirtualFilterChain vfc = new VirtualFilterChain (fwRequest, chaîne, filtres); vfc.Dofilter (fwRequest, fwResponse); } classe statique privée VirtualFilterChain implémente FilterChain {private final FilterChain OriginalChain; Liste finale privée <filtre> Filtres supplémentaires; Fire-feu final privé Firefest FireweLeDequest; Taille INT finale privée; private int currentPosition = 0; VirtualFilterChain privé (FirewalDeRequest FireweLeDequest, filterchain chaîne, liste <filter> Filtres supplémentaires) {this.originalchain = chaîne; this.AdditionalFilters = supplémentaires; this.size = supplémentaireFilters.size (); this.firewalleDequest = FirewalDeRequest; } public void dofilter (ServLetRequest Request, servletResponse réponse) lève ioException, servlexception {if (currentPosition == size) {if (logger.isdebugeNabled ()) {logger.debug (Urlutils.buildrequestUrl (procédure de feu de foyer) + " } // désactiver le rayage du chemin à mesure que nous quittons la chaîne de filtre de sécurité this.firewalleDequest.Reset (); OriginalChain.Dofilter (demande, réponse); } else {currentPosition ++; Filter NextFilter = AdditionalFilters.get (CurrentPosition - 1); if (logger.isdebugeNabled ()) {logger.debug (urlutils.builDrequestUrl (FirewElDequest) + "en position" + CurrentPosition + "de" + size + "dans une chaîne de filtre supplémentaire; Firing Filter:" "+ nextFilter.getClass (). GetImplleName () +" ""); } nextFilter.dofilter (demande, réponse, this); }}}}se référer à
https://spring.io/guides/topicals/spring-security-architecture/
https://docs.spring.io/spring-security/site/docs/5.0.5.release/reference/htmlsingle/#overall-architecture
Résumer
Ce qui précède est l'intégralité du contenu de cet article. J'espère que le contenu de cet article a une certaine valeur de référence pour l'étude ou le travail de chacun. Si vous avez des questions, vous pouvez laisser un message pour communiquer. Merci pour votre soutien à wulin.com.