shiro认证和授权(二)

背景:

       我们在使用 shiro 进行认证和授权的时候,通常用到的 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();

       从上面的代码我们可以看出,认证的代码很简单,如下所示:

UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
currentUser.login(token);
currentUser.logout();

       授权的代码也很简单,如下所示: 

currentUser.hasRole("schwartz")
currentUser.isPermitted("winnebago:drive:eagle5")

一、认证流程

shiro认证和授权(二)

1.1、流程解析 

       在上面的图片中,根据序号,其实我们大概可以猜出 shiro 的认证流程

       1、首先调用 Subject.login(token) 进行登录,其中 token 是封装了用户的相关信息,它会自动委托给 SecurityManager

       2、SecurityManager 负责真正的身份验证逻辑,它会委托给 Authenticator 进行身份验证。

       3、Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现。

       4、Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Relam 身份验证,默认 ModularRelamAuthenticator 会调用 AuthenticationStrategy 进行多 Realm 身份验证。

       5、Authenticator 会把相应的 token 传给 Relam ,从 Relam 获取身份验证信息,如果没有返回/抛出异常表示身份验证失败,此处可以配置多个 Relam ,将按照相应的顺序及策略进行访问。

1.2、代码追踪

       我们从 login 方法开始 debug 一下,用简单的方式追踪下 shiro 源码的认证逻辑:

subject.login(token);
|
Subject subject = this.securityManager.login(this, token);
|
AuthenticationInfo info = this.authenticate(token);
|
this.authenticator.authenticate(token);
|
AuthenticationInfo info = this.doAuthenticate(token);
|
Collection<Realm> realms = this.getRealms();
doSingleRealmAuthentication(realm, token);
|
AuthenticationInfo info = realm.getAuthenticationInfo(token);
|
AuthenticationInfo info = realm.doGetAuthenticationInfo(token);

       一条线下来,从 login 到委托给 authenticator,到最后调用 realm doGetAuthenticationInfo() 方法。所以,从源码上来看,如果要实现 shiro 的认证逻辑,至少要准备一个 Realm 组件和初始化 SecurityManager 组件。

1.3、常见异常

       1、DisabledAccountException:禁用的账号

       2、LockedAccountException:锁定的账号

       3、UnknownAccountException:错误的账号

       4、ExcessiveAttemptsException:登录失败次数过多

       5、IncorrectCredentialsException:错误的凭证

       6、ExpiredCredentialsException:过期的凭证

二、授权流程

shiro认证和授权(二)

2.1、流程解析

       从上图中,我们可以知道授权的流程如下:

       1、调用 Subject isPermitted() 方法或者 hasRole() 方法。

       2、委托给 SecurityManager 处理。

       3、SecurityManager 接着会委托给 Authorizer

       4、Authorizer 会判断 Realm 的角色/权限是否和传入的匹配。

       5、若匹配 isPermitted()hasRole() 方法会返回 true,否则返回 false 表示授权失败。

2.2、代码追踪

       我们从 hasRole 方法开始 debug 一下,用简单的方式追踪下 shiro 源码的授权逻辑:

subject.hasRole("admin")
|
this.securityManager.hasRole(this.getPrincipals(), roleIdentifier)
|
this.authorizer.hasRole(principals, roleIdentifier)
|
AuthorizationInfo info = this.getAuthorizationInfo(principal);
return info.getRoles().contains(roleIdentifier)
|
info = this.doGetAuthorizationInfo(principals);(realm)

       所以,shiro 判断用户是否有权限首先会从 realm 中获取用户所拥有的权限角色信息,然后再匹配当前的角色或权限是否包含,从而判定用户是否有权限!说到权限,很多人自然会

想起权限系统,涉及到几个关键对象:

       1、Subject:主体

       2、Resource:资源

       3、Role:角色

       4、Permission:权限

2.3、授权判断方式

       shiro 支持三种方式的授权,编码实现注解实现以及 JSP Taglig实现,下面分别介绍下

2.3.1、方式一:编码实现

       通过写 if/else 授权代码块完成。

if(subject.hasRole("admin")) {
    // 有权限
}else {
    // 无权限
}

2.3.2、方式二:注解实现

       通过在执行的 Java 方法上放置相应的注解完成,没有权限将抛出相应的异常。

@RequiresRoles("admin")
public void hello() {
	// 有权限
}

2.3.3、方式三:通过 JSP 标签实现

       在 JSP/GSP 页面通过相应的标签完成。这种方式需要在 jsp 页面引入 shiro 的标签,如下所示:

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags"%>
<shiro:hasRole name="admin">
    <!--有权限-->
</shiro:hasRole>

2.4、权限的规则设定Permissions

规则:

       资源标识符冒号(:)操作,冒号(:)对象实例 ID 即对哪个资源的哪个实例可以进行什么操作,其默认支持通配符权限字符串,冒号(:)表示资源/操作/实例的分割。

       逗号(,)表示操作的分割。

       星号(*)表示任意资源/操作/实例。

多层次管理:

       例如:user:query,user:edit

       冒号是一个特殊字符,它用来分隔权限字符串的下一部件:第一部分是权限被操作的领域(打印机),第二部分是被执行的操作。

       多个值:每个部件能够保护多个值。因此,除了授予用户 user:queryuser:edit 权限外,也可以简单地授予它们一个:user:query,edit

       还可以用 * 号代替所有的值,如:user.* ,也可以写:*:query,表示某个用户在所有的领域都有 query 的权限。

2.5、Shiro 的 Permissions

实例级访问控制,这种情况通常会使用三个部件:操作被付诸实施的实例。如:user:edit:manager

       也可以使用通配符来定义,如:user:edit:*user:*:*user:*:manager

       部分省略通配符:缺少的部件意味着用户可以访问所有与之匹配的值,比如:user:edit 等价于 user:edit:*user 等价于 user:*:*

       注意:通配符只能从字符串的结尾处省略部件,也就是说 user:edit 并不等价于 user:*:edit

2.6、JSP的标签

       Shiro 提供了 JSTL 标签用于在 JSP 界面进行权限控制,如根据登录用户显示相应的页面按钮。下面简单的介绍几个标签。

       guest 标签:用户没有身份验证时显示相应信息,即游客访问信息。

<shiro:guest>
	欢迎游客访问,<a href="login.jsp">登录</a>
</shiro:guest>

       user 标签:用户已经经过认证/记住我登录后显示相应的信息。

<shiro:user>
	欢迎[<shiro:principal/>]登录,<a href="logout">退出</a>
</shiro:user>

       authenticated 标签:用户已经身份验证通过,即 Subject.login 登录成功,不是记住我登录的。

<shiro:authenticated>
	用户[<shiro:principal/>]身份已验证通过
</shiro:authenticated>

       notAuthenticated 标签:用户未进行身份验证,即没有调用 Subject.login 进行登录,包括记住我自动登录的也属于未进行身份验证。

<shiro:notAuthenticated>
	未身份验证(包括记住我)
</shiro:notAuthenticated>

       principal 标签:显示用户身份信息,默认调用 Subject.getPrincipal() 获取,即 Primary Principal

<shiro:principal property="username"/>

       hasRole 标签:如果当前 Subject 有角色将显示 body 体内容。

<shiro:hasRole name="admin">
	用户[<shiro:principal/>]拥有角色admin
</shiro:hasRole>

       hasAnyRoles 标签:如果当前 Subject 有任意一个角色(或者关系)将显示在 body 体内容。

<shiro:hasAnyRoles name="admin,user">
	用户[<shiro:principal/>]拥有角色admin或user</br>
</shiro:hasAnyRoles>

       lacksRole 标签:如果当前 Subject 没有角色将显示 body 体内容。

<shiro:lacksRole name="admin">
    用户[<shiro:principal/>] 没有角色admin </br>
</shiro:lacksRole>

       hasPermissions 标签:如果当前 Subject 有权限将显示 body 体内容。

<shiro:hasPermissions name="user:create">
    用户[<shiro:principal/>]拥有权限user:create </br>
</shiro:hasPermissions>

       lacksPermissions 标签:如果当前 Subject 没有权限将显示 body 体内容。

<shiro:lacksPermissions name="org:create">
    用户[<shiro:principal/>]没有权限org:create </br>
</shiro:lacksPermissions>

 

上一篇:P1425 小鱼的游泳时间


下一篇:观察者模式 (Observer Pattern)