Apache Shiro第1部分–基础

Apache Shiro (最初称为JSecurity)是Java安全框架。 它被接受并于2010年成为Apache顶级项目。它的目标是功能强大且易于使用。

该项目正在积极开发中,用户和开发人员的邮件列表均处于活动状态。 最重要的区域记录在其网页上。 但是,它在文档上有很多空白。 仅从文档中就不可能学会使用大多数Shiro功能。 幸运的是,该代码的注释很好,在我尝试过的地方也很容易阅读。

Shiro的主要功能是:

  • 验证,
  • 授权,
  • 密码学
  • 会话管理。

在本文中,我们尝试演示Shiro的各种功能。 我们从简单的不安全Web应用程序开始,然后向其中添加安全功能。 所有代码在Github上的SimpleShiroSecuredApplication项目中可用。
不安全的应用程序

不安全的应用程序代码位于unsecured_application分支中。 应用程序代表一个虚构公司的内部系统。 该公司有四个部门:

  • 管理员,
  • 修理工
  • 科学家们,
  • 销售。

每个部门都有自己的页面。 每个页面都包含用户用来完成其工作的按钮。 当用户按下按钮时,工作就完成了。 例如,任何维修人员都可以转到维修人员页面,然后按“维修冰箱”按钮。 该按钮将修复冰箱并显示成功消息。

每个用户都有自己的帐户页面。 帐户页面包含用户的私人数据。 由于不安全的应用程序尚无用户,因此帐户页面不执行任何操作。 此外,还有一个页面包含所有应用程序功能。 任何人都可以做的所有事情都可以在此页面上完成。

任何人都可以做任何工作并查看所有页面 。示例应用程序在测试类RunWaitTest中运行。 以这种方式使用单元测试不是最佳实践,但现在并不重要。 如果您运行该类,则该应用程序将位于http:// localhost:9180 / simpleshirosecuredapplication / url中。

添加身份验证

首先,我们必须验证用户的身份。 最简单,最标准的身份验证是通过用户名和密码来完成的。 用户填写其用户名和密码,然后系统验证提供的值是否与某个用户帐户匹配。

对于最简单的应用程序,将用户名和密码存储在纯文本文件中就足够了。 在更现实的情况下,用户名和密码存储在持久性存储中,或者通过其他系统(例如ldap或活动目录)进行验证。 Shiro支持所有提到的身份验证方法。 如果开箱即用的身份验证功能不足,则可以使用自己的验证实现来扩展框架。

在本章中,我们将基于用户名和密码的身份验证添加到应用程序中。 用户名和密码存储在静态纯文本Shiro ini文件中。

新要求:可以登录和注销用户。 该应用程序仅可用于登录用户。 成功登录会将用户重定向到他自己的帐户页面。 所有登录用户仍然可以访问所有应用程序功能和页面。

所需步骤:

  • 添加Apache Shiro,
  • 创建登录页面,
  • 配置用户和密码,
  • 创建注销页面。

添加Apache Shiro

Shiro通过Servlet过滤器集成到Web应用程序中。 过滤器在servlet之前拦截请求和响应,并执行所有必要的任务(例如,标识当前登录的用户,将登录的用户附加到当前线程等)。 默认的Shiro筛选器提供基本的安全功能,例如:

  • 强制用户登录,
  • 执行ssl,
  • 检查页面访问权限。

如果您想了解有关默认Shiro过滤器的更多信息,那么最好的起点是DefaultFilter枚举。 它列出了所有默认可用的Shiro过滤器。 如果这些不足以满足您的需求,则可以创建自定义的。

我们将使用高度可配置的IniShiroFilter 。 它从ini文件读取Shiro配置并初始化安全框架。 它不执行任何安全检查。 权限检查,用户登录,协议检查等都委托给默认或自定义过滤器。 IniShiroFilter仅初始化它们。

文档javadoc中都介绍了Ini配置。 Ini文件配置包含四个部分:

  • [main]部分包含Shiro初始化。 过滤器和自定义对象在此处配置。
  • [用户]部分定义用户,密码和角色。
  • [角色]部分将角色与权限相关联。
  • [urls]部分指定对应用程序页面(url)的访问权限。 通过将默认或自定义过滤器绑定到url来完成此操作。

将Apache Shiro依赖项添加到pom.xml:

<properties>
    <shiro.version>1.1.0</shiro.version>
</properties>
<dependencies>
    <dependency>
        <groupid>org.apache.shiro</groupid>
        <artifactid>shiro-core</artifactid>
        <version>${shiro.version}</version>
    </dependency>
    <dependency>
        <groupid>org.apache.shiro</groupid>
        <artifactid>shiro-web</artifactid>
        <version>${shiro.version}</version>
    </dependency>
</dependencies>

创建Shiro.ini文件并将其放在类路径中。 将web.xml配置为在每个请求之前调用IniShiroFilter:

<filter>
    <filter-name>ShiroFilter</filter-name>
    <filter-class>org.apache.shiro.web.servlet.IniShiroFilter</filter-class>
    <init-param>
        <param-name>configPath</param-name>
        <param-value>classpath:Shiro.ini</param-value>
    </init-param>
</filter>

<filter-mapping>
    <filter-name>ShiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

创建登录页面

登录页面是带有提交按钮,用户名和密码字段的简单html页面。 登录功能默认为Shiro authc过滤器处理。 Authc过滤器仅允许登录用户访问url。 如果用户未登录,过滤器会将其重定向到登录页面。

登录页面上的表单名称必须为“ loginform”,其提交方法必须为“ post”。 创建login.jsp页面:

<form name="loginform" action="" method="post">
<table align="left" border="0" cellspacing="0" cellpadding="3">
    <tr>
        <td>Username:</td>
        <td><input type="text" name="user" maxlength="30"></td>
    </tr>
    <tr>
        <td>Password:</td>
        <td><input type="password" name="pass" maxlength="30"></td>
    </tr>
    <tr>
        <td colspan="2" align="left"><input type="checkbox" name="remember"><font size="2">Remember Me</font></td>
    </tr>
    <tr>
        <td colspan="2" align="right"><input type="submit" name="submit" value="Login"></td>
    </tr>
</table> 
</form>

为所有应用程序页面启用authc过滤器:

[main] 
# specify login page
authc.loginUrl = /simpleshirosecuredapplication/account/login.jsp

# name of request parameter with username; if not present filter assumes 'username'
authc.usernameParam = user
# name of request parameter with password; if not present filter assumes 'password'
authc.passwordParam = pass
# does the user wish to be remembered?; if not present filter assumes 'rememberMe'
authc.rememberMeParam = remember

# redirect after successful login
authc.successUrl  = /simpleshirosecuredapplication/account/personalaccountpage.jsp

[urls]
# enable authc filter for all application pages
/simpleshirosecuredapplication/**=authc

更新: Shiro自动执行上下文相关的路径匹配。 由于SimpleShiroSecuredApplication没有设置上下文路径,因此Shiro.ini中的完整路径是必需的。 但是,如果应用程序上下文路径为/ simpleshirosecuredapplication,则路径可能是相对的:例如,简单的/ ** = authc或/account/personalaccountpage.jsp。

由于通过网络发送未加密的用户名和密码是不安全的,因此我们应强制使用ssl登录。 SSL过滤器正是这样做的。 它具有一个可选参数:ssl端口号。 如果省略port参数,它将使用默认的ssl端口443。

在Shiro中配置ssl之前,我们必须在Web服务器上启用它。 具体操作取决于Web服务器。 我们展示了如何在Jetty中启用它。 首先,使用自签名证书创建密钥库:

keytool -genkey -keyalg RSA -alias jetty -keystore keystore -storepass secret -validity 360 -keysize 2048

回答所有问题,最后按Enter键,以便密钥库密码和密钥密码相同。

其次,将密钥库添加到项目中,并将Jetty配置为使用ssl。 Java代码在AbstractContainerTest类中可用。

现在,可以在Shiro.ini中配置ssl过滤器:

[urls]
# force ssl for login page
/simpleshirosecuredapplication/account/login.jsp=ssl[8443],authc
# enable authc filter for the all application pages; as Shiro reads urls from up to down, must be last
/simpleshirosecuredapplication/**=authc

配置用户和密码

现在,SimpleShiroSecuredApplication仅适用于登录用户。 现在,我们需要添加一些用户,以便人们可以登录。配置在Shiro.ini文件的[用户]部分中完成。 部分条目的格式为:

username = password, roleName1, roleName2, ..., roleNameN

以下部分创建七个用户,所有用户都具有相同的密码“ heslo”:

[users]
administrator=heslo,Administrator
friendlyrepairmen=heslo,repairmen
unfriendlyrepairmen=heslo,repairmen
mathematician=heslo,scientist
physicien=heslo,scientist
productsales=heslo,sales
servicessales=heslo,sales

现在可以登录到应用程序。 但是,如果用户犯了错误,则不会显示任何合理的错误消息。 此外,密码存储在纯文本文件中。

错误处理

如果用户在登录时出错,则Shiro会将其重定向回登录页面。 该页面看起来与以前完全相同,这可能会使用户感到困惑。

新要求:每次尝试登录失败后,显示错误消息。

每当发生身份验证错误时,都会引发异常。 默认情况下,表单身份验证过滤器会捕获异常并将其类名称存储在request参数中。 由于我们希望自定义发送到页面的数据,因此我们必须扩展FormAuthenticationFilter并重写setFailureAttribute方法:

@Override
protected void setFailureAttribute(ServletRequest request, AuthenticationException ae) {
  String message = ae.getMessage();
  request.setAttribute(getFailureKeyAttribute(), message);
}

VerboseFormAuthenticationFilter替换表单授权过滤器,并将其配置为使用'simpleShiroApplicationLoginFailure'请求属性来保存错误信息:

[main]
# replace form authentication filter with verbose filter 
authc = org.meri.simpleshirosecuredapplication.servlet.VerboseFormAuthenticationFilter
# request parameter with login error information; if not present filter assumes 'shiroLoginFailure'
authc.failureKeyAttribute=simpleShiroApplicationLoginFailure

在login.jsp页面中显示错误:

<% 
  String errorDescription = (String) request.getAttribute("simpleShiroApplicationLoginFailure");
  if (errorDescription!=null) {
%>
Login attempt was unsuccessful: <%=errorDescription%>
<% 
  }
%>

当心:真实的应用程序不应显示太多的登录错误信息。 消息“尝试登录失败。” 没有更多信息通常就足够了。

散列密码

当前应用程序版本的所有密码均以纯文本格式存储。 最好只存储和比较密码哈希。

负责身份验证的对象称为领域 。 默认情况下,Shiro使用带可插入密码匹配器的IniRealm来比较密码。 我们将用ini的SHA-256哈希替换ini中的密码,并将IniRealm配置为使用SHA-256哈希匹配器。

生成密码的SHA-256哈希:

import org.apache.shiro.crypto.hash.Sha256Hash;

public static void main(String[] args) {
    Sha256Hash sha256Hash = new Sha256Hash("heslo");
    System.out.println(sha256Hash.toHex());
}

将Shiro配置为比较密码哈希而不是密码本身:

[main] 
# define matcher matching hashes instead of passwords
sha256Matcher = org.apache.shiro.authc.credential.HashedCredentialsMatcher
sha256Matcher.hashAlgorithmName=SHA-256

# enable matcher in iniRealm (object responsible for authentication)
iniRealm.credentialsMatcher = $sha256Matcher

用密码哈希替换用户密码:

[users]
administrator=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, Administrator
friendlyrepairmen=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, repairmen
unfriendlyrepairmen=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, repairmen
mathematician=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005, scientist
physicien=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005,  scientist
productsales=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005,        sales
servicessales=56b1db8133d9eb398aabd376f07bf8ab5fc584ea0b8bd6a1770200cb613ca005,  sales

注意:无法在ini配置中指定salt。

创建注销页面

具有登录功能的任何应用程序也应具有注销功能。 使用Shiro注销当前用户很容易,请使用以下命令:

//acquire currently logged user and log him out
SecurityUtils.getSubject().logout();

注销页面如下所示:

<%@ page import="org.apache.shiro.SecurityUtils" %>
<% SecurityUtils.getSubject().logout();%>
You have succesfully logged out.

添加授权

我们通过向应用程序添加授权来结束第一部分。 我们从限制用户访问页面开始。 任何用户都不能看到其他部门的页面。 由于用户仍然能够使用“所有应用程序功能”页面或在浏览器中编辑URL来执行任何操作,因此这仅为项目提供了部分安全性。 我们将其称为页面级授权。

然后,我们限制了用户自己执行操作的能力。 即使用户打开“所有应用程序功能”页面或在浏览器中编辑URL,也将只允许他执行其部门特定的功能。 我们将其称为功能级别授权。

新要求:用户无法查看不属于他的部门的页面。 用户只能执行其部门职能。 以前的规则唯一的例外是管理员,管理员可以执行管理和修复功能。

页面授权

页面级授权是通过角色过滤器完成的。 过滤器的参数部分可以包含任意数量的角色。 登录的用户只有拥有所有提供的角色,才能访问页面。

像往常一样,在Shiro.ini文件中配置角色过滤器:

[urls]
# force ssl for login page
/simpleshirosecuredapplication/account/login.jsp=ssl[8443],authc

# only users with some roles are allowed to use role-specific pages 
/simpleshirosecuredapplication/repairmen/**=authc, roles[repairman]
/simpleshirosecuredapplication/sales/**=authc, roles[sales]
/simpleshirosecuredapplication/scientists/**=authc, roles[scientist]
/simpleshirosecuredapplication/adminarea/**=authc, roles[Administrator]

# enable authc filter for the all application pages; as Shiro reads urls from up to down, must be last
/simpleshirosecuredapplication/**=authc

测试安全性是否有效:以任何销售用户身份登录,单击“主页”,然后单击“维修人员页面”链接。 您会看到一个难看的错误。

我们完成页面授权并将错误替换为重定向到错误页面。 默认的Shiro过滤器具有属性validateUrl。 如果发生未经授权的访问,过滤器会将用户重定向到指定的url。

[main]
# redirect to an error page if user does not have access rights
roles.unauthorizedUrl = /simpleshirosecuredapplication/account/accessdenied.jsp

accessdenied.jsp:

<body>
Sorry, you do not have access rights to that area.
</body>

功能授权

现在所有部门页面均已安全。 但是,任何用户仍可以在“所有应用程序功能”页面上执行任何功能。 此外,任何登录的用户都可以编辑url,从而可以执行任何操作。 例如,如果您以销售人员身份登录并将https:// localhost:8443 / simpleshirosecuredapplication / masterservlet?action = MANAGE_REPAIRMEN放入url中,则该应用程序也将执行管理修复功能(然后将引发空指针异常,但存在安全漏洞)已经完成了)。

我们为每个功能分配唯一的权限 。 它们分为几组:

  • 所有权限都在“功能”组中,
  • 所有管理权限都在“管理”组中,
  • 所有修复权限都在“修复”组中,
  • 所有销售权限都在“销售”组中,
  • 所有科学许可都在“科学”组中。

Shiro支持表示为字符串的多级权限。 级别用符号“:”分隔。 例如,“功能:管理:修理工”具有三个级别:“功能”,“管理”和“修理工”。 多级权限允许轻松进行权限分组。 例如,科学组属于功能组,并且包含三个权限:

  • 职能:科学:研究,
  • 功能:科学:写作文章,
  • 职能:科学:准备谈话。

操作将在完成记录的用户权限之前对其进行验证:

public String doIt() {
    String neededPermission = getNeededPermission();
    // acquire logged user and check permission
    if (SecurityUtils.getSubject().isPermitted(neededPermission))
        return "Function " + getName() + " run succesfully.";

    throw new UnauthorizedException("Logged user does not have " + neededPermission + " permission");
}

注意:实现相同目标的另一种方法是通过注释。

PerformFunctionAndGoBackServlet servlet捕获授权异常并将其转换为错误消息:

private String performAction(String actionName) {
    try {
        Actions action = findAction(actionName);
        String result = action == null ? null : action.doIt();
        log.debug("Performed function with result: " + result);
        return result;
    } catch (ShiroException ex) {
        log.debug("Function failed with " + ex.getMessage() + " message.");
        return "Error: " + ex.getMessage();
    }
}

最后,我们需要在Shiro.ini文件中配置角色的权限。 Shiro支持通配符以获得多级权限。 因此,我们不必分别指定每个部门的许可:

[roles]
# members of departments should be able to perform all departmental functions
sales=functions:sale:*
scientist=functions:science:*
repairman=functions:repair:*

# administrators are able to do all management functions and repair functions
Administrator=functions:manage:*,functions:repair:*

您现在可以在“所有应用程序功能”页面上尝试功能。 如果登录的用户没有所需的权限,则会在页面顶部显示错误消息。 此外,如果您以销售人员身份登录并尝试入侵https:// localhost:8443 / simpleshirosecuredapplication / masterservlet?action = MANAGE_REPAIRMEN,则会在控制台中看到错误消息(而不是成功消息)。

结束

最终的应用程序可以在Github上的“ static_authentication_and_authorization”分支中找到。

第二部分中,我们将创建自定义领域,并将用户,密码,角色和权限从ini文件移动到数据库。 第三部分专门介绍Apache Shiro加密软件包。

参考: Apache Shiro第1部分– JCG合作伙伴 Maria Jurcovicova的基础知识,来自This is Stuff博客。


翻译自: https://www.javacodegeeks.com/2012/05/apache-shiro-part-1-basics.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值