Prefacio
Los populares marcos de autorización general ahora incluyen el Shiro de Apache y la Spring Family Spring Security. Cuando se trata de la autenticación de microservicio actual, necesitamos utilizar nuestro marco de autorización para construir nuestros propios servicios de autenticación. Hoy, el primer ministro ha sido el primer ministro.
Spring Security implementa principalmente autenticación (autenticación, solución ¿Quién es usted?) Y el control de acceso (control de acceso, es decir, ¿qué puede hacer?, También conocido como autorización). Spring Security separa la autenticación de la arquitectura de autorización y proporciona puntos de extensión.
Objetos centrales
El código principal está bajo el paquete Spring-Security-Core. Para comprender la seguridad de la primavera, debe prestar atención a los objetos centrales en el interior.
SecurityContexTholder, SecurityContext and Authentication
SecurityContexTholder es un contenedor de almacenamiento para SecurityContext. El almacenamiento de ThreadLocal se usa de forma predeterminada, lo que significa que los métodos SecurityContext están disponibles en el mismo hilo.
SecurityContext almacena principalmente la información principal de la aplicación y está representada por autenticación en Spring Security.
Obtenga el director:
Objeto principal = SecurityContexTholder.getContext ().
En Spring Security, puede echar un vistazo a la definición de autenticación:
La autenticación de la interfaz pública se extiende principal, serializable {colección <? extiende a GrantedAuthority> getAuthorities (); / *** Por lo general, contraseña*/ Object GetCredentials (); /*** almacena detalles adicionales sobre la solicitud de autenticación. Estos pueden ser una dirección IP *, número de serie certificado, etc. */ Object GetDetails (); /*** Se usa para identificar si está autenticado. Si inicia sesión con un nombre de usuario y contraseña, generalmente es el nombre de usuario*/ Object getPrincipal (); / *** si está autenticado*/ boolean isauthenticated (); void setAuthenticated (boolean isAuthenticated) arroja ilegalargumentException;}En aplicaciones prácticas, generalmente se usa UserNamePassWordAuthenticationToken:
Public Abstract Class AbstractAuthenticationToken Implementa Autenticación, CredentialScontainer {} public class UserNamePassWderAuthenticationToken extiende AbstractAuthenticationToken {}Un proceso de autenticación común suele ser así: cree un UserNamePassWordAuthenticationToken y luego dale a la autenticación de AuthenticationManager (descrito en detalle más adelante). Si se pasa la autenticación, la información de autenticación se almacenará a través del SecurityContexTholder.
UserNamePasswlayAuthenticationToken AuthenticationToken = new UserNamePassWalDauthenticationToken (LoginvM.GetUsername (), LoginvM.GetPassword ()); Autenticación Autenticación = this.AuthenticationManager.authenticate (autenticación de autenticación); SecurityContexTholder.getContext ().
UserDetails y userDetailsService
UserDetails es una interfaz clave en Spring Security, que se utiliza para representar un principal.
La interfaz pública userDetails extiende serializable { / *** La información de autorización del usuario puede entenderse como rol* / colección <? extiende a GrantedAuthority> getAuthorities (); / ** * contraseña de usuario * * @return la contraseña */ string getPassword (); / *** Nombre de usuario**/ String getUsername (); booleano isaccountnonexpired (); booleano isaccountnonlocked (); booleano iscredentialsnonexpired (); boolean isEnabled ();}UserDetails proporciona la información necesaria requerida para la autenticación. En uso real, puede implementar UserDetails por sí mismo y agregar información adicional, como correo electrónico, móvil y otra información.
En la autenticación, el director suele ser el nombre de usuario. Podemos obtener dientes de usuario a través del principal a través de userdetailsservice:
Interfaz pública UserDetailsService {UserDetails LoadUserByUserName (String UserName) lanza usernAmenotFoundException;}Otorgada
Como se menciona en UserDetAls, GrantedAuthority puede entenderse como un papel, como role_administrator o role_hr_superervisor.
resumen
Certificación de autenticación
AuthenticationManager
La autenticación se logra principalmente a través de la interfaz AuthenticationManager, que solo contiene un método:
Interfaz pública AutenticationManager {Autenticación de autenticación (autenticación de autenticación) lanza AuthenticationException;}El método Authenticate () principalmente hace tres cosas:
AuthenticationException es una excepción de tiempo de ejecución, que generalmente es manejada por la aplicación de manera común. El código de usuario generalmente no necesita ser atrapado y procesado específicamente.
La implementación predeterminada de AuthenticationManager es Providermanager, que delega un conjunto de instancias de autenticación para implementar la autenticación.
AuthenticationProvider y AuthenticationManager son similares a la autenticación, ambos contienen autenticación, pero tiene un soporte de método adicional para permitir la consulta de si la persona que llama admite un tipo de autenticación dado:
Public Interface AutenticationProvider {autenticación de autenticación (autenticación de autenticación) lanza AuthenticationException; Boolean Supports (clase <?> Autenticación);}Providermanager contiene un conjunto de administradores de autenticación. Al ejecutar la autenticación, atraviesa proveedores y luego llama al soporte. Si es compatible, ejecuta el método de autenticación que atraviesa el proveedor actual. Si un proveedor se autentica con éxito, rompa.
Autenticación de autenticación pública (autenticación de autenticación) lanza AuthenticationException {class <? extiende la autenticación> TOTEST = Authentication.getClass (); AuthenticationException lastException = null; Resultado de la autenticación = nulo; boolean debug = logger.isDeBugeNabled (); For (AuthenticationProvider Provider: GetProviders ()) {if (! Provider.Supports (TOTEST)) {continuar; } if (debug) {logger.debug ("intento de autenticación usando" + proveedor.getClass (). getName ()); } try {result = Provider.authenticate (autenticación); if (resultado! = null) {copyDetails (autenticación, resultado); romper; }} Catch (AccountStatusException e) {PrepareException (e, autenticación); // SEC-546: Evite los proveedores adicionales de las encuestas si la falla de autenticación se debe a // el lanzamiento de estado de la cuenta no válido E; } catch (internalAuthenticationServiceException e) {preparareException (e, autenticación); tirar E; } catch (AuthenticationException e) {lastException = e; }} if (resultado == null && parent! = null) {// Permitir que el padre lo intente. prueba {resultado = parent.authenticate (autenticación); } catch (ProvidernotFoundException e) {// ignora como lanzaremos a continuación si no se produjo otra excepción antes de // llamar a los padres y el padre // puede lanzar ProvidernotFound a pesar de que un proveedor en el niño ya manejó la solicitud} Catch (AuthenticationException e) {LastException = e; }} if (resultado! = NULL) {if (ERASECREDENTALSAFTERAUTHENTICACIÓN && (resultado instanceo de credentialScontainer)) {// La autenticación está completa. Eliminar credenciales y otros datos secretos // de la autenticación ((credencialscontainer) resultado) .EraSecredentials (); } EventPublisher.PublishAuthenticationSuccess (resultado); resultado de retorno; } // El padre era nulo, o no se autenticó (o arrojó una excepción). if (lastException == null) {lastException = new ProvidernotFoundException (Messages.getMessage ("Providermanager.ProvidernotFound", new Object [] {Totest.getName ()}, "no se encontró autenticación para {0}"); } prepararException (lastException, autenticación); tirar lastexception; }Como se puede ver en el código anterior, Providermanager tiene un padre opcional. Si el padre no está vacío, se llama a los parent.authenticate (autenticación)
Autenticación
AuthenticationProvider tiene muchas implementaciones. Lo que más le preocupa es generalmente DaoauthenticationProvider, heredado de AbstractUserDetailSauthenticationProvider. El núcleo es implementar la autenticación a través de UserDetails. DaoAuthenticationProvider se cargará automáticamente de forma predeterminada y no es necesario configurar manualmente.
Veamos primero el abstractUserDetailSauthenticationProvider y veamos la autenticación más central:
Autenticación de autenticación pública (autenticación de autenticación) lanza AutenticationException {// debe ser UserNamePassWalDauthenticationToken Afirmación.ISInStanceOf (usernamePassWalDauthenticationToken.class, autenticación, mensajes.getMessage ("abstractUserDetailSauthenticationProvider.onlysUpports", solo usernameMeTeMaStaSagaSagaseSAWLEPASTAMENTAMEN apoyado ")); // Obtener un nombre de usuario String UserName = (Authentication.getPrincipal () == NULL)? "None_Provided": Authentication.getName (); Boolean Cachewasused = verdadero; // Obtenga UserDetails de Cache User = this.usercache.getuserFromCache (nombre de usuario); if (user == null) {cachewasused = false; Pruebe {// Método de resumen de RemieveUser para obtener usuarios de usuario = RemieveUser (UserName, (UserNamePassWordAuthenticationToken) Autenticación); } Catch (usernAmenotFoundException NotFound) {logger.debug ("usuario" + nombre de usuario + "'no encontrado"); if (hideUsernotFoundExceptions) {Throw New BadCredentialSException (Messages.getMessage ("AbstractUserDetailSauthenticationProvider.BadCredententials", "Credenciales malas")); } else {lanzar nofound; }} Afirmar.notnull (usuario, "Recuperuser devuelto nulo - una violación del contrato de interfaz"); } try {// Pre-check, defaultPeauthenticationChecks, verifique si el usuario está bloqueado o si la cuenta está disponible para preauthenticationChecks.eck (usuario); // Método de resumen, verificación personalizada de Autenticación de Autenticación (usuario, (UserNamePassWordAuthenticationToken) Autenticación); } Catch (excepción de AuthenticationException) {if (cachewasused) {// Hubo un problema, así que intente nuevamente después de verificar // estamos utilizando los últimos datos (es decir, no desde el caché) Cachewasused = false; user = RemieveUser (UserName, (UserNamePassWordAuthenticationToken) Autenticación); preauthenticationChecks.eck (usuario); ADEMÁS ADICIONACIÓN DE ATENCIÓN (USUARIO, (USERNAMEPASSWORDAUTHENTICACIÓN AUTENTACIÓN); } else {tirar excepción; }} // Post-check PausePostAuthenticationChecks, verifique IsCredentialSnonExpired PostAuthenticationChecks.check (usuario); if (! Cachewasususeus) {this.userCache.putuserInfache (usuario); } Object PrincipalToREnn = user; if (forcePrIpRipalAsstring) {PrincipalToReturn = user.getUsername (); } return CreateSuccessAuthentication (PrincipalToReturn, autenticación, usuario); }La prueba anterior se basa principalmente en la implementación de las colas de usuario, donde la lógica de adquisición y verificación del usuario se implementa mediante clases específicas. La implementación predeterminada es DaoauthenticationProvider. El núcleo de esta clase es permitir a los desarrolladores proporcionar a UserDetailsService para obtener dientes de usuario y contraseña para verificar si la contraseña es válida:
UserDetailsService privado UserService DetailsService; contraseña privada de contraseña contraseña;
Para ver la implementación específica, recuperar User, llame directamente a UserDetailsService para obtener el usuario:
Los usuarios finales protegidos details RemieveUser (String UserName, UserNamePassWordAuthenticationToken Authentication) lanza AuthenticationException {UserDetails LoadedUser; Pruebe {loadeduser = this.getuserDetailsService (). LoadUserByUserName (nombre de usuario); } Catch (usernAmenotFoundException NotFound) {if (Authentication.getCredentials ()! = NULL) {String PresentedPassword = Authentication.getCredentials (). ToString (); PasswordEncoder.IsSisSwordValid (UserNoTFoundEnnedPassword, PresentedPassword, NULL); } tirar nofound; } Catch (Exception RepositoryProblem) {tire New GonalAuthenticationServiceException (RepositoryProblem.getMessage (), RepositoryProblem); } if (loadedUser == null) {lanzar newalAutTauthenticationServiceException ("UserDetailsService devuelto NULL, que es una violación del contrato de interfaz"); } return loadeduser; }Veamos la verificación:
Vacío protegido adicional AuthenticationChecks (UserDetails UserDetails, UserNamePassWordAuthenticationToken Autenticación) lanza AuthenticationException {Object salt = null; if (this.saltSource! = null) {sal = this.saltSource.getSalt (userDetails); } if (autenticación.getCredentials () == null) {logger.debug ("fallado de autenticación: no se proporcionan credenciales"); tirar nueva badcredentialSexception (Messages.getMessage ("AbstractUserDetailSauthenticationProvider.BadCredentials", "Credenciales malas")); } // Obtenga la cadena de contraseña del usuario PresentedPassword = Authentication.getCredentials (). ToString (); // Compare si la contraseña después de PasswordEncoder es la misma que la contraseña de UserDetails if (! PasswordEncoder.estisSwordValid (userDetails.getPassword (), PresentedPassword, Salt)) {logger.debug ("Falló de autenticación: la contraseña no coincide con el valor almacenado"); tirar nueva badcredentialSexception (Messages.getMessage ("AbstractUserDetailSauthenticationProvider.BadCredentials", "Credenciales malas")); }}Resumen: para personalizar la autenticación, use DaOauthenticationProvider, solo necesita proporcionarle contraseña y userDetailsService.
Personalizar los gerentes de autenticación
Spring Security proporciona una clase de constructor AuthenticationManagerBuilder, que le permite implementar rápidamente la autenticación personalizada.
Consulte la descripción oficial del código fuente:
SecurityBuilder solía crear un AuthenticationManager. Permite una construcción fácilmente de autenticación de memoria, autenticación LDAP, autenticación basada en JDBC, agregando userDetailsService y agregando autenticación de Provider.
AuthenticationManagerBuilder se puede utilizar para crear un autenticación de autenticación, que puede crear autenticación basada en la memoria, autenticación LDAP, autenticación JDBC y agregar userDetailsService y AuthenticationProvider.
Uso simple:
@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)public class ApplicationSecurity extends 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 (contraseña () } Catch (Exception e) {Throw New BeanInitializationException ("Falló de configuración de seguridad", e); }} @Override protegido void configure (httpsecurity http) lanza la excepción {http .addfilterbefore (corsfilter, usernamePasswordAuthenticationFilter.class) .ExceptionHandling () .AuthenticationEntryPoint (ProblemsUppport) .CessDandiedHandler (ProblemsUppport) .and ().).).).). .disable () .Headers () .FrameOptions () .disable () .And () .sessionManagement () .sessionCreationPolicy (sessionCreatPolicy.Stateless) .And () .AuthorizeRequests () .antMatchers ("/API/Registro"). Permitall (). .antMatchers ("/api/autenticate"). Permitall () .antMatchers ("/API/Account/Reset-Password/Init"). Permitall () .antMatchers ("/API/Account/Reset-Password/Final .antMatchers ("/api/**"). Authenticated () .antMatchers ("/gestión/salud"). Permitall () .antMatchers ("/gestión/**"). Hasauthority (autoritionSconstants.admin) .antMatchers ("/v2/api-docs/**"). Permitall () .antMatchers ("/Swagger-Resources/Configuration/UI"). Permitall () .antMatchers ("/Swagger-ui/index.html"). HASAUTHORITY (AuthoritiesConstants.admin) .and () .Apply (SecurityConfigurerAdapter ()); }}Autorización y control de acceso
Una vez que la autenticación es exitosa, podemos continuar autorizando, que se implementa a través de AccessDecisionManager. Hay tres implementaciones del marco, el valor predeterminado se basa en la afirmación, que se realiza a través de AccessDecisionVoter, que es un poco como el Providermanager se confía a la autenticación para la autenticación.
Public void Decision (autenticación de autenticación, objeto objeto, colección <EftigAttribute> configattributes) lanza AccessDeniedException {int deny = 0; // Traversal DecisionVoter para (AccessDecisionVoter Voter: getDecisionVoters ()) {// votación int resultado = vot.vote (autenticación, objeto, configattributes); if (logger.isDebugeNabled ()) {logger.debug ("votante:" + votante + ", devuelto:" + resultado); } switch (resultado) {case accessDecisionVoter.access_granted: return; Case AccessDecisionVoter.Access_Denied: Deny ++; romper; predeterminado: ruptura; }} // veto if (deny> 0) {lanzar nueva accessdeniedException (mensajes.getMessage ("abstractAccessDecisionManager.accessDenied", "Access se niega")); } // Para llegar tan lejos, cada accessDecisionVoter abstenió a checkAllowifallabStainDecisions (); }Echemos un vistazo a AccessDecisionVoter:
Boolean Supports (ConfigAttribute Attribute); Boolean Supports (Class <?> Clazz); int voto (autenticación Autenticación, S Objeto S, Collection <EnfigAttribute> Atributos);
El objeto es el recurso al que el usuario desea acceder, y configattribute es la condición que debe cumplirse el objeto. Por lo general, la carga útil es una cadena, como rol_admin. Así que echemos un vistazo a la implementación de RoleVoter. El núcleo es extraer la autenticación de la autenticación y luego comparar con configattribute si se cumplen las condiciones.
Public Boolean Supports (configattribute attribute) {if ((attribute.getAttribute ()! = null) && attribute.getAttribute (). startshith (getRolePrefix ())) {return true; } else {return false; }} Public Boolean Supports (class <?> Clazz) {return true; } public int vote (autenticación autenticación, objeto objeto, colección <EftigAttribute> atributos) {if (autenticación == null) {return access_denied; } int resultado = access_abstain; // Obtener la información de información de la Autoridad de Grado <? extiende la Autoridad de GranTeDauthoridad = ExtractAuthorities (autenticación); for (configattribute attribute: attributes) {if (this.supports (attribute)) {// access denegado por resultado predeterminado = access_denied; // Intenta encontrar una autoridad otorgada de correspondencia para (autoridad de Autoridad de Grande: autoridades) {// Determinar si hay una autoridad coincidente si (attribute.getAttribute (). Equals (autory.getAuthority ())) {// Puede acceder a return access_granted; }}}} Resultado de retorno; }Aquí tengo que preguntar, ¿de dónde vino ConfigAttribute? De hecho, se encuentra en la configuración de la aplicación de aplicaciones anterior.
Cómo implementar la seguridad web
Spring Security (para backends de UI y HTTP) en la capa web se basa en filtros de servlet, y la siguiente figura muestra la jerarquía típica de los controladores para una sola solicitud HTTP.
Spring Security se registra en la capa web a través de FilterChainProxy como un solo filtro, filtro dentro del proxy.
FilterChainProxy es equivalente a un contenedor de filtro. A través de VirtualFilterChain, cada filtro interno se llama en secuencia.
public void dofilter (solicitud de servletRequest, respuesta de servicio de servicio, cadena de filtro de filtro) lanza ioexception, servletException {boolean clearContext = request.getAttribute (filtre_applied) == null; if (clearContext) {try {request.setAttribute (filtre_applied, boolean.true); dofilterinternal (solicitud, respuesta, cadena); } Finalmente {SecurityContexTholder.ClearContext (); request.removeAttribute (filter_applied); }} else {dofilterInternal (solicitud, respuesta, cadena); }} private void dofilterInternal (solicitud de servletRequest, respuesta de servicio de servicio, cadena de filtro de filtro) lanza ioexception, servletException {firewalledRequest fwrequest = firewall .getfirewalledRequest ((httpservletRequest) solicitud); HttpServletResponse fwResponse = firewall .getfirewalledResponse ((httpservletResponse) respuesta); List <filter> filters = getFilters (FWRQUEST); if (filters == null || filters.size () == 0) {if (logger.isdeBugeNabled ()) {logger.debug (urlutils.buildRequesturl (fwrequest) + (filters == null? "No tiene filtros coincidentes": "tiene una lista de filtros vacía")); } fwrequest.reset (); Chain.dofilter (fwRQUEST, FWRESPONSE); devolver; } VirtualFilterChain vfc = new VirtualFilterChain (fwRQuest, cadena, filtros); vfc.dofilter (fwrequest, fwresponse); } Clase estática privada VirtualFilterChain implementa FilterChain {private final FilterChain OriginalChain; Lista final privada <Tilter> Filtros adicionales; Request firewalled Request privado Firewalled Firewalled; Tamaño privado final de int; privado int currentPosition = 0; Private VirtualFilterChain (FirewalledRequest FirewalledRequest, FilterChain Chain, List <Sterry> adicional Filters) {this.OriginalChain = cadena; this. this.size = adicionalfilters.size (); this.fireWalledRequest = FirewalledRequest; } public void dofilter (ServletRequest Solicit, Respuesta ServletResponse) lanza IOException, ServLetException {if (currentPosition == size) {if (logger.isdeBugeNabled ()) {logger.deBug (urlutils.buildRequUrl (firewalledRequest + "alcanzó la cadena de filtro adicional; procedimiento; } // Desactivar las rayas de ruta a medida que salimos de la cadena de filtro de seguridad this.fireWalledRequest.reset (); OriginalChain.dofilter (solicitud, respuesta); } else {currentPosition ++; Filtro nextFilter = adicionalFilters.get (CurrentPosition - 1); if (logger.isdeBugeNabled ()) {logger.debug (urluTils.BuildRequestUrl (FireWalledRequest) + "At Position" + CurrentPosition + "de" + size + "en cadena de filtro adicional; filtro de disparo: '" + nextFilter.getClass (). GetSimplename () + ""); } nextFilter.dofilter (solicitud, respuesta, esto); }}}}referirse a
https://spring.io/guides/topicals/spring-security-architecture/
https://docs.spring.io/spring-security/site/docs/5.0.5.release/reference/htmlsinglez/#overall-architecture
Resumir
Lo anterior es todo el contenido de este artículo. Espero que el contenido de este artículo tenga cierto valor de referencia para el estudio o el trabajo de todos. Si tiene alguna pregunta, puede dejar un mensaje para comunicarse. Gracias por su apoyo a Wulin.com.