【Shiro】Apache Shiro架构之自定义realm

2024-06-22 05:32

本文主要是介绍【Shiro】Apache Shiro架构之自定义realm,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Shiro系列文章: 
【Shiro】Apache Shiro架构之身份认证(Authentication) 
【Shiro】Apache Shiro架构之权限认证(Authorization) 
【Shiro】Apache Shiro架构之集成web 
【Shiro】Apache Shiro架构之实际运用(整合到Spring中)

   
  之前写的博客里都是使用.ini文件来获取信息的,包括用户信息,角色信息,权限信息等。进入系统时,都是从.ini文件这读取进入的。实际中除非这个系统特别特别简单,否则一般都不是这样干的,这些信息都是需要在数据库中进行维护的,所以就需要用到自定义realm了。

写在前面:这篇博文是基于上一篇Shiro集成web基础之上修改的。

1. 数据库建表

  首先在数据库中新建三个表:t_user,t_role和t_permission,分别存储用户信息,角色信息和权限信息,建表语句如下:

CREATE TABLE `t_role` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',`rolename` varchar(20) DEFAULT NULL COMMENT '角色名称',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8CREATE TABLE `t_user` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '用户主键',`username` varchar(20) NOT NULL COMMENT '用户名',`password` varchar(20) NOT NULL COMMENT '密码',`role_id` int(11) DEFAULT NULL COMMENT '外键关联role表',PRIMARY KEY (`id`),KEY `role_id` (`role_id`),CONSTRAINT `t_user_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8CREATE TABLE `t_permission` (`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',`permissionname` varchar(50) NOT NULL COMMENT '权限名',`role_id` int(11) DEFAULT NULL COMMENT '外键关联role',PRIMARY KEY (`id`),KEY `role_id` (`role_id`),CONSTRAINT `t_permission_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

  每个表中我添加了一些测试数据,如下: 
user

role

permission

2. 自定义realm

  自定义realm中需要操作数据库,所有首先得先写一个dao,使用的是原始的jdbc,主要是下面的自定义realm。

public class UserDao {//根据用户名查找用户public User getByUsername(Connection conn, String username) throws Exception {User resultUser = null;String sql = "select * from t_user where username=?";PreparedStatement ps = conn.prepareStatement(sql);ps.setString(1, username);ResultSet rs = ps.executeQuery();if(rs.next()) {resultUser = new User();resultUser.setId(rs.getInt("id"));resultUser.setUsername(rs.getString("username"));resultUser.setPassword(rs.getString("password"));}return resultUser;}//根据用户名查找改用户所拥有的角色public Set<String> getRoles(Connection conn, String username) throws Exception {Set<String> roles = new HashSet<String>();String sql = "select * from t_user u, t_role r where u.role_id=r.id and u.username=?";PreparedStatement ps = conn.prepareStatement(sql);ps.setString(1, username);ResultSet rs = ps.executeQuery();while(rs.next()) {roles.add(rs.getString("rolename"));}return roles;}//根据用户名查找该用户角色所拥有的权限public Set<String> getPerms(Connection conn, String username) throws Exception {Set<String> perms = new HashSet<String>();String sql = "select * from t_user u, t_role r, t_permission p where u.role_id=r.id and p.role_id=r.id and u.username=?";PreparedStatement ps = conn.prepareStatement(sql);ps.setString(1, username);ResultSet rs = ps.executeQuery();while(rs.next()) {perms.add(rs.getString("permissionname"));}return perms;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

  有了dao了,接下来就可以写自定义的realm了,自定义realm需要继承AuthorizingRealm类,因为该类封装了很多方法,它也是一步步继承自Realm类的,继承了AuthorizingRealm类后,需要重写两个方法:

doGetAuthenticationInfo()方法:用来验证当前登录的用户,获取认证信息 
doGetAuthorizationInfo()方法:用来为当前登陆成功的用户授予权限和角色(已经登陆成功了)

  下面来看一下具体的实现:

public class MyRealm extends AuthorizingRealm {private UserDao userDao = new UserDao();// 为当前登陆成功的用户授予权限和角色,已经登陆成功了@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String username = (String) principals.getPrimaryPrincipal(); //获取用户名SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();Connection conn = null;try {conn = DbUtil.getConnection();authorizationInfo.setRoles(userDao.getRoles(conn, username)); //设置角色authorizationInfo.setStringPermissions(userDao.getPerms(conn, username)); //设置权限            } catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {try {DbUtil.closeConnection(conn);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}return authorizationInfo;}// 验证当前登录的用户,获取认证信息@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {String username = (String) token.getPrincipal(); // 获取用户名Connection conn = null;try {conn = DbUtil.getConnection();User user = userDao.getByUsername(conn, username); // 仅仅是根据用户名查出的用户信息,不涉及到密码if (user != null) {AuthenticationInfo authcInfo = new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), "myrealm");return authcInfo;} else {return null;}} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {try {DbUtil.closeConnection(conn);} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}return null;}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

  从上面两个方法中可以看出:验证身份的时候是根据用户输入的用户名先从数据库中查出该用户名对应的用户,这时候并没有涉及到密码,也就是说到这一步的时候,即使用户输入的密码不对,也是可以查出来该用户的,然后将该用户的正确信息封装到authcInfo 中返回给Shiro,接下来就是Shiro的事了,它会根据这里面的真实信息与用户前台输入的用户名和密码进行校验, 这个时候也要校验密码了,如果校验通过就让用户登录,否则跳转到指定页面。同理,权限验证的时候也是先根据用户名获取与该用户名有关的角色和权限,然后封装到authorizationInfo中返回给Shiro。

3. 修改ini文件

  在该配置文件中,[users]和[roles]的信息就可以删掉了,因为这些信息都是从数据库中维护的,另外还要在文件中指定我们自定义的realm的完全限定名,并且指定securityManager的realm使用我们自定义的realm,如下:

[main]
authc.loginUrl=/login
roles.unauthorizedUrl=/unauthorized.jsp
perms.unauthorizedUrl=/unauthorized.jsp#定义自己的realm
myRealm=demo.shiro.realm.MyRealm
securityManager.realms=$myRealm#定义请求的地址需要做什么验证
[urls]
/login=anon 
/admin=authc 
/student=roles[teacher]
/teacher=perms["user:create"]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

  这样我们自定义的realm就搞定了,根据配置文件,当我们请求…/admin的时候会进行身份认证,所以会进入LoginServlet中,当调用currentUser.login(token);的时候,就会进入我们自定义的realm中的doGetAuthenticationInfo方法进行身份初始化,然后交给Shiro去验证。当我们请求…./student的时候,也会先进行身份验证,就是上面的过程,然后验证通过,当我们再次请求…/student的时候,就会进入我们自定义的realm中的doGetAuthorizationInfo方法进行权限的初始化,然后交给Shiro去验证。 
  下一篇博文我将总结一下如何将Shiro运用到实际项目中,即如何将Shiro整合到spring中。

本文转自:csdn eson_15

这篇关于【Shiro】Apache Shiro架构之自定义realm的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1083419

相关文章

通信系统网络架构_2.广域网网络架构

1.概述          通俗来讲,广域网是将分布于相比局域网络更广区域的计算机设备联接起来的网络。广域网由通信子网于资源子网组成。通信子网可以利用公用分组交换网、卫星通信网和无线分组交换网构建,将分布在不同地区的局域网或计算机系统互连起来,实现资源子网的共享。 2.网络组成          广域网属于多级网络,通常由骨干网、分布网、接入网组成。在网络规模较小时,可仅由骨干网和接入网组成

ROS话题通信流程自定义数据格式

ROS话题通信流程自定义数据格式 需求流程实现步骤定义msg文件编辑配置文件编译 在 ROS 通信协议中,数据载体是一个较为重要组成部分,ROS 中通过 std_msgs 封装了一些原生的数据类型,比如:String、Int32、Int64、Char、Bool、Empty… 但是,这些数据一般只包含一个 data 字段,结构的单一意味着功能上的局限性,当传输一些复杂的数据,比如:

响应式架构

介绍 响应式架构(Reactive Architecture)是一种面向服务和事件的系统设计方法,旨在提高系统的可扩展性、弹性和容错能力。它适用于构建分布式系统,特别是在云环境和微服务架构中。响应式架构的核心理念是通过事件驱动和数据流来实现各个组件之间的解耦,从而提高整个系统的响应能力和可靠性。 响应式架构的主要特点包括: 响应性:系统能够快速响应外部事件和内部变化,确保在各种负载和故障情

添加自定义的CALayer

iOS开发UI篇—CAlayer(创建图层) 一、添加一个图层 添加图层的步骤: 1.创建layer 2.设置layer的属性(设置了颜色,bounds才能显示出来) 3.将layer添加到界面上(控制器view的layer上)  1 // 2 // YYViewController.m 3 // 01-创建一个简单的图层 4 // 5 //

android自定义View的和FramgentActivity的一个小坑

对于自定义View //加载样式TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.TitleBarView, defStyleAttr, 0);setTitle(typedArray.getString(R.styleable.TitleBarView_main_title));//不能写成

大型网站架构演化(六)——使用反向代理和CDN加速网站响应

随着网站业务不断发展,用户规模越来越大,由于中国复杂的网络环境,不同地区的用户访问网站时,速度差别也极大。有研究表明,网站访问延迟和用户流失率正相关,网站访问越慢,用户越容易失去耐心而离开。为了提供更好的用户体验,留住用户,网站需要加速网站访问速度。      主要手段:使用CDN和反向代理。如图。     使用CDN和反向代理的目的都是尽早返回数据给用户,一方面加快用户访问速

大型网站架构演化(五)——数据库读写分离

网站在使用缓存后,使绝大部分数据读操作访问都可以不通过数据库就能完成,但是仍有一部分读操作(缓存访问不命中、缓存过期)和全部的写操作需要访问数据库,在网站的用户达到一定规模后,数据库因为负载压力过大而成为网站的瓶颈。      目前豆粉的主流数据库都提供主从热备功能,通过配置两台数据库主从关系,可以将一台数据库服务器的数据更新同步到另一台服务器上。网站利用数据库的这一功能,

大型网站架构演化(四)——使用应用服务器集群改善网站的并发能力

使用集群是网站解决高并发、海量数据问题的常用手段。当一台服务器的处理能力、存储空间不足时,不要企图去更换更强大的服务器,对大型服务器而言,不管多么强大的服务器,都满足不了网站持续增长的业务需求。这种情况下,更恰当的做法是增加一台服务器分担原有服务器的访问及存储压力。 对网站架构而言,只要能通过增加一台服务器的方式改善负载压力,就可以以同样的方式持续增加服务器不断改善系统性能,从而实现系统

大型网站架构演化(二)——应用服务和数据服务分离

随着网站业务的发展,一台服务器逐渐不能满足需求:越来越多的用户访问导致性能越来越差,越来越多的数据导致存储空间不足。这时就需要将应用和数据分离。应用和数据分离后整个网站使用三台服务器:应用服务器、文件服务器和数据库服务器,如图。              这三台服务器对硬件资源的要求各不相同: 应用服务器需要处理大量的业务逻辑,因此需要更快更强大的CPU;

大型网站架构演化(一)——初始阶段的网站架构

大型网站的技术挑战主要来自于庞大的用户,高并发的访问和海量的数据,任何简单的业务一旦需要处理数以P计的数据和面对数以亿计的用户,问题就会变得很棘手。大型网站架构主要是解决这类问题。         大型网站都是从小型网站发展而来,网站架构也是一样,是从小型网站架构逐步演化而来。小型网站最开始时没有太多人访问,只需要一台服务器就绰绰有余,这时的网站架构如图所示。