Préface
Lorsque vous utilisez SpringCloud pour construire un système distribué avec l'architecture de microservice, OAuth2.0 est la norme de l'industrie pour la certification. Spring Security OAuth2 fournit également un ensemble complet de solutions pour prendre en charge l'utilisation de OAuth2.0 dans l'environnement Spring Cloud / Spring Boot, fournissant des composants prêts à l'emploi. Cependant, au cours du processus de développement, nous constaterons que parce que les composantes de la sécurité de Spring OAuth2 sont particulièrement complètes, cela rend très gênant à étendre ou n'est pas facile de spécifier directement la solution d'extension, telle que:
Faire face à ces scénarios, il est prévu que de nombreuses personnes qui ne connaissent pas Spring Security Oauth2 ne pourront pas commencer. Sur la base des exigences du scénario ci-dessus, comment intégrer élégamment la connexion du code de vérification SMS et la connexion tierce, et comment être considéré comme élégamment intégré? Il y a les exigences suivantes:
Sur la base des exigences de conception ci-dessus, nous présenterons en détail comment développer un ensemble de composants d'authentification de connexion intégrés pour répondre aux exigences ci-dessus dans l'article.
Lisez cet article que vous devez connaître sur le système de certification OAuth2.0, Springboot, Springsecurity, Spring Cloud et d'autres connaissances connexes
Idées
Jetons un coup d'œil au processus d'authentification de Spring Security OAuth2:
Dans ce processus, il n'y a pas beaucoup de points d'entrée, et l'idée de connexion intégrée est la suivante:
Après avoir accédé à ce processus, vous pouvez essentiellement intégrer élégamment la connexion tierce.
accomplir
Après avoir introduit les idées, le code suivant montre comment l'implémenter:
La première étape consiste à définir l'intercepteur pour intercepter les demandes de connexion
/ ** * @author liqiu * @Date 2018-3-30 ** / @ ComponentPublic Class IntegrationAuthenticationFilter étend génériquefilterBean implémente applicationContextAware {private static final string auth_type_parm_name = "Auth_type"; chaîne finale statique privée oauth_token_url = "/ oauth / token"; Collection privée <IntegrationAuthenticator> Authenticators; application application privée applicationContext; Private requestmatcher requestmatcher; Public IntegrationAuthenticationFilter () {this.requestmatcher = new Orrequestmatcher (new AntpathRequestmatcher (oauth_token_url, "get"), new AntpathRequestmatcher (oauth_token_url, "post")); } @Override public void dofilter (servletRequest servletRequest, servletResponse ServletResponse, filterchain filterChain) lance ioexception, servlexception {httpservletRequest request = (httpservletRequest) servleTrequest; HttpServletResponse Response = (httpServletResponse) servletResponse; if (requestmatcher.matches (request)) {// Définir les informations de connexion intégrées intégration Authentication intégrationAuthentication = new IntegrationAuthentication (); intégrationAuthentication.setAuthType (request.getParameter (auth_type_parm_name)); intégrationAuthentication.setAuthParameters (request.getParameTermap ()); IntégrationAuthenticationContext.set (intégrationAuthentication); essayez {// prétraitement this.prepare (intégrationAuthentication); FilterChain.Dofilter (demande, réponse); // Post-traitement de cette.compte (intégrationAuthentication); } enfin {intégrationAuthenticationContext.Clear (); }} else {filterchain.dofilter (request, réponse); }} / ** * PRÉCROSSING * @PARAM Intégration Authentication * / private void prépare (intégrationAuthentication IntegrationAuthentication) {// Lazy Loading Authenticator if (this.authenticators == null) {synchronized (this) {map <string, intégrationAuthentiat applicationContext.getBeansOfType (intégrationAuthenticator.class); if (intégrationAuthenticatorMap! = null) {this.authenticators = intégrationAuthenticAtormap.values (); }}} if (this.authenticators == null) {this.authenticators = new ArrayList <> (); } pour (IntegrationAuthenticator Authenticator: Authenticators) {if (authenticator.Support (intégrationAuthentication)) {authenticator.prepare (intégrationAuthentication); }}} / ** * Post-traitement * @Param IntegrationAuthentication * / private void complet (intégrationAuthentication IntegrationAuthentication) {for (IntegrationAuthenticator Authentification: Authentications) {if (authenticator.Support (intégrationAuthentication)) {Authentication.ComPtete (intégrationAuthentication); }}} @Override public void setApplicationContext (ApplicationContext ApplicationContext) lève BeanSexception {this.ApplicationContext = applicationContext; }}Dans cette classe, deux parties des travaux sont principalement terminées: 1. Obtenez le type d'authentification actuel en fonction des paramètres, 2. Appelez une intégration Authenticatrice différente.
Étape 2: Mettez l'intercepteur dans la chaîne d'interception
/ ** * @Author liqiu * @Date 2018-3-7 ** / @ configuration @ perteAuthorizationserverpublic classe AutorisationserverConfiguration étend les autorisations de redéconnection de redéconnection de redéconnection de redéconnection; @Autowired Private AuthenticationManager AuthenticationManager; @Autowired Private IntegrationUserDetailSService IntegrationAserDetailSService; @Autowired private weBResponseExceptionStanslateur weBResponseExceptionStanslateur; @Autowired Private IntegrationAuthenticationFilter IntegrationAuthenticationFilter; @Autowired Private DatabaseCachableClientDetailSService redistientDetailSService; @Override public void configure (clientTailsServiceConfigurer les clients) lève une exception {// todo persist les clients détaillent les clients.WithClientDetails (re-divientDetailSService); } @Override public void configure (AuthorizationsErveredpointsConfigurer Endpoints) {endpoint .TokenStore (new Redistokenstore (redisconnectionfactory)) // .AccessStOnConverter (JWTACCESSTOKENCONverter ()) .AuthenticationManager (AuthenticationManager) .ExceptionTransSerat .ReUseReFreshTokens (false) .UserDetailSService (intégrationUserDetailSService); } @Override public void configure (AuthorizationserSeCurityConfigurer Security) lève une exception {Security.AllowFormAuthenticationForClients () .TokenKeyAccess ("isAuthenticated ()") .CheckTokenCcess ("permatication ()") .AddTokenDpointPuthenticationFilter (intégrationAuthenticationFilter); } @Bean Public PasswordEncoder PasswordEncoder () {return new BCryptPasswordEncoder (); } @Bean public JWTACCESSTOKENCONverter JWTACCESSTOKENCONverter () {JWTACCESSTOKENCONVERTER JWTACCESSTOKENCONVERTER = NEW JWTACCESSTOKENCONverter (); JWTACCESSTOKENCONVERTER.SETSIGNINGKEY ("COLA-CLOUD"); retour jwtaccesstokenConverter; }}Mettez l'intercepteur dans la chaîne d'authentification en appelant la sécurité. .AddTokenDendPointAuthenticationFilter (IntegrationAuthenticationFilter); méthode.
Étape 3: traiter les informations de l'utilisateur en fonction du type d'authentification
@ServicePublic Class IntegrationUserDeTailsService implémente userDetailSService {@autowired private upmClient upMclient; Liste privée <IntegrationAuthenticator> Authenticators; @Autowired (obligatoire = false) public void setIntegrationAuthenticators (list <intégrationAuthenticator> Authenticators) {this.authenticators = authenticators; } @Override public User LoadUserByUserName (String Username) lève UserNamenotFoundException {intégrationAuthentication IntegrationAuthentication = IntegrationAuthenticationContext.get (); // juger s'il s'agit d'une connexion intégrée if (intégrationAuthentication == null) {intégrationAuthentication = new IntegrationAuthentication (); } intégrationAuthentication.SetUserName (nom d'utilisateur); UserVo userVo = this.authentiCate (intégrationAuthentication); if (userVo == null) {lancer un nouveau userNamenotFoundException ("nom d'utilisateur ou erreur de mot de passe"); } Utilisateur utilisateur = new user (); Beanutils.copyProperties (userVo, utilisateur); this.setAuthorize (utilisateur); RETOUR UTILISATEUR; } / ** * Définir les informations d'autorisation * * @param utilisateur * / public void setAuthorize (utilisateur utilisateur) {Authorize Authorize = this.upmClient.getAuthorize (user.getId ()); user.setRoles (Authorize.GetRoles ()); user.setResources (autorise.getResources ()); } private userVo Authenticiate (intégrationAuthentication IntegrationAuthentication) {if (this.authenticators! = null) {for (IntegrationAuthenticator Authenticator: Authenticator) {if (authenticator.support (intégrationAuthentication)) {return authenticator.authenticate (intégrationAuthentication); }} return null; }}Voici une intégrationUserDetailSService. La méthode Authenticate sera appelée dans la méthode LoadUserByUserName. Dans la méthode d'authentification, le type d'authentification de contexte actuel appellera une intégration Authenticatrice différente pour obtenir des informations utilisateur. Jetons un coup d'œil à la façon dont le nom d'utilisateur et le mot de passe par défaut sont gérés:
@ Composant @ primairePublic class userNamepasswordAuthenticator étend abstractPreparable IntegrationAuthenticator {@autowired privé ucclient ucclient; @Override public userVo Authentication (intégrationAuthentication IntegrationAuthentication) {return ucclient.finSUserByUserName (intégrationAuthentication.getUsername ()); } @Override public void prépare (intégration Authentication IntegrationAuthentication) {} @Override Public Boolean Support (intégrationAuthentication IntegrationAuthentication) {return stringUtils.isempty (intégrationAuthentication.getAuthType ()); }}UserNamePasswordAuthenticator ne gérera que le type d'authentification par défaut sans type d'authentification spécifié. Cette classe obtient principalement des mots de passe via le nom d'utilisateur. Ensuite, regardons comment gérer la connexion du code de vérification d'image:
/ ** * Authentification du code de vérification intégré * @author liqiu * @Date 2018-3-31 ** / @ ComponentPublic Class VerificationCodeIntegrationAuthenticator étend usernamepasswordAuthenticator {private final static String Verification_code_auth_type = "vc"; @Autowired Private VCCClient VCCClient; @Override public void prépare (intégrationAuthentication IntegrationAuthentication) {String vctoken = intégrationAuthentication.getAuthParameter ("vc_token"); String VCCODE = intégrationAuthentication.getAuthParAmètre ("VC_CODE"); // Vérification Vérification CodeResult <boolean> result = vccClient.validate (vctoken, vccode, null); if (! result.getData ()) {Throw new oAuth2Exception ("Vérification Code Erreur"); }} @Override Public Boolean Support (intégrationAuthentication IntegrationAuthentication) {return Verification_Code_Auth_Type.equals (intégrationAuthentication.getAuthType ()); }}VerificationCodeIntegrationAuthenticator hérite userNamepasswordAuthenticator car il doit seulement vérifier si le code de vérification est correct dans la méthode de préparation et si l'utilisateur l'a obtenu en utilisant le nom d'utilisateur et le mot de passe. Cependant, le type d'authentification est "VC" avant de pouvoir être traité. Jetons un coup d'œil à la façon dont la connexion du code de vérification SMS est gérée:
@ComponentPublic Class SMSIntegrationAuthenticator étend AbstractPreparable IntegrationAuthenticator implémente ApplicationEventPublisheraware {@autowired privé ucclient ucclient; @Autowired Private VCCClient VCCClient; @Autowired Private MotwordEncoder PasswordEncoder; Application PrivateEventPublisher ApplicationEventPublisher; chaîne statique finale privée sms_auth_type = "sms"; @Override public userVo Authentication (intégrationAuthentication IntegrationAuthentication) {// Obtenez le mot de passe, la valeur réelle est la chaîne de code de vérification mot de passe = intégrationAuthentication.getAuthParamètre ("mot de passe"); // Obtenez le nom d'utilisateur, la valeur réelle est le numéro de téléphone mobile String username = intégrationAuthentication.getUsername (); // Publier des événements, vous pouvez écouter des événements pour enregistrer automatiquement l'utilisateur. ApplicationEventPublisher.publisheVent (new smsAuthentiCateBeForeEvent (intégrationAuthentication)); // requête l'utilisateur via le numéro de téléphone mobile userVo userVo = this.ucClient.FindUserByPhonenumber (nom d'utilisateur); if (userVo! = null) {// Définissez le mot de passe en tant que code de vérification userVo.SetPassword (PasswordEncoder.encode (mot de passe)); // Publier des événements, vous pouvez écouter les événements pour la notification des messages. } return userVo; } @Override public void prépare (intégrationAuthentication IntegrationAuthentication) {String smStoken = intégrationAuthentication.getAuthParameter ("sms_token"); String smscode = intégrationAuthentication.getAuthParameter ("mot de passe"); String username = intégrationAuthentication.getAuthParameter ("username"); Result <boolean> result = vccClient.validate (smStoken, smscode, nom d'utilisateur); if (! result.getData ()) {Throw New OAuth2Exception ("Vérification Code Erreur ou expiré"); }} @Override Public Boolean Support (intégrationAuthentication IntegrationAuthentication) {return sms_auth_type.equals (intégrationAuthentication.getAuthType ()); } @Override public void setApplicationEventPublisher (ApplicationEventPublisher ApplicationEventPublisher) {this.ApplicationEventPublisher = ApplicationEventPublisher; }}SMSIntegrationAuthenticator sera prétraitement le code de vérification SMS connecté pour déterminer s'il est illégal. S'il est illégal, il interrompra directement la connexion. Si le prétraitement est passé, les informations de l'utilisateur seront obtenues via le numéro de téléphone mobile lors de l'obtention des informations de l'utilisateur, et le mot de passe sera réinitialisé pour passer la vérification de mot de passe ultérieure.
Résumer
Dans cette solution, l'utilisation principale de la chaîne de responsabilité et du modèle de conception de l'adaptateur pour résoudre le problème de la connexion intégrée, améliore l'évolutivité et ne pollue pas le code source du ressort. Si vous souhaitez hériter d'autres connexions, il vous suffit d'implémenter une intégration Authenticatrice personnalisée.
Adresse du projet: https://gitee.com/leecho/cola-cloud
Téléchargement local: cola-cloud_jb51.rar
Ce qui précède est tout le contenu de cet article. J'espère que cela sera utile à l'apprentissage de tous et j'espère que tout le monde soutiendra davantage Wulin.com.