2021 년 Shiro를 모르시겠습니까? -3. 사용자 정의 Realm 구현을위한 신원 인증 소스 코드 분석

머리말

우리는 인증이든 권한이든 데이터 획득이 Realm에서 온다는 것을 이미 알고 있습니다. Realm은 데이터 소스와 동일합니다. 이전 기사에서 IniRealm을 사용하여 구성 파일 shiro.ini를로드했습니다. 또한 ini는 일시적인 해결책 일 뿐이라고합니다. 실제 개발에서는 사용자 정보와 권한 정보를 ini 파일에 넣는 것이 불가능합니다. 모두 데이터베이스에서 파생 된 것입니다. 그러면 시스템에서 제공하는 IniRealm이 우리의 요구를 충족시킬 수 없습니다. . Realm을 사용자 정의하여 실제 장면을 구현해야합니다. 실제로 ini 파일은 아파치가 학습 및 사용을 제공하는 전략 일뿐입니다. Realm을 직접 정의하는 방법을 살펴 보겠습니다.

1. 사용자 이름 및 암호의 기본 획득

인증의 실현은 다음과 같이 코드의 한 줄에 있습니다.

subject.login(authenticationToken);

이 코드 줄을 위해 이전 코드가 준비되어 있습니다. Shiro의 신원 인증이 Authenticator를 통해 이루어 졌다고하지 않았습니까? 여기서 주체가 로그인 방법을 호출하는 것뿐입니까? 이것은 앞서 언급 한 Shiro 아키텍처와 일치하지 않습니까? 아래에서 함께 살펴 보겠습니다.

1. 사용자 이름 확인

디버그는 다음 코드를 실행합니다.

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();
        }

    }
}

subject.login ()이 핵심 콘텐츠라는 것을 알고 있으며 dubug를 실행하여 입력하고 메서드의 맨 아래 계층이 ID 인증을 구현하는 방법을 확인합니다.
클릭 한 후 다음 그림과 같이 DelegatingSubject에 로그인 방법을 입력합니다. 입력 한 내용은
여기에 사진 설명 삽입
subject의 로그인 방법이 아닙니다. 여기서 DelegatingSubject는 실제로 Subject의 구현 클래스이지만이 방법에서는 사용자 정보에 대한 판단, 예, 토큰으로 계속 호출하므로 계속 클릭하여 살펴 봅니다. 아래 그림과 같이 DefaultSecuityManager의 보안 관리자에 로그인 방법을 입력하여 주제의 기본 주제는 여전히 보안 관리자의 인증 방법입니다.
여기에 사진 설명 삽입
: 그러나 분명히이 여기에 우리는 우리가 따라 계속해야 토큰을 통과 계속하고 우리는 우리가 AuthentticatingSecurityManager, 아래의 그림과 같이 보안 관리자를 입력 한 것을 발견, 끝이 아니다
여기에 사진 설명 삽입
그러나 경우에도 정도 많은 레이어가 캡슐화되어 있습니다. 토큰은 여전히 ​​한 단계에서 처리되지 않고 호출이 계속되고 있습니다. 계속해서 호출을 추적 한 다음 아래 그림과 같이 AbstractAuthenticator 인증자를 입력해야합니다.
여기에 사진 설명 삽입

그러나이 인증자는 끝이 아닙니다. 아래 그림과 같이 계속해서 ModularRealmAuthenticator 클래스를 입력합니다.
여기에 사진 설명 삽입
그러면 메서드의 마지막 코드 줄에서 메서드가 계속 호출되지만 아직 처리되지 않음을 알 수 있습니다. 계속해서 후속 조치를 취한 후이 클래스의 다른 메소드에 들어가면 메소드가 토큰 등을 지원하는지 확인하고 토큰 확인을 시작하여 목적지에서 멀지 않음을 표시합니다. 계속해서 코드를 따라 가서 AutenticatingRealm 클래스에 들어 갔음을 확인합니다. 아래 그림과 같이 :
여기에 사진 설명 삽입
먼저 캐시로 이동하여 Token으로 표시된 사용자가 인증되었는지 확인합니다. 분명히 캐시에 데이터가 없습니다. 그래서 우리는 세 번째 줄을 입력합니다. 계속해서 후속 작업을해야합니다. 클릭하여 SimpleAccountRealm 클래스의 doGetAuthenticationInfo를 입력하고 입력합니다. 아래 그림과 같이
여기에 사진 설명 삽입
코드에서 토큰이 계속되지 않았 음을 분명히 알 수 있습니다. 아래로 통과시켰다. 분명히, 이것은 우리가 찾고있는 코드를 호출의 끝 지점이 로컬. SimpleAccountRealm의 doGetAuthenticationInfo 방법은 여기에 인증의 진정한 실현.입니다 우리가 시로 인증 깊은 캡슐을 가지고 찾을 수 있습니다, 그것은 정말 찾기 어렵습니다. 다음과 같이이 메서드의 내용을 살펴 보겠습니다.

   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;
   }

코드의 첫 번째 줄은 토큰을 usernamePasswordToken 유형으로 강제 변환합니다. 실제로이 유형을 사용합니다.
코드의 두 번째 줄은 토큰의 사용자 이름을 가져 와서이 클래스의 getUser 객체에 전달하고 SimpleAccount 객체를 반환합니다. token.getUserName이 전달한 사용자 이름이어야한다는 것을 이미 알고 있으므로 getUser를 호출하는 목적은 무엇입니까? 이 사용자 이름으로 무엇을 사용할까요?이 메서드를 입력하고 살펴 보겠습니다.
여기에 사진 설명 삽입
이 메서드를 입력 한 후이 메서드가 가져 오는 것이 우리가 구성한 ini 파일의 정보임을 확인하고이 메서드가 확인하는 방법임을 알게됩니다. 사용자 이름의 존재. 이전 코드 수준으로 돌아갑니다. 여기 SimpleAccount 객체가 있습니다. 분명히 수신 된 객체가 null이 아닌 한 사용자 이름 확인이 성공했음을 의미합니다. 다음 방법은 사용자가 잠겨 있는지 여부를 확인하는 것입니다. 사용자 자격 증명이 만료되었습니다. 확인이 시점에서 확인 후 사용자 이름이 올바른 것으로 나타 났지만 암호가 확인되지 않은 이유는 무엇입니까?

2. 비밀번호 확인

위에서 사용자 이름이 확인되는 방법을 찾았지만 암호는 보이지 않습니다. 이제 코드를 계속 진행하고 암호의 확인 위치를 찾습니다. F8은이 메서드를 실행하고 돌아와서 메서드는 마지막으로 SimpleAccount 개체를 반환합니다. 사용자 이름이 확인되고 반환 된 SimpleAccount는 암호 확인을위한 것이어야합니다. 아래에 표시된대로이 SimpleAccount 개체를 다시 여기로 반환하는 코드를 따릅니다. 여기에서
여기에 사진 설명 삽입
호출이 반환 된 사용자 이름을 먼저 입력합니다. 캐시 후 그림의 빨간색 부분을 서둘러 입력합니다. 이름에 따르면이 부분은 자격 증명 정보가 사용자의 토큰과 일치하는지 여부에 대한 주장이고 사용자가 찾은 ini 파일에서 암호 확인이 구현 된 곳이어야합니다. 아래 그림과
여기에 사진 설명 삽입
같이이 부분의 구현을 보려면 클릭 해 보겠습니다. 분명히 표시된 줄은 암호가 일치하는지 확인하는 것입니다. 다음 IncorrectCredentialsException 또한 우리는 다음 그림과 같이 암호가 비정상적으로 일치하고 그림에 표시된 위치에서 다음 레이어 전화를 입력하지 않습니다 말했다 또한 타의 추종을 불허하는 시나리오에 발생합니다 :
여기에 사진 설명 삽입
우리는 찾을 수 있습니다 여기에 토큰과 정보의 비밀번호를 기반으로 생성 된 두 개의 Object 객체가 있습니다.이 두 객체 객체는 실제로는 비밀번호이며, 두 객체를 비교하기 위해 equals를 사용합니다. 일치가 성공하면 검증이 통과됩니다. 그렇지 않으면, 확인에 실패하고 false를 반환합니다. 이전 수준으로 돌아 가면 잘못된 ID 자격 증명 예외가 발생합니다. 여기에 사용자 이름과 암호가 모두 확인되었습니다.

3. 사용자 이름 및 비밀번호 확인 요약

사용자 이름과 암호의 인증 과정은 위에서 언급되었고, 난 여기가 반복되지 않습니다. 위의 과정을 통해, 우리가 이해 할 수 있습니다 사용자 이름이 SimpleAccountRealm에서 doGetAuthenticationInfo 방법으로 확인 , 암호 검증 AuthenticatingRealm에 에서 assertCredentialsMatch에 의해 구현됩니다 . 암호 확인을 관찰하면 여기서 아무것도 할 필요가 없음을 알 수 있습니다. 사용자가 인증 될 때 사용자 정보 (암호 정보 포함) 만 반환하면됩니다. 프로그램은 사용자 로그인 정보를 보유합니다., 구성 파일의 정보를 확인하고 일치가 확인되면 데이터베이스를 사용하여 사용자 정보를 가져와도 여전히 SimpleAccout 정보를 반환하며 여기에서 자동으로 판단되므로 암호를 얻을 수 있습니다. 암호 확인을 위해 사람의 개입이 필요하지 않습니다. Shiro는 자동으로 암호 인증을 구현하도록 도와줍니다. 위의 호출에 익숙하다면 사용자 인증 SimpleAccountRealm의 실제 구현 클래스가 실제임을 알 수 있습니다. AuthenticatingRealm의 구현 클래스입니다.

2. 사용자 지정 영역

위의 검증 과정에 따르면 Realm을 너무 오래 구현하려면 SimpleAccountRealm처럼 doGetAuthenticationInfo 메소드를 구현하는 것처럼 AuthenticatingRealm을 상속해야한다는 것을 알 수 있습니다.

1. 사용자 지정 영역 ---- FirstRealm

FirstRealm이라는 자체 Realm 중 하나로 이동해 보겠습니다.

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

자체 Realm을 정의했지만 이제 메소드에 아무것도없고 반환 된 정보가 비어 있으므로 비교를 수행하면 UnknownAccountException이라고하는 알 수없는 사용자 이름이보고됩니다. 비교를 위해 여전히 하드 코딩 된 방법을 사용합니다. 기본 학생들은 데이터베이스에서 데이터를 얻을 수있을 것이라고 믿습니다. 여기서는 작성하지 않겠습니다. SimpleAccountRealm의 구현 클래스를 참조하여 방법을 완성합니다. 다음과 같이 :

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());
    }
}

위의 코드에서 주석 처리 한 코드가 두 군데 있음을 알 수 있습니다. 첫 번째 자리는 retun null 자리에 있습니다. 여기서 알 수없는 사용자에 대한 예외를 수동으로 던지거나 null을 던지도록 선택할 수 있습니다. 직접., 상위 레벨 호출은 알 수없는 사용자 예외를 발생시키는 데 도움이되므로 여기서 예외를 발생시키지 않아도 상관 없습니다. 두 번째 코드는 데이터베이스 데이터를 가져 오는 척하는 것입니다. 여기서 약어는 두 개를 씁니다. 이 두 가지 방법을 사용하기 때문에 다른 반환이 가능합니다. 관심이 있으시면 다른 방법을 시도해 볼 수 있습니다. 물론 반환 값도 변경해야합니다.

2. 사용자 정의 Realm 사용

ID 인증을위한 코드를 작성할 때 IniRealm을 주입 했으므로 다음과 같이 구현 한 Realm으로 대체 할 수 있습니다.

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();
        }

    }
}

그런 다음 청원 코드를 실행하고 자신이 정의한 Realm이 제대로 작동하는지 확인합니다. 실행 결과는 다음과 같습니다.

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

사용자 이름 확인이 통과 된 것으로 확인되었습니다. 오류 보고서에서 비밀번호가 잘못되었음을 알 수 있으며 로그인 비밀번호가 획득 한 비밀번호와 일치하지 않음을 발견했습니다. 비밀번호를 daye로 변경하고 다시 시도하십시오. 다음과 같습니다.

false
用户名验证通过
true

Process finished with exit code 0

그런 다음 신원 인증에 성공했습니다.

3. 요약

첫 번째 부분에서는 Subject에서 로그인 방법의 하위 수준 구현을 소개하고 방법이 어떻게 신원 확인을 수행하는지 파악했습니다. 방법의 하단은 실제로 인증자를 통해 들어오는 로그인 정보를 인증하는 것임을 발견했습니다. Realm의 신원 정보에 의존하여 사용자 정보가 합법적인지 판단해야합니다. 소스 코드에 따르면 SimpleAccountRealm에서 단계별로 doGetAuthenticationInfo 메소드를 찾았고 여기에서 사용자 이름 인증이 완료되었음을 알았습니다. 암호 인증은 AuthenticatingRealm의 assertCredentialsMatch에 있습니다.이 작업은 여기에서 수행되며 암호 인증은 우리가 참여할 필요가 없으며 Shiro가 우리를 대신하여 수행하므로 사용자 인증을 완료하고 사용자와 함께 SimpleAccount 객체를 반환하기 만하면됩니다. 정보 및 암호 정보. 이 아이디어에 따라 우리는 FirstRealm을 직접 설계했습니다. 또한 SimpleAccountRealm과 같은 AuthenticatingRealm을 상속 한 다음 사용자 인증을 구현 한 doGetAuthenticatonInfo 메서드를 구현하여 사용자 정의 Realm 구현을 완료했습니다. 현재 개발 중입니다. 자신이 정의한 Realm을 구현하는 방법도 있지만 Realm의 주입 방법은 다를 것이며 후속 기사에서 계속해서 소개 할 것입니다.

추천

출처blog.csdn.net/m0_46897923/article/details/114804895