【shiro从入门到实战教程】第四章 Realm解析

四、Realm解析

4.1 Realm概述

Shiro 从 Realm 获取安全数据(如用户、角色、权限),即 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作。

4.1.1 两个概念

principal:主体的标识,可以有多个,但是需要具有唯一性,常见的有用户名,手机号,邮箱等。

credential:凭证,一般就是密码。

4.1.2 继承结构

在这里插入图片描述

Realm提供待验证数据的比对值,即安全数据源,可以理解为数据的源头,可以是数据库,文件等。Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作。

Realm接口:

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

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

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

CachingRealm抽象类:

CachingRealm提供了可缓存的Realm。

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

通过该源码可知,AuthenticatingRealm实现了shiro的认证流程,也就是调用login方法后如果有缓存则返回缓存认证信息,如果没有缓存则调用doGetAuthenticationInfo方法,我们自定义Realm就需要实现该方法。

AuthorizingRealm抽象类:

AuthorizingRealm 其实现了Authorizer接口,Authorizer接口中定义了has*、check等权限认证的方法,AuthorizingRealm中实现了这些权限认证的方法,每个has,check*方法执行时都会执行如下getAuthorizationInfo方法。

4.2 IniRealm

主要是将数据存放到相应的 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 JdbcRealm

JdbcRealm的方式访问数据库,通过与数据库的连接,验证相应的登录用户与授权。

4.3.1 默认的数据库SQL语句

users表:

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 自定义Realm

自定义Realm继承 AuthorizingRealm 重写 doGetAuthorizationInfo 方法做鉴权和 doGetAuthenticationInfo 方法做认证。

4.4.1 步骤

  • 创建一个类 ,继承AuthorizingRealm -> AuthenticatingRealm -> CachingRealm -> Realm。
  • 重写鉴权方法 doGetAuthorizationInfo。
  • 重写认证方法 doGetAuthenticationInfo。

4.4.2 方法

  • 用户登陆的时会调用 doGetAuthenticationInfo。
  • 进行权限校验的时会调用 doGetAuthorizationInfo。

4.4.3 对象介绍

  • UsernamePasswordToken: 对应就是shiro的token中有Principal标识和Credential凭证。

    UsernamePasswordToken -> HostAuthenticationToken -> AuthenticationToken。

  • SimpleAuthorizationInfo: 代表用户角色权限信息。

  • SimpleAuthenticationInfo: 代表该用户的认证信息。

4.4.4 自定义Realm类

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