Você não conhecerá Shiro em 2021? -3. Analise o código-fonte de autenticação de identidade para implementar um Realm personalizado

Prefácio

Já sabemos que não importa se sejamos autenticação ou autorização, a aquisição de dados vem de Realm. Realm é equivalente a nossa fonte de dados. No artigo anterior, usamos IniRealm para carregar nosso arquivo de configuração shiro.ini. Ao mesmo tempo, nós Diz-se também que o ini é apenas uma solução temporária. No desenvolvimento real, é impossível colocar as informações do usuário e as informações de permissão no arquivo ini. Elas são todas derivadas do banco de dados. Então, o IniRealm fornecido pelo sistema não pode atender às nossas necessidades .Você precisa personalizar o Realm para perceber a cena real. Na verdade, o arquivo ini é apenas uma estratégia para o apache nos fornecer aprendizado e uso. Vamos ver como definir um Realm por nós mesmos.

1. A aquisição padrão de nome de usuário e senha

A realização da autenticação está em uma linha do código, da seguinte maneira:

subject.login(authenticationToken);

O código anterior está preparado para esta linha de código. Não disse que a autenticação de identidade de Shiro é obtida por meio do Autenticador? Aqui, é apenas que o assunto chama o método de login? Isso não combina com a arquitetura Shiro mencionada anteriormente? Vamos dar uma olhada juntos abaixo.

1. Verificação do nome do usuário

Nós depuramos e executamos o seguinte código:

public class TestAuthenticator {
    
    
    public static void main(String[] args) {
    
    
        //第一步,获取Security Manager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //第二步,使用Security Manager加载配置文件,注意前面已经说过Realm是身份认证与权限的数据获取的地方,所以调用setRealm
        defaultSecurityManager.setRealm(new IniRealm("classpath:shiro.ini"));
        //第三步,使用SecurityUtils获取Subject
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        Subject subject = SecurityUtils.getSubject();
        //第四步,获取用户登录Token
        UsernamePasswordToken authenticationToken = new UsernamePasswordToken("zhaoyun","daye");
        try {
    
    
            System.out.println(subject.isAuthenticated());
            //第五步,登录
            subject.login(authenticationToken);
            System.out.println(subject.isAuthenticated());
        } catch (UnknownAccountException e) {
    
    
            e.printStackTrace();
        }catch(IncorrectCredentialsException e){
    
    
            e.printStackTrace();
        }catch(Exception e){
    
    
            e.printStackTrace();
        }

    }
}

Sabemos que subject.login () é o conteúdo principal, executamos dubug para entrar e vemos como a camada inferior do método implementa a autenticação de identidade.
Após clicar em, inserimos o método de login em DelegatingSubject, conforme mostrado na figura a seguir: O que é
Insira a descrição da imagem aqui
digitado não é o método de login de subject, aqui DelegatingSubject é na verdade a classe de implementação de Subject, mas neste método descobrimos que não há julgamento sobre as informações do usuário e sim, continuamos a chamar com o token, então continuamos a clicar e dar uma olhada. Conforme mostrado na figura abaixo, inserimos o método de login no gerenciador de segurança do DefaultSecuityManager, indicando que o assunto subjacente do assunto ainda é o método de autenticação no gerenciador de segurança.
Insira a descrição da imagem aqui
Mas obviamente, este não é o fim, aqui continuamos a passar o Token, temos que continuar a acompanhar, e então descobrimos que entramos no AuthentticatingSecurityManager, o gerenciador de segurança conforme mostrado na figura abaixo:
Insira a descrição da imagem aqui
Mas mesmo assim muitas camadas foram encapsuladas, encontramos isso. O token ainda não foi processado em uma etapa e a chamada ainda está em andamento. Precisamos continuar a rastrear a chamada e, em seguida, inserir o autenticador AbstractAuthenticator conforme mostrado na figura abaixo:
Insira a descrição da imagem aqui

Mas esse autenticador não é o fim, continue acompanhando e entre na classe ModularRealmAuthenticator, conforme mostrado na figura abaixo:
Insira a descrição da imagem aqui
Então podemos descobrir que na última linha do código do método, o método continua a ser chamado, mas ainda não processado Depois de continuar o acompanhamento, descobrimos que ele entra Quando se trata de outro método nesta classe, o método verifica se ele suporta tokens, etc., e começa a verificar os tokens, indicando que não estamos longe do destino. Continue acompanhando o código e descubra que inserimos a classe AutenticatingRealm. Conforme mostrado na figura abaixo:
Insira a descrição da imagem aqui
Primeiro vá para o cache para saber se o usuário representado pelo Token está autenticado. Obviamente, não há dados no cache, então entramos na terceira linha. Precisamos continuar a acompanhar. Clique para entrar e entrar no doGetAuthenticationInfo da classe SimpleAccountRealm. Conforme mostrado na figura abaixo:
Insira a descrição da imagem aqui
A partir do código, podemos ver claramente que o Token não continuou a ser transmitido. Obviamente, este é o ponto final da chamada de código que estamos procurando. O método doGetAuthenticationInfo de SimpleAccountRealm é a realização real da autenticação aqui. Localmente , podemos descobrir que Shiro tem um encapsulamento profundo de certificação, e é realmente difícil de encontrar. Vamos dar uma olhada no conteúdo desse método da seguinte maneira:

   protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
    
       UsernamePasswordToken upToken = (UsernamePasswordToken)token;
       SimpleAccount account = this.getUser(upToken.getUsername());
       if (account != null) {
    
    
           if (account.isLocked()) {
    
    
               throw new LockedAccountException("Account [" + account + "] is locked.");
           }

           if (account.isCredentialsExpired()) {
    
    
               String msg = "The credentials for account [" + account + "] are expired";
               throw new ExpiredCredentialsException(msg);
           }
       }

       return account;
   }

A primeira linha de código converte forçosamente o token no tipo usernamePasswordToken. Na verdade, usamos esse tipo.
A segunda linha de código obtém o nome de usuário no token e o passa para o objeto getUser desta classe e retorna um objeto SimpleAccount. Já sabemos que token.getUserName deve ser o nome de usuário que passamos, então qual é o propósito de chamar getUser com este nome de usuário? O que usar? Vamos inserir este método e dar uma olhada: Após
Insira a descrição da imagem aqui
inserir este método, descobrimos que o que este método obtém são as informações do arquivo ini que configuramos, então sabemos que este método é um método para verificar a existência de nosso nome de usuário. Retornamos ao nível de código anterior. Aqui está um objeto SimpleAccount. Obviamente, desde que o objeto recebido não seja nulo, isso significa que a verificação do nome do usuário foi bem-sucedida. O método a seguir é verificar se o usuário está bloqueado e se as credenciais do usuário expiraram. Verificação. Neste ponto, descobrimos que o nome de usuário está correto após a verificação, mas por que a senha não foi verificada?

2. Verificação de senha

Acima, descobrimos como o nome de usuário é verificado, mas a senha não é vista. Agora continuamos o código agora e descemos para encontrar o local de verificação da senha. F8 executa este método e retorna para descobrir que o método finalmente retorna um objeto SimpleAccount, uma vez que o nome de usuário foi verificado e retornado SimpleAccount deve ser para verificação de senha, siga o código para retornar este objeto SimpleAccount aqui, conforme mostrado abaixo: Após a
Insira a descrição da imagem aqui
chamada ser retornada aqui, podemos descobrir que o o nome do usuário é colocado primeiro Após o cache, entraremos rapidamente na parte vermelha da imagem. De acordo com o nome, podemos ver que esta parte é uma afirmação de que as informações de credencial correspondem ao token do usuário e ao usuário encontrado no arquivo ini, então aqui deve ser O local onde a verificação de senha é implementada, vamos clicar para ver a implementação desta parte, conforme mostrado na figura abaixo:
Insira a descrição da imagem aqui
Obviamente a linha marcada é para determinar se a senha corresponde, e a seguinte IncorrectCredentialsException também é lançada no cenário sem correspondência, que também é. Dissemos que a senha não corresponde de forma anormal e, em seguida, insira a próxima chamada de camada a partir da posição marcada na figura, conforme mostrado na figura a seguir:
Insira a descrição da imagem aqui
Podemos descobrir que aqui estão dois objetos Objeto gerados com base na senha no token e nas informações. Esses dois objetos Objeto são na verdade É a senha e, em seguida, use igual para comparar os dois objetos. Se a correspondência for bem-sucedida, a verificação é aprovada. Caso contrário, a verificação falha e retorna falso. Retorne ao nível anterior, e uma exceção de credencial de identidade inválida será lançada. Aqui temos o nome de usuário e as senhas são todas verificadas.

3. Resuma a verificação do nome de usuário e senha

O processo de autenticação de nome de usuário e senha foi mencionado acima, e não vou repeti-lo aqui. Por meio do processo acima, podemos entender que o nome de usuário é verificado pelo método doGetAuthenticationInfo em SimpleAccountRealm , e a verificação de senha está em AuthenticatingRealm É implementado por assertCredentialsMatch in . Observando a verificação da senha, podemos descobrir que não precisamos fazer nada aqui. Precisamos apenas retornar as informações do usuário (incluindo as informações da senha) quando o usuário for autenticado. O programa irá manter as informações de login do usuário. Verifique com as informações no arquivo de configuração, se a correspondência for verificada, mesmo se usarmos o banco de dados para obter as informações do usuário, ainda retornamos uma informação SimpleAccout, ela ainda é julgada automaticamente aqui, então nós pode obter a senha Nenhuma intervenção humana é necessária para a verificação da senha. Shiro nos ajudará automaticamente a implementar a autenticação de senha. Se você estiver familiarizado com a chamada acima, descobriremos que a classe de implementação real de autenticação de usuário SimpleAccountRealm é real classe de implementação de AuthenticatingRealm. Subclass.

2. Custom Realm

De acordo com o processo de verificação acima, podemos descobrir que se quisermos implementar nosso Realm por tanto tempo, precisamos herdar AuthenticatingRealm, assim como SimpleAccountRealm para implementar o método doGetAuthenticationInfo.

1. Custom Realm ---- FirstRealm

Vamos para o topo de um de nosso próprio Realm chamado FirstRealm.

public class FirstRealm extends AuthenticatingRealm {
    
    
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
    
        
        return null;
    }
}

Definimos nosso próprio Realm, mas agora não há nada no método e as informações retornadas estão vazias, então quando a comparação for realizada, o nome de usuário desconhecido, que é o que chamamos de UnknownAccountException, será relatado. senha e ainda usa o método codificado para comparação. Acredito que os alunos básicos serão capazes de obter os dados do banco de dados. Não vou escrever aqui. Referimo-nos à classe de implementação de SimpleAccountRealm para completar o método, do seguinte modo:

public class FirstRealm extends AuthenticatingRealm {
    
    
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
    
        UsernamePasswordToken upToken = (UsernamePasswordToken)authenticationToken;
        String userName = upToken.getUsername();
        //假设这就是从数据库获取的用户信息
        SimpleAccount simpleAccount = getAccountInfo();
        if(simpleAccount.getPrincipals().asList().contains(userName)) {
    
    
            System.out.println("用户名验证通过");
            return simpleAccount;
        }else{
    
    
            return null;
//            throw new UnknownAccountException();
        }
    }

    private SimpleAccount getAccountInfo(){
    
    
        return new SimpleAccount("zhaoyun","1111",this.getName());
//        return  new SimpleAuthenticationInfo("zhaoyun","1111",this.getName());
    }
}

A partir do código acima, podemos ver que há dois lugares do código que comentei. Observe que o primeiro lugar é no lugar nulo de retorno. Aqui podemos escolher lançar manualmente uma exceção para um usuário desconhecido ou lançar um nulo diretamente., A chamada de nível superior nos ajudará a lançar uma exceção de usuário desconhecido, então não importa se não lançarmos uma exceção aqui. O segundo código é onde pretendemos obter os dados do banco de dados. A abreviatura aqui escreve dois retornos diferentes porque usamos Esses dois métodos são possíveis. Se você estiver interessado, pode tentar outro. Claro, o valor de retorno também deve ser alterado.

2. Use um reino personalizado

Quando escrevemos o código para autenticação de identidade, injetamos IniRealm na época, para que possamos substituí-lo pelo Realm que implementamos, da seguinte maneira:

public class TestAuthenticator {
    
    
    public static void main(String[] args) {
    
    
        //第一步,获取Security Manager
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager();
        //第二步,使用Security Manager加载配置文件,注意前面已经说过Realm是身份认证与权限的数据获取的地方,所以调用setRealm
        defaultSecurityManager.setRealm(new FirstRealm());
        //第三步,使用SecurityUtils获取Subject
        SecurityUtils.setSecurityManager(defaultSecurityManager);
        Subject subject = SecurityUtils.getSubject();
        //第四步,获取用户登录Token
        UsernamePasswordToken authenticationToken = new UsernamePasswordToken("zhaoyun","daye");
        try {
    
    
            System.out.println(subject.isAuthenticated());
            //第五步,登录
            subject.login(authenticationToken);
            System.out.println(subject.isAuthenticated());
        } catch (UnknownAccountException e) {
    
    
            e.printStackTrace();
        }catch(IncorrectCredentialsException e){
    
    
            e.printStackTrace();
        }catch(Exception e){
    
    
            e.printStackTrace();
        }

    }
}

Em seguida, executamos o código de peticionamento e sentimos se o Realm definido por nós está funcionando bem. Os resultados em execução são os seguintes:

false
用户名验证通过
org.apache.shiro.authc.IncorrectCredentialsException: Submitted credentials for token [org.apache.shiro.authc.UsernamePasswordToken - zhaoyun, rememberMe=false] did not match the expected credentials.
	at org.apache.shiro.realm.AuthenticatingRealm.assertCredentialsMatch(AuthenticatingRealm.java:603)
	at org.apache.shiro.realm.AuthenticatingRealm.getAuthenticationInfo(AuthenticatingRealm.java:581)
	at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:180)
	at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:273)
	at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198)
	at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106)
	at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:275)
	at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:260)
	at org.example.TestAuthenticator.main(TestAuthenticator.java:33)

Process finished with exit code 0

Descobrimos que a verificação de nosso nome de usuário foi aprovada. A partir do relatório de erro, podemos saber que a senha está errada e, em seguida, descobrimos que nossa senha de login é inconsistente com a senha obtida. Altere as senhas para daye e tentaremos novamente, conforme segue:

false
用户名验证通过
true

Process finished with exit code 0

Em seguida, descobrimos que a autenticação de identidade foi bem-sucedida.

Três. Resumo

Na primeira parte, apresentamos a implementação de baixo nível do método de login no Assunto e descobrimos como o método obtém a verificação de identidade. Descobrimos que a parte inferior do método é, de fato, autenticar as informações de login de entrada por meio do autenticador e o autenticador novamente Você precisa confiar nas informações de identidade no Realm para determinar se as informações do usuário são legais. De acordo com o código-fonte, encontramos o método doGetAuthenticationInfo em SimpleAccountRealm passo a passo e descobrimos que a autenticação do nome do usuário é feita aqui, e a autenticação de senha está no assertCredentialsMatch em AuthenticatingRealm. Isso é feito aqui, e a autenticação de senha não exige que participemos, Shiro fará isso para nós, então só precisamos concluir a autenticação do usuário e retornar um objeto SimpleAccount com o usuário informações e informações de senha. De acordo com essa ideia, projetamos nosso próprio FirstRealm. Também herdamos AuthenticatingRealm como SimpleAccountRealm e, em seguida, implementamos o método doGetAuthenticatonInfo, onde implementamos a autenticação do usuário, para concluirmos a implementação do Realm personalizado. Estamos em desenvolvimento. também a maneira de implementar o Realm definido por você, mas o método de injeção do Realm será diferente e os artigos de acompanhamento continuarão a introduzi-lo.

Acho que você gosta

Origin blog.csdn.net/m0_46897923/article/details/114804895
Recomendado
Clasificación