Shiro安全框架之与web集成(2)

为了深入理解Shiro与web项目集成的工作原理,我们搭建一个普通的web项目

1. 导入相关依赖

pom.xml: shiro-core和shiro-web

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<java.version>1.8</java.version>
<shiro.version>1.2.2</shiro.version>
</properties>
...
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<!--因为是合web项目集成,因此需要导入shiro-web这个jar包-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
...

2.在web.xml中配置shiro的过滤器shiroFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<web-app...>
<!--初始化securityManager对象所需要的环境配置-->
<!--主要是为了加载ini文件,如果将shiro.ini放在WEB-INF下或者classpath根目录下,这个也不用配置,EnvironmentLoaderListener会自动读取并且加载-->
<!--<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>-->
<!--
从Shiro1.2开始引入了Environment/WebEnvironment的概念,即由它们的实现提供相应的SecurityManager及其相应的依赖。
ShiroFilter会自动找到Environment然后获取相应的依赖。
底层:返回反射创建shiroEnvironmentClass对象,调用其init方法.
shiroEnvironmentClass中的init方法创建SecurityManager实例并绑定到当前运行环境
-->
<listener>
<listener-class>org.apache.shiro.web.env.EnvironmentLoaderListener</listener-class>
</listener>


<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>

</web-app>

具体可以参考:官方文档

这里简单说下EnvironmentLoaderListenerShiroFilter的作用:

  1. 在容器启动时创建 WebEnvironment 对象,并由该对象来读取 Shiro 配置文件,创建WebSecurityManager 与 FilterChainResolver 对象,它们都在后面将要出现的 ShiroFilter 中起到了重要作用。
  2. 从 web.xml 中同样可以得知,ShiroFilter 是整个 Shiro 框架的门面,因为它拦截了所有的请求,后面是需要 Authentication(认证)还是需要 Authorization(授权)都由它说了算。

3. 创建shiro.ini文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
[main]
#默认是/login.jsp(登录认证的url,默认是/login.jsp)
authc.loginUrl=/login
#用户无需要的角色时跳转的页面
roles.unauthorizedUrl=/nopermission.jsp
#用户无需要的权限时跳转的页面
perms.unauthorizedUrl=/nopermission.jsp
#登出之后重定向的页面
logout.redirectUrl=/login

[users]
; admin用户拥有admin这个角色
admin=666,admin
;zhangsan拥有deptMgr这个角色
zhangsan=666,deptMgr

[roles]
;admin这个角色拥有employee和department的所有操作权限
admin=*:*
;admin=employee:*,department:*
deptMgr=department:view

[urls]
#静态资源可以匿名访问
/static/**=anon
#访问员工列表需要身份认证及需要拥有admin角色
/employee=authc,roles[admin]
#访问部门列表需要身份认证及需要拥有department:view的权限
/department=authc,perms["department:view"]
#当请求loginOut,会被logout捕获并清除session
/loginOut=logout
#所有的请求都需要身份认证(会跳转到authc.loginUrl指定的url)
/**=authc

上面配置中的anon,authc,logout..等等都是shiro中的默认过滤器,官方文档:默认过滤器,下面列举了一些常用的默认过滤器:

1
2
3
4
5
6
7
8
9
10
11
过滤器简称		对应的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
ssl org.apache.shiro.web.filter.authz.SslFilter

说明:

  • anon:匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例“/static/**=anon”
  • authc:表示需要认证(登录)才能使用;示例“/**=authc”.主要属性:usernameParam:表单提交的用户名参数名( username); passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe); loginUrl:登录页面地址(/login.jsp);successUrl:登录成功后的默认重定向地址; failureKeyAttribute:登录失败后错误信息存储key(shiroLoginFailure);

  • authcBasic:Basic HTTP身份验证拦截器,主要属性: applicationName:弹出登录框显示的信息(application);

  • roles:角色授权拦截器,验证用户是否拥有资源角色;示例“/admin/**=roles[admin]”
  • perms:权限授权拦截器,验证用户是否拥有资源权限;示例“/user/create=perms[“user:create”]”
  • user:用户拦截器,用户已经身份验证/记住我登录的都可;示例“/index=user”
  • logout:退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/);示例“/logout=logout”
  • port:端口拦截器,主要属性:port(80):可以通过的端口;示例“/test= port[80]”,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样
  • rest:rest风格拦截器,自动根据请求方法构建权限字符串(GET=read,POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串; 示例“/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete”权限字符串进行权限匹配(所有都得匹配,isPermittedAll);
  • ssl:SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口(443);其他和port拦截器一样;

注:

anon,authcBasic,auchc,user是认证过滤器,

perms,roles,ssl,rest,port是授权过滤器

4.拦截器的执行原理

从上图可以看出,排号越前的默认拦截器优先级越高

4.1 authc登录拦截器工作原理

authc拦截器有2个作用:

  1. 校验是否已经登录

    请求进来时,拦截并判断当前用户是否登录了,如果已经登录了放行, 如果没有登录,跳转到authc.loginUrl属性配置的路径,注意:默认是/login.jsp

  2. 执行登录认证

    请求进来时,如果请求的路径为authc.loginUrl属性配置的路径(没配置,默认是/login.jsp)时,如果当前用户没有登录,authc这个拦截器会尝试获取请求中的账号跟密码值,然后比对ini配置文件或者realm中的用户列表,如果比对正确,直接执行登录操作,反之,抛异常,跳转到authc.loginUrl指定的路径

    注意:请求中账号与密码必须固定为username 跟password, 如果需要改动必须额外指定,authc.usernameParam=xxx authc.passwordParam=xxxx

    我们在shiro.ini文件中已经配置了user,shiro的拦截器会自动进行校验,不需要再像传统方式一样在这里获取用户名和密码,然后比对,然后存入session….

4.2 authc登录成功之后处理逻辑:

4.3 authc登录失败后的异常处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@WebServlet(name = "LoginServlet", urlPatterns = "/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

// 如果登录失败,从request中获取认证异常信息,exceptionClassName就是shiro的异常类名
String exceptionClassName = (String) req.getAttribute("shiroLoginFailure");

if (UnknownAccountException.class.getSimpleName().equals(exceptionClassName)) {
req.setAttribute("errorMsg", "账号异常!");
} else if (IncorrectCredentialsException.class.getSimpleName().equals(exceptionClassName)) {
req.setAttribute("errorMsg", "密码异常!");
} else {
req.setAttribute("errorMsg", "其他异常!");
}

//不需要再像传统方式一样在这里获取用户名和密码,然后比对,然后存入session....

req.getRequestDispatcher("/WEB-INF/views/login.jsp").forward(req, resp);
}
}

5.shiro的jsp标签

1
2
3
4
5
6
7
8
9
10
11
12
标签名称			                  标签条件(均是显示标签内容)
<shiro:authenticated> 登录之后
<shiro:notAuthenticated> 不在登录状态时
<shiro:guest> 用户在没有RememberMe时
<shiro:user> 用户在RememberMe时
<shiro:hasAnyRoles name="abc,123" > 在有abc或者123角色时
<shiro:hasRole name="abc"> 拥有角色abc
<shiro:lacksRole name="abc"> 没有角色abc
<shiro:hasPermission name="abc"> 拥有权限资源abc
<shiro:lacksPermission name="abc"> 没有abc权限资源
<shiro:principal> 显示用户身份名称
<shiro:principal property="username"/> 显示用户身份中的属性值

当具有对department资源的add权限的时候,才显示新增标签

1
2
3
<shiro:hasPermission name="department:add">
<a href="/department?cmd=input">新增</a>
</shiro:hasPermission>

上面就是shiro和web矿建的简单集成…

欢迎访问我的博客网站