Apache Shiro 是一个框架,可用于身份验证和受权。虽然这两个术语表明的是不一样的含义,但出于它们在应用程序安全性方面各自的角色考虑,它们有时会被交换使用。
身份验证 指的是验证用户的身份。在验证用户身份时,须要确认用户的身份的确如他们所声称的那样。在大多数应用程序中,身份验证是经过用户名和密码的组合完成的。只要用户选择了他人很难猜到的密码,那么用户名和密码的组合一般就足以确立身份。可是,还有其余的身份验证方式可用,好比指纹、证书和生成键。
一旦身份验证过程成功地创建起身份,受权 就会接管以便进行访问的限制或容许。 因此,有这样的可能性:用户虽然经过了身份验证能够登陆到一个系统,可是未通过受权,不许作任何事情。还有一种多是用户虽然具备了某种程度的受权,却并未通过身份验证。
在为应用程序规划安全性模型时,必须处理好这两个元素以确保系统具备足够的安全性。身份验证是应用程序常见的问题(特别是在只有用户和密码组合的状况下),因此让框架来处理这项工做是一个很好的作法。合理的框架可提供通过测试和维护的优点,让您能够集中精力处理业务问题,而不是解决其解决方案已经实现的问题。
Apache Shiro 提供了一个可用的安全性框架,各类客户机均可将这个框架应用于它们的应用程序。本文中的这些例子旨在介绍 Shiro 并着重展现对用户进行身份验证的基本任务。
本文只针对Jeesite中shiro的用法进行整理,不会包括shiro环境配置和搭建等内容。java
spring-context-shiro.xml是shiro的主配置文件,配置信息是重点主要是安全认证过滤器的配置。它规定哪些url须要进行哪些方面的认证和过滤web
<!-- 安全认证过滤器 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="p" value="${adminPath}/login" /> <property name="successUrl" value="${adminPath}" /> <property name="filters"> <map> <entry key="authc" value-ref="formAuthenticationFilter"/> </map> </property> <property name="filterChainDefinitions"> <value> /static/** = anon /userfiles/** = anon ${adminPath}/login = authc ${adminPath}/logout = logout ${adminPath}/** = user </value> </property> </bean>
shiroFilter是shiro的安全认证过滤器,其中,spring
securityManager:指定一个负责管理的bean,这个新的bean在接下来会定义,其中包含了认证的主要逻辑。数据库
loginUrl:没有登陆的用户请求须要登陆的页面时自动跳转到登陆页面,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。apache
successUrl:登陆成功默认跳转页面,不配置则跳转至”/”。若是登录前点击的一个须要登陆的页面,则在登陆自动跳转到那个须要登陆的页面。不跳转到此。安全
unauthorizedUrl:没有权限默认跳转的页面。session
map中的entry指定了authc权限所对应的过滤器实体框架
而属性中的filterChainDefinitions则详细规定啦不一样的url的对应权限jsp
anon:例子/admins/**=anon 没有参数,表示能够匿名使用。ide
authc:例如/admins/user/**=authc表示须要认证(登陆)才能使用,没有参数
roles:例子/admins/user/=roles[admin],参数能够写多个,多个时必须加上引号,而且参数之间用逗号分割,当有多个参数时,例如admins/user/=roles["admin,guest"],每一个参数经过才算经过,至关于hasAllRoles()方法。
perms:例子/admins/user/**=perms[user:add:],参数能够写多个,多个时必须加上引号,而且参数之间用逗号分割,例如/admins/user/=perms["user:add:,user:modify:*"],当有多个参数时必须每一个参数都经过才经过,想当于isPermitedAll()方法。
rest:例子/admins/user/=rest[user],根据请求的方法,至关于/admins/user/=perms[user:method] ,其中method为post,get,delete等。
port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString
是你访问的url里的?后面的参数。
authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证
ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https
user:例如/admins/user/**=user没有参数表示必须存在用户,当登入操做时不作检查
注:anon,authcBasic,auchc,user是认证过滤器,perms,roles,ssl,rest,port是受权过滤器
<!-- 定义 Shiro 主要业务对象 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <!-- <property name="sessionManager" ref="sessionManager" /> --> <property name="realm" ref="systemAuthorizingRealm" /> <property name="cacheManager" ref="shiroCacheManager" /> </bean>
这部分代码定义了securitymanager的主要属性的实体,systemAuthorizingRealm和shiroCacheManager都是本身实现的,分别用于进行验证管理和cache管理。从配置文件能看出,配置文件指定了做为安全逻辑的几个结构,除了这两部分,还包括formAuthenticationFilter。其中realm和filter在com.thinkgem.jeesite.modules.sys.security包里实现。
从可见都逻辑上讲,FormAuthenticationFilter类是用户验证时所接触的第一个类。
当用户登陆任意界面时,shiro会对当前状态进行检查。若是发现须要登陆,则会自动跳转到配置文件里loginUrl属性所指定的url中。
而这一url又被指定为authc权限,即须要验证。接着,authc的filter被指定为formAuthenticationFilter,所以login页面所提交的信息被改filter截获进行处理。
其中的核心逻辑是createToken函数:
protected AuthenticationToken createToken(S2ervletRequest request, ServletResponse response) { String username = getUsername(request); String password = getPassword(request); if (password==null){ password = ""; } boolean rememberMe = isRememberMe(request); String host = getHost(request); String captcha = getCaptcha(request); return new U
UsernamePasswordToken是security包里的第四个类,它继承自shiro的同名类,用于shiro处理中的参数传递。createtoken函数接受到login网页所接受的表单,生成一个token传给下一个类处理。
函数中的rememberme以及captcha分别表示的是记住用户功能和验证码功能,这部分也是shiro自身携带,咱们并不须要修改。filter是经过aop的方式结合到系统里的,所以并无具体的接口实现。
shiro的最终处理都将交给Real进行处理。由于在Shiro中,最终是经过Realm来获取应用程序中的用户、角色及权限信息的。一般状况下,在Realm中会直接从咱们的数据源中获取Shiro须要的验证信息。能够说,Realm是专用于安全框架的DAO.
Realm中有个参数是systemService,这个即是spring的具体业务逻辑,其中也包含了具体的DAO,正是在这个部分,shiro与spring的接口具体的结合了起来。
realm当中有两个函数特别重要,分别是用户认证函数和受权函数。
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authcToken; if (LoginController.isValidateCodeLogin(token.getUsername(), false, false)){ // 判断验证码 Session session = SecurityUtils.getSubject().getSession(); String code = (String)session.getAttribute(ValidateCodeServlet.VALIDATE_CODE); if (token.getCaptcha() == null || !token.getCaptcha().toUpperCase().equals(code)){ throw new CaptchaException("验证码错误."); } } User user = getSystemService().getUserByLoginName(token.getUsername()); if (user != null) { byte[] salt = Encodes.decodeHex(user.getPassword().substring(0,16)); return new SimpleAuthenticationInfo(new Principal(user), user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName()); } else { return null; } }
以上即是用户认证函数,中间关于验证码检测的部分咱们暂且不表,其最核心的语句是
函数的参数AuthenticationToken即是filter所截获并生成的token实例,其内容是login表单里的内容,一般是用户名和密码。在前一句话里,getSystemService取到了systemService实例,该实例中又含有配置好的userDAO,可以直接与数据库交互。所以将用户id传入,取出数据库里所存有的user实例,接着在SimpleAuthenticationInfo生成过程当中分别以user实例的用户名密码,以及token里的用户名密码作为参数,验证密码是否相同,以达到验证的目的。
doGetAuthorizationInfo函数是受权的函数,其具体的权限是在实现类中以annotation的形式指派的,它负责验证用户是否有权限访问。详细的细节容我以后添加。
LoginController是本该实现登陆认证的部分。因为shiro的引入和AOP的使用,jeesite中的LoginController只处理验证以后的部分。
若是经过验证,系统中存在user实例,则返回对应的主页。不然从新定位于login页面。
经常使用的annotation主要以下:
@RequiresAuthentication
要求当前Subject 已经在当前的session 中被验证经过才能被注解的类/实例/方法访问或调用。
验证用户是否登陆,等同于方法subject.isAuthenticated() 结果为true时。
@RequiresUser
须要当前的Subject 是一个应用程序用户才能被注解的类/实例/方法访问或调用。要么是经过验证被确认,或者在以前session 中的'RememberMe'服务被记住。
验证用户是否被记忆,user有两种含义:一种是成功登陆的(subject.isAuthenticated() 结果为true);另一种是被记忆的(subject.isRemembered()结果为true)。
@RequiresGuest
要求当前的Subject 是一个“guest”,也就是他们必须是在以前的session中没有被验证或记住才能被注解的类/实例/方法访问或调用。
验证是不是一个guest的请求,与@RequiresUser彻底相反。
换言之,RequiresUser == !RequiresGuest。此时subject.getPrincipal() 结果为null.
@RequiresRoles
要求当前的Subject 拥有全部指定的角色。若是他们没有,则该方法将不会被执行,并且AuthorizationException 异常将会被抛出。例如:@RequiresRoles("administrator")
或者@RequiresRoles("aRoleName");
void someMethod();
若是subject中有aRoleName角色才能够访问方法someMethod。若是没有这个权限则会抛出异常AuthorizationException。
@RequiresPermissions 要求当前的Subject 被容许一个或多个权限,以便执行注解的方法,好比: @RequiresPermissions("account:create") 或者@RequiresPermissions({"file:read", "write:aFile.txt"} ) void someMethod();