在 Web 项目中应用 Apache Shiro 开源权限框架.docx
《在 Web 项目中应用 Apache Shiro 开源权限框架.docx》由会员分享,可在线阅读,更多相关《在 Web 项目中应用 Apache Shiro 开源权限框架.docx(26页珍藏版)》请在冰豆网上搜索。
在Web项目中应用ApacheShiro开源权限框架
在Web项目中应用ApacheShiro开源权限框架
ApacheShiro是功能强大并且容易集成的开源权限框架,它能够完成认证、授权、加密、会话管理等功能。
认证和授权为权限控制的核心,简单来说,“认证”就是证明你是谁?
Web应用程序一般做法通过表单提交用户名及密码达到认证目的。
“授权”即是否允许已认证用户访问受保护资源。
关于Shiro的一系列特征及优点,很多文章已有列举,这里不再逐一赘述,本文重点介绍Shiro在WebApplication中如何实现验证码认证以及如何实现单点登录。
用户权限模型
在揭开Shiro面纱之前,我们需要认知用户权限模型。
本文所提到用户权限模型,指的是用来表达用户信息及用户权限信息的数据模型。
即能证明“你是谁?
”、“你能访问多少受保护资源?
”。
为实现一个较为灵活的用户权限数据模型,通常把用户信息单独用一个实体表示,用户权限信息用两个实体表示。
1.用户信息用LoginAccount表示,最简单的用户信息可能只包含用户名loginName及密码password两个属性。
实际应用中可能会包含用户是否被禁用,用户信息是否过期等信息。
2.用户权限信息用Role与Permission表示,Role与Permission之间构成多对多关系。
Permission可以理解为对一个资源的操作,Role可以简单理解为Permission的集合。
3.用户信息与Role之间构成多对多关系。
表示同一个用户可以拥有多个Role,一个Role可以被多个用户所拥有。
图1.用户权限模型
认证与授权
Shiro认证与授权处理过程
∙被Shiro保护的资源,才会经过认证与授权过程。
使用Shiro对URL进行保护可以参见“与Spring集成”章节。
∙用户访问受Shiro保护的URL;例如http:
//host/security/action.do。
∙Shiro首先检查用户是否已经通过认证,如果未通过认证检查,则跳转到登录页面,否则进行授权检查。
认证过程需要通过Realm来获取用户及密码信息,通常情况我们实现JDBCRealm,此时用户认证所需要的信息从数据库获取。
如果使用了缓存,除第一次外用户信息从缓存获取。
∙认证通过后接受Shiro授权检查,授权检查同样需要通过Realm获取用户权限信息。
Shiro需要的用户权限信息包括Role或Permission,可以是其中任何一种或同时两者,具体取决于受保护资源的配置。
如果用户权限信息未包含Shiro需要的Role或Permission,授权不通过。
只有授权通过,才可以访问受保护URL对应的资源,否则跳转到“未经授权页面”。
ShiroRealm
在Shiro认证与授权处理过程中,提及到Realm。
Realm可以理解为读取用户信息、角色及权限的DAO。
由于大多Web应用程序使用了关系数据库,因此实现JDBCRealm是常用的做法,后面会提到CASRealm,另一个Realm的实现。
清单1.实现自己的JDBCRealm
01
public class MyShiroRealm extendsAuthorizingRealm{
02
03
//用于获取用户信息及用户权限信息的业务接口
04
privateBusinessManagerbusinessManager;
05
06
//获取授权信息
07
protectedAuthorizationInfodoGetAuthorizationInfo(
08
PrincipalCollectionprincipals){
09
Stringusername=(String)principals.fromRealm(
10
getName()).iterator().next();
11
12
if(username!
= null){
13
//查询用户授权信息
14
Collectionpers=businessManager.queryPermissions(username);
15
if(pers!
= null&&!
pers.isEmpty()){
16
SimpleAuthorizationInfoinfo= newSimpleAuthorizationInfo();
17
for(Stringeach:
pers)
18
info.addStringPermissions(each);
19
20
returninfo;
21
}
22
}
23
24
returnnull;
25
}
26
27
//获取认证信息
28
protectedAuthenticationInfodoGetAuthenticationInfo(
29
AuthenticationTokenauthcToken) throwsAuthenticationException{
30
UsernamePasswordTokentoken=(UsernamePasswordToken)authcToken;
31
//通过表单接收的用户名
32
Stringusername=token.getUsername();
33
34
if(username!
= null&&!
"".equals(username)){
35
LoginAccountaccount=businessManager.get(username);
36
37
if(account!
= null){
38
return newSimpleAuthenticationInfo(
39
account.getLoginName(),account.getPassword(),getName());
40
}
41
}
42
43
returnnull;
44
}
45
}
代码说明:
1.businessManager表示从数据库获取用户信息及用户权限信息的业务类,实际情况中可能因用户权限模型设计不同或持久化框架选择不同,这里没给出示例代码。
2.doGetAuthenticationInfo方法,取用户信息。
对照用户权限模型来说,就是取LoginAccount实体。
最终我们需要为Shiro提供AuthenticationInfo对象。
3.doGetAuthorizationInfo方法,获取用户权限信息。
代码给出了获取用户Permission的示例,获取用户Role的代码类似。
为Shiro提供的用户权限信息以AuthorizationInfo对象形式返回。
为何对Shiro情有独钟
或许有人要问,我一直在使用Spring,应用程序的安全组件早已选择了SpringSecurity,为什么还需要Shiro?
当然,不可否认SpringSecurity也是一款优秀的安全控制组件。
本文的初衷不是让您必须选择Shiro以及必须放弃SpringSecurity,秉承客观的态度,下面对两者略微比较:
1.简单性,Shiro在使用上较SpringSecurity更简单,更容易理解。
2.灵活性,Shiro可运行在Web、EJB、IoC、GoogleAppEngine等任何应用环境,却不依赖这些环境。
而SpringSecurity只能与Spring一起集成使用。
3.可插拔,Shiro干净的API和设计模式使它可以方便地与许多的其它框架和应用进行集成。
Shiro可以与诸如Spring、Grails、Wicket、Tapestry、Mule、ApacheCamel、Vaadin这类第三方框架无缝集成。
SpringSecurity在这方面就显得有些捉衿见肘。
与Spring集成
在JavaWebApplication开发中,Spring得到了广泛使用;与EJB相比较,可以说Spring是主流。
Shiro自身提供了与Spring的良好支持,在应用程序中集成Spring十分容易。
有了前面提到的用户权限数据模型,并且实现了自己的Realm,我们就可以开始集成Shiro为应用程序服务了。
Shiro的安装
Shiro的安装非常简单,在Shiro官网下载shiro-all-1.2.0.jar、shiro-cas-1.2.0.jar(单点登录需要),及SLF4J官网下载Shiro依赖的日志组件slf4j-api-1.6.1.jar。
Spring相关的JAR包这里不作列举。
这些JAR包需要放置到Web工程/WEB-INF/lib/目录。
至此,剩下的就是配置了。
配置过滤器
首先,配置过滤器让请求资源经过Shiro的过滤处理,这与其它过滤器的使用类似。
web.xml
01
02
shiroFilter
03
04
org.springframework.web.filter.DelegatingFilterProxy
05
06
07
08
shiroFilter
09
/*
10
Spring配置
接下来仅仅配置一系列由Spring容器管理的Bean,集成大功告成。
各个Bean的功能见代码说明。
01
02
03
04
05
06
07
map>
08
09
map>
10
11
12
13
/=anon
14
/login.do*=authc
15
/logout.do*=anon
16
17
#权限配置示例
18
/security/account/view.do=authc,perms[SECURITY_ACCOUNT_VIEW]
19
20
/**=authc
21
22
23
24
25
26
class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
27
28
29
30
31
--businessManager用来实现用户名密码的查询-->
32
33
34
35
36
37
class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
38
39
40
class="org.apache.shiro.cache.ehcache.EhCacheManager">
41
42
43
44
45
class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter"/>
代码说明:
1.shiroFilter中loginUrl为登录页面地址,successUrl为登录成功页面地址(如果首先访问受保护URL登录成功,则跳转到实际访问页面),unauthorizedUrl认证未通过访问的页面(前面提到的“未经授权页面”)。
2.shiroFilter中filters属性,formAuthenticationFilter配置为基于表单认证的过滤器。
3.shiroFilter中filterChainDefinitions属性,anon表示匿名访问(不需要认证与授权),authc表示需要认证,perms[SECURITY_ACCOUNT_VIEW]表示用户需要提供值为“SECURITY_ACCOUNT_VIEW”Permission信息。
由此可见,连接地址配置为authc或perms[XXX]表示为受保护资源。
4.securityManager中realm属性,配置为我们自己实现的Realm。
关于Realm,参见前面“ShiroRealm”章节。
5.myShiroRealm为我们自己需要实现的Realm类,为了减小数据库压力,添加了缓存机制。
6.shiroCacheManager是Shiro对缓存框架EhCache的配置。
实现验证码认证
验证码是有效防止暴力破解的一种手段,常用做法是在服务端产生一串随机字符串与当前用户会话关联(我们通常说的放入Session),然后向终端用户展现一张经过“扰乱”的图片,只有当用户输入的内容与服务端产生的内容相同时才允许进行下一步操作。
产生验证码
作为演示,我们选择开源的验证码组件kaptcha。
这样,我们只需要简单配置一个Servlet,页面通过IMG标签就可以展现图形验证码。
01
--captchaservlet-->
02
03
kaptcha
04
05
com.google.code.kaptcha.servlet.KaptchaServlet
06
07
08
09
kaptcha
10
/images/kaptcha.jpg
11
扩展UsernamePasswordToken
Shiro表单认证,页面提交的用户名密码等信息,用UsernamePasswordToken类来接收,很容易想到,要接收页面验证码的输入,我们需要扩展此类:
01
public class CaptchaUsernamePasswordToken extendsUsernamePasswordToken{
02
03
privateStringcaptcha;
04
05
//省略getter和setter方法
06
07
publicCaptchaUsernamePasswordToken(Stringusername, char[]password,
08
booleanrememberMe,Stringhost,Stringcaptcha){
09
super(username,password,rememberMe,host);
10
this.captcha=captcha;
11
}
12
}
扩展FormAuthenticationFilter
接下来我们扩展FormAuthenticationFilter类,首先覆盖createToken方法,以便获取CaptchaUsernamePasswordToken实例;然后增加验证码校验方法doCaptchaValidate;最后覆盖Shiro的认证方法executeLogin,在原表单认证逻辑处理之前进行验证码校验。
01
public class CaptchaFormAuthenticationFilter extendsFormAuthenticationFilter{
02
03
public static finalStringDEFAULT_CAPTCHA_PARAM= "captcha";
04
05
privateStringcaptchaParam=DEFAULT_CAPTCHA_PARAM;
06
07
publicStringgetCaptchaParam(){
08
returncaptchaParam;
09
}
10
11
public voidsetCaptchaParam(StringcaptchaParam){
12
this.captchaParam=captchaParam;
13
}
14
15
protectedStringgetCaptcha(ServletRequestrequest){
16
returnWebUtils.getCleanParam(request,getCaptchaParam());
17
}
18
19
//创建Token
20
protectedCaptchaUsernamePasswordTokencreateToken(
21
ServletRequestrequest,ServletResponseresponse){
22
23
Stringusername=getUsername(request);
24
Stringpassword=getPassword(request);
25
Stringcaptcha=getCaptcha(request);
26
booleanrememberMe=isRememberMe(request);
27
Stringhost=getHost(request);
28
2