项目准备
-
新建MAVEN Web项目,在
pom.xml
中引入依赖如下<dependencies> <!-- shiro核心的依赖 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-core</artifactId> <version>1.2.2</version> </dependency> <!-- shiro对web支持的依赖 --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-web</artifactId> <version>1.2.2</version> </dependency> <!-- serlvet-api --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.1.3</version> </dependency> </dependencies>
-
创建
mainServlet
,loginServlet
,logoutServlet
如下@WebServlet(name = "mainServlet", urlPatterns = "/main") public class MainAction extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.getRequestDispatcher("/WEB-INF/views/main.jsp").forward(req, resp); } }
@WebServlet(name = "loginServlet", urlPatterns = "/login") public class LoginServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String username = req.getParameter("username"); String password = req.getParameter("password"); if ("zhangsan".equals(username) && "666".equals(password)) { req.setAttribute("userName", username); //登陆成功 req.getRequestDispatcher("/main").forward(req, resp); } else { req.setAttribute("errorMsg", "账号密码有误"); req.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(req, resp); } } }
@WebServlet(name = "logoutServlet", urlPatterns = "/logout") public class logoutServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.getSession().invalidate(); resp.sendRedirect("/login.jsp"); } }
集成Shiro
在web.xml
中配置Shiro过滤器与监听器
在/webapp/WEB-INF/web.xml
中配置Shiro的过滤器shiroFilter
并让其拦截所有请求.同时配置监听器EnvironmentLoaderListener
监听ServletContext
的销毁和创建并随之销毁和创建Shiro环境(主要是创建SecurityManager
).
<!-- 配置Shiro过滤器,拦截所有请求 -->
<filter>
<filter-name>shiroFilter</filter-name>
<filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<!-- 拦截所有的请求 -->
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 监听器EnvironmentLoaderListener监听ServletContext的创建于销毁 -->
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>
<!-- 初始化Shiro环境所需参数 -->
<context-param>
<param-name>shiroEnvironmentClass</param-name>
<param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value>
</context-param>
<context-param>
<param-name>shiroConfigLocations</param-name>
<param-value>classpath:shiro.ini</param-value>
</context-param>
观察源码发现,EnvironmentLoaderListener
实现了ServletContextListener
接口,用来监听ServletContext
的创建与销毁.同时它继承自EnvironmentLoader
,在ServletContext
创建时调用其父类的initEnvironment()
方法创建Shiro环境,EnvironmentLoader
有两个重要属性如下:
/**
* Servlet Context config param for specifying the {@link WebEnvironment} implementation class to use:
* {@code shiroEnvironmentClass}
*/
public static final String ENVIRONMENT_CLASS_PARAM = "shiroEnvironmentClass";
/**
* Servlet Context config param for the resource path to use for configuring the {@link WebEnvironment} instance:
* {@code shiroConfigLocations}
*/
public static final String CONFIG_LOCATIONS_PARAM = "shiroConfigLocations";
他们都在web.xml
中通过context-param
被赋值.
在shiro.ini
中配置拦截规则
在resources/shiro.ini
中配置Shiro如下:
[main]
# 登陆成功后跳转到的页面,默认是/login.jsp
authc.loginUrl=/login
# 用户无需要的角色时跳转的页面
roles.unauthorizedUrl=/nopermission.jsp
# 用户无需要的权限时跳转的页面
perms.unauthorizedUrl=/nopermission.jsp
# 登出之后重定向的页面
logout.redirectUrl=/login
[urls]
# 静态资源可以匿名访问
/static/**=anon
# 访问员工列表需要身份认证及需要拥有admin角色
/employee=authc,roles[admin]
# 访问部门列表需要身份认证及需要拥有department:view的权限
/department=authc,perms["department:view"]
# 当请求logout,会被logout捕获并清除session
/logout=logout
# 所有的请求都需要身份认证
/**=authc
[users]
admin=password1,admin
user=password2,deptMgr
[roles]
admin=employee:*,department:*
deptMgr=department:view
在
[urls]
中配置的路径是从上往下依次命中的.
在上边[main]
和[url]
段落中配置的均为Shiro的过滤器,Shiro中的过滤器及其顺序如下:
过滤器简称 | 对应的java类 |
---|---|
anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
user | org.apache.shiro.web.filter.authc.UserFilter |
logout | org.apache.shiro.web.filter.authc.LogoutFilter |
port | org.apache.shiro.web.filter.authz.PortFilter |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
各拦截器作用如下:
anon
: 匿名拦截器,即不需要登录即可访问,一般用于静态资源过滤. 例如/static/**=anon
authc
: 表示需要认证(登录)才能使用.例如/**=authc
.其主要属性如下:usernameParam
: 表单提交的用户名参数名,默认为username
passwordParam
: 表单提交的密码参数名,默认为password
rememberMeParam
: 表单提交的密码参数名rememberMe
loginUrl
: 登录页面地址,默认为/login.jsp
successUrl
: 登录成功后若session内未存储要跳转的url,则重定向到该地址failureKeyAttribute
: 登录失败后错误信息存储的key,默认为shiroLoginFailure
authcBasic
: Basic HTTP身份验证拦截器roles
: 角色授权拦截器,验证用户是否拥有资源角色.例如/admin/**=roles[admin]
perms
: 权限授权拦截器,验证用户是否拥有资源权限.例如/user/create=perms["user:create"]
user
: 用户拦截器,用户已经身份验证/记住我都可登录.例如/index=user
logout
: 退出拦截器,主要属性,其主要属性如下:redirectUrl
: 退出成功后重定向的地址.例如logout.redirectUrl=/login
port
: 端口拦截器,主要属性如下:port
: 请求可以通过的端口,例如/test= port[80]
,如果用户访问的不是80端口,将自动将请求端口改为80并重定向到该80端口.
rest
: rest风格拦截器,自动根据请求方法构建权限字符串(GET=read,POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)ssl
:SSL拦截器,只有请求协议是https才能通过,否则自动跳转会https端口(443)
登陆过滤器authc
执行原理
我们看到在原有的loginServlet
中,我们规定了用户的token为["zhangsan","666"]
,但我们使用此token登陆后虽然页面能正常跳转,但是系统并没有记录下我们的登陆状态(再次访问新的url需要重复输入账号密码).
实际上,这体现了authc
拦截器的作用,因为我们在shiro.ini
中配置了/**=authc
,表示除了上面配置的url之外,所有请求都会被authc
拦截器拦截.authc
拦截器的拦截过程如下:
-
发送请求时,
authc
拦截器拦截请求,若发现当前用户已登录则放行;若当前用户未登录,则跳转到authc.loginUrl
属性所规定的路径(默认是/login.jsp
). -
跳转到
authc.loginUrl
定义的路径后,authc
会自动从请求参数中寻找username
和password
属性,进行身份验证.- 若验证成功,查询缓存中是否存在被拦截到的url,若存在则跳转到该url,不存在则跳转到
authc.successUrl
定义的路径. - 若验证失败,则进入
loginServlet
(当然,验证成功之后也会进入loginServlet
),因此我们在loginServlet
中只需处理验证失败的情况即可
@WebServlet(name = "loginServlet", urlPatterns = "/login") public class LoginServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doPost(req, resp); } @Override // 此方法不处理登陆成功的情况,登陆成功Shiro会自动跳转到缓存的URL路径 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // Shiro为将上次登陆时发生错误的全类名存储在request对象的shiroLoginFailure中 String exceptionClassName = (String) req.getAttribute("shiroLoginFailure"); // 判断上次登陆时发生错误的类型 if (exceptionClassName != null) { if (UnknownAccountException.class.getName().equals(exceptionClassName)) { req.setAttribute("errorMsg", "账号不存在"); } else if (IncorrectCredentialsException.class.getName().equals(exceptionClassName)) { req.setAttribute("errorMsg", "账号存在但密码错误"); } else { req.setAttribute("errorMsg", "其他错误"); } } // 跳转到登录页面 req.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(req, resp); } }
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>登录</title> </head> <body> ${errorMsg} <form action="/login" method="post"> 用户名<input type="text" name="username"><br/> 密码<input type="password" name="password"><br/> <input type="submit" value="登录"> </form> </body> </html>
- 若验证成功,查询缓存中是否存在被拦截到的url,若存在则跳转到该url,不存在则跳转到
在jsp中使用Shiro标签
在jsp中可以使用Shiro标签进行授权,常见标签如下
jsp标签 | 意义 |
---|---|
<shiro:authenticated> | 用户登录之后显示 |
<shiro:notAuthenticated> | 用户未登录之后显示 |
<shiro:guest> | 用户没有RememberMe时显示 |
<shiro:user> | 用户RememberMe时显示 |
<shiro:hasAnyRoles name="role1,role2"> | 用户拥有角色role1 或role2 时显示 |
<shiro:hasRole name="role1"> | 用户拥有角色role1 时显示 |
<shiro:lacksRole name="role1"> | 用户没有角色role1 时显示 |
<shiro:hasPermission name="permission1"> | 用户拥有权限permission1 时显示 |
<shiro:lacksPermission name="permission1"> | 用户没有权限permission1 时显示 |
<shiro:principal> | 显示用户身份名称 |
<shiro:principal property="username"/> | 显示用户身份中的属性值 |
在jsp中使用Shiro标签的步骤如下:
-
引用Shiro标签库
<%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
-
使用Shiro标签
<div class="userinfo"> 当前登陆用户:<shiro:principal/> <a href="/logout">退出</a> </div>
<shiro:hasPermission name="department:edit"> <a href="/department/edit/1">编辑</a> </shiro:hasPermission> <shiro:hasPermission name="department:edit"> <a href="/department/delete/1">删除</a> </shiro:hasPermission>