该项目正在积极开发中,用户和开发人员的邮件列表均处于活动状态。 最重要的区域记录在其网页上。 但是,它在文档上有很多空白。 仅从文档中就不可能学会使用大多数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