【课程13】自定义Realm.xmind36.8KB
【课程13】用户授权流程.xmind0.2MB
【课程13】shiro简介.xmind0.3MB
【课程13】拓展知...登录.xmind15.4KB
【课程13】renren_f...集成.xmind0.3MB
【课程13】用户认证流程.xmind0.2MB
【课程13预习】shir...概念.xmind59.9KB
简介
官方源码:https://github.com/apache/shiro
在Web系统中我们经常要涉及到权限问题,例如不同角色的人登录系统,他操作的功能、按钮、菜单是各不相同的,这就是所谓的权限。
而构建一个互联网应用,权限校验管理是很重要的安全措施,这其中主要包含:
Shiro对以上功能都进行了很好的支持,它可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。这不就是我们想要的嘛,而且Shiro的API也是非常简单。
组件结构
外部架构
Shiro有三个主要的概念,Subject、SecurityManager 和Realms,下图是这三个组件的交互关系。
Subject:就是与系统交互的当前”用户”,用户不仅仅是人,也可以是第三方服务,爬虫等正在与系统交互的任何事物。
Subject currentUser = SecurityUtils.getSubject();
在获取了Subject对象之后,就可以执行包括登录、登出、获取会话、权限校验等操作。Shiro的简单易用的API,使得我们在程序的任何地方都能很方便地获取当前登录用户,并进行登录用户的各项基本操作。
SecurityManager:是Shiro架构的核心,协调内部各个安全组件之间的交互,通常情况下,一旦SecurityManager和它的内部各个组件被配置好之后就不会再用到,开发者通常是查看Subject 的API。
当我们和Subject交互的时候,实际上是SecurityManager在背后协调跟Subject安全相关的操作。
SecurityManager则管理所有用户的安全操作,它是Shiro框架的核心。一旦其初始化配置完成,我们就不会再调用其相关API了,而是将精力集中在了Subject相关的权限操作上了。
Realms:在Shiro和用户的应用程序之间扮演着桥梁和连接器的作用。当需要验证或者授权的时候,Shiro从一个或者多个配置的Realms中查找。
这种情况下,Realm是一个安全的DAO,它封装了具体数据库连接的细节,当Shiro需要的时候为Shiro提供需要的数据。当配置Shiro的时候,必须配置至少一个Realm来验证和授权。SecurityManager 可以配置多个Realm,至少需要一个。
Shiro提供了即用的Realm用来连接到各种安全的数据源,像LDAP, 关系型数据库(JDBC), 文本配置的INI和properties文件等。 用户可以插入自己的Realm 实现,如果默认的Realm 不能满足需求的话。
- 记住一点,Shiro不会去维护用户、维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可。
详细架构
Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;
SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authorizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;
Subject及Realm,分别是主体及验证主体的数据源。
Session:Shiro提供一个权限的企业级Session解决方案,可以运行在简单的命令行或者是智能手机平台上,也可以工作在大型的集群应用上。
以往我们需要使用Session的一些特性支持时,往往只能将服务部署在web容器或者EJB的Session特性。
Shiro的Session管理方案比上述两种方案都更简单,而且他可以运行在任何应用中,与容器无关。
在Shiro中,session的生命周期都在SessionManager中进行管理
SessionManager:如果写过Servlet就应该知道Session的概念,Session呢需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;所有呢,Shiro就抽象了一个自己的Session来管理主体与应用之间交互的数据;这样的话,比如我们在Web环境用,刚开始是一台Web服务器;接着又上了台EJB服务器;这时想把两台服务器的会话数据放到一个地方,这个时候就可以实现自己的分布式会话(如把数据放到Memcached服务器);
SessionDAO:DAO大家都用过,数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;
CacheManager:缓存控制器,来管理如用户、角色、权限等的缓存的;因为这些数据基本上很少去改变,放到缓存中后可以提高访问的性能
Cryptography:密码模块,Shiro提高了一些常见的加密组件用于如密码加密/解密的。
The SecurityManager
SecurityManager 执行安全操作,管理用户的状态,Shiro的默认SecurityManager 实现包括以下部分
为了简化配置并且使应用灵活,Shiro的实现都是高度模块化设计的。
SecurityManager 通常扮演一个轻量的容器,代表其他组件,这个装饰模式的设计可以通过上面的架构图看出。
其他组件各司其职,而SecurityManager 负责协调各个组件。
一个简单例子
官网例子:http://shiro.apache.org/tutorial.html
官网git:https://github.com/apache/shiro/tree/master/samples/quickstart
在应用中使用Shiro,我们首先要明白的是在Shiro中的所有组件都和一个核心组件相关,这个组件就是SecurityManager。
我们将会在Shiro架构章节详细讲述Shiro的设计细节,现在我们只要知道Shiro SecurityManager是所有使用Shiro的应用的核心,并且每个应用都需要一个SecurityManager就已经足够了。因此,第一件事就是我们必须在我们的应用中获取一个SecurityManager实例。
关于subject
在独立应用中调用getSubject()可以从指定位置的用户数据返回一个Subject,在服务器环境下(例如 web app),将基于当前线程或收到的请求返回一个Subject。
关于session
Session是一个特定的Shiro实例,它不仅代表最常用的普通HttpSesstion,而且还带有一些额外的东西,其中最大的区别是:Shiro Session并不需要HTTP环境!
如果在web应用中使用,默认情况下Session就是HttpSession。但是在非web环境下,例如在我们教程中创建的这个应用里,Shiro将自动默认使用企业级会话管理。也就是说在你的应用中你可以使用同一套API而不用关心你应用的发布环境。这就为那些需要使用会话却不想强制使用HttpSession或则EJB会话的应用打开了一个新的世界。而且,客户端还可以分享会话数据。
关于SecurityManager
通过ini的方式可以配置SecurityManager,里面包含用户信息、角色、权限、url权限信息。SecurityManager通常是单例的。
SecurityManager则管理所有用户的安全操作,它是Shiro框架的核心。一旦其初始化配置完成,我们就不会再调用其相关API了,而是将精力集中在了Subject相关的权限操作上了。
常用API
#获取当前用户
Subject currentUser = SecurityUtils.getSubject();
#判断用户已经认证
currentUser.isAuthenticated()
#用户登录
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
#记住我
token.setRememberMe(true); currentUser.login(token);
#判断是否有角色权限
currentUser.hasRole("schwartz")
#判断是否有资源操作权限
currentUser.isPermitted("lightsaber:wield")
#登出
currentUser.logout();
拓展:
- 注解判断是否有权限,通过spring aop的形式完成
@RequiresRoles("user")
@RequiresRoles({"user","admin"})
@RequiresRoles(value={"user","admin"},logical=Logical.OR)
@RequiresPermissions("index:hello")
@RequiresPermissions({"index:hello","index:world"})
@RequiresPermissions(value={"index:hello","index:world"},logical=Logical.OR)
@RequiresAuthentication
@RequiresUser
@RequiresGusst
步骤解析
认证流程
- 1、首先调用Subject.login(token)进行登录,其会自动委托给Security Manager,调用之前必须通过SecurityUtils. setSecurityManager()设置;
- 2、SecurityManager负责真正的身份验证逻辑;它会委托给Authenticator进行身份验证;
- 3、Authenticator才是真正的身份验证者,Shiro API中核心的身份认证入口点,此处可以自定义插入自己的实现;
- 4、Authenticator可能会委托给相应的AuthenticationStrategy进行多Realm身份验证,默认ModularRealmAuthenticator会调用AuthenticationStrategy进行多Realm身份验证;
- 5、Authenticator会把相应的token传入Realm,从Realm获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。
使用UsernamePasswordToken,支持最常见的用户名/口令的认证方式。它是org.apache.shiro.authc.AuthenticationToken接口的一个实现
常见异常
- DisabledAccountException(禁用的帐号)
- LockedAccountException(锁定的帐号)
- UnknownAccountException(错误的帐号)
- ExcessiveAttemptsException(登录失败次数过多)
- IncorrectCredentialsException (错误的凭证)
- ExpiredCredentialsException(过期的凭证)
认证流程自定义的功能
- 把session信息存到redis、memcache等缓存中间件中
- 其中,session的id可以自定义生成格式:属性:sessionIdGenerator
- AuthenticationToken - 用户Subject提交的有关登录主体和凭证的基本信息组合
- 默认方式UsernamePasswordToken,即密码登录模式
- 通过implement AuthenticationToken的方式来实现自定义的登录方式和特殊的必需登录数据的索取
- 如:jwt方式的一般需要实现AuthenticationToken接口添加上token字段信息
授权流程
授权(Authorization)也叫做访问控制,是一个对资源的访问进行管理的过程,也就是说在应用程序汇总,谁有怎样的权限(用户可以看到什么内容,可以进行什么操作)。在授权中需了解的几个关键对象:主体(Subject)、资源(Resource)、权限(Permission)、角色(Role)
授权的三要素
主体
主体,即访问应用的用户,在Shiro中使用Subject代表该用户。用户只有授权后才允许访问相应的资源。
资源
在应用中用户可以访问的任何东西,比如访问JSP页面、查看/编辑某些数据、访问某个业务方法、打印文本等等都是资源。用户只要授权后才能访问。
权限
安全策略中的原子授权单位,通过权限我们可以表示在应用中用户有没有操作某个资源的权力。即权限表示在应用中用户能不能访问某个资源,如:
访问用户列表页面
查看/新增/修改/删除用户数据(即很多时候都是CRUD(增查改删)式权限控制)
打印文档等等。。。
角色
角色代表了操作集合,可以理解为权限的集合,一般情况下我们会赋予用户角色而不是权限,即这样用户可以拥有一组权限,赋予权限时比较方便。典型的如:项目经理、技术总监、CTO、开发工程师等都是角色,不同的角色拥有一组不同的权限。
- 1、首先调用Subject.isPermitted*/hasRole*接口,其会委托给SecurityManager,而SecurityManager接着会委托给Authorizer;
- 2、Authorizer是真正的授权者,如果我们调用如isPermitted(“user:view”),其首先会通过PermissionResolver把字符串转换成相应的Permission实例;
- 3、在进行授权之前,其会调用相应的Realm获取Subject相应的角色/权限用于匹配传入的角色/权限;
- 4、Authorizer会判断Realm的角色/权限是否和传入的匹配,如果有多个Realm,会委托给ModularRealmAuthorizer进行循环判断,如果匹配如isPermitted*/hasRole*会返回true,否则返回false表示授权失败
ModularRealmAuthorizer进行多Realm匹配流程:
1、首先检查相应的Realm是否实现了实现了Authorizer;
2、如果实现了Authorizer,那么接着调用其相应的isPermitted*/hasRole*接口进行匹配;
3、如果有一个Realm匹配那么将返回true,否则返回false。
Realm授权
1、如果Realm进行授权的话,应该继承AuthorizingRealm,其流程是:
(1)如果调用hasRole*,则直接获取AuthorizationInfo.getRoles()与传入的角色比较即可;
(2)首先如果调用如isPermitted(“user:view”),首先通过PermissionResolver将权限字符串转换成相应的Permission实例,默认使用WildcardPermissionResolver,即转换为通配符的WildcardPermission;
2、通过AuthorizationInfo.getObjectPermissions()得到Permission实例集合;通过AuthorizationInfo. getStringPermissions()得到字符串集合并通过PermissionResolver解析为Permission实例;然后获取用户的角色,并通过RolePermissionResolver解析角色对应的权限集合(默认没有实现,可以自己提供);
3、接着调用Permission. implies(Permission p)逐个与传入的权限比较,如果有匹配的则返回true,否则false.
Shiro支持三种方式实现授权过程:
Subject subject = SecurityUtils.getSubject
if
} else {
}
@RequiresRoles("admin")
public void hello() {
}
<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>
jsp页面引入shiro标签
<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
自定义Realm
Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。如我们之前的ini配置方式将使用org.apache.shiro.realm.text.IniRealm。
- AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token):
表示获取身份验证信息;
- AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals):
表示根据用户身份获取授权信息。
在线会话管理
获取当前会话总人数
@Autowired
private SessionDAO sessionDAO;
int size = sessionDAO.getActiveSessions().size()
强制下线
Session session = sessionDAO.readSession(subject.getSession().getId());
sessionDAO.delete(session);
subject.logout();
Assert.isTrue(!subject.isAuthenticated());
springboot集成shiro
第一步,导入pom坐标文件
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring-boot-starter</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.4.0</version>
</dependency>
第二步:继承AuthorizingRealm,重写授权和认证方法。
@Component
public class OAuth2Realm extends AuthorizingRealm{
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
}
}
第三步:自定义shiro过滤器,自定重写isAccessAllowed、onAccessDenied、onLoginFailure等方法
public class OAuth2Filter extends AuthenticatingFilter {
}
第四步:定义shiro配置。注意自定义Bean。securityManager、和shiroFilter。
@Configuration
public class ShiroConfig {
@Bean("securityManager")
public SecurityManager securityManager(OAuth2Realm oAuth2Realm, SessionManager sessionManager){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(oAuth2Realm);
return securityManager;
}
@Bean("shiroFilter")
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
}
}
第五步:在需要权限控制的地方,使用注解。
renren-fast的shiro解析
1、sys_user[用户]表,保存用户相关数据,通过sys_user_role[用户与角色关联]表,与
sys_role[角色]表关联;sys_menu[菜单]表通过sys_role_menu[菜单与角色关联]表,与
sys_role[角色]表关联
2、 sys_menu表,保存菜单相关数据,并在perms字段里,保存了shiro的权限标识,也就是
说,拥有此菜单,就拥有perms字段里的所有权限,比如,某用户拥有的菜单权限标
识 sys:user:info ,就可以访问下面的方法
3 . 在shiro配置代码里,配置为 anon 的,表示不经过shiro处理,配置为 oauth2 的,表示经过 OAuth2Filter 处理,前后端分离的接口,都会交给 OAuth2Filter 处理,这样就保证,没有权限的请求,拒绝访问。
单点登录
什么是单点登录?单点登录全称Single Sign On(以下简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分
1、登录
相比于单系统登录,sso需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。间接授权通过令牌实现,sso认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到了授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。
这个过程,也就是单点登录的原理,用下图说明
下面对上图简要描述
- 用户访问系统1的受保护资源,系统1发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
- sso认证中心发现用户未登录,将用户引导至登录页面
- 用户输入用户名密码提交登录申请
- sso认证中心校验用户信息,创建用户与sso认证中心之间的会话,称为全局会话,同时创建授权令牌
- sso认证中心带着令牌跳转会最初的请求地址(系统1)
- 系统1拿到令牌,去sso认证中心校验令牌是否有效
- sso认证中心校验令牌,返回有效,注册系统1
- 系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源
- 用户访问系统2的受保护资源
- 系统2发现用户未登录,跳转至sso认证中心,并将自己的地址作为参数
- sso认证中心发现用户已登录,跳转回系统2的地址,并附上令牌
- 系统2拿到令牌,去sso认证中心校验令牌是否有效
- sso认证中心校验令牌,返回有效,注册系统2
- 系统2使用该令牌创建与用户的局部会话,返回受保护资源
用户登录成功之后,会与sso认证中心及各个子系统建立会话,用户与sso认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过sso认证中心,全局会话与局部会话有如下约束关系
- 局部会话存在,全局会话一定存在
- 全局会话存在,局部会话不一定存在
- 全局会话销毁,局部会话必须销毁
你可以通过博客园、百度、csdn、淘宝等网站的登录过程加深对单点登录的理解,注意观察登录过程中的跳转url与参数
2、注销
单点登录自然也要单点注销,在一个子系统中注销,所有子系统的会话都将被销毁,用下面的图来说明
sso认证中心一直监听全局会话的状态,一旦全局会话销毁,监听器将通知所有注册系统执行注销操作。
下面对上图简要说明
- 用户向系统1发起注销请求
- 系统1根据用户与系统1建立的会话id拿到令牌,向sso认证中心发起注销请求
- sso认证中心校验令牌有效,销毁全局会话,同时取出所有用此令牌注册的系统地址
- sso认证中心向所有注册系统发起注销请求
- 各注册系统接收sso认证中心的注销请求,销毁局部会话
- sso认证中心引导用户至登录页面
sso文章:https://www.cnblogs.com/ywlaker/p/6113927.html
sso代码:https://github.com/sheefee/simple-sso