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
基础才是编程人员应该深入研究的问题,比如:
1)List/Set/Map内部组成原理|区别
2)mysql索引存储结构&如何调优/b-tree特点、计算复杂度及影响复杂度的因素。。。
3)JVM运行组成与原理及调优
4)Java类加载器运行原理
5)Java中GC过程原理|使用的回收算法原理
6)Redis中hash一致性实现及与hash其他区别
7)Java多线程、线程池开发、管理Lock与Synchroined区别
8)Spring IOC/AOP 原理;加载过程的。。。
【+加关注】。