Apache Shiro的配置主要分为四部分:
Filter Name | Class |
anon | org.apache.shiro.web.filter.authc.AnonymousFilter |
authc | org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic | org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
perms | org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port | org.apache.shiro.web.filter.authz.PortFilter |
rest | org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles | org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl | org.apache.shiro.web.filter.authz.SslFilter |
user | org.apache.shiro.web.filter.authc.UserFilter |
Apache Shiro 提供了一个可用的安全性框架,各种客户机都可将这个框架应用于它们的应用程序。本文中的这些例子旨在介绍 Shiro 并着重展示对用户进行身份验证的基本任务。
本文只针对Jeesite中shiro的用法进行整理,不会包括shiro环境配置和搭建等内容。
spring-context-shiro.xml是shiro的主配置文件,配置信息是重点主要是安全认证过滤器的配置。它规定哪些url需要进行哪些方面的认证和过滤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<
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的安全认证过滤器,其中,
而属性中的filterChainDefinitions则详细规定啦不同的url的对应权限
1
2
3
4
5
6
7
|
class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
|
这部分代码定义了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函数:
1
2
3
4
5
6
7
8
9
10
11
|
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
UsernamePasswordToken(username, password.toCharArray(), rememberMe, host, captcha);
}
|
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当中有两个函数特别重要,分别是用户认证函数和授权函数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
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;
}
}
|
以上便是用户认证函数,中间关于验证码检测的部分我们暂且不表,其最核心的语句是
1
2
3
|
User user = getSystemService().getUserByLoginName(token.getUsername());
以及另一句语句
return new SimpleAuthenticationInfo(new Principal(user), user.getPassword().substring(16), ByteSource.Util.bytes(salt), getName());
|
函数的参数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();