Prefácio
As estruturas populares de autorização geral agora incluem o Shiro de Apache e a Family Spring Spring Security. Quando se trata da autenticação de microsserviço atual, precisamos usar nossa estrutura de autorização para criar nossos próprios serviços de autenticação. Hoje, o primeiro -ministro tem sido o primeiro -ministro.
A segurança da primavera implementa principalmente a autenticação (autenticação, solução quem é você?) E controle de acesso (controle de acesso, ou seja, o que você pode fazer?, Também conhecido como autorização). A segurança da primavera separa a autenticação da arquitetura da autorização e fornece pontos de extensão.
Objetos principais
O código principal está no pacote de núcleo de segurança da mola. Para entender a segurança da primavera, você precisa prestar atenção aos objetos principais dentro.
SecurityContextholder, SecurityContext e Autenticação
O SecurityContextholder é um contêiner de armazenamento para o SecurityContext. O armazenamento ThreadLocal é usado por padrão, o que significa que os métodos de segurança do SecurityContext estão disponíveis no mesmo thread.
O SecurityContext armazena principalmente as informações principais do aplicativo e é representado por autenticação em segurança da primavera.
Obtenha o diretor:
Objeto principal = SecurityContextholder.getContext (). GetAuthentication (). GetPrincipal (); if (instância principal de userDetails) {string userName = ((userDetails) principal) .getUserName ();} {string userName = principal.tostring ();};Na segurança da primavera, você pode dar uma olhada na definição de autenticação:
A autenticação da interface pública estende o principal, serializável {coleção <? estende a concessão de autoridade> getAuthorities (); / *** geralmente senha*/ objeto getCredentials (); /*** armazena detalhes adicionais sobre a solicitação de autenticação. Este pode ser um endereço IP *, número de série do certificado etc. */ objeto getDetails (); /*** Usado para identificar se é autenticado. Se você efetuar login com um nome de usuário e senha, geralmente é o nome de usuário*/ objeto getPrincipal (); / *** se é autenticado*/ boolean isauthenticated (); Void setauthenticated (boolean isauthenticated) lança ilegalArgumentException;}Em aplicações práticas, geralmente é usado o UserNamePasswordAthenticationToken:
classe abstrata public abstractAuthenticationToken implementa autenticação, credencialscontainer {} public class UsernamePasswordAthenticationToken estende abstrateAthenticationToken {}Um processo de autenticação comum geralmente é assim: Crie um UserNamePasswordAuthenticationToken e entregue -o ao AuthenticationManager para autenticação (descrito em detalhes posteriormente). Se a autenticação for passada, as informações de autenticação serão armazenadas através do SecurityContextholder.
UsernamePassWordAtHenticationToken AuthenticationToken = new UsernamePassWordAuthenticationToken (LoginVm.getUserName (), LoginVm.getPassword (); autenticação autenticação = this) AuthenticationManager.Authenticate (AuthentationTeation); SecurityContextholdR.;
UserDetails e UserDetailSService
UserDetails é uma interface essencial no Spring Security, que é usado para representar um principal.
interface pública userDetails estende serializável { / *** Informações de autorização do usuário podem ser entendidas como função* / coleção <? estende a concessão de autoridade> getAuthorities (); / ** * Senha do usuário * * @return the senha */ string getPassword (); / *** nome de usuário**/ string getUserName (); boolean isaccountnonexirird (); boolean isaccountnonlocked (); iscredentials booleansNonexirired (); boolean isenabled ();}UserDetails fornece as informações necessárias necessárias para a autenticação. No uso real, você pode implementar o UserDetails sozinho e adicionar informações adicionais, como email, celular e outras informações.
Em autenticação, o diretor geralmente é o nome de usuário. Podemos obter o UserDetails através do principal por meio do UserDetailSService:
interface pública UserDetailSService {userDetails loadUserByUserName (String Username) lança UserNameNotFoundException;}Concedida autoridade
Conforme mencionado no UserDetails, a concessão de autoridade pode ser entendida como uma função, como Role_Administrator ou Role_HR_Supervisor.
resumo
Certificação de autenticação
AuthenticationManager
A autenticação é alcançada principalmente através da interface AuthenticationManager, que contém apenas um método:
interface pública AuthenticationManager {autenticação Authentication (autenticação Authentication) lança autenticaçãoException;}O método autenticate () faz principalmente três coisas:
AuthenticationException é uma exceção de tempo de execução, que geralmente é tratada pelo aplicativo de maneira comum. O código do usuário geralmente não precisa ser capturado e processado especificamente.
A implementação padrão do AuthenticationManager é o ProviderManager, que delega um conjunto de instâncias do AuthenticationProvider para implementar a autenticação.
AuthenticationProvider e AuthenticationManager são semelhantes à autenticação, ambos contêm autenticação, mas possui um suporte de método adicional para permitir a consulta se o chamador suporta um determinado tipo de autenticação:
interface pública AuthenticationProvider {autenticação Authentication (autenticação Authentication) lança autenticaçãoException; Suportes booleanos (classe <?> autenticação);}O ProviderManager contém um conjunto de opções de autenticação. Ao executar a autenticação, ele atravessa os provedores e depois chama o suporte. Se suportado, ele executa o método de autenticação que atravessa o provedor atual. Se um provedor for autenticado com sucesso, quebre.
Autenticação pública Autenticação (autenticação de autenticação) lança autenticaçãoException {class <? estende a autenticação> totest = autenticação.getClass (); AuthenticationException lastException = null; Resultado de autenticação = nulo; debug boolean = logger.isdebugenabled (); para (provedor de autenticaçãoProvider: getProviders ()) {if (! provider.supports (totest)) {continuação; } if (debug) {Logger.debug ("Autenticação Tentativa de" + provider.getClass (). getName ()); } tente {resultado = provider.authenticate (autenticação); if (resultado! = null) {copyDetails (autenticação, resultado); quebrar; }} Catch (AccountStatusexception e) {prepareexception (e, autenticação); // Sec-546: Evite pesquisar provedores adicionais se a falha de autenticação for devida ao // status da conta inválida lançar e; } catch (internaTeAthenticationServiceException e) {prepareexception (e, autenticação); jogar e; } catch (autenticaçãoException e) {lastException = e; }} if (resultado == null && parent! = null) {// Permita que o pai tente. tente {resultado = parent.authenticate (autenticação); } catch (providerNotfoundException e) {// ignore como lançaremos abaixo se nenhuma outra exceção ocorreu antes de // chamando o pai e o pai // pode lançar o ProviderNotfound, mesmo que um provedor na criança já tenha lidado com a solicitação} catch (autenticaçãoException e) {lastException = e; }} if (resultado! = null) {if (eraseCredentialSafterauthentication && (resultado de resultado de credencialscontainer)) {// Autenticação está concluída. Remover credenciais e outros dados secretos // do resultado da autenticação ((credencialscontainer). } EventPublisher.publishaThenticationsuccess (resultado); resultado de retorno; } // pai foi nulo ou não autenticado (ou lançou uma exceção). if (lastException == null) {lastException = new ProvideNotFoundException (MessageS.getMessage ("ProviderManager.ProviderNotFound", new Object [] {ToTest.getName ()}, "Nenhum autenticaçãoProvider encontrado para {0})); } preparexception (LastException, autenticação); lançar a LastException; }Como pode ser visto no código acima, o ProviderManager possui um pai opcional. Se o pai não estiver vazio, pai.authenticate (autenticação) é chamado
AuthenticationProvider
AuthenticationProvider tem muitas implementações. O que você mais está preocupado é geralmente daoauthenticationProvider, herdado do AbstractUserDetailSauthenticationProvider. O núcleo é implementar a autenticação por meio do UserDetails. O DAOAuthenticationProvider será carregado automaticamente por padrão e não precisa ser configurado manualmente.
Vamos primeiro olhar para o abstrumserDetailSauthenticationProvider e analisar a autenticação mais central:
Autenticação pública Autenticação (autenticação Autenticação) lança AuthenticationException {// deve ser UsernamePassWordAuthentication suportado ")); // obtém nome de usuário string userName = (autenticação.getPrincipal () == null)? "None_proved": autentication.getName (); CachewasUsed booleano = true; // Obtenha userDetails do cache user = this.userCache.getUserFromCache (nome de usuário); if (user == null) {cachewasUsed = false; tente {// recuperar o método abstrato para obter o usuário do usuário = recuperar a autenticação (nome de usuário, (UsernamePasswordAthenticationToken) autenticação); } Catch (UserNameNotFoundException notfound) {Logger.debug ("User '" + Nome de usuário + "' não encontrado"); if (hideusernotfoundExceptions) {lança nova badCredentialSexception (MessageS.getMessage ("AbstractUserDetailSauthenticationProvider.badcredentials", "Bad Credenciais")); } else {tiro notfound; }} Assert.NotNull (Usuário, "Retrieveser retornou nulo - uma violação do contrato de interface"); } tente {// pré-check, defaultPreauthenticationChecks, verifique se o usuário está bloqueado ou se a conta está disponível para pré-autenticaçãoChecks.check (usuário); // Método abstrato, Verifique personalizado adicionalAuthenticationChecks (Usuário, (UsernamePasswordAthenticationToken) Autenticação); } Catch (exceção da AuthenticationException) {if (cachewasUsed) {// houve um problema, então tente novamente depois de verificar // Estamos usando os dados mais recentes (ou seja, não do cache) CachewasUsed = false; Usuário = RetrieveUser (nome de usuário, (UsernamePasswordAthenticationToken) Autenticação); preauthenticationchecks.check (usuário); adicionalAuthenticationChecks (Usuário, (UserNamePasswordAthenticationToken) Autenticação); } else {throw Exception; }} // pós-check defaultPostaThenticationChecks, verifique ascredentialsNonexirirends. if (! CachewasUsed) {this.usercache.putUserincache (usuário); } Objeto principal e Usuário; if (forcePrincipalasstring) {principal. } Return CreateSucceSauthentication (PrincipalTeTurn, Autenticação, Usuário); }O teste acima é baseado principalmente na implementação do UserDetails, onde a lógica de aquisição e verificação do usuário é implementada por classes específicas. A implementação padrão é DaoauthenticationProvider. O núcleo desta classe é permitir que os desenvolvedores forneçam a UserDetailSService para obter o UserDetails e o PasswordEncoder para verificar se a senha é válida:
UserDEtailSService privado de UserDetailSService; private senhancoder wastwrendEncoder;
Para ver a implementação específica, recupere, ligue diretamente a UserDetailSService para obter o usuário:
O UserDetails final protegido Rectrieveser (String UserName, UserNamePassWordAuthenticationToken Authentication) lança AuthenticationException {userDetails carregamento; tente {loadedUser = this.getUserDetailSService (). loadUserByUserName (nome de usuário); } catch (userNameNotFoundException notfound) {if (autenticação.getCredentials ()! = null) {string apresentadoPassword = autenticação.getCredentials (). tostring (); PasswordEncoder.ISPASSWORDVALID (UserNotFoundEncodedPassword, ApresentouDPassword, NULL); } jogue não -fundido; } catch (excepcionário repositoryproblem) {lança new internaTeuthenticationServiceException (repositoryproblem.getMessage (), repositoryproblem); } if (carregedUser == NULL) {THRET NOVA internaThenticationServiceException ("UserDetailSService retornou nulo, que é uma violação de contrato de interface"); } return loadedUser; }Vejamos a verificação:
void protegido adicionalAuthenticationChecks (userDetails userDetails, UserNamePasswordAthenticationToken Authentication) lança autenticaçãoException {objeto sal = null; if (this.saltsource! = null) {sal = this.saltsource.getSalt (userDetails); } if (autenticação.getCredentials () == null) {Logger.debug ("Autenticação falhou: nenhuma credenciais fornecidas"); lançar uma nova BadCredentialSexception (MessageS.GetMessage ("AbstractUserDetailSauthenticationProvider.badcredentials", "Bad Credenciais")); } // Obtenha a senha de senha do usuário apresentadaPassword = autenticação.getCredentials (). Tostring (); // Compare se a senha após a senha do codificador é a mesma que a senha dos userDetails se (! lançar uma nova BadCredentialSexception (MessageS.GetMessage ("AbstractUserDetailSauthenticationProvider.badcredentials", "Bad Credenciais")); }}Resumo: Para personalizar a autenticação, use DaoaAthentationProvider, você só precisa fornecer a PasswordEncoder e o UserDetailSService.
Personalize gerentes de autenticação
A Spring Security fornece um Builder Class AuthenticationManagerBuilder, que permite implementar rapidamente a autenticação personalizada.
Veja a descrição oficial do código -fonte:
SecurityBuilder usado para criar um AuthenticationManager. Permite construir facilmente a autenticação de memória, autenticação LDAP, autenticação baseada em JDBC, adicionar UserDetailSService e adicionar autenticação doPROVER.
O AuthenticationManagerBuilder pode ser usado para criar um AuthenticationManager, que pode criar autenticação baseada em memória, autenticação LDAP, autenticação JDBC e adicionar UserDetailSservice e AuthenticationProvider.
Uso simples:
@Configuration@enableWebSecurity@EnableGlobalMethodSecurity (PrePostEnabled = true, SecureDenabled = true) Public Class Applicationsecurity estende WebSecurityConfigureRAdApter {Public SecurityConfiguration (AuthenticationManagerBuilder AuthenticationManager, UserDetailService CORSFILTER, SecurityProblemSupport ProblemaPort) {this.authenticationManagerBuilder = AuthenticationManagerBuilder; this.UserDetailSService = UserDetailSService; this.TokenProvider = TokenProvider; this.corsFilter = corsFilter; this.problemSupport = Problemupport; } @PostConstruct public void init () {try {autenticationManagerBuilder .UserDetailSService (UserDetailSService) .PasswordEncoder (PasswordEncoder ()); } catch (Exceção e) {lança nova beaninitializationException ("Falha na configuração de segurança", e); }} @Override Protected void Configure (httpsecurity http) lança exceção {http .addfilterBefore (corsFilter, userNamePassswordAtHenticationFilter.Cllass) .ExceptionHandling () .authenticSoint (problemas. .Disable () .Headers () .FrameOptions () .Disable () .And () .SessionManagement () .SessionCreationPolicy (sessionCreationPolicy.StatEless) .And () .AuthorizeReQuests () .ntmatchers ("/api/registro"). .antmatchers ("/api/autenticate"). Permitall () .antmatchers ("/api/conta/reset-pastword/init"). Permitall () .antmatchers ("/api/account/reset-pas-password/finalize"). Permitall () .AntMatchers ("/api/infild-Fo"). .ntmatchers ("/api/**"). autenticated () .antmatchers ("/gerenciamento/saúde"). Permitall () .antmatchers ("/gerenciamento/**"). Hasauthority (autoridadesconstants.admin) .ntmatchers ("/v2/api-docs/**"). .antmatchers ("/swagger-resources/configuration/ui"). Permitall () .antmatchers ("/swagger-ui/index.html"). hasauthority (autoridadesconstants.admin) .and () .Apply (SecurityCigureRadapter ()); }}Autorização e controle de acesso
Depois que a autenticação for bem -sucedida, podemos continuar a autorizar, que é implementado através do AccessDecisionManager. Existem três implementações da estrutura, o padrão é afirmativo baseado, que é feito através do AccessDecisionVoter, que é um pouco como o provedorManager é confiado a autenticação dos fornecedores de autenticação.
Decisão pública void (autenticação de autenticação, objeto, objeto, coleta <configattribute> configattributes) lança AccessDeniedException {int neny = 0; // Traversal DecisionVoter para (AccessDecisionVoter Votador: getDecisionVoters ()) {// votação int resultado = vote.vote (autenticação, objeto, configattributes); if (logger.isdebugenabled ()) {Logger.debug ("Votador:" + eleitor + ", retornado:" + resultado); } switch (resultado) {case AccessDecisionVoter.access_granted: return; Case AccessDecisionVoter.access_denied: negar ++; quebrar; Padrão: quebra; }} // veto if (negar> 0) {throw new accessdeniedException (messages.getMessage ("abstrateAccessDecisionManager.accessdenied", "Access é negado")); } // Para chegar até aqui, todo accessDecisionVoter se abstinha de checkOllowIlabstaindecisions (); }Vamos dar uma olhada no AccessDecisionVoter:
suportes booleanos (atributo configattribute); suporte booleano (classe <?> clazz); int vote (autenticação de autenticação, objeto s, atributos de coleção <figattribute>);
Objeto é o recurso que o usuário deseja acessar e o Configattribute é a condição de que o objeto precisa ser atendido. Geralmente a carga útil é uma string, como Role_Admin. Então, vamos dar uma olhada na implementação do RoleVoter. O núcleo é extrair a autoridade concedida da autenticação e comparar com o Configattribute se as condições são atendidas.
public boolean supports (attribute configattribute) {if ((attribute.getAttribute ()! = null) && attribute.getAttribute (). startSwith (getRoleprefix ())) {return true; } else {return false; }} public boolean suporta (classe <?> clazz) {return true; } public int vote (autenticação de autenticação, objeto, coleção <configattribute> atributos) {if (autenticação == null) {return access_denied; } int resultado = access_abstain; // recebe informações de informação concedida <? estende a concessão de autoridade> autoridades = extrações (autenticação); for (configattribute attribute: atributes) {if (this.supports (attribute)) {// acesso negado por resultado padrão = access_denied; // tentam encontrar uma autoridade concedida correspondente para (autoridade concedida da autoridade: autoridades) {// determinar se existe uma autoridade correspondente se (attribute.getAttribute (). Igual a (autoridade.getauthority ())) {// você pode acessar o retorno access_granted; }}}} Retorne resultado; }Aqui tenho que perguntar de onde veio o Configattribute? De fato, está na configuração do Applicationsecurity acima.
Como implementar a segurança da web
A segurança da primavera (para back -ends de interface do usuário e HTTP) na camada da Web é baseada nos filtros do servlet, e a figura a seguir mostra a hierarquia típica de manipuladores para uma única solicitação HTTP.
A segurança da primavera é registrada na camada da web através do filtro -Proxy como um único filtro, filtro dentro do proxy.
O filtroChainProxy é equivalente a um contêiner de filtro. Através do VirtualFilterchain, cada filtro interno é chamado em sequência.
public void Dofilter (Solicitação de servletRequest, resposta servletResponse, cadeia de filtragem) lança IoException, servletexception {boolean clearcontext = request.getAttribute (filter_applied) == null; if (clearcontext) {try {request.SetAttribute (filtro_applied, boolean.True); dofilterinternal (solicitação, resposta, cadeia); } finalmente {SecurityContextholder.clearContext (); request.RemoVeattribute (filtro_applied); }} else {dofilterinternal (solicitação, resposta, cadeia); }} private void Dofilterinternal (Solicitação de servletRequest, resposta servletResponse, cadeia de filtragem) lança IoException, servletexception {firewalledRequest fwrequest = Firewall .getFirewalledRequest ((httpslerTrequest) request); HttpServletResponse fWRESPSE = firewall .getFireWalledResponse (((httpServletResponse) resposta); List <filter> filters = getFilters (fwrequest); if (filtros == null || filters.size () == 0) {if (logger.isdebugenabled ()) {Logger.debug (urlutils.buildRequesturl (fwrequest) + (filtros == null? "Não tem filtros correspondentes": "tem uma lista de filtro"); } fwrequest.reset (); Chain.Dofilter (fwrequest, fwresponse); retornar; } VirtualFilterChain vfc = new VirtualFilterChain (fwrequest, cadeia, filtros); vfc.dofilter (fwrequest, fwresponse); } classe estática privada virtualfilterchain implementa o filtro {private final FilterChain OriginalChain; Lista final privada <Filter> AdicionalFilters; FirewalledRequest FirewalledRequest do FirewalledRequest; tamanho final privado int; private int currentposition = 0; Private VirtualFilterchain (FirewallEdRequest FirewallEdRequest, Cadeia de Filtro, List <filter> adicionalFilters) {this.originalChain = Chain; this.AdditionalFilters = adicionalFilters; this.size = adicionalFilters.size (); this.firewallledRequest = firewalledRequest; } public void Dofilter (Solicitação de servletRequest, resposta servletResponse) lança IoException, servletexception {if (currentPosition == size) {if (logger.isdebugenabled ()) {Logger.debug (urlutils.buildRequesturl (FIREWLEDRELDRELDRETRESTRETRESTR) } // Desativar a faixa de caminho quando saímos da cadeia de filtros de segurança. originalchain.dofilter (solicitação, resposta); } else {currentPosition ++; Filtro nextFilter = adicionalFilters.get (currentPosition - 1); if (logger.isdebugenabled ()) {Logger.debug (urlutils.buildRequesturl (firewalledRequest) + "na posição" + currentposition + "de" size + "na cadeia de filtro adicional;" Filtro de disparo: " + nextFilter.getClass (). } nextFilter.dofilter (solicitação, resposta, isso); }}}}consulte
https://spring.io/guides/topicals/spring-security-architecture/
https://docs.spring.io/spring-security/site/docs/5.0.5.release/reference/htmlsingle/#overall-architecture
Resumir
O acima é o conteúdo inteiro deste artigo. Espero que o conteúdo deste artigo tenha certo valor de referência para o estudo ou trabalho de todos. Se você tiver alguma dúvida, pode deixar uma mensagem para se comunicar. Obrigado pelo seu apoio ao wulin.com.