Um artigo leva você a obter o SpringBoot e SpringSecurity para obter a função de login automático

O login automático é uma função muito comum quando estamos desenvolvendo software. Por exemplo, fazemos login na caixa de correio QQ:

Insira a descrição da imagem aqui
Em muitos sites, veremos opções semelhantes ao fazer o login. Afinal, é muito problemático sempre permitir que os usuários insiram seu nome de usuário e senha.

A função de login automático significa que após o usuário efetuar login com sucesso, dentro de um determinado período de tempo, se o usuário fechar o navegador e reabri-lo, ou o servidor reiniciar, não há necessidade de o usuário efetuar login novamente e o usuário ainda pode acessar diretamente os dados da interface

Como um recurso comum, nosso Spring Security também deve fornecer suporte correspondente.Neste artigo, veremos como implementar esse recurso no Spring Security.

1. Junte-se a lembrar-me

Para conveniência de configuração, basta adicionar duas dependências:

Insira a descrição da imagem aqui
Adicione o seguinte código à classe de configuração:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    @Bean
    PasswordEncoder passwordEncoder(){
    
    
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    
    
        auth.inMemoryAuthentication()
                .withUser("yolo")
                .password("123").roles("admin");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .rememberMe()
                .and()
                .csrf().disable();
    }
}

Podemos ver que aqui basta adicionar uma .rememberMe()lata, o recurso de login automático é adicionado inserido com sucesso.

Em seguida, adicionamos aleatoriamente uma interface de teste:

@RestController
public class HelloController {
    
    
    @GetMapping("/hello")
    public String hello(){
    
    
        return "Hello Yolo !!!";
    }
}

Insira a descrição da imagem aqui
Nesse momento, todos descobriram que a página de login padrão tem mais uma opção, que é lembrar de mim. Inserimos o nome de usuário e a senha, marcamos a caixa Lembrar-me e clicamos no botão de login para realizar a operação de login.

Como você pode ver, nos dados de login, além do nome de usuário e senha, existe também um lembrete, o motivo de mostrar isso é para avisar a todos se você precisa customizar a página de login, que tal a chave da opção Lembrar? escrever.

Depois que o login for bem-sucedido, ele irá automaticamente para a interface hello. Observamos que, quando o sistema acessa a interface hello, o cookie carrega:

Insira a descrição da imagem aqui
Foi anotado aqui mais um remember-me, que é o núcleo implementado aqui, sobre isso remember-mevou explicar, vamos testar os resultados.

Em seguida, fechamos o navegador e reabrimos o navegador. Em circunstâncias normais, o navegador é fechado e reaberto. Se precisarmos acessar a interface hello novamente, precisaremos fazer login novamente. Mas, neste momento, vamos visitar a interface hello novamente e descobrir que podemos acessá-la diretamente sem fazer login novamente. Isso mostra que nossa configuração de RememberMe entrou em vigor (ou seja, a função de login automático entrará em vigor na próxima vez).

2. Análise de princípio

É lógico que se o navegador for fechado e reaberto, será necessário fazer o login novamente. Agora não há necessidade de esperar. Então, como essa função funciona?

Primeiro, vamos analisar o extra no cookie remember-me. Este valor é uma string transcodificada em Base64. Podemos usar algumas ferramentas on-line na Internet para decodificá-lo. Você pode simplesmente escrever duas linhas de código para decodificá-lo você mesmo:


    @Test
    void contextLoads() {
    
    
        String s = new String(
                Base64.getDecoder().decode("eW9sbzoxNjAxNDczNTY2NTA1OjlmMGY5YjBjOTAzYmNjYmU3ZjMwYWM0NjVlZjEzNmQ5"));
        System.out.println("s = " + s);
    }

Execute este código, a saída é a seguinte:

s = yolo:1601473566505:9f0f9b0c903bccbe7f30ac465ef136d9

Isso pode ser visto com a string Base64 :separada em três partes:

(1) O primeiro parágrafo é o nome do usuário, isso não precisa ser questionado.
(2) O segundo parágrafo parece ser um carimbo de data / hora. Após analisá-lo por meio de ferramentas online ou código Java, descobrimos que se trata de um dado duas semanas depois.
(3) No terceiro parágrafo, não vou vendê-lo. Este é o valor calculado usando a função hash MD5. Seu formato de texto simples é
username + ":" + tokenExpiryTime + ":" + password + ":" + keyque a última chave é um valor de sal hash, que pode ser usado para evitar que o token seja modificado.

Aprendi um cookie remember-meapós o significado, então temos que lembrar o meu processo de login e muito fácil de adivinhar o.

Depois que o navegador é fechado, e depois reaberto, os usuários vão acessar a interface ola, a seguir irão carregar o cookie remember-mepara o servidor, após o serviço para obter o valor, pode-se calcular facilmente o nome do usuário e a data de validade, e então de acordo com o usuário A senha do usuário é recuperada por nome e, em seguida, o valor hash é calculado por meio da função hash MD5 e, em seguida, o valor hash calculado é comparado com o valor hash passado pelo navegador para confirmar se o token é válido.

O processo é assim. A seguir, verificaremos a exatidão desse processo por meio da análise do código-fonte.

Três, análise do código-fonte

Em seguida, usamos o código-fonte para verificar se o que dissemos acima está correto.

Aqui são apresentados principalmente a partir de dois aspectos, um é o processo de geração do token de lembrar-me e o outro é o processo de analisá-lo.

1. Gerar

Os principais métodos de processamento gerados são:TokenBasedRememberMeServices#onLoginSuccess:

@Override
public void onLoginSuccess(HttpServletRequest request, HttpServletResponse response,
  Authentication successfulAuthentication) {
    
    
 String username = retrieveUserName(successfulAuthentication);
 String password = retrievePassword(successfulAuthentication);
 if (!StringUtils.hasLength(password)) {
    
    
  UserDetails user = getUserDetailsService().loadUserByUsername(username);
  password = user.getPassword();
 }
 int tokenLifetime = calculateLoginLifetime(request, successfulAuthentication);
 long expiryTime = System.currentTimeMillis();
 expiryTime += 1000L * (tokenLifetime < 0 ? TWO_WEEKS_S : tokenLifetime);
 String signatureValue = makeTokenSignature(expiryTime, username, password);
 setCookie(new String[] {
    
     username, Long.toString(expiryTime), signatureValue },
   tokenLifetime, request, response);
}
protected String makeTokenSignature(long tokenExpiryTime, String username,
  String password) {
    
    
 String data = username + ":" + tokenExpiryTime + ":" + password + ":" + getKey();
 MessageDigest digest;
 digest = MessageDigest.getInstance("MD5");
 return new String(Hex.encode(digest.digest(data.getBytes())));
}

(1) Primeiro, extraia o nome de usuário / senha da autenticação para fazer o login com sucesso.
(2) Após o sucesso do login, a senha poderá ser apagada, portanto, se a senha não for obtida no início, recarregue o usuário do UserDetailsService e obtenha a senha novamente.
(3) Em seguida, obtenha o período de validade do token, que é de duas semanas por padrão.
(4) O próximo makeTokenSignaturemétodo invocado para calcular um valor de hash, um valor de hash é calculado de acordo com o nome de usuário e o token é uma senha válida, chave. Se não tivermos para definir esta chave, o padrão é RememberMeConfigurer#getKeydefinir métodos, seu valor é uma string UUID.
(5) Por fim, coloque o nome do usuário, o período de validade do token e o valor de hash calculado no Cookie.

Com relação ao quarto ponto, deixe-me falar sobre isso novamente.

Como não definimos sua própria chave, o valor padrão da chave é uma string UUID, isso trará um problema, se o servidor for reiniciado, a chave torna-se, levando a distribuir antes que todo remember-metoken de logon automático seja inválido, portanto, Podemos especificar esta chave. A maneira de especificar é a seguinte:

@Override
protected void configure(HttpSecurity http) throws Exception {
    
    
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .rememberMe()
            .key("yolo")
            .and()
            .csrf().disable();
}

Se você configurar a chave, ainda poderá acessar a interface hello mesmo se o servidor reiniciar, mesmo se o navegador for aberto e fechado

Este é o processo de geração de tokens para lembrar de mim. Quanto a como chegar ao método onLoginSuccess, aqui pode lhe dar um pequeno lembrete:

AbstractAuthenticationProcessingFilter # doFilter -> AbstractAuthenticationProcessingFilter # successfulAuthentication -> AbstractRememberMeServices # loginSuccess -> TokenBasedRememberMeServices # onLoginSuccess。

2. Análise

Quando o usuário desliga e depois abre o navegador, acesse novamente /helloa interface, o processo de certificação é mais ou menos como isso?

Como dissemos antes, uma série de funções no Spring Security são implementadas por meio de uma cadeia de filtros e RememberMeessa função, obviamente, não é exceção.

Spring Security fornece RememberMeAuthenticationFilterclasses projetadas para fazer coisas relacionadas, olhamos para RememberMeAuthenticationFiltero doFiltermétodo:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
  throws IOException, ServletException {
    
    
 HttpServletRequest request = (HttpServletRequest) req;
 HttpServletResponse response = (HttpServletResponse) res;
 if (SecurityContextHolder.getContext().getAuthentication() == null) {
    
    
  Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
    response);
  if (rememberMeAuth != null) {
    
    
    rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);
    SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);
    onSuccessfulAuthentication(request, response, rememberMeAuth);
    if (this.eventPublisher != null) {
    
    
     eventPublisher
       .publishEvent(new InteractiveAuthenticationSuccessEvent(
         SecurityContextHolder.getContext()
           .getAuthentication(), this.getClass()));
    }
    if (successHandler != null) {
    
    
     successHandler.onAuthenticationSuccess(request, response,
       rememberMeAuth);
     return;
    }
   }
  chain.doFilter(request, response);
 }
 else {
    
    
  chain.doFilter(request, response);
 }
}

Este método é a parte mais crítica é que, se o SecurityContextHoldernão pode fazer o usuário logar na instância atual, então invoque a rememberMeServices.autoLoginlógica para logar, nós olhamos este método:

public final Authentication autoLogin(HttpServletRequest request,
  HttpServletResponse response) {
    
    
 String rememberMeCookie = extractRememberMeCookie(request);
 if (rememberMeCookie == null) {
    
    
  return null;
 }
 logger.debug("Remember-me cookie detected");
 if (rememberMeCookie.length() == 0) {
    
    
  logger.debug("Cookie was empty");
  cancelCookie(request, response);
  return null;
 }
 UserDetails user = null;
 try {
    
    
  String[] cookieTokens = decodeCookie(rememberMeCookie);
  user = processAutoLoginCookie(cookieTokens, request, response);
  userDetailsChecker.check(user);
  logger.debug("Remember-me cookie accepted");
  return createSuccessfulAuthentication(request, user);
 }
 catch (CookieTheftException cte) {
    
    
  
  throw cte;
 }
 cancelCookie(request, response);
 return null;
}

Você pode ver, isso é para extrair cookieinformações, e as cookieinformações são decodificadas, após a decodificação e, em seguida, chamar o processAutoLoginCookiemétodo para fazer a verificação, processAutoLoginCookiea abordagem de código que não vou colocar, o processo principal é primeiro obter um nome de usuário e data de validade, depois o nome de usuário A senha do usuário é consultada e, em seguida, o valor de hash é calculado por meio da função hash MD5 e, em seguida, o valor de hash obtido é comparado com o valor de hash passado pelo navegador para confirmar se o token é válido e, em seguida, se deve fazer login eficaz.

Quatro, resumo

Leia o artigo acima, pode ter descoberto que se abrirmos a RememberMefunção, o mais central é colocar cookieum token, e o token está quebrando os sessionlimites, mesmo se o servidor for reiniciado, mesmo se o navegador for fechado e reaberto , Contanto que o token não tenha expirado, os dados podem ser acessados.

Depois que o token é perdido, outras pessoas podem usá-lo para fazer login em nosso sistema à vontade, o que é uma operação muito perigosa.

Mas na verdade isso é um paradoxo: para melhorar a experiência do usuário (menos login), nosso sistema inevitavelmente leva a alguns problemas de segurança, mas podemos reduzir os riscos de segurança ao mínimo por meio da tecnologia.

Acho que você gosta

Origin blog.csdn.net/nanhuaibeian/article/details/108630616
Recomendado
Clasificación