Shiro学习02:Shiro整合JavaEE

项目准备

  1. 新建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>
    
  2. 创建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 web应用入口之EnvironmentLoaderListener类

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类
anonorg.apache.shiro.web.filter.authc.AnonymousFilter
authcorg.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
rolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFilter
permsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
userorg.apache.shiro.web.filter.authc.UserFilter
logoutorg.apache.shiro.web.filter.authc.LogoutFilter
portorg.apache.shiro.web.filter.authz.PortFilter
restorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilter

各拦截器作用如下:

  1. anon: 匿名拦截器,即不需要登录即可访问,一般用于静态资源过滤. 例如/static/**=anon
  2. authc: 表示需要认证(登录)才能使用.例如/**=authc.其主要属性如下:
    • usernameParam: 表单提交的用户名参数名,默认为username
    • passwordParam: 表单提交的密码参数名,默认为password
    • rememberMeParam: 表单提交的密码参数名rememberMe
    • loginUrl: 登录页面地址,默认为/login.jsp
    • successUrl: 登录成功后若session内未存储要跳转的url,则重定向到该地址
    • failureKeyAttribute: 登录失败后错误信息存储的key,默认为shiroLoginFailure
  3. authcBasic: Basic HTTP身份验证拦截器
  4. roles: 角色授权拦截器,验证用户是否拥有资源角色.例如/admin/**=roles[admin]
  5. perms: 权限授权拦截器,验证用户是否拥有资源权限.例如/user/create=perms["user:create"]
  6. user: 用户拦截器,用户已经身份验证/记住我都可登录.例如/index=user
  7. logout: 退出拦截器,主要属性,其主要属性如下:
    • redirectUrl: 退出成功后重定向的地址.例如logout.redirectUrl=/login
  8. port: 端口拦截器,主要属性如下:
    • port: 请求可以通过的端口,例如/test= port[80],如果用户访问的不是80端口,将自动将请求端口改为80并重定向到该80端口.
  9. rest: rest风格拦截器,自动根据请求方法构建权限字符串(GET=read,POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)
  10. 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会自动从请求参数中寻找usernamepassword属性,进行身份验证.

    • 若验证成功,查询缓存中是否存在被拦截到的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>
    

在jsp中使用Shiro标签

在jsp中可以使用Shiro标签进行授权,常见标签如下

jsp标签意义
<shiro:authenticated>用户登录之后显示
<shiro:notAuthenticated>用户未登录之后显示
<shiro:guest>用户没有RememberMe时显示
<shiro:user>用户RememberMe时显示
<shiro:hasAnyRoles name="role1,role2">用户拥有角色role1role2时显示
<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标签的步骤如下:

  1. 引用Shiro标签库

    <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
    
  2. 使用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>
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值