Java-Shiro(五):Shiro Realm讲解(二)IniRealm的用法、JdbcRelam的用法

引入

上一篇在讲解Realm简介时,介绍过Realm包含大概4类缺省的Realm,本章主要讲解:

1)IniRealm的用法;

2)JdbcRealm基于mysql   默认表及查询语句实现认证、授权;

3)JdbcRealm基于mysql自定义表及查询语句实现认证、授权。

4)自定义Realm。

在上一张讲解时,截图了一张Realm的类图,这里需要重复引用下:

 

从图中,我们可以看出AuthenticatingRealm是AuthorizingRealm的父类:

1)AuthenticatingRealm类内部对外提供了抽象认证接口:

2)AuthorizingRealm    类内部对外提供了抽象授权接口:

3)因AuthorizingRealm继承了AuthenticationgRelam,因此AuthorizingRealm具有了认证、授权接口。

因此后边讲解自定义Realm时,我们会采用继承 AuthorizingRealm的方式去实现自定义Realm。

IniRealm的用法

在Shiro缺省的几种实际上IniRealm和PropertiesRealm的用法最为类似。

主要是将用户信息、用户角色、用户资源信息存储到相应的*.ini文件中,认证和授权时从文件中查找相应的数据是否存在。

*.ini主要分为4个部分:[main]、[users]、[roles]、[urls]

示例:
shiro-config.ini文件:

[main] 
#authenticator 
#配置验证器
authenticator=org.apache.shiro.authc.pam.ModularRealmAuthenticator 
#配置策略
authenticationStrategy=org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy 
#将验证器和策略关联起来
authenticator.authenticationStrategy=$authenticationStrategy 
securityManager.authenticator=$authenticator 
  
#authorizer 
authorizer=org.apache.shiro.authz.ModularRealmAuthorizer 
permissionResolver=org.apache.shiro.authz.permission.WildcardPermissionResolver 
authorizer.permissionResolver=$permissionResolver 
securityManager.authorizer=$authorizer 
  
#realm 
dataSource=com.alibaba.druid.pool.DruidDataSource 
dataSource.driverClassName=com.mysql.jdbc.Driver 
dataSource.url=jdbc:mysql://localhost:3306/shiro 
dataSource.username=root 
dataSource.password=123456

jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm 
jdbcRealm.dataSource=$dataSource 
jdbcRealm.permissionsLookupEnabled=true
securityManager.realms=$jdbcRealm 
[users] 
#提供了对用户/密码及其角色的配置,用户名=密码,角色1,角色2
zhangsan=123,admin,member
lisi=1234,member

[roles] 
#提供了角色及权限之间关系的配置,角色=权限1,权限2
admin=article:update,article:delete,article:query
member=article:query
  
[urls] 
#用于web,提供了对web url拦截相关的配置,url=拦截器[参数],拦截器 
/index.html = anon 
/admin/** = authc, roles[admin], perms["article:query"] 

1)[main]模块:

主要负责shiro相关配置:securityManger,realm,credentialsMatcher,authenticator,authorizer等。
给securityManger配置authenticator(认证器),authenticator.authenticationStrategy(认证器的多realm策略)
给securityManger配置authorizer(授权器),authorizer.permissionResolver(授权器的资源解析器)
给securityManger配置realms,realm(认证、授权 存储器).属性设置(JdbcRealm.dataSource/JdbcRealm.permissionLookupEnabled)
给realm配置credentialsMatcher(凭证匹配器:它内部包含采用哪种方式进行加密:md5、sha256,slat[盐值],hashIterations[加密迭代多少次])

2)[users]模块:
提供了对用户/密码及其角色的配置,用户名=密码,角色1,角色2,示例:

[users]
zhangsan=123,admin,member lisi=1234,member

3)[roles]模块:
提供了角色及权限之间关系的配置,角色=权限1,权限2,示例:

[roles]
admin=article:update,article:delete,article:query member=article:query

4)[urls]模块:
用于web,提供了对web url拦截相关的配置,url=拦截器[参数],拦截器,示例:

[urls]
/index.html = anon /admin/** = authc, roles[admin], perms["article:query"]

上边配置信息在shiro比较早点版本(shiro1.4.2就不是特别好用了)可使用IniSecurityManagerFactory进行加载:

Factory<org.apache.shiro.mgt.SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro-config.ini"); 
org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance(); 
 
//将SecurityManager设置到SecurityUtils 方便全局使用 
SecurityUtils.setSecurityManager(securityManager); 
Subject subject = SecurityUtils.getSubject(); 
UsernamePasswordToken token = new UsernamePasswordToken("zhang", "123"); 
subject.login(token); 
  
Assert.assertTrue(subject.isAuthenticated()); 

 实际上边配置shiro-config.xml中[main]配置信息等同于:

        DefaultSecurityManager securityManager = new DefaultSecurityManager();

        // #配置验证器
        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
        // #配置策略
        AtLeastOneSuccessfulStrategy authenticationStrategy = new AtLeastOneSuccessfulStrategy();
        // #将验证器和策略关联起来
        authenticator.setAuthenticationStrategy(authenticationStrategy);
        securityManager.setAuthenticator(authenticator);

        // #授权器
        ModularRealmAuthorizer authorizer = new ModularRealmAuthorizer();
        WildcardPermissionResolver permissionResolver = new WildcardPermissionResolver();
        authorizer.setPermissionResolver(permissionResolver);
        securityManager.setAuthorizer(authorizer);

        DruidDataSource dataSource = new DruidDataSource();
        {
            dataSource.setUsername("root");
            dataSource.setPassword("123456");
            dataSource.setUrl(
                    "jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false");
            dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        }

        JdbcRealm jdbcRealm = new JdbcRealm();
        jdbcRealm.setDataSource(dataSource);
        // JdbcRealm.permissionsLookupEnabled 默认为false,必须设置为true才能进行角色的授权。
        jdbcRealm.setPermissionsLookupEnabled(true);

        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("MD5");
        credentialsMatcher.setHashIterations(8);
        jdbcRealm.setCredentialsMatcher(credentialsMatcher);
        
        securityManager.setRealms(Arrays.asList(jdbcRealm));

使用时,通过SecurityUtils与securityManager进行关联,然后获取Subject对象,通过subject进行认证验证,授权验证,退出认证。

        SecurityUtils.setSecurityManager(securityManager);

        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123");
        subject.login(token);
        System.out.println("是否通过认证:" + subject.isAuthenticated());

        System.out.println("是否拥有admin角色:" + subject.hasRole("admin"));
        // 必须设置JdbcRealm为jdbcRealm.setPermissionsLookupEnabled(true)
        // 是否拥有修改的权限
        System.out.println("是否拥有user:update角色:" + subject.isPermitted("user:update"));

        subject.logout();
        System.out.println("是否通过认证:" + subject.isAuthenticated());

IniRealm的使用示例:

1)认证用法:

配置文件 shiroAuthenticator.ini

#用来认证
#============================
#设置用户,可设置多名用户
[users]
# 用户名=密码
zhangsan=123
lisi=1234

用法测试:

    /**
     * 测试认证
     */
    @Test
    public void testAuthenticator() {
        // 1)根据IniRealm进行数据读取 *.ini 配置,另外也可以通过JdbcRealm读取数据中存储的信息,也可以采用内存存储方式,还可以用户自定义。
        // Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager需验证用户身份,那么它需要从Realm获取响应的用户进行比较以确定用户身份是否合法;
        // 也需要从Realm得到用户相应的角色、权限进行验证用户是否能进行操作;
        // 可以把Realm看成DataSource。
        Realm iniRealm = new IniRealm("classpath:shiroAuthenticator.ini");

        // 2) 管理所有的Subject,负责认证、授权、会话以及缓存的管理。
        // SecurityManager:安全管理器,即所有与安全有关的操作都会与SecurityManager交互;
        // 且其管理所有Subject;
        // 可以看出它是Shiro的核心,它负责与Shiro的其他组件进行交互,它相当于SpringMVC中DispatcherServlet的角色。
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(iniRealm);

        SecurityUtils.setSecurityManager(defaultSecurityManager);
        // Subject:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject。
        // Subject代表了当前"用户",这个用户不一定就是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;
        // 与Subject的所有交互都委托给SecurityManager;
        // Subject其实是一个门面,SecurityManager才是实际的执行者;
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123");
        // 进行认证
        subject.login(token);

        System.out.println("是否认证通过:" + subject.isAuthenticated());
    }

2)授权的用法:

配置文件 shiroAuthorizer.ini

#用来授权
#===========================
#设置用户及用户角色
[users]
# 用户名=密码,角色
zhangsan=123,admin
lisi=1234,member

#设置角色与角色权限
[roles]
# 角色=资源1,资源2
admin=user:delete,user:update,user:query
member=user:query

用法测试:

    /**
     * 测试认证+授权
     */
    @Test
    public void testAuthorizer() {
        Realm iniRealm = new IniRealm("classpath:shiroAuthorizer.ini");
        DefaultSecurityManager defaultSecurityManager = new DefaultSecurityManager(iniRealm);

        SecurityUtils.setSecurityManager(defaultSecurityManager);
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123");
        // 调用 Realm#getAuthenticationinfo()
        subject.login(token);

        System.out.println("是否认证通过:" + subject.isAuthenticated());

        // 调用 Realm#getAuthorizationinfo()
        System.out.println("是否授权admin角色:" + subject.hasRole("admin"));
        System.out.println("是否拥有user:update资源:" + subject.isPermitted("user:update"));
        System.out.println("是否拥有user:delete资源:" + subject.isPermitted("user:delete"));
        System.out.println("是否拥有user:create资源:" + subject.isPermitted("user:create"));

        subject.logout();

        System.out.println("是否认证通过:" + subject.isAuthenticated());

        System.out.println("是否授权admin角色:" + subject.hasRole("admin"));
        System.out.println("是否拥有user:update资源:" + subject.isPermitted("user:update"));
        System.out.println("是否拥有user:delete资源:" + subject.isPermitted("user:delete"));
        System.out.println("是否拥有user:create资源:" + subject.isPermitted("user:create"));
    }

JdbcRealm的用法

使用JdbcRealm内部默认表和查询SQL

JdbcRealm实际上在代码内部缺省在关系数据库中存在三张表:

`users`{id,username,password,password_salt}
`user_roles`{id,username,role_name}
`roles_permissions`{id,role_name,permission}

其实,从JdbcRealm内部定义的查询语句可以看出内置表结构:

    /**
     * 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 = ?";

JdbcRealm使用时,需要在关系数据库中创建默认的那三张表,然后配置信息写入到对应表中。

-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(64) NOT NULL,
  `password` varchar(64) NOT NULL,
  `password_salt` varchar(64) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_idx_username` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of users
-- ----------------------------
BEGIN;
INSERT INTO `users` VALUES (1, 'zhangsan', '0349a8cd4b237e190e6e4b471e214ef3','eC4mL7bZ5sN8');
INSERT INTO `users` VALUES (2, 'lisi', '4d7a330008c2191c4c76b9b3dd59841d','qN3fT4eK2yQ9');
COMMIT;

-- ----------------------------
-- Table structure for user_roles
-- ----------------------------
DROP TABLE IF EXISTS `user_roles`;
CREATE TABLE `user_roles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(64) NOT NULL,
  `role_name` varchar(64) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_idx_role_name` (`role_name`),
  KEY `idx_username` (`username`),
  KEY `idx_role_name` (`role_name`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user_roles
-- ----------------------------
BEGIN;
INSERT INTO `user_roles` VALUES (1, 'zhangsan', 'admin');
INSERT INTO `user_roles` VALUES (2, 'lisi', 'member');
COMMIT;

-- ----------------------------
-- Table structure for roles_permissions
-- ----------------------------
DROP TABLE IF EXISTS `roles_permissions`;
CREATE TABLE `roles_permissions` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(64) DEFAULT NULL,
  `permission` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_idx_role_name_permission` (`role_name`,`permission`),
  KEY `idx_role_name` (`role_name`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of roles_permissions
-- ----------------------------
BEGIN;
INSERT INTO `roles_permissions` VALUES (2, 'admin', 'user:delete');
INSERT INTO `roles_permissions` VALUES (1, 'admin', 'user:update');
INSERT INTO `roles_permissions` VALUES (3, 'member', 'user:query');
COMMIT;

使用测试代码:

    /**
     * 使用默认 JdbcRealm 中相关表进行相关登录认证、使用默认的sql语句进行授权
     */
    @Test
    public void testJdbcRealmByDefaultDBConfiguration() {
        DefaultSecurityManager securityManager = new DefaultSecurityManager();

        // #配置验证器
        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
        // #配置策略
        AtLeastOneSuccessfulStrategy authenticationStrategy = new AtLeastOneSuccessfulStrategy();
        // #将验证器和策略关联起来
        authenticator.setAuthenticationStrategy(authenticationStrategy);
        securityManager.setAuthenticator(authenticator);

        // #授权器
        ModularRealmAuthorizer authorizer = new ModularRealmAuthorizer();
        WildcardPermissionResolver permissionResolver = new WildcardPermissionResolver();
        authorizer.setPermissionResolver(permissionResolver);
        securityManager.setAuthorizer(authorizer);

        DruidDataSource dataSource = new DruidDataSource();
        {
            dataSource.setUsername("root");
            dataSource.setPassword("123456");
            dataSource.setUrl(
                    "jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false");
            dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        }

        JdbcRealm jdbcRealm = new JdbcRealm();
        jdbcRealm.setDataSource(dataSource);
        // JdbcRealm.permissionsLookupEnabled 默认为false,必须设置为true才能进行角色的授权。
        jdbcRealm.setPermissionsLookupEnabled(true);

        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("MD5");
        credentialsMatcher.setHashIterations(8);
        jdbcRealm.setCredentialsMatcher(credentialsMatcher);
        
        securityManager.setRealms(Arrays.asList(jdbcRealm));

        SecurityUtils.setSecurityManager(securityManager);
        Subject subject = SecurityUtils.getSubject();

        UsernamePasswordToken token = new UsernamePasswordToken("zhangsan", "123");
        subject.login(token);
        System.out.println("是否通过认证:" + subject.isAuthenticated());

        System.out.println("是否拥有admin角色:" + subject.hasRole("admin"));
        // 必须设置JdbcRealm为jdbcRealm.setPermissionsLookupEnabled(true)
        // 是否拥有修改的权限
        System.out.println("是否拥有user:update角色:" + subject.isPermitted("user:update"));

        subject.logout();
        System.out.println("是否通过认证:" + subject.isAuthenticated());
    }

打印信息:

log4j:WARN No appenders could be found for logger (com.alibaba.druid.pool.DruidDataSource).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
是否通过认证:true
是否拥有admin角色:true
是否拥有user:update角色:true
是否通过认证:false

JdbcRealm使用自定义表和插叙SQL

需要在关系数据自定义三张表:my_user,my_role,my_permission。

-- ----------------------------
-- Table structure for my_user
-- ----------------------------
DROP TABLE IF EXISTS `my_user`;
CREATE TABLE `my_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(64) NOT NULL,
  `password` varchar(64) NOT NULL,
  `password_salt` varchar(64) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_idx_username` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of my_user
-- ----------------------------
BEGIN;
INSERT INTO `my_user` VALUES (1, 'zhangsan', '0349a8cd4b237e190e6e4b471e214ef3','eC4mL7bZ5sN8');
INSERT INTO `my_user` VALUES (2, 'lisi', '4d7a330008c2191c4c76b9b3dd59841d','qN3fT4eK2yQ9');
COMMIT;

-- ----------------------------
-- Table structure for my_role
-- ----------------------------
DROP TABLE IF EXISTS `my_role`;
CREATE TABLE `my_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(64) NOT NULL,
  `role_name` varchar(64) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_idx_role_name` (`role_name`) USING BTREE,
  KEY `idx_username` (`username`),
  KEY `idx_role_name` (`role_name`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of my_role
-- ----------------------------
BEGIN;
INSERT INTO `my_role` VALUES (1, 'zhangsan', 'admin');
INSERT INTO `my_role` VALUES (2, 'lisi', 'member');
COMMIT;
-- ----------------------------
-- Table structure for my_permission
-- ----------------------------
DROP TABLE IF EXISTS `my_permission`;
CREATE TABLE `my_permission` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_name` varchar(64) DEFAULT NULL,
  `permission` varchar(512) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `un_idx_role_name_permission` (`role_name`,`permission`) USING BTREE,
  KEY `idx_role_name` (`role_name`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of my_permission
-- ----------------------------
BEGIN;
INSERT INTO `my_permission` VALUES (2, 'admin', 'user:delete');
INSERT INTO `my_permission` VALUES (1, 'admin', 'user:update');
INSERT INTO `my_permission` VALUES (3, 'member', 'user:query');
COMMIT;

使用测试:

    @Test
    public void testJdbcRealmByMyDBConfiguration() {
        DruidDataSource dataSource = new DruidDataSource();
        {
            dataSource.setUsername("root");
            dataSource.setPassword("123456");
            dataSource.setUrl(
                    "jdbc:mysql://localhost:3306/mydb?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false");
            dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        }

        JdbcRealm jdbcRealm = new JdbcRealm();
        jdbcRealm.setDataSource(dataSource);
        // JdbcRealm.permissionsLookupEnabled 默认为false,必须设置为true才能进行角色的授权。
        jdbcRealm.setPermissionsLookupEnabled(true);
        jdbcRealm.setSaltStyle(SaltStyle.COLUMN); // 启用表 password_salt 字段
        
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName("MD5");
        credentialsMatcher.setHashIterations(8);
        jdbcRealm.setCredentialsMatcher(credentialsMatcher);
        
        jdbcRealm.setAuthenticationQuery("select `password`,`password_salt` from `my_user` where `username`=? ");
        jdbcRealm.setUserRolesQuery("select `role_name` from `my_role` where `username`=? ");
        jdbcRealm.setPermissionsQuery("select `permission` from `my_permission` where role_name=? ");

        // 创建SecurityManager;
        DefaultSecurityManager securityManager = new DefaultSecurityManager(jdbcRealm);

        SecurityUtils.setSecurityManager(securityManager);
        Subject subject = SecurityUtils.getSubject();

        subject.logout();
        UsernamePasswordToken token = new UsernamePasswordToken("lisi", "1234");
        subject.login(token);

        System.out.println("是否通过认证:" + subject.isAuthenticated());
        System.out.println("是否已经授权member角色:" + subject.hasRole("member"));
        System.out.println("是否已经授权admin角色:" + subject.hasRole("admin"));

        System.out.println("是否已经拥有user:create资源:" + subject.isPermitted("user:create"));
        System.out.println("是否已经拥有user:update资源:" + subject.isPermitted("user:update"));
        System.out.println("是否已经拥有user:delete资源:" + subject.isPermitted("user:delete"));
        System.out.println("是否已经拥有user:query资源:" + subject.isPermitted("user:query"));

        subject.logout();
        System.out.println("是否通过认证:" + subject.isAuthenticated());
    }

打印结果:

log4j:WARN No appenders could be found for logger (com.alibaba.druid.pool.DruidDataSource).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
是否通过认证:true
是否已经授权member角色:true
是否已经授权admin角色:false
是否已经拥有user:create资源:false
是否已经拥有user:update资源:false
是否已经拥有user:delete资源:false
是否已经拥有user:query资源:true
是否通过认证:false

 

posted @ 2019-12-23 23:49  cctext  阅读(825)  评论(0编辑  收藏  举报