Prefacio
Cuando se usa SpringCloud para construir un sistema distribuido con arquitectura de microservicio, OAuth2.0 es el estándar de la industria para la certificación. Spring Security OAuth2 también proporciona un conjunto completo de soluciones para admitir el uso de OAuth2.0 en el entorno de arranque de Spring Cloud/Spring, proporcionando componentes listos para usar. Sin embargo, durante el proceso de desarrollo, encontraremos que debido a que los componentes de Spring Security OAuth2 son particularmente completos, esto hace que sea muy inconveniente extender o no es fácil especificar directamente la solución de extensión, como:
Al enfrentar estos escenarios, se espera que muchas personas que no están familiarizadas con Spring Security OAuth2 no puedan comenzar. Según los requisitos de escenario anteriores, ¿cómo integrar elegantemente el inicio de sesión del código de verificación SMS e inicio de sesión de terceros, y cómo considerarse elegantemente integrado? Existen los siguientes requisitos:
Según los requisitos de diseño anteriores, introduciremos en detalle cómo desarrollar un conjunto de componentes integrados de autenticación de inicio de sesión para cumplir con los requisitos anteriores en el artículo.
Lea este artículo que necesita saber sobre el sistema de certificación OAuth2.0, SpringBoot, SpringSecurity, Spring Cloud y otros conocimientos relacionados
Ideas
Echemos un vistazo al proceso de autenticación de Spring Security OAuth2:
En este proceso, no hay muchos puntos de entrada, y la idea del inicio de sesión integrado es la siguiente:
Después de acceder a este proceso, básicamente puede integrar el inicio de sesión de terceros elegantemente.
lograr
Después de presentar las ideas, el siguiente código muestra cómo implementarlo:
El primer paso es definir las solicitudes de inicio de sesión de interceptor para interceptar
/** * @author liqiu * @date 2018-3-30 **/ @componentPublic class integrationAuthenticationFilter extiende GenericFilterBean implementos AplicationContextAware {private static final String Auth_type_Parm_name = "Auth_Type"; cadena final estática privada oauth_token_url = "/oauth/token"; Colección privada <integrationAuthenticator> autenticadores; application privateContext ApplicationContext; REQUIERSE PRIVADO SELCHMATCHER; 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) lanza IOException, ServletException {HttpServletRequest request = (httpServletRequest) ServletRequest; HttpservletResponse respuesta = (httpservletResponse) servletResponse; if (requestMatcher.matches (request)) {// Establecer información integrada IntegrationAuthentication IntegrationAuthentication = new IntegrationAuthentication (); IntegrationAuthentication.setAuthtype (request.getParameter (Auth_Type_Parm_Name)); IntegrationAuthentication.SetAuthParameters (request.getParametermap ()); IntegrationAuthenticationContext.set (IntegrationAuthentication); intente {// preprocesando this.prepare (integraciónAuthentication); filterChain.dofilter (solicitud, respuesta); // Postprocesamiento this.complete (IntegrationAuthentication); } Finalmente {integraciónAuthenticationContext.Clear (); }} else {FilterChain.DoFilter (solicitud, respuesta); }} / *** Preprocesamiento* @param integración Authentication* / private void prepare (integracióneuthentication IntegrationAuthentication) {// Lazy Loading Authenticator if (this.authenticators == null) {sincronizado (this) {map <tr. applicationContext.getBeAnsofType (integración Authenticator.class); if (integraciónAuthenticatormap! = NULL) {this.authenticators = integración AuthentAtAnmap.Values (); }}} if (this.authenticators == null) {this.authenticators = new ArrayList <> (); } para (integración Authenticator Authenticator: Authenticators) {if (Authenticator.Support (IntegrationAuthentication)) {Authenticator.Prepare (integración Authentication); }}} / *** Postprocesamiento* @param integración Authentication* / private void completo (integrationAuthentication IntegrationAuthentication) {para (integración Authenticator Authentication: Authentications) {if (Authenticator.Support (integración Authentication) {Authentication. }}} @Override public void setApplicationContext (ApplicationContext ApplicationContext) lanza Beansexception {this.ApplicationContext = ApplicationContext; }}En esta clase, dos partes del trabajo se completan principalmente: 1. Obtenga el tipo de autenticación actual de acuerdo con los parámetros, 2. Llame a una integración diferente.
Paso 2: Coloque el interceptor en la cadena de intercepción
/** * @author liqiu * @Date 2018-3-7 **/ @configuration @enableAuthorizationserverPublic Class AutorizationserverConfiguration extiende autorizationserverConfigurerAdapter {@aUtoWired redisección privada redisección redisección; @AUTOWIRED AuthenticationManager de autenticación de autenticación; @AUTOWIREDEDREDEDREDEDREDUSerDetailsService IntegrationUserDetailsService; @AUtowired WebResponseExceptionTranslator WebResponseExceptionTranslator; @AUTOWIREDEDREDEDREDED AUTHENTICACIÓNFILTER IntegrationAuthenticationFilter; @AUTOWIRED DataBASECACHABLECLECLECLECLIENTEDETAILSSERVICE REDISCLIENTDETAILSSERVICE; @Override public void configure (clientDetailsServiceConfigurer clientes) lanza la excepción {// TODO Persist Clients Detalles CLIENTS.WithClientDetails (redisclientDetailservice); } @Override public void configure (autorizationserVerEndPointSconfigurer endpoints) {endpoints .TokenStore (new RedistokenStore (redisConnectionFactory)) // .accesstokenconverter (jwtaccessTokenconverter () .AuthenticationManager (autenticación) .ExceptionTransLator (WeBebonSlator (We WeBsLator) .reuseRefreshTokens (falso) .UserDetailsService (IntegrationUserDetailsService); } @Override public void configure (autorizationServerSecurityConfigurer Security) lanza la excepción {Security.AllowFormAuthenticationForClients () .TokenkeyAccess ("Isauthenticated ()") .CheckTokenAccess ("Permitall ()"). } @Bean public PasswordEncoder PasswordEncoder () {return new bcRyptPassPassWordEnder (); } @Bean public Jwtaccesstokenconverter jwtaccesstokenconverter () {jwtaccesstokenconverter jwtaccesstokenconverter = new jwtaccesstokenconverter (); jwtaccesstokenconverter.setsigningkey ("cola-nube"); return jwtaccesstokenconverter; }}Ponga al interceptor en la cadena de autenticación llamando a la seguridad. .addTokenendPointAuthenticationFilter (integración AuthenticationFilter); método.
Paso 3: procesar información del usuario de acuerdo con el tipo de autenticación
@ServicePublic Class IntegrationUserDetailsService Implementa UserDetailsService {@aUtoWired private upmclient upmclient; Lista privada <integrationAuthenticator> autenticadores; @AUTOWIRED (requerido = falso) public void setinteGrationAuthenticators (List <ClegationAuthenticator> autenticadores) {this.authenticators = Authenticators; } @Override Public User LoadUserByUserName (String UserName) lanza usernAmenotFoundException {integración Authentication IntegrationAuthentication = IntegrationAuthenticationContext.get (); // juzga si se trata de un integro de inicio de sesión if (integrationAuthentication == null) {integrationAuthentication = new IntegrationAuthentication (); } integración Authentication.SetUsername (nombre de usuario); Uservo uservo = this.authenticate (integraciónAuthentication); if (uservo == null) {lanzar new UserNaMenotFoundException ("Error de nombre de usuario o contraseña"); } Usuario user = new User (); Beanutil.copyproperties (Uservo, usuario); this.setAuthorize (usuario); devolver el usuario; } / ** * Establecer información de autorización * * @param user * / public void setAuthorize (usuario de usuario) {autorizar autorize = this.upmclient.getAuthorize (user.getID ()); user.setRoles (autorize.getRoles ()); user.setResources (autorize.getResources ()); } private Uservo Authenticate (IntegrationAuthentication IntegrationAuthentication) {if (this.authenticators! = null) {for (integración autenticador de autenticador: autenticators) {if (autenticator.support (integrationAuthentication) {return Authenticator.authenticate (integración Authentication); }} return null; }}Aquí hay un IntegrationUserDetailsService. El método de autenticación se llamará en el método LoadUserByUserName. En el método Authenticate, el tipo de autenticación de contexto actual llamará a diferente IntegrationAuthenticator para obtener información del usuario. Echemos un vistazo a cómo se manejan el nombre de usuario y la contraseña predeterminados:
@Componente @primaria, classpublic UserNamePassWordAuthenticator extiende AbstractPreparableInseRationAuthenticator {@aUtowired private ucclient ucclient; @Override Public Uservo Authentication (IntegrationAuthentication IntegrationAuthentication) {return ucclient.finduserByUserName (integraciónAuthentication.getUsername ()); } @Override public void Prepare (integración de integración IntegrationAuthentication) {} @Override Public Boolean Support (IntegrationAuthentication IntegrationAuthentication) {return stringUtils.isEmpty (integración Authentication.getAuthType ()); }}USERNAMEPASSWORDAUTHENTATOR solo manejará el tipo de autenticación predeterminado sin el tipo de autenticación especificado. Esta clase obtiene principalmente contraseñas a través del nombre de usuario. A continuación, echemos un vistazo a cómo manejar el código de verificación de la imagen Iniciar sesión:
/*** Autenticación del código de verificación integrado* @author liqiu* @date 2018-3-31 **/ @componentPublic class verificationCodeIntegRationAuthenticator extiende usernamePasswordAuthenticator {VERIFICACIÓN ESTÁTICA FINAL PRIVADA VERIFICACIÓN_CODE_AUTH_TYPE = "VC"; @AUtowired private VCCClient VCCClient; @Override public void Prepare (integración Authentication IntegrationAuthentication) {String vcToken = integración Authentication.getAuthParameter ("VC_Token"); String VCCode = integración Authentication.GetAuthParameter ("VC_CODE"); // Verificación de verificación CodeResult <Boolean> result = VCCClient.Validate (VCToken, VCCode, NULL); if (! result.getData ()) {tire nuevo OAUTH2Exception ("Error de código de verificación"); }} @Override Public Boolean Support (IntegrationAuthentication IntegrationAuthentication) {return verification_code_auth_type.equals (integrationAuthentication.getAuthType ()); }}Verificación CODEDEGRATIONAUTHENTATOR Hereeda a UserNamePassWordAuthenticator porque solo necesita verificar si el código de verificación es correcto en el método Prepare y si el usuario lo ha obtenido utilizando el nombre de usuario y la contraseña. Sin embargo, el tipo de autenticación es "VC" antes de que pueda procesarse. Echemos un vistazo a cómo se maneja el inicio de sesión del código de verificación SMS:
@ComponentPublic SMSInteGrationAuthenticator extiende AbstractPreparableInseGrationAuthenticator implementos AplicationEventPublisheraAwe {@AUTOWIREDIREDIRD Private UCClient UCClient; @AUtowired private VCCClient VCCClient; @AUTOWIREDIREDIRD PasswordEncoder PasswordEncoder; Application privado EventPublisher Aplication EventPublisher; cadena estática final privada sms_auth_type = "sms"; @Override public Uservo Authentication (integración Authentication IntegrationAuthentication) {// Obtener contraseña, el valor real es el código de verificación String Password = IntegrationAuthentication.GetAuthParameter ("Passwase"); // Obtener nombre de usuario, el valor real es el número de teléfono móvil String UserName = integrationAuthentication.getUsername (); // Publicar eventos, puede escuchar eventos para registrar automáticamente el usuario este.ApplicationEventPublisher.PublishEvent (nuevo smsauthenticateBeForeVent (integración Authentication)); // Consulta el usuario a través del número de teléfono móvil uservo uservo = this.ucclient.finduserbyphonEnumber (nombre de usuario); if (uservo! = null) {// Establezca la contraseña como el código de verificación Uservo.setPassword (contraseñaCoder.Encode (contraseña)); // Publicar eventos, puede escuchar eventos para la notificación de mensajes this.ApplicationEventPublisher.PublisheVent (nuevo SmSAuthenticateSuccessEvent (integración Authentication)); } return Uservo; } @Override public void Prepare (integración Authentication IntegrationAuthentication) {String smstoken = integración Authentication.getAuthParameter ("SMS_TOKIN"); String SMScode = integración Authentication.GetAuthParameter ("Password"); String UserName = integración Authentication.GetAuthParameter ("Nombre de usuario"); Result <Boolean> result = VCCClient.Validate (Smstoken, SMScode, UserName); if (! result.getData ()) {tire nuevo OAUTH2Exception ("Error de código de verificación o expirado"); }} @Override Public Boolean Support (IntegrationAuthentication IntegrationAuthentication) {return sms_auth_type.equals (integrationAuthentication.getAuthType ()); } @Override public void setApplicationEventPublisher (ApplicationEventPublisher ApplicationEventPublisher) {this.ApplicationEventPublisher = ApplicationEventPublisher; }}SMSInteGrationAuthenticator preprocesará el código de verificación SMS conectado para determinar si es ilegal. Si es ilegal, interrumpirá directamente el inicio de sesión. Si se pasa el preprocesamiento, la información del usuario se obtendrá a través del número de teléfono móvil al obtener la información del usuario, y la contraseña se restablecerá para aprobar la verificación posterior de la contraseña.
Resumir
En esta solución, el uso principal de la cadena de responsabilidad y el patrón de diseño del adaptador para resolver el problema del inicio de sesión integrado, mejora la escalabilidad y no contamina el código fuente de la primavera. Si desea heredar otros inicios de sesión, solo necesita implementar un IntegrationAuthenticator personalizado.
Dirección del proyecto: https://gitee.com/leecho/colacloud
Descarga local: COLA CLOUD_JB51.rar
Lo anterior es todo el contenido de este artículo. Espero que sea útil para el aprendizaje de todos y espero que todos apoyen más a Wulin.com.