【shiro入門から実戦までのチュートリアル】第4章 領域分析

四、レルム分析

4.1 レルムの概要

Shiro は Realm からセキュリティ データ (ユーザー、ロール、パーミッションなど) を取得します。また、レルムからユーザーの対応するロールを取得する必要があります。パーミッションは、ユーザーが操作を実行できるかどうかを確認するために使用されます。

4.1.1 2 つの概念

プリンシパル: 複数のプリンシパル識別子が存在する可能性がありますが、それらは一意である必要があります. 一般的なものには、ユーザー名、携帯電話番号、電子メールアドレスなどがあります.

credential: 資格情報。通常はパスワードです。

4.1.2 継承構造

ここに画像の説明を挿入

Realm は、検証対象のデータ、つまりセキュリティ データ ソースの比較値を提供します。これは、データベース、ファイルなどのデータのソースとして理解できます。Shiro は Realm からセキュリティ データ (ユーザー、ロール、パーミッションなど) を取得します。つまり、SecurityManager は Realm から対応するユーザーを取得して、ユーザーの ID が正当かどうかを比較し、ユーザーの対応するロールを取得する必要があります。ユーザーが操作を実行できるかどうかを確認するレルム/権限。

レルム インターフェイス:

public interface Realm {
    
    
    String getName();//返回一个唯一的Realm名字  

    boolean supports(AuthenticationToken var1);//判断此Realm是否支持此Token  

    AuthenticationInfo getAuthenticationInfo(AuthenticationToken var1) throws AuthenticationException;//根据Token获取认证信息 
}

CachingRealm 抽象クラス:

CachingRealm は、キャッシュ可能なレルムを提供します。

AuthenticatingRealm 抽象クラス:

AuthenticatingRealm は Realm の 3 つのインターフェイス メソッドをすべて実装し、getAuthenticationInfo メソッドの実装は次のとおりです。

public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    
    
	// 先获取缓存信息,通过CachingRealm中设置的CacheManager来实现的
   AuthenticationInfo info = this.getCachedAuthenticationInfo(token);
   if (info == null) {
    
    
   		// 如果缓存信息为空,则执行本类中的doGetAuthenticationInfo方法,该方法是抽象方法,由实现类实现
       info = this.doGetAuthenticationInfo(token);
       log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
       if (token != null && info != null) {
    
    
           this.cacheAuthenticationInfoIfPossible(token, info);
       }
   } else {
    
    
       log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
   }

   if (info != null) {
    
    
       this.assertCredentialsMatch(token, info);
   } else {
    
    
       log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
   }

   return info;
}

ソース コードから、AuthenticationRealm が shiro の認証プロセスを実装していることがわかります。つまり、login メソッドを呼び出した後、キャッシュがあればキャッシュされた認証情報を返し、キャッシュがなければ doGetAuthenticationInfo メソッドを呼び出します。 . Realm をカスタマイズするときに、このメソッドを実装する必要があります。

AuthorizingRealm 抽象クラス:

AuthorizingRealm は、has*、checkなどの権限認証メソッドを定義する Authorizer インターフェースを実装します. AuthorizingRealm は、これらの権限認証メソッドを実装します.それぞれの has および check* メソッドが実行されると、次の getAuthorizationInfo メソッドが実行されます.

4.2 初期レルム

主にxxx.ini対応する、ファイルから対応するデータが存在するかどうかを調べます。

4.2.1.ini ファイル

[users]
# 配置用户信息与角色
admin=123,root,user
tom=111,user
[roles]
# 配置角色和权限信息
root=emp:find,emp:add,emp:edit,emp:remove,dept:*
user=emp:find,dept:find

アクセス許可の構成時にワイルドカードを使用できます*

  • *すべての権限を示します。
  • dept:*dept モジュールの下のすべての権限を示します。

4.2.2 テストケース

public class IniRealmTest {
    
    

    @Test
    public void run(){
    
    
        DefaultSecurityManager securityManager = new DefaultSecurityManager();

        //创建IniRealm对象,加载ini文件
        IniRealm iniRealm = new IniRealm("classpath:shiro.ini");

        securityManager.setRealm(iniRealm);

        SecurityUtils.setSecurityManager(securityManager);

        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin", "123");

        subject.login(usernamePasswordToken);

        System.out.println("身份认证结果:" + subject.isAuthenticated());
        System.out.println("是否拥有root角色:" + subject.hasRole("root"));
        System.out.println("是否拥有user角色:" + subject.hasRole("user"));
        // 权限
        System.out.println("是否拥有emp:find权限:" + subject.isPermitted("emp:find"));
        System.out.println("是否拥有emp:add权限:" + subject.isPermitted("emp:add"));
        System.out.println("是否拥有emp:edit权限:" + subject.isPermitted("emp:edit"));
        System.out.println("是否拥有emp:remove权限:" + subject.isPermitted("emp:remove"));
        System.out.println("是否拥有dept:find权限:" + subject.isPermitted("dept:find"));
        System.out.println("是否拥有dept:add权限:" + subject.isPermitted("dept:add"));
        System.out.println("是否拥有dept:edit权限:" + subject.isPermitted("dept:edit"));
        System.out.println("是否拥有dept:remove权限:" + subject.isPermitted("dept:remove"));

        subject.logout();
        System.out.println("身份认证结果:" + subject.isAuthenticated());
    }
}

4.3 Jdbcレルム

JdbcRealm はデータベースにアクセスし、データベースとの接続を通じて、対応するログイン ユーザーと承認を検証します。

4.3.1 デフォルトのデータベース SQL ステートメント

ユーザー テーブル:

CREATE TABLE `users`  (
    `id` int PRIMARY KEY AUTO_INCREMENT,
    `username` varchar(100),
    `password` varchar(100),
    `password_salt` varchar(100)
);

INSERT INTO `users` VALUES (1, 'admin', '123456', NULL);
INSERT INTO `users` VALUES (2, 'tom', '111111', NULL);

user_roles表:

CREATE TABLE `user_roles` (
    `username` varchar(100),
    `role_name` varchar(100),
    PRIMARY KEY (`username`, `role_name`)
);

INSERT INTO `user_roles` VALUES ('admin', 'root');
INSERT INTO `user_roles` VALUES ('admin', 'user');
INSERT INTO `user_roles` VALUES ('tom', 'user');

roles_permissions 表:

CREATE TABLE `roles_permissions` (
    `role_name` varchar(100),
    `permission` varchar(100),
    PRIMARY KEY (`role_name`, `permission`)
);

INSERT INTO `roles_permissions` VALUES ('root', 'news:add');
INSERT INTO `roles_permissions` VALUES ('root', 'news:del');
INSERT INTO `roles_permissions` VALUES ('root', 'news:edit');
INSERT INTO `roles_permissions` VALUES ('root', 'news:find');
INSERT INTO `roles_permissions` VALUES ('user', 'news:find');

4.3.2 JdbcRealm で定義された SQL ステートメント

/*--------------------------------------------
|             C O N S T A N T S             |
============================================*/
/**
* The default query used to retrieve account data for the user.
*/
protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?";

/**
* The default query used to retrieve account data for the user when {@link #saltStyle} is COLUMN.
*/
protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?";

/**
* The default query used to retrieve the roles that apply to a user.
*/
protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";

/**
* The default query used to retrieve permissions that apply to a particular role.
*/
protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";

4.3.3 テストケース

public class JdbcRealmTest {
    
    
    @Test
    public void run(){
    
    
        DefaultSecurityManager securityManager = new DefaultSecurityManager();

        //创建JdbcRealm对象
        JdbcRealm jdbcRealm = new JdbcRealm();
        //设置数据源
        DruidDataSource dataSource=new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/shiro");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        jdbcRealm.setDataSource(dataSource);
        //默认为false,必须设置为true才能进行角色的授权【重要】
        jdbcRealm.setPermissionsLookupEnabled(true);

        securityManager.setRealm(jdbcRealm);

        SecurityUtils.setSecurityManager(securityManager);

        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin", "123456");

        subject.login(usernamePasswordToken);

        System.out.println("身份认证结果:" + subject.isAuthenticated());
        System.out.println("是否拥有root角色:" + subject.hasRole("root"));
        System.out.println("是否拥有user角色:" + subject.hasRole("user"));
        // 权限鉴定
        System.out.println("是否拥有news:find权限:" + subject.isPermitted("news:find"));
        System.out.println("是否拥有news:add权限:" + subject.isPermitted("news:add"));
        System.out.println("是否拥有news:edit权限:" + subject.isPermitted("news:edit"));
        System.out.println("是否拥有news:del权限:" + subject.isPermitted("news:del"));

        subject.logout();
        System.out.println("身份认证结果:" + subject.isAuthenticated());
    }
}

4.4 カスタムレルム

Custom Realm は AuthorizingRealm を継承し、認証用の doGetAuthorizationInfo メソッドと認証用の doGetAuthenticationInfo メソッドを書き換えます。

4.4.1 手順

  • AuthorizingRealm -> AuthenticatingRealm -> CachingRealm -> Realm を継承するクラスを作成します。
  • 認証メソッド doGetAuthorizationInfo を書き換えます。
  • 認証メソッド doGetAuthenticationInfo をオーバーライドします。

4.4.2 メソッド

  • ユーザーがログインすると、doGetAuthenticationInfo が呼び出されます。
  • 権限の検証を実行するときに DoGetAuthorizationInfo が呼び出されます。

4.4.3 オブジェクト紹介

  • UsernamePasswordToken: これに対応して、Shiro のトークンには Principal ロゴと Credential certificate があります。

    UsernamePasswordToken -> HostAuthenticationToken -> AuthenticationToken。

  • SimpleAuthorizationInfo: ユーザー ロールの承認情報を表します。

  • SimpleAuthenticationInfo: ユーザーの認証情報を表します。

4.4.4 カスタムレルムクラス

public class MyRealm extends AuthorizingRealm {
    
    

    /*
     * doGetAuthorizationInfo 实现权限鉴定的方法
     * @param principalCollection 用户标识集合
     * @return org.apache.shiro.authz.AuthorizationInfo 权限信息对象
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    
    

        System.out.println("权限鉴定doGetAuthorizationInfo...");

        //用户标识集合中获取当前用户的主标识,主标识和身份认证的信息对象有关
        String username = (String) principalCollection.getPrimaryPrincipal();

        //根据用户标识查询当前用户的角色编码列表以及权限编码列表
        //角色名称列表
        Set<String> roleSet = this.findRoleByUsername(username);
        Set<String> permissionSet = this.findPermissionByUsername(username);

        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //在SimpleAuthorizationInfo中设置当前用户的角色名称集合和权限名称集合
        simpleAuthorizationInfo.setRoles(roleSet);
        simpleAuthorizationInfo.setStringPermissions(permissionSet);

        // simpleAuthorizationInfo.addRole(单个角色名称);
        // simpleAuthorizationInfo.addRoles(角色名称列表或集合);
        // simpleAuthorizationInfo.addStringPermission(单个权限名称);
        // simpleAuthorizationInfo.addStringPermissions(权限名称列表或集合);

        return simpleAuthorizationInfo;
    }

    /*
     * doGetAuthenticationInfo 实现身份认证的方法
     * @param authenticationToken 身份认证令牌对象
     * @return org.apache.shiro.authc.AuthenticationInfo 身份认证信息对象
     * @throws AuthenticationException 当身份认证出错,抛出此异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    
    

        System.out.println("身份认证doGetAuthenticationInfo...");

        //从Token中获取用户名/用户标识
        String username = (String) authenticationToken.getPrincipal();

        //从数据库中查询到的密码
        String password = this.findUserByUsername(username);

        //用户的初步验证
        if(password == null || "".equals(password)){
    
    
            //表示身份认证失败
            return null;
        }

        //将数据库中查询到的密码交给shiro来进行密码的比对
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, password, this.getName());
        
        return simpleAuthenticationInfo;
    }

    /**
     * 1.根据用户名查询用户对象
     */
    private String findUserByUsername(String username){
    
    
        return userMap.get(username);
    }

    /**
     * 2.根据用户名查询该用户所拥有的所有角色
     */
    private Set<String> findRoleByUsername(String username){
    
    
        return roleMap.get(username);
    }

    /**
     * 3.根据用户名查询该用户所拥有的所有权限
     */
    private Set<String> findPermissionByUsername(String username){
    
    
        return permissionMap.get(username);
    }

    //模拟数据
    private static final Map<String, String> userMap = new HashMap<>();
    private static final Map<String, Set<String>> roleMap = new HashMap<>();
    private static final Map<String, Set<String>> permissionMap = new HashMap<>();
    static {
    
    
        //用户
        userMap.put("admin", "123456");
        userMap.put("steven", "111111");

        //角色
        Set<String> roleSet1 = new HashSet<>();
        roleSet1.add("root");
        roleSet1.add("user");
        Set<String> roleSet2 = new HashSet<>();
        roleSet2.add("user");
        roleMap.put("admin", roleSet1);
        roleMap.put("steven", roleSet2);

        //权限
        Set<String> permissionSet1 = new HashSet<>();
        permissionSet1.add("stu:find");
        permissionSet1.add("stu:add");
        permissionSet1.add("stu:edit");
        permissionSet1.add("stu:remove");
        Set<String> permissionSet2 = new HashSet<>();
        permissionSet2.add("stu:find");
        permissionMap.put("admin", permissionSet1);
        permissionMap.put("steven", permissionSet2);
    }
}

4.4.5 テストケース

public class MyRealmTest {
    
    

    @Test
    public void run(){
    
    
        DefaultSecurityManager securityManager = new DefaultSecurityManager();

        MyRealm myRealm = new MyRealm();
        securityManager.setRealm(myRealm);

        SecurityUtils.setSecurityManager(securityManager);

        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken("admin", "123456");

        //在执行login方法时,会自动调用realm中的身份认证的方法
        subject.login(usernamePasswordToken);

        System.out.println("身份认证结果:" + subject.isAuthenticated());

        //在执行判断角色和权限的方法时,会自动调用realm中的权限鉴定的方法
        System.out.println("是否拥有root角色:" + subject.hasRole("root"));
        System.out.println("是否拥有user角色:" + subject.hasRole("user"));
        System.out.println("是否拥有stu:find权限:" + subject.isPermitted("stu:find"));
        System.out.println("是否拥有stu:add权限:" + subject.isPermitted("stu:add"));
        System.out.println("是否拥有stu:edit权限:" + subject.isPermitted("stu:edit"));
        System.out.println("是否拥有stu:remove权限:" + subject.isPermitted("stu:remove"));
    }
}

4.5 Shiro認証・認可プロセスのソースコード解釈

4.5.1 認証プロセスの解釈

subject.login(usernamePasswordToken); を実行すると、認証プロセスがトリガーされます。

1. DelegatingSubject クラスの login() メソッドが実行されます。

2. DefaultSecurityManager クラスの login() メソッドが実行されます。

3. AuthenticatingSecurityManager クラスの authenticate() メソッドが実行されます。

4. AbstractAuthenticator クラスの authenticate() メソッドが実行されます。

5. ModularRealmAuthenticator クラスの doAuthenticate() メソッドが実行されます。

6. ModularRealmAuthenticator クラスの doSingleRealmAuthentication() メソッドが実行されます。

7. AuthenticatingRealm クラスの getAuthenticationInfo() メソッドが実行されます。

8. カスタム Realm クラスで doGetAuthenticationInfo() メソッドを実行します。

9. パスワード検証メソッド: AuthenticatingRealm クラスの assertCredentialsMatch() メソッドが実行されます。

4.5.2 認可プロセスの解釈

subject.hasRole("admin"); を実行すると、承認プロセスがトリガーされます。

1. DelegatingSubject クラスの hasRole() メソッドが実行されます。

2. AuthorizingSecurityManager クラスの hasRole() メソッドが実行されます。

3. ModularRealmAuthorizer クラスの hasRole() メソッドが実行されます。

4. AuthorizingRealm クラスの getAuthorizationInfo() メソッドが実行されます。

5. カスタム Realm クラスで doGetAuthorizationInfo() メソッドを実行します。

6. AuthorizingRealm クラスの hasRole() メソッドが実行されます。

おすすめ

転載: blog.csdn.net/ligonglanyuan/article/details/125678001