Shiro登录中遇到了问题
记录二次开发中遇到的问题, 如果系统学习Shiro, 推荐跟我学Shrio.
问题
- 项目是要将验证从本地改为LDAP验证, 但是因为jeecms的验证和授权中, 用户和角色以及权限的信息都来自本地, 大量的改造不适合.
- 域名的拦截, 因为IP被重置
HTTP 302
HTTP 302
Found
重定向状态码表明请求的资源被暂时的移动到了由Location
头部指定的 URL 上。就是请求的资源被重定向到了新的地址。
要查看Filter是否对关键词路径进行了拦截。
Shiro
项目中用的jeecms, 安全验证用的shiro,架构是Spring+SpringMVC,查看了一下web.xml,有关于shiro的filter,用的DelegatingFilterProxy类做代理,在SpringContext中配置Shiro的bean。
DelegatingFilterProxy
是对于servlet filter的代理,通过spring容器管理filter的生命周期,可以通过Spring容器注入需要的bean,以及读取需要的配置文件。
【web.xml】
<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
【shiroContext.xml】
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> </bean>
拦截器链
- 先执行Shiro自己的Filter链
- 执行Servlet的Filter链
自定义拦截器
- 可以根据自己的需求扩展拦截器,继承要扩展的拦截器
- 对相应的方法进行扩展
- 在配置文件中修改对应的Filter类名
// 代码参考<跟我学shiro>
public class MyAccessControlFilter extends AccessControlFilter { protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { System.out.println("access allowed"); return true; } protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { System.out.println("访问拒绝也不自己处理,继续拦截器链的执行"); return true; } }
[filters]
myFilter4=com.github.zhangkaitao.shiro.chapter8.web.filter.MyAccessControlFilter
[urls]
/** 代码参考<跟我学shiro>
执行过程
- ShiroFilter提供Shiro的入口
- AdviceFilter提供AOP的风格,分为preHandler,postHandle,afterCompletion三个方法针对AOP的前后增强(预处理和进行后处理)
- PathMatchingFilter匹配ANT风格的请求路径,解析拦截器参数
- onPreHandle方法将路径绑定参数配置传给mappedValue,然后进行一些验证
- AccessControlFilter访问控制功能,如是否允许访问,拒绝后如何处理等
- isAccessAllowed表示是否允许访问
- onAccessDenied表示当拒绝访问时是否处理了
- 如果扩展访问控制可以继承AccessControlFilter,如果添加通用数据可以继承PathMatchingFilter
DelegatingSubject
shiro通过FormAuthenticationFilter来进行表单验证,如果在验证前要进行其他比如验证码的验证,可以自定义一个继承的子类.扩展对应的方法.
这里是对访问控制的扩展,继承关系为:FormAuthenticationFilter --> AuthenticatingFilter --> AuthenticationFilter --> AccessControlFilter.
可以在扩展的拦截器中重写executeLogin方法,在里面扩展对访问的控制
// 源码
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);
}
}
这里的执行是获取当前的subject --> 根据请求创建token -->执行subject.login()方法 --> 返回成功或者失败的处理
login()方法执行中,会进行一系列的处理,首先是交给DelegatingSubject,后面的没有详细研究,最后调用realm的doGetAuthenticationInfo方法获取身份验证的相关信息,通过SimpleAuthenticationInf返回AuthenticationInfo.
SimpleAuthenticationInfo
用来返回AuthenticationInfo信息,而AuthenticationInfo有两个作用
- 如果Realm是AuthenticatingRealm子类,则提供给AuthenticatingRealm内部使用的CredentialsMatcher进行凭据验证
- 提供给SecurityManager来创建Subject(提供身份信息)
credentialsMatcher
protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
CredentialsMatcher cm = getCredentialsMatcher();
if (cm != null) {
if (!cm.doCredentialsMatch(token, info)) {
//not successful - throw an exception to indicate this:
String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
throw new IncorrectCredentialsException(msg);
}
} else {
throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
"credentials during authentication. If you do not wish for credentials to be examined, you " +
"can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
}
}
CredentialsMatcher实现了token和info的凭证验证.
授权问题
关于授权, 一开始进入了一个误区, 认为在通过验证之后会走授权的部分, 当页面出现授权问题的时候, 发现打过的断点没有进去, 仔细看了下开涛大神的讲解, 是当要访问对应的资源时, 才会验证当前用户的授权情况.
如果想对资源设置权限, 可以应用Spring的AOP注解, 在对应的AnnotationController控制器(你的Controller)上加@RequiresRoles("admin"), 如果抛出异常, 可以用Spring的ExceptionHandler拦截处理.
判断是否已通过验证
// 获取到当前subject Subject subject = SecurityUtils.getSubject(); // 判断是否已通过验证或者已经记住 if (subject.isAuthenticated()|| subject.isRemembered()) { ... }
问题1解决方式
不改造本地验证和授权方式, 让ldap用户落地, 但是不包括密码, 每次根据user和password去判断验证的方式和是否新增用户.
在继承了AuthorizingRealm的CmsAuthorizingRealm类中修改doGetAuthenticationInfo方法即可.
问题2解决方式
原因
因为登陆后Cookie里面设置了域名,如果当前访问的域名和这个Cookie的域名不一致,将无法登陆成功。
但是域名和IP本地做了映射,并且在系统中做了配置,应该不是因为域名和IP而导致的创建了不同的session,更何况session的唯一标示是Jsessionid。
但是cookie是根据域名匹配的,如果域名被修改,那么就会导致后台拿不到JSESSIONID,也就会显示当前未登录。
而现在系统的部署,域名是虚拟生成的,任何访问的客户端,本地域名都不会被看到,推断是因为这个原因导致。