Apache Shiro是Java的一个安全框架,使用的人也越来越多,但很多人只是停留在了会使用的的阶段,可能配置文件也只是网上demo的复制修改,却不知道真正的含义是什么。
今天我用shiro的拦截器为入口,简单的过一点shiro的源码 ,有错误的地方希望大家能给指出。
首先我们看一下配置文件中拦截器的拦截规则,这里有两个拦截器,一个是formAuthenticationFilter 这个是shiro 包自带的一个拦截器,roleOrFilter是自定义的一个拦截器。对于/api/user/login 这个接口不做拦截。对/api/user/check 需要过两个拦截器。
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<property name="securityManager" ref="securityManager"/>
<property name="loginUrl" value="/login.html"/>
<property name="unauthorizedUrl" value="/error.html"/>
<property name="filters">
<util:map>
<entry key="authc" value-ref="formAuthenticationFilter"/>
<entry key="roleOrFilter" value-ref="roleOrFilter"/>
</util:map>
</property>
<property name="filterChainDefinitions">
<value>
/api/user/login = anon
/api/user/check = authc, roleOrFilter["管理员"]
</value>
</property>
</bean>
当我在URL地址栏输入 http://127.0.0.1:8080/api/user/check,并点击回车的时候,拦截器就开始工作了。首先是到了authc这个拦截器。这个拦截器继承了 AuthenticatingFilter
程序在运行一定的时候会进入到onPrehandle,这个方法结果的返回会决定我们的请求是否被允许
//只要isAccessAllowed或者onAccessDenied有一个为真,就返回true,继续执行
public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);
}
先看isAccessAllowed这个方法
/**如果满足(1).当前的subject是被认证过的。(2).用户请求的不是登录页面,但是在定义该过滤器时,使用了PERMISSIVE=”permissive”参数。只要满足两个条件的一个即可允许操作**/
@Override
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
return super.isAccessAllowed(request, response, mappedValue) ||(!isLoginRequest(request, response) && isPermissive(mappedValue));
}
//其实就是判断当前的subject是不是被认证过的
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
Subject subject = getSubject(request, response);
return subject.isAuthenticated();
}
//判断当前的请求是不是登陆请求
protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {
return pathsMatch(getLoginUrl(), request);
}
//判断当前的拦截器是不是配置了PERMISSIVE=”permissive”参数,如果配置了就可以通过
protected boolean isPermissive(Object mappedValue) {
if(mappedValue != null) {
String[] values = (String[]) mappedValue;
return Arrays.binarySearch(values, PERMISSIVE) >= 0;
}
return false;
}
因为我们没有登陆,即没有对当前的subject认证过,isAccessAllowed返回false。
再看onAccessDenied
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
if (isLoginRequest(request, response)) {
if (isLoginSubmission(request, response)) {
if (log.isTraceEnabled()) {
log.trace("Login submission detected. Attempting to execute login.");
}
return executeLogin(request, response);
} else {
if (log.isTraceEnabled()) {
log.trace("Login page view.");
}
//allow them to see the login page ;)
return true;
}
} else {
if (log.isTraceEnabled()) {
log.trace("Attempting to access a path which requires authentication. Forwarding to the " +
"Authentication url [" + getLoginUrl() + "]");
}
saveRequestAndRedirectToLogin(request, response);
return false;
}
}
//判断当前的其实是不是一个HTTP的POST请求
protected boolean isLoginSubmission(ServletRequest request, ServletResponse response) {
return (request instanceof HttpServletRequest) && WebUtils.toHttp(request).getMethod().equalsIgnoreCase(POST_METHOD);
}
//经过前面的判断是不是POST请求后,程序就为我们创建一个token,但是我们并没有传入userName和password在创建的时候就会抛出异常了
protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
AuthenticationToken token = createToken(request, response);
if (token == null) {
String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
"must be created in order to execute a login attempt.";
throw new IllegalStateException(msg);
}
try {
Subject subject = getSubject(request, response);
subject.login(token);
return onLoginSuccess(token, subject, request, response);
} catch (AuthenticationException e) {
return onLoginFailure(token, e, request, response);
}
}
/**saveRequest就是把一个request保存在session中,redirectToLogin这里就是返回到设置的登录页面,在开头自定义的过滤器中就是重载了这个函数,在实际项目中,一般都会重载这个函数,比方说返回到指定的页面*/
protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
saveRequest(request);
redirectToLogin(request, response);
}
就这样在第一个authc拦截器请求就会被拒绝了。就不会走我们的第二个roleOrFilter拦截器了。
那如果我在配置文件上不写上authc,而是让程序直接走我们的自定义的拦截器那会是什么过程呢。
public class RoleOrFilter extends AuthorizationFilter {
protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
Subject subject = getSubject(servletRequest, servletResponse);
String[] rolesArray = (String[]) o;
if (rolesArray == null || rolesArray.length == 0) {
return true;
}
for (int i = 0; i < rolesArray.length; i++) {
if (subject.hasRole(rolesArray[i])) {
return true;
}
}
return false;
}
}
其实在走到hasRole的时候,hasRole内部实现代码机制就会有检测当前的subject是不是被认证的操作,所以你没有登陆就直接调用一个接口,就算没有写authc直接到第二个拦截器也会被拦下。