Shiro 框架基础知识归纳

目录

一、URL 基本配置

二、身份认证

三、身份认证流程

四、AuthenticationStrategy 接口的默认实现

五、授权

六、Permissions 规则(资源标识符)

七、ModularRealmAuthorizer 进行多 Realm 匹配流程

八、JSTL 标签

九、权限注解

十、自定义拦截器

十一、会话管理

十二、缓存:CacheManagerAware 接口

十三、RememberMe


一、URL 基本配置


格式URL=拦截器[参数],例如:

/login.jsp = anon

   ■  anno (anonymous)拦截器表示匿名访问(不登录即可访问);
   ■  authc(authentication)拦截器表示需要身份认证后才能访问;
URL 配置模式使用了Ant 风格模式 Ant 路径通配符支持*** 需要注意的是匹配符不包含目录分割符/
   ■  _?匹配一个字符。
   ■  _*  :匹配零个或多个字符串。
   ■  _**匹配路径中的零个或多个路径。
注意URL 权限采取第一次匹配优先的方式。

二、身份认证


在 Shrio 中用户需要提供 principals(身份)和 credentials(证明)给 Shiro,从而能验证用户身份。

基本流程【1】调用 SecurityManager 获取 subject
【2】验证当前用户是否登录,subject.isAuthenticated()
【3】若未验证,则将用户名和密码封装为 UsernamePasswordToken 对象;
【4】调用 subject.login(AutenticationToken)方法验证,会调用5中的方法,实现用户认证操作;
【5】自定义 Realm,从数据库中获取用户记录,返回给 Shiro;
     1)实际上只需继承 org.apache.shiro.realm.AuthenticatingRealm 类。
     2)实现 doGetAuthenticationInfo(AuthenticationToken)  方法。---->方法中的 Token==login 方法传过来的 Token。
【6】由 Shiro 完成密码比对;
因为 Shiro 缓存的原因,当用户 Token 登录成功之后,你回退重新登入 Token 但是密码错误时,发现扔能够登录成功。所以当每次结束时需要出发 logout 命令,简单办法如下:在 Shiro 拦截其中添加如下代码。/shiro/logout==需要发送的URL。

/shiro/logout = logout

三、身份认证流程


【1】首先调用 Subject.login(token) 进行登录,其会委托给 SecurityManager
【2】securityManager 负责真正的身份验证逻辑,他会委托给 Authenticator 进行身份认证;
【3】Authentication 才是真正的身份验证者,Shrio API 中核心的身份认证入口点,此处可以插入自己的实现;
【4】Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份认证,默认 ModularRealmAuthenticator 会调用 AuthenticationStrategy 进行多 Real 身份认证;
【5】Authenticator 会把相应的 token 传入 Realm,从 realm 获取身份认证信息,如果没有返回/抛出异常表示认证失败。此处可以配置多个 realm,将按照相应的顺序及策略进行访问;
Realm:Shiro 从 Realm 中获取安全数据(如用户,角色,权限)即 SecurityManager 要验证用户身份,需要从 Realm 中获取相应的用户进行比较以确定用户的身份是否合法;需要从用户中得要相应的角色和权限判断用户是否可以操作。Realm 一般继承 AuthorizingRealm(授权)即可;其继承了 AuthenticatingRealm(即身份认证),而他也间接继承了CachingRealm(带有缓存实现)。
Authenticator:职责是验证用户账号,是 Shiro API 中身份认证的核心入口点,如果验证成功。将返回 AuthenticationInfo 验证信息;此信息中包含身份及凭证;如果异常则会抛出相应的 AuthenticationException异常。

四、AuthenticationStrategy 接口的默认实现


【1】FirstSuccessfulStrategy:只要有一个 Realm 验证成功即可,只返回第一个 Realm 身份认证成功的认证信息,其它忽略;
【2】AtLeastOneSuccessfulStrategy:只要有一个 Realm 验证成功即可,和 FirstSuccessfulStrategy 不同的是。将返回所有认证成功的 Realm 信息。
【3】AllSuccessfulStrategy:所有 Realm 认证成功才算成功,且返回所有 Realm 身份认证信息,如果一个失败就是失败。

ModularRealmAuthenticator 默认是 AtLeastOneSusccessfulStrategy 策略

 修改认证策略


	
		
	

当用户输入密码时,我们通常会对密码进行加密:在 Shiro 中我们需要对输入的密码加密后认证比较时,只需要在配置文件中配置密码的加密方式:替换当前 Realm 的 credentialsMatcher 属性. 直接使用 HashedCredentialsMatcher 对象, 并设置加密算法即可。


	
		
			//加密方式
			//加密次数
		
	

  如何使两个原始密码相同的密码,加密后不一样。(MD5盐值加密)我们将用户名看做是唯一的元素。

ByteSource credentialsSalt = ByteSource.Util.bytes(username);
SimpleAuthenticationInfo info = null; 
info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);

realmName:当前 realm 对象的 name. 调用父类的 getName() 方法即可:String realmName = getName();

【1】使用 ByteSource.Util.bytes() 来计算盐值;
【2】盐值需要唯一:一般使用随机字符串或 user id;
【3】使用 new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations); 来计算盐值加密后的密码的值.

String hashAlgorithmName = "MD5";
Object credentials = "123456";
Object salt = ByteSource.Util.bytes("user");;
int hashIterations = 1024;
Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);

  有时候我们会将用户密码存入多个库,用不同的加密方式。例如:


	
		
			
			
		
	



	
		
			
			
		
	

  当配置多个 Realm 时,需要配置 autenticator 来存放。


		  
		
			
			
		
	

  最后将 authentication 配置在 SecurityManager 中。


	
	

  通常我们会将多个 Reals 配置在 SecurityManager 中:(因为系统初始化的时候,会通过认证是否为ModularRealmAuthenticator,如果是则将 Realms 的值赋给 Authencator)


	
	
	
	
		
			
			
		
	
	
	

五、授权


【1】也叫访问控制,即在应用中控制谁访问那些资源(如访问页面、编辑数据、页面操作等)在授权中需要了解基本关键对象:
   ●  主体(Subject):访问应用的用户;
   ●  资源(Resource):在应用中用户可以访问的 URL;
   ●  权限(Permission):在应用中用户能不能访问某个资源;
   ●  角色(Role):权限的集合;
【2】Shrio 支持三种授权方式:
   ●  编程式:通过 if/else代码块来完成;
   ●  注解式:通过在执行的方法上放置相应的注解来完成,没有权限则抛出相应异常。(@RequiresRoles("admin"));
   ●  JSP/GSP:在 JSP/GSP页面通过相应的标签完成。();
Shrio 内置了许多默认拦截器,比如身份认证、授权等相关的。(可参考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举拦截器)。授权相关拦截器如下:

Shiro 框架基础知识归纳_第1张图片

六、Permissions 规则(资源标识符


对象实例ID:即对那个资源的那个实例可以进行什么样的操作。默认支持通配符:(:表示资源/操作/实例的分割)
  ■ ,表示操作的分割。(user:query, edit)实例访问控制(user:edit:manager)
  ■  *表示任意资源/操作/实例。(user:*)实例访问使用通配符(user:edit:*)
流程如下【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表示授权失败。

授权需要继承 AuthorizingRealm 类,并实现其 doGetAuthorizationInfo 方法。AuthorizingRealm 类继承自 AuthenticatingRealm, 但没有实现 AuthenticatingRealm 中的 doGetAuthenticationInfo, 所以认证和授权只需要继承 AuthorizingRealm 就可以了. 同时实现他的两个抽象方法.

七、ModularRealmAuthorizer 进行多 Realm 匹配流程


【1】首先检查相应的 Realm 是否实现了实现了 Authorizer;
【2】如果实现了 Authorizer,那么接着调用其相应的 isPermitted*/hasRole* 接口进行匹配;
【3】如果有一个 Realm 匹配那么将返回 true,否则返回 false。

//授权会被 shiro 回调的方法
@Override
protected AuthorizationInfo doGetAuthorizationInfo(
		PrincipalCollection principals) {
	//1. 从 PrincipalCollection 中来获取登录用户的信息
	Object principal = principals.getPrimaryPrincipal();
	
	//2. 利用登录的用户的信息来伪造当前用户的角色或权限(可能需要查询数据库)
	Set roles = new HashSet<>();
	roles.add("user");
	if("admin".equals(principal)){
		roles.add("admin");
	}
	
	//3. 创建 SimpleAuthorizationInfo, 并设置其 reles 属性.
	SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
	
	//4. 返回 SimpleAuthorizationInfo 对象. 
	return info;
}

八、Shiro 提供了 JSTL 标签用于在 JSP 页面进行权限控制,如通过登录用户显示相应的按钮


1guest 标签:用户没有登录时显示相应信息,即游客访问信息。欢迎游客访问
2user 标签:用户已经认证/记住我登录之后显示相应信息。欢迎xx登录
3authenticated 标签:用户已经身份认证通过,即shiro.login登录成功,不是记住我登录的。用户已认证
4notAuthenticated 标签:用户未进行身份认证,即没有调用Subject.login登录,包括记住我登录的也输入未认证登录。
5pincipal标签:显示用户身份信息,默认调用Subject.getPrincipal()获取,即Primary Principal。
【6】hasRole标签:如果当前Subject有角色,将显示body体内容:用户[]用户角色admin

【7】hasAnyRole标签:如果Subject有任意一个角色(或的关系)将显示body中的内容。用户XX角色
【8】lacksRole标签:如果当前Subject没有角色将显示body中的内容。没有xx角色
【9】hasPermission:如果当前Subject有权限将显示body中的内容。拥有权限user:create

【10】lacksPermission:如果当前Subject没有权限将显示body中的内容没有权限org:create

九、权限注解


@RequiresAuthentication:表示当前Subject已经通过login进行了身份验证;即 Subject. isAuthenticated() 返回 true;
@RequiresUser:表示当前 Subject 已经身份验证或者通过记住我登录的;
@RequiresGuest:表示当前Subject没有身份验证或通过记住我登录过,即是游客身份;
@RequiresRoles(value={“admin”, “user”}, logical=Logical.AND):表示当前 Subject 需要角色 admin 和user;
@RequiresPermissions (value={“user:a”, “user:b”},logical= Logical.OR):表示当前 Subject 需要权限 user:a 或user:b;

十、自定义拦截器


通过自定义拦截器可以扩展功能,例如:动态url-角色/权限访问控制的实现、根据 Subject 身份信息获取用户信息绑定到 Request(即设置通用数据)、验证码验证、在线用户信息的保存等。

我们一般在获取用户权限的时候,都是从数据库中获取的。所以在配置文件中会通过工厂方式创建出需要的对象。根据代码跟踪,我们发现获取到用户权限的时候会调用 filterChainDefinitionMap 方法,得到我们在配置中配置的所有权限,已LinkedHashMap 的方式收集。所以我们需要重新配置 filterChainDefinitionMap。如下:

 
        
        
        
        
        
        
 
    
    
 
    
 

创建相应的实体类,创建一个方法用来返回我们需要的 LinkedHashMap 实体。在方法中通过获取数据库,拿到对应用户的角色/权限。

package com.yintong.shiro.factory;

import java.util.LinkedHashMap;

public class FilterChainDefinitionMapBuilder {

	public LinkedHashMap buildFilterChainDefinitionMap(){
		LinkedHashMap map = new LinkedHashMap<>();
		
		map.put("/login.jsp", "anon");
		map.put("/shiro/login", "anon");
		map.put("/shiro/logout", "logout");
		map.put("/user.jsp", "authc,roles[user]");
		map.put("/admin.jsp", "authc,roles[admin]");
		map.put("/list.jsp", "user");
		
		map.put("/**", "authc");
		
		return map;
	}
	
}

十一、会话管理


Shiro 提供了完整的企业级会话管理功能,不依赖于底层容器(如web容器tomcat),不管 JavaSE 还是 JavaEE 环境都可以使用,提供了会话管理、会话事件监听、会话存储/持久化、容器无关的集群、失效/过期支持、对Web 的透明支持、SSO 单点登录的支持等特性。
【1】Subject.getSession():即可获取会话;
【2】session.getId():获取当前会话的唯一标识;
【3】session.getHost():获取当前Subject的主机地址;
【4】session.getTimeout() & session.setTimeout(毫秒):获取/设置当前Session的过期时间;
【5】session.getStartTimestamp() & session.getLastAccessTime():获取会话的启动时间及最后访问时间;如果是 JavaSE 应用需要自己定期调用 session.touch() 去更新最后访问时间;如果是 Web 应用,每次进入 ShiroFilter 都会自动调用 session.touch() 来更新最后访问时间。
session.touch() & session.stop():更新会话最后访问时间及销毁会话;当Subject.logout()时会自动调用 stop 方法来销毁会话。如果在web中,调用 HttpSession. invalidate()也会自动调用Shiro Session.stop 方法进行销毁Shiro 的会话;
session.setAttribute(key, val) &session.getAttribute(key) &session.removeAttribute(key):设置/获取/删除会话属性;在整个会话范围内都可以对这些属性进行操作

主要特点:web 层的 session 通常在 service 层不能够获取,但是 shiro 的 session 能够在 service 层获取到。

SessionDao 的种类
【1】AbstractSessionDAO 提供了 SessionDAO 的基础实现,如生成会话ID等;
【2】CachingSessionDAO 提供了对开发者透明的会话缓存的功能,需要设置相应的 CacheManager;
【3】MemorySessionDAO 直接在内存中进行会话维护;
【4】EnterpriseCacheSessionDAO 提供了缓存功能的会话维护,默认情况下使用 MapCache 实现,内部使用ConcurrentHashMap 保存缓存的会话;
会话验证
【1】Shiro 提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话;
【2】出于性能考虑,一般情况下都是获取会话时来验证会话是否过期并停止会话的;但是如在 web 环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,Shiro 提供了会话验证调度器SessionValidationScheduler;
【3】Shiro 也提供了使用Quartz会话验证调度器:QuartzSessionValidationScheduler;

十二、缓存:CacheManagerAware 接口


Shiro 内部相应的组件(DefaultSecurityManager)会自动检测相应的对象(如Realm)是否实现了CacheManagerAware 并自动注入相应的CacheManager。Shiro 提供了 CachingRealm,其实现了CacheManagerAware 接口,提供了缓存的一些基础实现;AuthenticatingRealm 及 AuthorizingRealm 也分别提供了对AuthenticationInfo 和 AuthorizationInfo 信息的缓存。
Session缓存
【1】如 SecurityManager 实现了 SessionSecurityManager,其会判断 SessionManager 是否实现了CacheManagerAware 接口,如果实现了会把CacheManager 设置给它;
【2】SessionManager 也会判断相应的 SessionDAO(如继承自CachingSessionDAO)是否实现了CacheManagerAware,如果实现了会把 CacheManager设置给它;
【3】设置了缓存的 SessionManager,查询时会先查缓存,如果找不到才查数据库

十三、RememberMe


Shiro 提供了记住我(RememberMe)的功能,基本流程如下:
【1】首先在登录页面选中 RememberMe 然后登录成功;如果是浏览器登录,一般会把 RememberMe 的Cookie 写到客户端并保存下来;
【2】关闭浏览器再重新打开;会发现浏览器还是记住你的;
【3】访问一般的网页服务器端还是知道你是谁,且能正常访问;
【4】但是比如我们访问淘宝时,如果要查看我的订单或进行支付时,此时还是需要再进行身份认证的,以确保当前用户还是你。
认证和记住我的区别:
【1】subject.isAuthenticated() 表示用户进行了身份验证登录的,即使有 Subject.login 进行了登录;
【2】subject.isRemembered():表示用户是通过记住我登录的,此时可能并不是真正的你(如你的朋友使用你的电脑,或者你的cookie 被窃取)在访问的;
【3】两者二选一,即 subject.isAuthenticated()==true,则subject.isRemembered()==false;反之一样;
建议:
【1】访问一般网页:如个人在主页之类的,我们使用user 拦截器即可,user 拦截器只要用户登录(isRemembered() || isAuthenticated())过即可访问成功;
【2】访问特殊网页:如我的订单,提交订单页面,我们使用authc 拦截器即可,authc 拦截器会判断用户是否是通过Subject.login(isAuthenticated()==true)登录的,如果是才放行,否则会跳转到登录页面叫你重新登录。
实现:如果要自己做RememeberMe,需要在登录之前这样创建Token:UsernamePasswordToken(用户名,密码,是否记住我),且调用UsernamePasswordToken 的:token.setRememberMe(true); 方法。
默认拦截器 user 表示记住我:表示 list.jsp 可以通过记住我登录后访问。

map.put("/list.jsp", "user");

设置记住我的有效时间:


        


----关注公众号,获取更多内容----

你可能感兴趣的:(登录模块)