[shiro from entry to actual combat tutorial] Chapter 4 Realm Analysis

Four, Realm analysis

4.1 Overview of Realm

Shiro obtains security data (such as users, roles, and permissions) from Realm, that is, if SecurityManager wants to verify the user's identity, it needs to obtain the corresponding user from Realm for comparison to determine whether the user's identity is legal; it also needs to obtain the user's corresponding role from Realm/ Permissions are used to verify whether the user can perform operations.

4.1.1 Two concepts

principal: There can be multiple principal identifiers, but they need to be unique. Common ones include user name, mobile phone number, email address, etc.

credential: Credentials, usually passwords.

4.1.2 Inheritance structure

insert image description here

Realm provides the comparison value of the data to be verified, that is, the security data source, which can be understood as the source of the data, which can be a database, a file, etc. Shiro obtains security data (such as users, roles, and permissions) from Realm, which means that SecurityManager needs to obtain the corresponding user from Realm for comparison to determine whether the user's identity is legal; it also needs to obtain the user's corresponding role from Realm /permission to verify whether the user can perform the operation.

Realm interface:

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

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

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

CachingRealm abstract class:

CachingRealm provides cacheable Realms.

AuthenticatingRealm abstract class:

AuthenticatingRealm implements all three interface methods of Realm, and the implementation of getAuthenticationInfo method is as follows:

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

From the source code, we can see that AuthenticatingRealm implements Shiro's authentication process, that is, after calling the login method, if there is a cache, it will return the cached authentication information. If there is no cache, it will call the doGetAuthenticationInfo method. We need to implement this method when we customize Realm.

AuthorizingRealm abstract class:

AuthorizingRealm implements the Authorizer interface, which defines has*, check and other authority authentication methods. AuthorizingRealm implements these authority authentication methods. When each has and check* method is executed, the following getAuthorizationInfo method will be executed.

4.2 IniRealm

It is mainly to store the data in the xxx.inicorresponding file system, and find out whether the corresponding data exists from the file.

4.2.1 ini file

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

Wildcards can be used when configuring permissions*

  • *Indicates all permissions.
  • dept:*Indicates all permissions under the dept module.

4.2.2 Test cases

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 JdbcRealm

JdbcRealm accesses the database, and verifies the corresponding login user and authorization through the connection with the database.

4.3.1 Default database SQL statement

users table:

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 table:

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 SQL statements defined in JdbcRealm

/*--------------------------------------------
|             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 Test cases

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

Custom Realm inherits AuthorizingRealm and rewrites doGetAuthorizationInfo method for authentication and doGetAuthenticationInfo method for authentication.

4.4.1 Steps

  • Create a class that inherits from AuthorizingRealm -> AuthenticatingRealm -> CachingRealm -> Realm.
  • Rewrite the authentication method doGetAuthorizationInfo.
  • Override the authentication method doGetAuthenticationInfo.

4.4.2 Methods

  • doGetAuthenticationInfo is called when the user logs in.
  • DoGetAuthorizationInfo will be called when performing permission verification.

4.4.3 Object introduction

  • UsernamePasswordToken: Correspondingly, there are Principal logo and Credential certificate in Shiro's token.

    UsernamePasswordToken -> HostAuthenticationToken -> AuthenticationToken。

  • SimpleAuthorizationInfo: Represents user role authorization information.

  • SimpleAuthenticationInfo: represents the user's authentication information.

4.4.4 Custom Realm class

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 Test Cases

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 Source Code Interpretation of Shiro Authentication and Authorization Process

4.5.1 Interpretation of the certification process

When we execute subject.login(usernamePasswordToken); the authentication process will be triggered:

1. The login() method in the DelegatingSubject class is executed.

2. The login() method in the DefaultSecurityManager class is executed.

3. The authenticate() method in the AuthenticatingSecurityManager class is executed.

4. The authenticate() method in the AbstractAuthenticator class is executed.

5. The doAuthenticate() method in the ModularRealmAuthenticator class is executed.

6. The doSingleRealmAuthentication() method in the ModularRealmAuthenticator class is executed.

7. The getAuthenticationInfo() method in the AuthenticatingRealm class is executed.

8. Execute the doGetAuthenticationInfo() method in the custom Realm class.

9. Password verification method: The assertCredentialsMatch() method in the AuthenticatingRealm class is executed.

4.5.2 Interpretation of authorization process

When we execute subject.hasRole("admin");, the authorization process will be triggered:

1. The hasRole() method in the DelegatingSubject class is executed.

2. The hasRole() method in the AuthorizingSecurityManager class is executed.

3. The hasRole() method in the ModularRealmAuthorizer class is executed.

4. The getAuthorizationInfo() method in the AuthorizingRealm class is executed.

5. Execute the doGetAuthorizationInfo() method in the custom Realm class.

6. The hasRole() method in the AuthorizingRealm class is executed.

Guess you like

Origin blog.csdn.net/ligonglanyuan/article/details/125678001