2017.2.12 开涛shiro教程-第七章-与Web集成
2017.2.9 开涛shiro教程-第七章-与Web集成(一)
原博客地址:http://jinnianshilongnian.iteye.com/blog/2018398
根据下载的pdf学习。
shiro提供了与Web集成的支持,通过ShiroFilter来拦截需要安全控制的URL。ShiroFilter是安全控制的入口点,负责读取配置文件,然后判断URL是否需要登录/权限工作。ShiroFilter 类似于如 Strut2/SpringMVC 这种 web 框架的前端控制器。
1.准备环境
1.1 pom.xml
此处我们使用了 jetty-maven-plugin 和 tomcat7-maven-plugin 插件;这样可以直接使用“mvn jetty:run ” 或 “ mvn tomcat7:run ” 运行webapp了 。 然后通过URL http://localhost:8080/chapter7/访问即可。
1 <build> 2 <finalName>chapter7</finalName> 3 <plugins> 4 <plugin> 5 <groupId>org.mortbay.jetty</groupId> 6 <artifactId>jetty-maven-plugin</artifactId> 7 <version>8.1.8.v20121106</version> 8 <configuration> 9 <webAppConfig> 10 <contextPath>/${project.build.finalName}</contextPath> 11 </webAppConfig> 12 </configuration> 13 </plugin> 14 15 <plugin> 16 <groupId>org.apache.tomcat.maven</groupId> 17 <artifactId>tomcat7-maven-plugin</artifactId> 18 <version>2.2</version> 19 <configuration> 20 <path>/${project.build.finalName}</path> 21 </configuration> 23 </plugin> 24 </plugins> 25 </build>
为了与web集成,加的两个依赖:
1 <dependencies> 2 ... 3 <dependency> 4 <groupId>org.apache.shiro</groupId> 5 <artifactId>shiro-web</artifactId> 6 <version>1.2.2</version> 7 </dependency> 8 9 <dependency> 10 <groupId>javax.servlet</groupId> 11 <artifactId>javax.servlet-api</artifactId> 12 <version>3.0.1</version> 13 <scope>provided</scope> 14 </dependency> 15 ... 16 </dependencies>
pom.xml文件的总览如下:
1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 2 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> 3 <parent> 4 <artifactId>shiro-example</artifactId> 5 <groupId>com.github.zhangkaitao</groupId> 6 <version>1.0-SNAPSHOT</version> 7 </parent> 8 <modelVersion>4.0.0</modelVersion> 9 <artifactId>shiro-example-chapter7</artifactId> 10 <packaging>war</packaging> 11 <name>shiro-example-chapter7</name> 12 <url>http://maven.apache.org</url> 13 14 15 <dependencies> 16 <dependency> 17 <groupId>junit</groupId> 18 <artifactId>junit</artifactId> 19 <version>4.9</version> 20 <scope>test</scope> 21 </dependency> 22 <dependency> 23 <groupId>commons-logging</groupId> 24 <artifactId>commons-logging</artifactId> 25 <version>1.1.3</version> 26 </dependency> 27 <dependency> 28 <groupId>org.apache.shiro</groupId> 29 <artifactId>shiro-core</artifactId> 30 <version>1.2.2</version> 31 </dependency> 32 33 <dependency> 34 <groupId>org.apache.shiro</groupId> 35 <artifactId>shiro-web</artifactId> 36 <version>1.2.2</version> 37 </dependency> 38 39 40 <dependency> 41 <groupId>mysql</groupId> 42 <artifactId>mysql-connector-java</artifactId> 43 <version>5.1.25</version> 44 </dependency> 45 <dependency> 46 <groupId>com.alibaba</groupId> 47 <artifactId>druid</artifactId> 48 <version>0.2.23</version> 49 </dependency> 50 51 <dependency> 52 <groupId>javax.servlet</groupId> 53 <artifactId>javax.servlet-api</artifactId> 54 <version>3.0.1</version> 55 <scope>provided</scope> 56 </dependency> 57 58 59 </dependencies> 60 61 <build> 62 <finalName>chapter7</finalName> 63 <plugins> 64 <plugin> 65 <groupId>org.mortbay.jetty</groupId> 66 <artifactId>jetty-maven-plugin</artifactId> 67 <version>8.1.8.v20121106</version> 68 <configuration> 69 <webAppConfig> 70 <contextPath>/${project.build.finalName}</contextPath> 71 </webAppConfig> 72 </configuration> 73 </plugin> 74 75 <plugin> 76 <groupId>org.apache.tomcat.maven</groupId> 77 <artifactId>tomcat7-maven-plugin</artifactId> 78 <version>2.2</version> 79 <configuration> 80 <path>/${project.build.finalName}</path> 81 </configuration> 82 83 </plugin> 84 </plugins> 85 86 87 </build> 88 </project>
1.2 web.xml
前面说过,shiroFilter是入口点,所以要在web.xml中配置shiroFilter及相关参数。
(1)读取配置文件:
从 Shiro 1.2 开始引入了 Environment/WebEnvironment 的概念,ShiroFilter 会自动找到 Environment 然后获取相应的依赖。
1 <web-app> 2 <!--- shiro 1.2 --> 3 <listener> 4 <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class> 5 </listener> 6 <context-param> 7 <param-name>shiroEnvironmentClass</param-name> 8 <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value><!-- 默认先从/WEB-INF/shiro.ini,如果没有找classpath:shiro.ini --> 9 </context-param> 10 <context-param> 11 <param-name>shiroConfigLocations</param-name> 12 <param-value>classpath:shiro.ini</param-value> 13 </context-param> 14 ... 15 </web-app>
(2)配置shiroFilter:
1 <web-app> 2 ... 3 <filter> 4 <filter-name>shiroFilter</filter-name> 5 <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class> 6 </filter> 7 <filter-mapping> 8 <filter-name>shiroFilter</filter-name> 9 <url-pattern>/*</url-pattern> 10 </filter-mapping> 11 </web-app>
web.xml文件的总览:即配置文件的读取及拦截设置。
1 <?xml version="1.0" encoding="UTF-8"?> 2 <web-app 3 xmlns="http://java.sun.com/xml/ns/javaee" 4 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 5 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 6 version="3.0" 7 metadata-complete="false"> 8 <!--- shiro 1.2 --> 9 <listener> 10 <listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class> 11 </listener> 12 <context-param> 13 <param-name>shiroEnvironmentClass</param-name> 14 <param-value>org.apache.shiro.web.env.IniWebEnvironment</param-value><!-- 默认先从/WEB-INF/shiro.ini,如果没有找classpath:shiro.ini --> 15 </context-param> 16 <context-param> 17 <param-name>shiroConfigLocations</param-name> 18 <param-value>classpath:shiro.ini</param-value> 19 </context-param> 20 <filter> 21 <filter-name>shiroFilter</filter-name> 22 <filter-class>org.apache.shiro.web.servlet.ShiroFilter</filter-class> 23 </filter> 24 <filter-mapping> 25 <filter-name>shiroFilter</filter-name> 26 <url-pattern>/*</url-pattern> 27 </filter-mapping> 28 29 <!--<error-page>--> 30 <!--<error-code>401</error-code>--> 31 <!--<location>/WEB-INF/jsp/unauthorized.jsp</location>--> 32 <!--</error-page>--> 33 34 </web-app>
这是我的项目中提供的配置:已经与spring集成了。具体的不再说,第十二章详细说明。
1 <web-app> 2 <listener> 3 <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 4 </listener> 5 <filter> 6 <filter-name>shiroFilter</filter-name> 7 <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 8 <init-param> 9 <param-name>targetFilterLifecycle</param-name> 10 <param-value>true</param-value> 11 </init-param> 12 </filter> 13 <filter-mapping> 14 <filter-name>shiroFilter</filter-name> 15 <url-pattern>/*</url-pattern> 16 </filter-mapping> 17 </web-app>
1.3 ini文件的配置
比之前的ini文件多出来的部分是url的配置。
1 ... 2 [main] 3 #默认是/login.jsp 4 authc.loginUrl=/login 5 roles.unauthorizedUrl=/unauthorized 6 perms.unauthorizedUrl=/unauthorized 7 8 [users] 9 zhang=123,admin 10 wang=123 11 12 [roles] 13 admin=user:*,menu:* 14 15 [urls] 16 /login=anon 17 /unauthorized=anon 18 /static/**=anon 19 /authenticated=authc 20 /role=authc,roles[admin] 21 /permission=authc,perms["user:create"]
最重要的就是urls部分的参数。格式为:url=拦截器[参数],拦截器[参数]
anon表示可以匿名访问(不需要登录也可访问),authc表示身份认证通过就可以访问,roles[admin]表示有admin角色才可以访问,perms:["user:create"]表示需要有user的create权限才可以访问。
1 [urls] 2 /login=anon 3 /unauthorized=anon 4 /static/**=anon 5 /authenticated=authc 6 /role=authc,roles[admin] 7 /permission=authc,perms["user:create"]
2.身份验证
2.1 配置需要身份验证的url
访问这些地址(authc)的时候会判断用户有没有登录,即如果没有登录会跳转到登录页面,默认为/login.jsp,也可以自己自定义配置。
1 /authenticated=authc 2 /role=authc,roles[admin] 3 /permission=authc,perms["user:create"] 4 5 authc.loginUrl=/login
2.2 LoginServlet
1 import org.apache.shiro.SecurityUtils; 2 import org.apache.shiro.authc.AuthenticationException; 3 import org.apache.shiro.authc.IncorrectCredentialsException; 4 import org.apache.shiro.authc.UnknownAccountException; 5 import org.apache.shiro.authc.UsernamePasswordToken; 6 import org.apache.shiro.subject.Subject; 7 8 import javax.servlet.ServletException; 9 import javax.servlet.annotation.WebServlet; 10 import javax.servlet.http.HttpServlet; 11 import javax.servlet.http.HttpServletRequest; 12 import javax.servlet.http.HttpServletResponse; 13 import java.io.IOException; 14 15 @WebServlet(name = "loginServlet", urlPatterns = "/login") 16 public class LoginServlet extends HttpServlet { 17 18 @Override 19 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 20 req.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(req, resp); 21 } 22 23 @Override 24 protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 25 String error = null; 26 String username = req.getParameter("username"); 27 String password = req.getParameter("password"); 28 Subject subject = SecurityUtils.getSubject(); 29 UsernamePasswordToken token = new UsernamePasswordToken(username, password); 30 token.setRememberMe(true); 31 try { 32 subject.login(token); 33 } catch (UnknownAccountException e) { 34 error = "用户名/密码错误"; 35 } catch (IncorrectCredentialsException e) { 36 error = "用户名/密码错误"; 37 } catch (AuthenticationException e) { 38 //其他错误,比如锁定,如果想单独处理请单独catch处理 39 error = "其他错误:" + e.getMessage(); 40 } 41 42 if(error != null) {//出错了,返回登录页面 43 req.setAttribute("error", error); 44 req.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(req, resp); 45 } else {//登录成功 46 req.getRequestDispatcher("/WEB-INF/jsp/loginSuccess.jsp").forward(req, resp); 47 } 48 } 49 }
2.3 测试
输入http://localhost:8080/chapter7/login 访问登录页面。
输入账号密码,登录成功后可以接着访问路径:http://localhost:8080/chapter7/authenticated
3 基于Basic与基于表单的拦截器身份验证
Shiro 内置了登录(身份验证)的实现:基于表单的和基于 Basic 的验证,其通过拦截器实现。
3.1 基于Basic的拦截器身份验证
(1)basicfilterlogin.ini文件
1 [main] 2 authcBasic.applicationName=please login 3 ………省略 users 4 [urls] 5 /role=authcBasic,roles[admin]
authcBasic 是 org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter 类型的实例,其用于实现基于 Basic 的身份验证;applicationName 用于弹出的登录框显示信息使用,如图:
[urls]部分配置了/role 地址需要走 authcBasic 拦截器,即如果访问/role 时还没有通过身份验证那么将弹出如上图的对话框进行登录,登录成功即可访问。
(2)web.xml
将web.xml中shiro的配置文件地址shiroConfigLocations 改为 shiro-basicfilterlogin.ini 即可。
(3)测试
输入http://localhost:8080/chapter7/role,弹出basic验证对话框,输入账号密码,即可访问/role。
3.2 基于表单的拦截器身份验证
(1)shiro-formfilterlogin.ini
authc 是 org.apache.shiro.web.filter.authc.FormAuthenticationFilter 类型的实例,其用于实现基于表单的身份验证;
注意,successUrl指定登录成功后重定向的默认地址(默认是“/”)(如果有上一个地址会自动重定向带该地址);
1 [main] 2 authc.loginUrl=/formfilterlogin 3 authc.usernameParam=username 4 authc.passwordParam=password 5 authc.successUrl=/ 6 authc.failureKeyAttribute=shiroLoginFailure 7 [urls] 8 /role=authc,roles[admin]
(2)web.xml
将web.xml中shiro的配置文件地址shiroConfigLocations 改为 shiro-formfilterlogin .ini 即可。
(3)loginServlet
1 @WebServlet(name = "formFilterLoginServlet", urlPatterns = "/formfilterlogin") 2 public class FormFilterLoginServlet extends HttpServlet { 3 ... 4 }
(4)测试
输入http://localhost:8080/chapter7/role,页面跳转到/formfilterlogin,输入账户密码后,重定向到之前的/role。
输入http://localhost:8080/chapter7/formfilterlogin,将跳转到默认的successUrl。
4.授权(角色/权限验证)
(1)shiro.ini
unauthorizedUrl 属性,用于指定授权失败时的跳转地址。
roles 是org.apache.shiro.web.filter.authz.RolesAuthorizationFilter 类型的实例
,Perms 是 org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter 类型的实例。
如果使用多个角色,使用,分隔,比如role[admin,user],且使用的是hasAllRole验证。
/role和/permission是拦截器路径。
1 [main] 2 roles.unauthorizedUrl=/unauthorized 3 perms.unauthorizedUrl=/unauthorized 4 [urls] 5 /role=authc,roles[admin] 6 /permission=authc,perms["user:create"]
(2)web.xml
把 shiroConfigLocations 改为 shiro.ini 即可。
(3)RoleServlet/PermissionServlet
1 @WebServlet(name = "roleServlet", urlPatterns = "/role") 2 public class RoleServlet extends HttpServlet { 3 4 @Override 5 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 6 Subject subject = SecurityUtils.getSubject(); 7 subject.checkRole("admin"); 8 req.getRequestDispatcher("/WEB-INF/jsp/hasRole.jsp").forward(req, resp); 9 } 10 }
1 @WebServlet(name = "permissionServlet", urlPatterns = "/permission") 2 public class PermissionServlet extends HttpServlet { 3 4 @Override 5 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 6 Subject subject = SecurityUtils.getSubject(); 7 subject.checkPermission("user:create"); 8 req.getRequestDispatcher("/WEB-INF/jsp/hasPermission.jsp").forward(req, resp); 9 } 10 }
(4)测试
输入http://localhost:8080/chapter7/login,输入zhang/123(能成功验证)登录,再访问/role或/permission时,能跳转到对应页面。
输入http://localhost:8080/chapter7/login,输入li/123(不能成功验证)登录,再访问/role或/permission时,跳转到/unauthorized。
5 退出
(1)shiro.ini
登录使用anon(不需登录即可访问)即可。
1 [urls] 2 /logout=anon
(2)web.xml
同样web.xml中shiro的配置文件改成对应的。
(3)LogoutServlet.xml
1 @WebServlet(name = "logoutServlet", urlPatterns = "/logout") 2 public class LogoutServlet extends HttpServlet { 3 4 @Override 5 protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { 6 SecurityUtils.getSubject().logout(); 7 req.getRequestDispatcher("/WEB-INF/jsp/logoutSuccess.jsp").forward(req, resp); 8 } 9 }
(4)测试
输入http://localhost:8080/chapter7/login,使用帐号“zhang/123”进行登录,登录成功后访问/logout 即可退出。
(5)shiro提供的logout
配置如下:logout是org.apache.shiro.web.filter.authc.LogoutFilter 类型的实例。
这样配置后,当访问/logout2时,会重定向到/login页面。
1 [main] 2 logout.redirectUrl=/login 3 [urls] 4 /logout2=logout