学Shiro完结版-3

第八章 拦截器机制——《跟我学Shiro》

8.1 拦截器介绍

Shiro使用了与Servlet一样的Filter接口进行扩展;所以如果对Filter不熟悉可以参考《Servlet3.1规范》http://www.iteye.com/blogs/subjects/Servlet-3-1了解Filter的工作原理。首先下图是Shiro拦截器的基础类图:

 

 

 

1、NameableFilter

NameableFilter给Filter起个名字,如果没有设置默认就是FilterName;还记得之前的如authc吗?当我们组装拦截器链时会根据这个名字找到相应的拦截器实例;

 

2、OncePerRequestFilter

OncePerRequestFilter用于防止多次执行Filter的;也就是说一次请求只会走一次拦截器链;另外提供enabled属性,表示是否开启该拦截器实例,默认enabled=true表示开启,如果不想让某个拦截器工作,可以设置为false即可。

 

3、ShiroFilter

ShiroFilter是整个Shiro的入口点,用于拦截需要安全控制的请求进行处理,这个之前已经用过了。

 

4、AdviceFilter

AdviceFilter提供了AOP风格的支持,类似于SpringMVC中的Interceptor:

Java代码  
  1. boolean preHandle(ServletRequest request, ServletResponse response) throws Exception  
  2. void postHandle(ServletRequest request, ServletResponse response) throws Exception  
  3. void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception;   

preHandler:类似于AOP中的前置增强;在拦截器链执行之前执行;如果返回true则继续拦截器链;否则中断后续的拦截器链的执行直接返回;进行预处理(如基于表单的身份验证、授权)

postHandle:类似于AOP中的后置返回增强;在拦截器链执行完成后执行;进行后处理(如记录执行时间之类的);

afterCompletion:类似于AOP中的后置最终增强;即不管有没有异常都会执行;可以进行清理资源(如接触Subject与线程的绑定之类的);

 

5、PathMatchingFilter

PathMatchingFilter提供了基于Ant风格的请求路径匹配功能及拦截器参数解析的功能,如“roles[admin,user]”自动根据“,”分割解析到一个路径参数配置并绑定到相应的路径:

Java代码  
  1. boolean pathsMatch(String path, ServletRequest request)  
  2. boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception   

pathsMatch:该方法用于path与请求路径进行匹配的方法;如果匹配返回true;

onPreHandle:在preHandle中,当pathsMatch匹配一个路径后,会调用opPreHandler方法并将路径绑定参数配置传给mappedValue;然后可以在这个方法中进行一些验证(如角色授权),如果验证失败可以返回false中断流程;默认返回true;也就是说子类可以只实现onPreHandle即可,无须实现preHandle。如果没有path与请求路径匹配,默认是通过的(即preHandle返回true)。

 

6、AccessControlFilter

AccessControlFilter提供了访问控制的基础功能;比如是否允许访问/当访问拒绝时如何处理等:

Java代码  
  1. abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;  
  2. boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception;  
  3. abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;   

isAccessAllowed:表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回true,否则false;

onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。

 

onPreHandle会自动调用这两个方法决定是否继续处理:

Java代码  
  1. boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {  
  2.     return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue);  
  3. }   

另外AccessControlFilter还提供了如下方法用于处理如登录成功后/重定向到上一个请求: 

Java代码  
  1. void setLoginUrl(String loginUrl) //身份验证时使用,默认/login.jsp  
  2. String getLoginUrl()  
  3. Subject getSubject(ServletRequest request, ServletResponse response) //获取Subject实例  
  4. boolean isLoginRequest(ServletRequest request, ServletResponse response)//当前请求是否是登录请求  
  5. void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException //将当前请求保存起来并重定向到登录页面  
  6. void saveRequest(ServletRequest request) //将请求保存起来,如登录成功后再重定向回该请求  
  7. void redirectToLogin(ServletRequest request, ServletResponse response) //重定向到登录页面   

比如基于表单的身份验证就需要使用这些功能。

 

到此基本的拦截器就完事了,如果我们想进行访问访问的控制就可以继承AccessControlFilter;如果我们要添加一些通用数据我们可以直接继承PathMatchingFilter。

 

8.2 拦截器链

Shiro对Servlet容器的FilterChain进行了代理,即ShiroFilter在继续Servlet容器的Filter链的执行之前,通过ProxiedFilterChain对Servlet容器的FilterChain进行了代理;即先走Shiro自己的Filter体系,然后才会委托给Servlet容器的FilterChain进行Servlet容器级别的Filter链执行;Shiro的ProxiedFilterChain执行流程:1、先执行Shiro自己的Filter链;2、再执行Servlet容器的Filter链(即原始的Filter)。

而ProxiedFilterChain是通过FilterChainResolver根据配置文件中[urls]部分是否与请求的URL是否匹配解析得到的。 

Java代码  
  1. FilterChain getChain(ServletRequest request, ServletResponse response, FilterChain originalChain);  

即传入原始的chain得到一个代理的chain。

Shiro内部提供了一个路径匹配的FilterChainResolver实现:PathMatchingFilterChainResolver,其根据[urls]中配置的url模式(默认Ant风格)=拦截器链和请求的url是否匹配来解析得到配置的拦截器链的;而PathMatchingFilterChainResolver内部通过FilterChainManager维护着拦截器链,比如DefaultFilterChainManager实现维护着url模式与拦截器链的关系。因此我们可以通过FilterChainManager进行动态动态增加url模式与拦截器链的关系。

 

DefaultFilterChainManager会默认添加org.apache.shiro.web.filter.mgt.DefaultFilter中声明的拦截器:

Java代码  
  1. public enum DefaultFilter {  
  2.     anon(AnonymousFilter.class),  
  3.     authc(FormAuthenticationFilter.class),  
  4.     authcBasic(BasicHttpAuthenticationFilter.class),  
  5.     logout(LogoutFilter.class),  
  6.     noSessionCreation(NoSessionCreationFilter.class),  
  7.     perms(PermissionsAuthorizationFilter.class),  
  8.     port(PortFilter.class),  
  9.     rest(HttpMethodPermissionFilter.class),  
  10.     roles(RolesAuthorizationFilter.class),  
  11.     ssl(SslFilter.class),  
  12.     user(UserFilter.class);  
  13. }   

下一节会介绍这些拦截器的作用。

 

如果要注册自定义拦截器,IniSecurityManagerFactory/WebIniSecurityManagerFactory在启动时会自动扫描ini配置文件中的[filters]/[main]部分并注册这些拦截器到DefaultFilterChainManager;且创建相应的url模式与其拦截器关系链。如果使用Spring后续章节会介绍如果注册自定义拦截器。

 

如果想自定义FilterChainResolver,可以通过实现WebEnvironment接口完成:

Java代码  
  1. public class MyIniWebEnvironment extends IniWebEnvironment {  
  2.     @Override  
  3.     protected FilterChainResolver createFilterChainResolver() {  
  4.         //在此处扩展自己的FilterChainResolver  
  5.         return super.createFilterChainResolver();  
  6.     }  
  7. }   

FilterChain之间的关系。如果想动态实现url-拦截器的注册,就可以通过实现此处的FilterChainResolver来完成,比如:

Java代码  
  1. //1、创建FilterChainResolver  
  2. PathMatchingFilterChainResolver filterChainResolver =  
  3.         new PathMatchingFilterChainResolver();  
  4. //2、创建FilterChainManager  
  5. DefaultFilterChainManager filterChainManager = new DefaultFilterChainManager();  
  6. //3、注册Filter  
  7. for(DefaultFilter filter : DefaultFilter.values()) {  
  8.     filterChainManager.addFilter(  
  9.         filter.name(), (Filter) ClassUtils.newInstance(filter.getFilterClass()));  
  10. }  
  11. //4、注册URL-Filter的映射关系  
  12. filterChainManager.addToChain("/login.jsp", "authc");  
  13. filterChainManager.addToChain("/unauthorized.jsp", "anon");  
  14. filterChainManager.addToChain("/**", "authc");  
  15. filterChainManager.addToChain("/**", "roles", "admin");  
  16.   
  17. //5、设置Filter的属性  
  18. FormAuthenticationFilter authcFilter =  
  19.          (FormAuthenticationFilter)filterChainManager.getFilter("authc");  
  20. authcFilter.setLoginUrl("/login.jsp");  
  21. RolesAuthorizationFilter rolesFilter =  
  22.           (RolesAuthorizationFilter)filterChainManager.getFilter("roles");  
  23. rolesFilter.setUnauthorizedUrl("/unauthorized.jsp");  
  24.   
  25. filterChainResolver.setFilterChainManager(filterChainManager);  
  26. return filterChainResolver;   

此处自己去实现注册filter,及url模式与filter之间的映射关系。可以通过定制FilterChainResolver或FilterChainManager来完成诸如动态URL匹配的实现。

 

然后再web.xml中进行如下配置Environment:  

Java代码  
  1. <context-param>  
  2. <param-name>shiroEnvironmentClass</param-name> <param-value>com.github.zhangkaitao.shiro.chapter8.web.env.MyIniWebEnvironment</param-value>  
  3. </context-param>   

 

8.3 自定义拦截器

通过自定义自己的拦截器可以扩展一些功能,诸如动态url-角色/权限访问控制的实现、根据Subject身份信息获取用户信息绑定到Request(即设置通用数据)、验证码验证、在线用户信息的保存等等,因为其本质就是一个Filter;所以Filter能做的它就能做。

 

对于Filter的介绍请参考《Servlet规范》中的Filter部分:

http://www.iteye.com/blogs/subjects/Servlet-3-1

 

1、扩展OncePerRequestFilter

OncePerRequestFilter保证一次请求只调用一次doFilterInternal,即如内部的forward不会再多执行一次doFilterInternal: 

Java代码  
  1. public class MyOncePerRequestFilter extends OncePerRequestFilter {  
  2.     @Override  
  3.     protected void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException {  
  4.         System.out.println("=========once per request filter");  
  5.         chain.doFilter(request, response);  
  6.     }  
  7. }   

然后再shiro.ini配置文件中:

Java代码  
  1. [main]  
  2. myFilter1=com.github.zhangkaitao.shiro.chapter8.web.filter.MyOncePerRequestFilter  
  3. #[filters]  
  4. #myFilter1=com.github.zhangkaitao.shiro.chapter8.web.filter.MyOncePerRequestFilter  
  5. [urls]  
  6. /**=myFilter1   

Filter可以在[main]或[filters]部分注册,然后在[urls]部分配置url与filter的映射关系即可。

 

2、扩展AdviceFilter

AdviceFilter提供了AOP的功能,其实现和SpringMVC中的Interceptor思想一样:具体可参考我的SpringMVC教程中的处理器拦截器部分:

http://www.iteye.com/blogs/subjects/kaitao-springmvc

Java代码  
  1. public class MyAdviceFilter extends AdviceFilter {  
  2.     @Override  
  3.     protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {  
  4.         System.out.println("====预处理/前置处理");  
  5.         return true;//返回false将中断后续拦截器链的执行  
  6.     }  
  7.     @Override  
  8.     protected void postHandle(ServletRequest request, ServletResponse response) throws Exception {  
  9.         System.out.println("====后处理/后置返回处理");  
  10.     }  
  11.     @Override  
  12.     public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception {  
  13.         System.out.println("====完成处理/后置最终处理");  
  14.     }  
  15. }   

preHandle:进行请求的预处理,然后根据返回值决定是否继续处理(true:继续过滤器链);可以通过它实现权限控制;

postHandle:执行完拦截器链之后正常返回后执行;

afterCompletion:不管最后有没有异常,afterCompletion都会执行,完成如清理资源功能。

 

然后在shiro.ini中进行如下配置: 

Java代码  
  1. [filters]  
  2. myFilter1=com.github.zhangkaitao.shiro.chapter8.web.filter.MyOncePerRequestFilter  
  3. myFilter2=com.github.zhangkaitao.shiro.chapter8.web.filter.MyAdviceFilter  
  4. [urls]  
  5. /**=myFilter1,myFilter2   

该过滤器的具体使用可参考我的SpringMVC教程中的处理器拦截器部分。

 

3PathMatchingFilter

PathMatchingFilter继承了AdviceFilter,提供了url模式过滤的功能,如果需要对指定的请求进行处理,可以扩展PathMatchingFilter: 

Java代码  
  1. public class MyPathMatchingFilter extends PathMatchingFilter {  
  2.     @Override  
  3.     protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {  
  4.        System.out.println("url matches,config is " + Arrays.toString((String[])mappedValue));  
  5.        return true;  
  6.     }  
  7. }   

preHandle:会进行url模式与请求url进行匹配,如果匹配会调用onPreHandle;如果没有配置url模式/没有url模式匹配,默认直接返回true;

onPreHandle:如果url模式与请求url匹配,那么会执行onPreHandle,并把该拦截器配置的参数传入。默认什么不处理直接返回true。

 

然后在shiro.ini中进行如下配置:

Java代码  
  1. [filters]  
  2. myFilter3=com.github.zhangkaitao.shiro.chapter8.web.filter.MyPathMatchingFilter  
  3. [urls]  
  4. /**= myFilter3[config]   

/**就是注册给PathMatchingFilter的url模式,config就是拦截器的配置参数,多个之间逗号分隔,onPreHandle使用mappedValue接收参数值。

 

4、扩展AccessControlFilter

AccessControlFilter继承了PathMatchingFilter,并扩展了了两个方法:

Java代码  
  1. public boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {  
  2.     return isAccessAllowed(request, response, mappedValue)  
  3.      || onAccessDenied(request, response, mappedValue);  
  4. }   

isAccessAllowed:即是否允许访问,返回true表示允许;

onAccessDenied:表示访问拒绝时是否自己处理,如果返回true表示自己不处理且继续拦截器链执行,返回false表示自己已经处理了(比如重定向到另一个页面)。

   

Java代码  
  1. public class MyAccessControlFilter extends AccessControlFilter {  
  2.     protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {  
  3.         System.out.println("access allowed");  
  4.         return true;  
  5.     }  
  6.     protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {  
  7.         System.out.println("访问拒绝也不自己处理,继续拦截器链的执行");  
  8.         return true;  
  9.     }  
  10. }   

然后在shiro.ini中进行如下配置:

Java代码  
  1. [filters]  
  2. myFilter4=com.github.zhangkaitao.shiro.chapter8.web.filter.MyAccessControlFilter  
  3. [urls]  
  4. /**=myFilter4  

  

5、基于表单登录拦截器 

之前我们已经使用过Shiro内置的基于表单登录的拦截器了,此处自己做一个类似的基于表单登录的拦截器。

Java代码  
  1. public class FormLoginFilter extends PathMatchingFilter {  
  2.     private String loginUrl = "/login.jsp";  
  3.     private String successUrl = "/";  
  4.     @Override  
  5.     protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {  
  6.         if(SecurityUtils.getSubject().isAuthenticated()) {  
  7.             return true;//已经登录过  
  8.         }  
  9.         HttpServletRequest req = (HttpServletRequest) request;  
  10.         HttpServletResponse resp = (HttpServletResponse) response;  
  11.         if(isLoginRequest(req)) {  
  12.             if("post".equalsIgnoreCase(req.getMethod())) {//form表单提交  
  13.                 boolean loginSuccess = login(req); //登录  
  14.                 if(loginSuccess) {  
  15.                     return redirectToSuccessUrl(req, resp);  
  16.                 }  
  17.             }  
  18.             return true;//继续过滤器链  
  19.         } else {//保存当前地址并重定向到登录界面  
  20.             saveRequestAndRedirectToLogin(req, resp);  
  21.             return false;  
  22.         }  
  23.     }  
  24.     private boolean redirectToSuccessUrl(HttpServletRequest req, HttpServletResponse resp) throws IOException {  
  25.         WebUtils.redirectToSavedRequest(req, resp, successUrl);  
  26.         return false;  
  27.     }  
  28.     private void saveRequestAndRedirectToLogin(HttpServletRequest req, HttpServletResponse resp) throws IOException {  
  29.         WebUtils.saveRequest(req);  
  30.         WebUtils.issueRedirect(req, resp, loginUrl);  
  31.     }  
  32.   
  33.     private boolean login(HttpServletRequest req) {  
  34.         String username = req.getParameter("username");  
  35.         String password = req.getParameter("password");  
  36.         try {  
  37.             SecurityUtils.getSubject().login(new UsernamePasswordToken(username, password));  
  38.         } catch (Exception e) {  
  39.             req.setAttribute("shiroLoginFailure", e.getClass());  
  40.             return false;  
  41.         }  
  42.         return true;  
  43.     }  
  44.     private boolean isLoginRequest(HttpServletRequest req) {  
  45.         return pathsMatch(loginUrl, WebUtils.getPathWithinApplication(req));  
  46.     }  
  47. }   

onPreHandle主要流程:

1、首先判断是否已经登录过了,如果已经登录过了继续拦截器链即可;

2、如果没有登录,看看是否是登录请求,如果是get方法的登录页面请求,则继续拦截器链(到请求页面),否则如果是get方法的其他页面请求则保存当前请求并重定向到登录页面;

3、如果是post方法的登录页面表单提交请求,则收集用户名/密码登录即可,如果失败了保存错误消息到“shiroLoginFailure”并返回到登录页面;

4、如果登录成功了,且之前有保存的请求,则重定向到之前的这个请求,否则到默认的成功页面。

 

shiro.ini配置

Java代码  
  1. [filters]  
  2. formLogin=com.github.zhangkaitao.shiro.chapter8.web.filter.FormLoginFilter  
  3. [urls]  
  4. /test.jsp=formLogin  
  5. /login.jsp=formLogin   

启动服务器输入http://localhost:8080/chapter8/test.jsp测试时,会自动跳转到登录页面,登录成功后又会跳回到test.jsp页面。

 

此处可以通过继承AuthenticatingFilter实现,其提供了很多登录相关的基础代码。另外可以参考Shiro内嵌的FormAuthenticationFilter的源码,思路是一样的。

 

6、任意角色授权拦截器

Shiro提供roles拦截器,其验证用户拥有所有角色,没有提供验证用户拥有任意角色的拦截器。

Java代码  
  1. public class AnyRolesFilter extends AccessControlFilter {  
  2.     private String unauthorizedUrl = "/unauthorized.jsp";  
  3.     private String loginUrl = "/login.jsp";  
  4.     protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {  
  5.         String[] roles = (String[])mappedValue;  
  6.         if(roles == null) {  
  7.             return true;//如果没有设置角色参数,默认成功  
  8.         }  
  9.         for(String role : roles) {  
  10.             if(getSubject(request, response).hasRole(role)) {  
  11.                 return true;  
  12.             }  
  13.         }  
  14.         return false;//跳到onAccessDenied处理  
  15.     }  
  16.   
  17.     @Override  
  18.     protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {  
  19.         Subject subject = getSubject(request, response);  
  20.         if (subject.getPrincipal() == null) {//表示没有登录,重定向到登录页面  
  21.             saveRequest(request);  
  22.             WebUtils.issueRedirect(request, response, loginUrl);  
  23.         } else {  
  24.             if (StringUtils.hasText(unauthorizedUrl)) {//如果有未授权页面跳转过去  
  25.                 WebUtils.issueRedirect(request, response, unauthorizedUrl);  
  26.             } else {//否则返回401未授权状态码  
  27.                 WebUtils.toHttp(response).sendError(HttpServletResponse.SC_UNAUTHORIZED);  
  28.             }  
  29.         }  
  30.         return false;  
  31.     }  
  32. }   

流程:

1、首先判断用户有没有任意角色,如果没有返回false,将到onAccessDenied进行处理;

2、如果用户没有角色,接着判断用户有没有登录,如果没有登录先重定向到登录;

3、如果用户没有角色且设置了未授权页面(unauthorizedUrl),那么重定向到未授权页面;否则直接返回401未授权错误码。

 

shiro.ini配置

Java代码  
  1. [filters]  
  2. anyRoles=com.github.zhangkaitao.shiro.chapter8.web.filter.AnyRolesFilter  
  3. [urls]  
  4. /test.jsp=formLogin,anyRoles[admin,user]  
  5. /login.jsp=formLogin   

此处可以继承AuthorizationFilter实现,其提供了授权相关的基础代码。另外可以参考Shiro内嵌的RolesAuthorizationFilter的源码,只是实现hasAllRoles逻辑。

 

8.4 默认拦截器

Shiro内置了很多默认的拦截器,比如身份验证、授权等相关的。默认拦截器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举拦截器:  

默认拦截器名

拦截器类

说明(括号里的表示默认值)

身份验证相关的

 

 

authc

org.apache.shiro.web.filter.authc

.FormAuthenticationFilter

基于表单的拦截器;如“/**=authc”,如果没有登录会跳到相应的登录页面登录;主要属性:usernameParam:表单提交的用户名参数名( username);  passwordParam:表单提交的密码参数名(password); rememberMeParam:表单提交的密码参数名(rememberMe);  loginUrl:登录页面地址(/login.jsp);successUrl:登录成功后的默认重定向地址; failureKeyAttribute:登录失败后错误信息存储key(shiroLoginFailure);

authcBasic

org.apache.shiro.web.filter.authc

.BasicHttpAuthenticationFilter

Basic HTTP身份验证拦截器,主要属性: applicationName:弹出登录框显示的信息(application);

logout

org.apache.shiro.web.filter.authc

.LogoutFilter

退出拦截器,主要属性:redirectUrl:退出成功后重定向的地址(/);示例“/logout=logout”

user

org.apache.shiro.web.filter.authc

.UserFilter

用户拦截器,用户已经身份验证/记住我登录的都可;示例“/**=user”

anon

org.apache.shiro.web.filter.authc

.AnonymousFilter

匿名拦截器,即不需要登录即可访问;一般用于静态资源过滤;示例“/static/**=anon”

授权相关的

 

 

roles

org.apache.shiro.web.filter.authz

.RolesAuthorizationFilter

角色授权拦截器,验证用户是否拥有所有角色;主要属性: loginUrl:登录页面地址(/login.jsp);unauthorizedUrl:未授权后重定向的地址;示例“/admin/**=roles[admin]”

perms

org.apache.shiro.web.filter.authz

.PermissionsAuthorizationFilter

权限授权拦截器,验证用户是否拥有所有权限;属性和roles一样;示例“/user/**=perms["user:create"]”

port

org.apache.shiro.web.filter.authz

.PortFilter

端口拦截器,主要属性:port(80):可以通过的端口;示例“/test= port[80]”,如果用户访问该页面是非80,将自动将请求端口改为80并重定向到该80端口,其他路径/参数等都一样

rest

org.apache.shiro.web.filter.authz

.HttpMethodPermissionFilter

rest风格拦截器,自动根据请求方法构建权限字符串(GET=read, POST=create,PUT=update,DELETE=delete,HEAD=read,TRACE=read,OPTIONS=read, MKCOL=create)构建权限字符串;示例“/users=rest[user]”,会自动拼出“user:read,user:create,user:update,user:delete”权限字符串进行权限匹配(所有都得匹配,isPermittedAll);

ssl

org.apache.shiro.web.filter.authz

.SslFilter

SSL拦截器,只有请求协议是https才能通过;否则自动跳转会https端口(443);其他和port拦截器一样;

其他

 

 

noSessionCreation

org.apache.shiro.web.filter.session

.NoSessionCreationFilter

不创建会话拦截器,调用 subject.getSession(false)不会有什么问题,但是如果 subject.getSession(true)将抛出 DisabledSessionException异常;

 

另外还提供了一个org.apache.shiro.web.filter.authz.HostFilter,即主机拦截器,比如其提供了属性:authorizedIps:已授权的ip地址,deniedIps:表示拒绝的ip地址;不过目前还没有完全实现,不可用。

 

这些默认的拦截器会自动注册,可以直接在ini配置文件中通过“拦截器名.属性”设置其属性:

Java代码  
  1. perms.unauthorizedUrl=/unauthorized  

另外如果某个拦截器不想使用了可以直接通过如下配置直接禁用:

Java代码  
  1. perms.enabled=false  

 

第九章 JSP标签——《跟我学Shiro》

Shiro提供了JSTL标签用于在JSP/GSP页面进行权限控制,如根据登录用户显示相应的页面按钮。

 

 

导入标签库

Java代码  
  1. <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>  

标签库定义在shiro-web.jar包下的META-INF/shiro.tld中定义。

 

guest标签 

Java代码  
  1. <shiro:guest>  
  2. 欢迎游客访问,<a href="${pageContext.request.contextPath}/login.jsp">登录</a>  
  3. </shiro:guest>   

 

用户没有身份验证时显示相应信息,即游客访问信息。

 

user标签 

Java代码  
  1. <shiro:user>  
  2. 欢迎[<shiro:principal/>]登录,<a href="${pageContext.request.contextPath}/logout">退出</a>  
  3. </shiro:user>   

用户已经身份验证/记住我登录后显示相应的信息。

  

authenticated标签 

Java代码  
  1. <shiro:authenticated>  
  2.     用户[<shiro:principal/>]已身份验证通过  
  3. </shiro:authenticated>   

用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的。    

 

notAuthenticated标签

<shiro:notAuthenticated>
    未身份验证(包括记住我)
</shiro:notAuthenticated> 

用户已经身份验证通过,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证。 

 

principal标签 

<shiro: principal/>

显示用户身份信息,默认调用Subject.getPrincipal()获取,即Primary Principal。

 

Java代码 
  1. <shiro:principal type="java.lang.String"/>  

相当于Subject.getPrincipals().oneByType(String.class)。 

 

Java代码 
  1. <shiro:principal type="java.lang.String"/>  

相当于Subject.getPrincipals().oneByType(String.class)。

 

Java代码 
  1. <shiro:principal property="username"/>  

相当于((User)Subject.getPrincipals()).getUsername()。   

 

hasRole标签 

Java代码 
  1. <shiro:hasRole name="admin">  
  2.     用户[<shiro:principal/>]拥有角色admin<br/>  
  3. </shiro:hasRole>   

如果当前Subject有角色将显示body体内容。

 

hasAnyRoles标签 

Java代码 
  1. <shiro:hasAnyRoles name="admin,user">  
  2.     用户[<shiro:principal/>]拥有角色admin或user<br/>  
  3. </shiro:hasAnyRoles>   

如果当前Subject有任意一个角色(或的关系)将显示body体内容。 

 

lacksRole标签 

Java代码 
  1. <shiro:lacksRole name="abc">  
  2.     用户[<shiro:principal/>]没有角色abc<br/>  
  3. </shiro:lacksRole>   

如果当前Subject没有角色将显示body体内容。 

  

hasPermission标签

Java代码 
  1. <shiro:hasPermission name="user:create">  
  2.     用户[<shiro:principal/>]拥有权限user:create<br/>  
  3. </shiro:hasPermission>   

如果当前Subject有权限将显示body体内容。 

  

lacksPermission标签

Java代码 
  1. <shiro:lacksPermission name="org:create">  
  2.     用户[<shiro:principal/>]没有权限org:create<br/>  
  3. </shiro:lacksPermission>   

如果当前Subject没有权限将显示body体内容。

 

另外又提供了几个权限控制相关的标签:

 

导入自定义标签库 

Java代码 
  1. <%@taglib prefix="zhang" tagdir="/WEB-INF/tags" %>  

 

示例

Java代码 
  1. <zhang:hasAllRoles name="admin,user">  
  2.     用户[<shiro:principal/>]拥有角色admin和user<br/>  
  3. </zhang:hasAllRoles>  
  4. <zhang:hasAllPermissions name="user:create,user:update">  
  5.     用户[<shiro:principal/>]拥有权限user:create和user:update<br/>  
  6. </zhang:hasAllPermissions>  
  7. <zhang:hasAnyPermissions name="user:create,abc:update">  
  8.     用户[<shiro:principal/>]拥有权限user:create或abc:update<br/>  
  9. </zhang:hasAnyPermissions>   

hasAllRoles表示拥有所有相关的角色;hasAllPermissions表示拥有所有相关的权限;hasAnyPermissions表示拥有任意一个相关的权限。

第十章 会话管理——《跟我学Shiro》

Shiro提供了完整的企业级会话管理功能,不依赖于底层容器(如web容器tomcat),不管JavaSE还是JavaEE环境都可以使用,提供了会话管理、会话事件监听、会话存储/持久化、容器无关的集群、失效/过期支持、对Web的透明支持、SSO单点登录的支持等特性。即直接使用Shiro的会话管理可以直接替换如Web容器的会话管理。

 

会话

所谓会话,即用户访问应用时保持的连接关系,在多次交互中应用能够识别出当前访问的用户是谁,且可以在多次交互中保存一些数据。如访问一些网站时登录成功后,网站可以记住用户,且在退出之前都可以识别当前用户是谁。

 

 

Shiro的会话支持不仅可以在普通的JavaSE应用中使用,也可以在JavaEE应用中使用,如web应用。且使用方式是一致的。 

Java代码  
  1. login("classpath:shiro.ini", "zhang", "123");  
  2. Subject subject = SecurityUtils.getSubject();  
  3. Session session = subject.getSession();   

登录成功后使用Subject.getSession()即可获取会话;其等价于Subject.getSession(true),即如果当前没有创建Session对象会创建一个;另外Subject.getSession(false),如果当前没有创建Session则返回null(不过默认情况下如果启用会话存储功能的话在创建Subject时会主动创建一个Session)。

 

Java代码  
  1. session.getId();  

获取当前会话的唯一标识。

  

Java代码  
  1. session.getHost();  

获取当前Subject的主机地址,该地址是通过HostAuthenticationToken.getHost()提供的。 

 

Java代码  
  1. session.getTimeout();  
  2. session.setTimeout(毫秒);   

获取/设置当前Session的过期时间;如果不设置默认是会话管理器的全局过期时间。

  

Java代码  
  1. session.getStartTimestamp();  
  2. session.getLastAccessTime();  

获取会话的启动时间及最后访问时间;如果是JavaSE应用需要自己定期调用session.touch()去更新最后访问时间;如果是Web应用,每次进入ShiroFilter都会自动调用session.touch()来更新最后访问时间。    

 

Java代码  
  1. session.touch();  
  2. session.stop();   

更新会话最后访问时间及销毁会话;当Subject.logout()时会自动调用stop方法来销毁会话。如果在web中,调用javax.servlet.http.HttpSession. invalidate()也会自动调用Shiro Session.stop方法进行销毁Shiro的会话。 

 

Java代码  
  1. session.setAttribute("key", "123");  
  2. Assert.assertEquals("123", session.getAttribute("key"));  
  3. session.removeAttribute("key");  

设置/获取/删除会话属性;在整个会话范围内都可以对这些属性进行操作。 

 

Shiro提供的会话可以用于JavaSE/JavaEE环境,不依赖于任何底层容器,可以独立使用,是完整的会话模块。

 

会话管理器

会话管理器管理着应用中所有Subject的会话的创建、维护、删除、失效、验证等工作。是Shiro的核心组件,顶层组件SecurityManager直接继承了SessionManager,且提供了SessionsSecurityManager实现直接把会话管理委托给相应的SessionManager,DefaultSecurityManager及DefaultWebSecurityManager默认SecurityManager都继承了SessionsSecurityManager。

 

 

 

 

SecurityManager提供了如下接口:

 

Java代码  
  1. Session start(SessionContext context); //启动会话  
  2. Session getSession(SessionKey key) throws SessionException; //根据会话Key获取会话   

 

另外用于Web环境的WebSessionManager又提供了如下接口:

 

Java代码  
  1. boolean isServletContainerSessions();//是否使用Servlet容器的会话  

 

Shiro还提供了ValidatingSessionManager用于验资并过期会话: 

Java代码  
  1. void validateSessions();//验证所有会话是否过期  

 

 

Shiro提供了三个默认实现:

DefaultSessionManager:DefaultSecurityManager使用的默认实现,用于JavaSE环境;

ServletContainerSessionManager:DefaultWebSecurityManager使用的默认实现,用于Web环境,其直接使用Servlet容器的会话;

DefaultWebSessionManager:用于Web环境的实现,可以替代ServletContainerSessionManager,自己维护着会话,直接废弃了Servlet容器的会话管理。

 

替换SecurityManager默认的SessionManager可以在ini中配置(shiro.ini):

 

Java代码  
  1. [main]  
  2. sessionManager=org.apache.shiro.session.mgt.DefaultSessionManager  
  3. securityManager.sessionManager=$sessionManager   

 

 

Web环境下的ini配置(shiro-web.ini):

 

<!--EndFragment-->

 

Java代码  
  1. [main]  
  2. sessionManager=org.apache.shiro.web.session.mgt.ServletContainerSessionManager  
  3. securityManager.sessionManager=$sessionManager  

   

 

另外可以设置会话的全局过期时间(毫秒为单位),默认30分钟:

 

Java代码  
  1. sessionManager. globalSessionTimeout=1800000   

 

默认情况下globalSessionTimeout将应用给所有Session。可以单独设置每个Session的timeout属性来为每个Session设置其超时时间。

 

另外如果使用ServletContainerSessionManager进行会话管理,Session的超时依赖于底层Servlet容器的超时时间,可以在web.xml中配置其会话的超时时间(分钟为单位): 

Java代码  
  1. <session-config>  
  2.   <session-timeout>30</session-timeout>  
  3. </session-config>  

  

在Servlet容器中,默认使用JSESSIONID Cookie维护会话,且会话默认是跟容器绑定的;在某些情况下可能需要使用自己的会话机制,此时我们可以使用DefaultWebSessionManager来维护会话:

Java代码  
  1. sessionIdCookie=org.apache.shiro.web.servlet.SimpleCookie  
  2. sessionManager=org.apache.shiro.web.session.mgt.DefaultWebSessionManager  
  3. sessionIdCookie.name=sid  
  4. #sessionIdCookie.domain=
  5. #sessionIdCookie.path=  
  6. sessionIdCookie.maxAge=1800  
  7. sessionIdCookie.httpOnly=true  
  8. sessionManager.sessionIdCookie=$sessionIdCookie  
  9. sessionManager.sessionIdCookieEnabled=true  
  10. securityManager.sessionManager=$sessionManager   

sessionIdCookie是sessionManager创建会话Cookie的模板:

sessionIdCookie.name:设置Cookie名字,默认为JSESSIONID;

sessionIdCookie.domain:设置Cookie的域名,默认空,即当前访问的域名;

sessionIdCookie.path:设置Cookie的路径,默认空,即存储在域名根下;

sessionIdCookie.maxAge:设置Cookie的过期时间,秒为单位,默认-1表示关闭浏览器时过期Cookie;

sessionIdCookie.httpOnly:如果设置为true,则客户端不会暴露给客户端脚本代码,使用HttpOnly cookie有助于减少某些类型的跨站点脚本攻击;此特性需要实现了Servlet 2.5 MR6及以上版本的规范的Servlet容器支持;

sessionManager.sessionIdCookieEnabled:是否启用/禁用Session Id Cookie,默认是启用的;如果禁用后将不会设置Session Id Cookie,即默认使用了Servlet容器的JSESSIONID,且通过URL重写(URL中的“;JSESSIONID=id”部分)保存Session Id。

 

另外我们可以如“sessionManager. sessionIdCookie.name=sid”这种方式操作Cookie模板。

 

会话监听器

会话监听器用于监听会话创建、过期及停止事件: 

Java代码  
  1. public class MySessionListener1 implements SessionListener {  
  2.     @Override  
  3.     public void onStart(Session session) {//会话创建时触发  
  4.         System.out.println("会话创建:" + session.getId());  
  5.     }  
  6.     @Override  
  7.     public void onExpiration(Session session) {//会话过期时触发  
  8.         System.out.println("会话过期:" + session.getId());  
  9.     }  
  10.     @Override  
  11.     public void onStop(Session session) {//退出/会话过期时触发  
  12.         System.out.println("会话停止:" + session.getId());  
  13.     }    
  14. }  

 

如果只想监听某一个事件,可以继承SessionListenerAdapter实现:

Java代码  
  1. public class MySessionListener2 extends SessionListenerAdapter {  
  2.     @Override  
  3.     public void onStart(Session session) {  
  4.         System.out.println("会话创建:" + session.getId());  
  5.     }  
  6. }  

 

在shiro-web.ini配置文件中可以进行如下配置设置会话监听器:

Java代码  
  1. sessionListener1=com.github.zhangkaitao.shiro.chapter10.web.listener.MySessionListener1  
  2. sessionListener2=com.github.zhangkaitao.shiro.chapter10.web.listener.MySessionListener2  
  3. sessionManager.sessionListeners=$sessionListener1,$sessionListener2  

 

会话存储/持久化 

Shiro提供SessionDAO用于会话的CRUD,即DAO(Data Access Object)模式实现:

Java代码  
  1. //如DefaultSessionManager在创建完session后会调用该方法;如保存到关系数据库/文件系统/NoSQL数据库;即可以实现会话的持久化;返回会话ID;主要此处返回的ID.equals(session.getId());  
  2. Serializable create(Session session);  
  3. //根据会话ID获取会话  
  4. Session readSession(Serializable sessionId) throws UnknownSessionException;  
  5. //更新会话;如更新会话最后访问时间/停止会话/设置超时时间/设置移除属性等会调用  
  6. void update(Session session) throws UnknownSessionException;  
  7. //删除会话;当会话过期/会话停止(如用户退出时)会调用  
  8. void delete(Session session);  
  9. //获取当前所有活跃用户,如果用户量多此方法影响性能  
  10. Collection<Session> getActiveSessions();   

Shiro内嵌了如下SessionDAO实现:

 

 

 

AbstractSessionDAO提供了SessionDAO的基础实现,如生成会话ID等;CachingSessionDAO提供了对开发者透明的会话缓存的功能,只需要设置相应的CacheManager即可;MemorySessionDAO直接在内存中进行会话维护;而EnterpriseCacheSessionDAO提供了缓存功能的会话维护,默认情况下使用MapCache实现,内部使用ConcurrentHashMap保存缓存的会话。

 

可以通过如下配置设置SessionDAO:

Java代码  
  1. sessionDAO=org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO  
  2. sessionManager.sessionDAO=$sessionDAO   

Shiro提供了使用Ehcache进行会话存储,Ehcache可以配合TerraCotta实现容器无关的分布式集群。

 

首先在pom.xml里添加如下依赖:

Java代码  
  1. <dependency>  
  2.     <groupId>org.apache.shiro</groupId>  
  3.     <artifactId>shiro-ehcache</artifactId>  
  4.     <version>1.2.2</version>  
  5. </dependency>   

 

接着配置shiro-web.ini文件:    

Java代码  
  1. sessionDAO=org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO  
  2. sessionDAO. activeSessionsCacheName=shiro-activeSessionCache  
  3. sessionManager.sessionDAO=$sessionDAO  
  4. cacheManager = org.apache.shiro.cache.ehcache.EhCacheManager  
  5. cacheManager.cacheManagerConfigFile=classpath:ehcache.xml  
  6. securityManager.cacheManager = $cacheManager   

sessionDAO. activeSessionsCacheName:设置Session缓存名字,默认就是shiro-activeSessionCache;

cacheManager:缓存管理器,用于管理缓存的,此处使用Ehcache实现;

cacheManager.cacheManagerConfigFile:设置ehcache缓存的配置文件;

securityManager.cacheManager:设置SecurityManager的cacheManager,会自动设置实现了CacheManagerAware接口的相应对象,如SessionDAO的cacheManager;

 

然后配置ehcache.xml:

Java代码  
  1. <cache name="shiro-activeSessionCache"  
  2.        maxEntriesLocalHeap="10000"  
  3.        overflowToDisk="false"  
  4.        eternal="false"  
  5.        diskPersistent="false"  
  6.        timeToLiveSeconds="0"  
  7.        timeToIdleSeconds="0"  
  8.        statistics="true"/>   

Cache的名字为shiro-activeSessionCache,即设置的sessionDAO的activeSessionsCacheName属性值。

 

另外可以通过如下ini配置设置会话ID生成器:

Java代码  
  1. sessionIdGenerator=org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator  
  2. sessionDAO.sessionIdGenerator=$sessionIdGenerator   

用于生成会话ID,默认就是JavaUuidSessionIdGenerator,使用java.util.UUID生成。

 

如果自定义实现SessionDAO,继承CachingSessionDAO即可:

Java代码  
  1. public class MySessionDAO extends CachingSessionDAO {  
  2.     private JdbcTemplate jdbcTemplate = JdbcTemplateUtils.jdbcTemplate();  
  3.      protected Serializable doCreate(Session session) {  
  4.         Serializable sessionId = generateSessionId(session);  
  5.         assignSessionId(session, sessionId);  
  6.         String sql = "insert into sessions(id, session) values(?,?)";  
  7.         jdbcTemplate.update(sql, sessionId, SerializableUtils.serialize(session));  
  8.         return session.getId();  
  9.     }  
  10. protected void doUpdate(Session session) {  
  11.     if(session instanceof ValidatingSession && !((ValidatingSession)session).isValid()) {  
  12.         return; //如果会话过期/停止 没必要再更新了  
  13.     }  
  14.         String sql = "update sessions set session=? where id=?";  
  15.         jdbcTemplate.update(sql, SerializableUtils.serialize(session), session.getId());  
  16.     }  
  17.     protected void doDelete(Session session) {  
  18.         String sql = "delete from sessions where id=?";  
  19.         jdbcTemplate.update(sql, session.getId());  
  20.     }  
  21.     protected Session doReadSession(Serializable sessionId) {  
  22.         String sql = "select session from sessions where id=?";  
  23.         List<String> sessionStrList = jdbcTemplate.queryForList(sql, String.class, sessionId);  
  24.         if(sessionStrList.size() == 0) return null;  
  25.         return SerializableUtils.deserialize(sessionStrList.get(0));  
  26.     }  
  27. }   

doCreate/doUpdate/doDelete/doReadSession分别代表创建/修改/删除/读取会话;此处通过把会话序列化后存储到数据库实现;接着在shiro-web.ini中配置:

Java代码  
  1. sessionDAO=com.github.zhangkaitao.shiro.chapter10.session.dao.MySessionDAO  

其他设置和之前一样,因为继承了CachingSessionDAO;所有在读取时会先查缓存中是否存在,如果找不到才到数据库中查找。

 

会话验证

Shiro提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话;出于性能考虑,一般情况下都是获取会话时来验证会话是否过期并停止会话的;但是如在web环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,Shiro提供了会话验证调度器SessionValidationScheduler来做这件事情。

 

可以通过如下ini配置开启会话验证:    

Java代码  
  1. sessionValidationScheduler=org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler  
  2. sessionValidationScheduler.interval = 3600000  
  3. sessionValidationScheduler.sessionManager=$sessionManager  
  4. sessionManager.globalSessionTimeout=1800000  
  5. sessionManager.sessionValidationSchedulerEnabled=true  
  6. sessionManager.sessionValidationScheduler=$sessionValidationScheduler   

sessionValidationScheduler:会话验证调度器,sessionManager默认就是使用ExecutorServiceSessionValidationScheduler,其使用JDK的ScheduledExecutorService进行定期调度并验证会话是否过期;

sessionValidationScheduler.interval:设置调度时间间隔,单位毫秒,默认就是1小时;

sessionValidationScheduler.sessionManager:设置会话验证调度器进行会话验证时的会话管理器;

sessionManager.globalSessionTimeout:设置全局会话超时时间,默认30分钟,即如果30分钟内没有访问会话将过期;

sessionManager.sessionValidationSchedulerEnabled:是否开启会话验证器,默认是开启的;

sessionManager.sessionValidationScheduler:设置会话验证调度器,默认就是使用ExecutorServiceSessionValidationScheduler。

 

Shiro也提供了使用Quartz会话验证调度器:

Java代码  
  1. sessionValidationScheduler=org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler  
  2. sessionValidationScheduler.sessionValidationInterval = 3600000  
  3. sessionValidationScheduler.sessionManager=$sessionManager   

使用时需要导入shiro-quartz依赖:

Java代码  
  1. <dependency>  
  2.      <groupId>org.apache.shiro</groupId>  
  3.      <artifactId>shiro-quartz</artifactId>  
  4.      <version>1.2.2</version>  
  5. </dependency>  

    

如上会话验证调度器实现都是直接调用AbstractValidatingSessionManager 的validateSessions方法进行验证,其直接调用SessionDAO的getActiveSessions方法获取所有会话进行验证,如果会话比较多,会影响性能;可以考虑如分页获取会话并进行验证,如com.github.zhangkaitao.shiro.chapter10.session.scheduler.MySessionValidationScheduler:

Java代码  
  1. //分页获取会话并验证  
  2. String sql = "select session from sessions limit ?,?";  
  3. int start = 0; //起始记录  
  4. int size = 20; //每页大小  
  5. List<String> sessionList = jdbcTemplate.queryForList(sql, String.class, start, size);  
  6. while(sessionList.size() > 0) {  
  7.   for(String sessionStr : sessionList) {  
  8.     try {  
  9.       Session session = SerializableUtils.deserialize(sessionStr);  
  10.       Method validateMethod =   
  11.         ReflectionUtils.findMethod(AbstractValidatingSessionManager.class,   
  12.             "validate", Session.class, SessionKey.class);  
  13.       validateMethod.setAccessible(true);  
  14.       ReflectionUtils.invokeMethod(validateMethod,   
  15.         sessionManager, session, new DefaultSessionKey(session.getId()));  
  16.     } catch (Exception e) {  
  17.         //ignore  
  18.     }  
  19.   }  
  20.  start = start + size;  
  21.   sessionList = jdbcTemplate.queryForList(sql, String.class, start, size);  
  22. }   

其直接改造自ExecutorServiceSessionValidationScheduler,如上代码是验证的核心代码,可以根据自己的需求改造此验证调度器器;ini的配置和之前的类似。

 

如果在会话过期时不想删除过期的会话,可以通过如下ini配置进行设置:

Java代码  
  1. sessionManager.deleteInvalidSessions=false  

默认是开启的,在会话过期后会调用SessionDAO的delete方法删除会话:如会话时持久化存储的,可以调用此方法进行删除。

 

如果是在获取会话时验证了会话已过期,将抛出InvalidSessionException;因此需要捕获这个异常并跳转到相应的页面告诉用户会话已过期,让其重新登录,如可以在web.xml配置相应的错误页面:

Java代码  
  1. <error-page>  
  2.     <exception-type>org.apache.shiro.session.InvalidSessionException</exception-type>  
  3.     <location>/invalidSession.jsp</location>  
  4. </error-page>  

 

sessionFactory

sessionFactory是创建会话的工厂,根据相应的Subject上下文信息来创建会话;默认提供了SimpleSessionFactory用来创建SimpleSession会话。

 

首先自定义一个Session:

Java代码  
  1. public class OnlineSession extends SimpleSession {  
  2.     public static enum OnlineStatus {  
  3.         on_line("在线"), hidden("隐身"), force_logout("强制退出");  
  4.         private final String info;  
  5.         private OnlineStatus(String info) {  
  6.             this.info = info;  
  7.         }  
  8.         public String getInfo() {  
  9.             return info;  
  10.         }  
  11.     }  
  12.     private String userAgent; //用户浏览器类型  
  13.     private OnlineStatus status = OnlineStatus.on_line; //在线状态  
  14.     private String systemHost; //用户登录时系统IP  
  15.     //省略其他  
  16. }   

OnlineSession用于保存当前登录用户的在线状态,支持如离线等状态的控制。

 

接着自定义SessionFactory:

Java代码  
  1. public class OnlineSessionFactory implements SessionFactory {  
  2.   
  3.     @Override  
  4.     public Session createSession(SessionContext initData) {  
  5.         OnlineSession session = new OnlineSession();  
  6.         if (initData != null && initData instanceof WebSessionContext) {  
  7.             WebSessionContext sessionContext = (WebSessionContext) initData;  
  8.             HttpServletRequest request = (HttpServletRequest) sessionContext.getServletRequest();  
  9.             if (request != null) {  
  10.                 session.setHost(IpUtils.getIpAddr(request));  
  11.                 session.setUserAgent(request.getHeader("User-Agent"));  
  12.                 session.setSystemHost(request.getLocalAddr() + ":" + request.getLocalPort());  
  13.             }  
  14.         }  
  15.         return session;  
  16.     }  
  17. }   

根据会话上下文创建相应的OnlineSession。

 

最后在shiro-web.ini配置文件中配置:

Java代码  
  1. sessionFactory=org.apache.shiro.session.mgt.OnlineSessionFactory  
  2. sessionManager.sessionFactory=$sessionFactory  

第十一章 缓存机制——《跟我学Shiro》

Shiro提供了类似于Spring的Cache抽象,即Shiro本身不实现Cache,但是对Cache进行了又抽象,方便更换不同的底层Cache实现。对于Cache的一些概念可以参考我的《Spring Cache抽象详解》:http://jinnianshilongnian.iteye.com/blog/2001040

 

Shiro提供的Cache接口: 

Java代码  
  1. public interface Cache<K, V> {  
  2.     //根据Key获取缓存中的值  
  3.     public V get(K key) throws CacheException;  
  4.     //往缓存中放入key-value,返回缓存中之前的值  
  5.     public V put(K key, V value) throws CacheException;   
  6.     //移除缓存中key对应的值,返回该值  
  7.     public V remove(K key) throws CacheException;  
  8.     //清空整个缓存  
  9.     public void clear() throws CacheException;  
  10.     //返回缓存大小  
  11.     public int size();  
  12.     //获取缓存中所有的key  
  13.     public Set<K> keys();  
  14.     //获取缓存中所有的value  
  15.     public Collection<V> values();  
  16. }  

  

Shiro提供的CacheManager接口: 

Java代码  
  1. public interface CacheManager {  
  2.     //根据缓存名字获取一个Cache  
  3.     public <K, V> Cache<K, V> getCache(String name) throws CacheException;  
  4. }  

  

Shiro还提供了CacheManagerAware用于注入CacheManager 

Java代码  
  1. public interface CacheManagerAware {  
  2.     //注入CacheManager  
  3.     void setCacheManager(CacheManager cacheManager);  
  4. }  

 

Shiro内部相应的组件(DefaultSecurityManager)会自动检测相应的对象(如Realm)是否实现了CacheManagerAware并自动注入相应的CacheManager。

  

本章用例使用了与第六章的代码。

 

Realm缓存

Shiro提供了CachingRealm,其实现了CacheManagerAware接口,提供了缓存的一些基础实现;另外AuthenticatingRealm及AuthorizingRealm分别提供了对AuthenticationInfo 和AuthorizationInfo信息的缓存。

 

ini配置   

Java代码  
  1. userRealm=com.github.zhangkaitao.shiro.chapter11.realm.UserRealm  
  2. userRealm.credentialsMatcher=$credentialsMatcher  
  3. userRealm.cachingEnabled=true  
  4. userRealm.authenticationCachingEnabled=true  
  5. userRealm.authenticationCacheName=authenticationCache  
  6. userRealm.authorizationCachingEnabled=true  
  7. userRealm.authorizationCacheName=authorizationCache  
  8. securityManager.realms=$userRealm  
  9.   
  10. cacheManager=org.apache.shiro.cache.ehcache.EhCacheManager  
  11. cacheManager.cacheManagerConfigFile=classpath:shiro-ehcache.xml  
  12. securityManager.cacheManager=$cacheManager   

userRealm.cachingEnabled:启用缓存,默认false;

userRealm.authenticationCachingEnabled:启用身份验证缓存,即缓存AuthenticationInfo信息,默认false;

userRealm.authenticationCacheName:缓存AuthenticationInfo信息的缓存名称;

userRealm. authorizationCachingEnabled:启用授权缓存,即缓存AuthorizationInfo信息,默认false;

userRealm. authorizationCacheName:缓存AuthorizationInfo信息的缓存名称;

cacheManager:缓存管理器,此处使用EhCacheManager,即Ehcache实现,需要导入相应的Ehcache依赖,请参考pom.xml;

 

因为测试用例的关系,需要将Ehcache的CacheManager改为使用VM单例模式:

this.manager = new net.sf.ehcache.CacheManager(getCacheManagerConfigFileInputStream());

改为

this.manager = net.sf.ehcache.CacheManager.create(getCacheManagerConfigFileInputStream());

 

测试用例 

Java代码  
  1. @Test  
  2. public void testClearCachedAuthenticationInfo() {  
  3.     login(u1.getUsername(), password);  
  4.     userService.changePassword(u1.getId(), password + "1");  
  5.   
  6.     RealmSecurityManager securityManager =  
  7.      (RealmSecurityManager) SecurityUtils.getSecurityManager();  
  8.     UserRealm userRealm = (UserRealm) securityManager.getRealms().iterator().next();  
  9.     userRealm.clearCachedAuthenticationInfo(subject().getPrincipals());  
  10.   
  11.     login(u1.getUsername(), password + "1");  
  12. }   

首先登录成功(此时会缓存相应的AuthenticationInfo),然后修改密码;此时密码就变了;接着需要调用Realm的clearCachedAuthenticationInfo方法清空之前缓存的AuthenticationInfo;否则下次登录时还会获取到修改密码之前的那个AuthenticationInfo;

 

Java代码  
  1. @Test  
  2. public void testClearCachedAuthorizationInfo() {  
  3.     login(u1.getUsername(), password);  
  4.     subject().checkRole(r1.getRole());  
  5.     userService.correlationRoles(u1.getId(), r2.getId());  
  6.   
  7.     RealmSecurityManager securityManager =  
  8.       (RealmSecurityManager) SecurityUtils.getSecurityManager();  
  9.     UserRealm userRealm = (UserRealm)securityManager.getRealms().iterator().next();  
  10.     userRealm.clearCachedAuthorizationInfo(subject().getPrincipals());  
  11.   
  12.     subject().checkRole(r2.getRole());  
  13. }   

和之前的用例差不多;此处调用Realm的clearCachedAuthorizationInfo清空之前缓存的AuthorizationInfo;

 

另外还有clearCache,其同时调用clearCachedAuthenticationInfo和clearCachedAuthorizationInfo,清空AuthenticationInfo和AuthorizationInfo。

 

UserRealm还提供了clearAllCachedAuthorizationInfo、clearAllCachedAuthenticationInfo、clearAllCache,用于清空整个缓存。

 

在某些清空下这种方式可能不是最好的选择,可以考虑直接废弃Shiro的缓存,然后自己通过如AOP机制实现自己的缓存;可以参考:

另外如果和Spring集成时可以考虑直接使用Spring的Cache抽象,可以考虑使用SpringCacheManagerWrapper,其对Spring Cache进行了包装,转换为Shiro的CacheManager实现:

https://github.com/zhangkaitao/es/blob/master/web/src/main/java/org/apache/shiro/cache/spring/SpringCacheManagerWrapper.java 

 

Session缓存

当我们设置了SecurityManager的CacheManager时,如:

Java代码  
  1. securityManager.cacheManager=$cacheManager  

 

当我们设置SessionManager时:

Java代码  
  1. sessionManager=org.apache.shiro.session.mgt.DefaultSessionManager  
  2. securityManager.sessionManager=$sessionManager   

如securityManager实现了SessionsSecurityManager,其会自动判断SessionManager是否实现了CacheManagerAware接口,如果实现了会把CacheManager设置给它。然后sessionManager会判断相应的sessionDAO(如继承自CachingSessionDAO)是否实现了CacheManagerAware,如果实现了会把CacheManager设置给它;如第九章的MySessionDAO就是带缓存的SessionDAO;其会先查缓存,如果找不到才查数据库。

 

对于CachingSessionDAO,可以通过如下配置设置缓存的名称:

Java代码  
  1. sessionDAO=com.github.zhangkaitao.shiro.chapter11.session.dao.MySessionDAO  
  2. sessionDAO.activeSessionsCacheName=shiro-activeSessionCache   

activeSessionsCacheName默认就是shiro-activeSessionCache。

第十二章 与Spring集成——《跟我学Shiro》

Shiro的组件都是JavaBean/POJO式的组件,所以非常容易使用Spring进行组件管理,可以非常方便的从ini配置迁移到Spring进行管理,且支持JavaSE应用及Web应用的集成。

 

在示例之前,需要导入shiro-spring及spring-context依赖,具体请参考pom.xml。

spring-beans.xml配置文件提供了基础组件如DataSource、DAO、Service组件的配置。

 

JavaSE应用

 

spring-shiro.xml提供了普通JavaSE独立应用的Spring配置:

Java代码  
  1. <!-- 缓存管理器 使用Ehcache实现 -->  
  2. <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">  
  3.     <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>  
  4. </bean>  
  5.   
  6. <!-- 凭证匹配器 -->  
  7. <bean id="credentialsMatcher" class="  
  8. com.github.zhangkaitao.shiro.chapter12.credentials.RetryLimitHashedCredentialsMatcher">  
  9.     <constructor-arg ref="cacheManager"/>  
  10.     <property name="hashAlgorithmName" value="md5"/>  
  11.     <property name="hashIterations" value="2"/>  
  12.     <property name="storedCredentialsHexEncoded" value="true"/>  
  13. </bean>  
  14.   
  15. <!-- Realm实现 -->  
  16. <bean id="userRealm" class="com.github.zhangkaitao.shiro.chapter12.realm.UserRealm">  
  17.     <property name="userService" ref="userService"/>  
  18.     <property name="credentialsMatcher" ref="credentialsMatcher"/>  
  19.     <property name="cachingEnabled" value="true"/>  
  20.     <property name="authenticationCachingEnabled" value="true"/>  
  21.     <property name="authenticationCacheName" value="authenticationCache"/>  
  22.     <property name="authorizationCachingEnabled" value="true"/>  
  23.     <property name="authorizationCacheName" value="authorizationCache"/>  
  24. </bean>  
  25. <!-- 会话ID生成器 -->  
  26. <bean id="sessionIdGenerator"   
  27. class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator"/>  
  28. <!-- 会话DAO -->  
  29. <bean id="sessionDAO"   
  30. class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO">  
  31.     <property name="activeSessionsCacheName" value="shiro-activeSessionCache"/>  
  32.     <property name="sessionIdGenerator" ref="sessionIdGenerator"/>  
  33. </bean>  
  34. <!-- 会话验证调度器 -->  
  35. <bean id="sessionValidationScheduler"   
  36. class="org.apache.shiro.session.mgt.quartz.QuartzSessionValidationScheduler">  
  37.     <property name="sessionValidationInterval" value="1800000"/>  
  38.     <property name="sessionManager" ref="sessionManager"/>  
  39. </bean>  
  40. <!-- 会话管理器 -->  
  41. <bean id="sessionManager" class="org.apache.shiro.session.mgt.DefaultSessionManager">  
  42.     <property name="globalSessionTimeout" value="1800000"/>  
  43.     <property name="deleteInvalidSessions" value="true"/>  
  44.     <property name="sessionValidationSchedulerEnabled" value="true"/>  
  45.    <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>  
  46.     <property name="sessionDAO" ref="sessionDAO"/>  
  47. </bean>  
  48. <!-- 安全管理器 -->  
  49. <bean id="securityManager" class="org.apache.shiro.mgt.DefaultSecurityManager">  
  50.     <property name="realms">  
  51.         <list><ref bean="userRealm"/></list>  
  52.     </property>  
  53.     <property name="sessionManager" ref="sessionManager"/>  
  54.     <property name="cacheManager" ref="cacheManager"/>  
  55. </bean>  
  56. <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) -->  
  57. <bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">  
  58. <property name="staticMethod"   
  59. value="org.apache.shiro.SecurityUtils.setSecurityManager"/>  
  60.     <property name="arguments" ref="securityManager"/>  
  61. </bean>  
  62. <!-- Shiro生命周期处理器-->  
  63. <bean id="lifecycleBeanPostProcessor"   
  64. class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>  

可以看出,只要把之前的ini配置翻译为此处的spring xml配置方式即可,无须多解释。LifecycleBeanPostProcessor用于在实现了Initializable接口的Shiro bean初始化时调用Initializable接口回调,在实现了Destroyable接口的Shiro bean销毁时调用 Destroyable接口回调。如UserRealm就实现了Initializable,而DefaultSecurityManager实现了Destroyable。具体可以查看它们的继承关系。

  

测试用例请参考com.github.zhangkaitao.shiro.chapter12.ShiroTest。 

 

Web应用

Web应用和普通JavaSE应用的某些配置是类似的,此处只提供一些不一样的配置,详细配置可以参考spring-shiro-web.xml。 

Java代码  
  1. <!-- 会话Cookie模板 -->  
  2. <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">  
  3.     <constructor-arg value="sid"/>  
  4.     <property name="httpOnly" value="true"/>  
  5.     <property name="maxAge" value="180000"/>  
  6. </bean>  
  7. <!-- 会话管理器 -->  
  8. <bean id="sessionManager"   
  9. class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">  
  10.     <property name="globalSessionTimeout" value="1800000"/>  
  11.     <property name="deleteInvalidSessions" value="true"/>  
  12.     <property name="sessionValidationSchedulerEnabled" value="true"/>  
  13.     <property name="sessionValidationScheduler" ref="sessionValidationScheduler"/>  
  14.     <property name="sessionDAO" ref="sessionDAO"/>  
  15.     <property name="sessionIdCookieEnabled" value="true"/>  
  16.     <property name="sessionIdCookie" ref="sessionIdCookie"/>  
  17. </bean>  
  18. <!-- 安全管理器 -->  
  19. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  20. <property name="realm" ref="userRealm"/>  
  21.     <property name="sessionManager" ref="sessionManager"/>  
  22.     <property name="cacheManager" ref="cacheManager"/>  
  23. </bean>   

1、sessionIdCookie是用于生产Session ID Cookie的模板;

2、会话管理器使用用于web环境的DefaultWebSessionManager;

3、安全管理器使用用于web环境的DefaultWebSecurityManager。 

 

Java代码  
  1. <!-- 基于Form表单的身份验证过滤器 -->  
  2. <bean id="formAuthenticationFilter"   
  3. class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">  
  4.     <property name="usernameParam" value="username"/>  
  5.     <property name="passwordParam" value="password"/>  
  6.     <property name="loginUrl" value="/login.jsp"/>  
  7. </bean>  
  8. <!-- Shiro的Web过滤器 -->  
  9. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
  10.     <property name="securityManager" ref="securityManager"/>  
  11.     <property name="loginUrl" value="/login.jsp"/>  
  12.     <property name="unauthorizedUrl" value="/unauthorized.jsp"/>  
  13.     <property name="filters">  
  14.         <util:map>  
  15.             <entry key="authc" value-ref="formAuthenticationFilter"/>  
  16.         </util:map>  
  17.     </property>  
  18.     <property name="filterChainDefinitions">  
  19.         <value>  
  20.             /index.jsp = anon  
  21.             /unauthorized.jsp = anon  
  22.             /login.jsp = authc  
  23.             /logout = logout  
  24.             /** = user  
  25.         </value>  
  26.     </property>  
  27. </bean>   

1、formAuthenticationFilter为基于Form表单的身份验证过滤器;此处可以再添加自己的Filter bean定义;

2、shiroFilter:此处使用ShiroFilterFactoryBean来创建ShiroFilter过滤器;filters属性用于定义自己的过滤器,即ini配置中的[filters]部分;filterChainDefinitions用于声明url和filter的关系,即ini配置中的[urls]部分。

 

接着需要在web.xml中进行如下配置: 

Java代码  
  1. <context-param>  
  2.     <param-name>contextConfigLocation</param-name>  
  3.     <param-value>  
  4.         classpath:spring-beans.xml,  
  5.         classpath:spring-shiro-web.xml  
  6.     </param-value>  
  7. </context-param>  
  8. <listener>  
  9.    <listener-class>  
  10. org.springframework.web.context.ContextLoaderListener  
  11. </listener-class>  
  12. </listener>   

通过ContextLoaderListener加载contextConfigLocation指定的Spring配置文件。

  

Java代码  
  1. <filter>  
  2.     <filter-name>shiroFilter</filter-name>  
  3.     <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>  
  4.     <init-param>  
  5.         <param-name>targetFilterLifecycle</param-name>  
  6.         <param-value>true</param-value>  
  7.     </init-param>  
  8. </filter>  
  9. <filter-mapping>  
  10.     <filter-name>shiroFilter</filter-name>  
  11.     <url-pattern>/*</url-pattern>  
  12. </filter-mapping>   

DelegatingFilterProxy会自动到Spring容器中查找名字为shiroFilter的bean并把filter请求交给它处理。

 

其他配置请参考源代码。

 

Shiro权限注解

Shiro提供了相应的注解用于权限控制,如果使用这些注解就需要使用AOP的功能来进行判断,如Spring AOP;Shiro提供了Spring AOP集成用于权限注解的解析和验证。

为了测试,此处使用了Spring MVC来测试Shiro注解,当然Shiro注解不仅仅可以在web环境使用,在独立的JavaSE中也是可以用的,此处只是以web为例了。

 

在spring-mvc.xml配置文件添加Shiro Spring AOP权限注解的支持: 

Java代码  
  1. <aop:config proxy-target-class="true"></aop:config>  
  2. <bean class="  
  3. org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">  
  4.     <property name="securityManager" ref="securityManager"/>  
  5. </bean>   

如上配置用于开启Shiro Spring AOP权限注解的支持;<aop:config proxy-target-class="true">表示代理类。

 

接着就可以在相应的控制器(AnnotationController)中使用如下方式进行注解: 

Java代码  
  1. @RequiresRoles("admin")  
  2. @RequestMapping("/hello2")  
  3. public String hello2() {  
  4.     return "success";  
  5. }   

访问hello2方法的前提是当前用户有admin角色。

 

当验证失败,其会抛出UnauthorizedException异常,此时可以使用Spring的ExceptionHandler(DefaultExceptionHandler)来进行拦截处理:

Java代码  
  1. @ExceptionHandler({UnauthorizedException.class})  
  2. @ResponseStatus(HttpStatus.UNAUTHORIZED)  
  3. public ModelAndView processUnauthenticatedException(NativeWebRequest request, UnauthorizedException e) {  
  4.     ModelAndView mv = new ModelAndView();  
  5.     mv.addObject("exception", e);  
  6.     mv.setViewName("unauthorized");  
  7.     return mv;  
  8. }   

如果集成Struts2,需要注意《Shiro+Struts2+Spring3 加上@RequiresPermissions 后@Autowired失效》问题:

http://jinnianshilongnian.iteye.com/blog/1850425

 

权限注解      

Java代码  
  1. @RequiresAuthentication  

表示当前Subject已经通过login进行了身份验证;即Subject. isAuthenticated()返回true。 

 

Java代码  
  1. @RequiresUser  

表示当前Subject已经身份验证或者通过记住我登录的。

 

Java代码  
  1. @RequiresGuest  

表示当前Subject没有身份验证或通过记住我登录过,即是游客身份。  

 

Java代码  
  1. @RequiresRoles(value={“admin”, “user”}, logical= Logical.AND)  

表示当前Subject需要角色admin和user。

 

Java代码  
  1. @RequiresPermissions (value={“user:a”, “user:b”}, logical= Logical.OR)  

表示当前Subject需要权限user:a或user:b。  

第十三章 RememberMe——《跟我学Shiro》

Shiro提供了记住我(RememberMe)的功能,比如访问如淘宝等一些网站时,关闭了浏览器下次再打开时还是能记住你是谁,下次访问时无需再登录即可访问,基本流程如下:

1、首先在登录页面选中RememberMe然后登录成功;如果是浏览器登录,一般会把RememberMe的Cookie写到客户端并保存下来;

2、关闭浏览器再重新打开;会发现浏览器还是记住你的;

3、访问一般的网页服务器端还是知道你是谁,且能正常访问;

 

4、但是比如我们访问淘宝时,如果要查看我的订单或进行支付时,此时还是需要再进行身份认证的,以确保当前用户还是你。

RememberMe配置

 

spring-shiro-web.xml配置:

Java代码  
  1. <!-- 会话Cookie模板 -->  
  2. <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">  
  3.     <constructor-arg value="sid"/>  
  4.     <property name="httpOnly" value="true"/>  
  5.     <property name="maxAge" value="-1"/>  
  6. </bean>  
  7. <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">  
  8.     <constructor-arg value="rememberMe"/>  
  9.     <property name="httpOnly" value="true"/>  
  10.     <property name="maxAge" value="2592000"/><!-- 30天 -->  
  11. </bean>   

sessionIdCookie:maxAge=-1表示浏览器关闭时失效此Cookie;

rememberMeCookie:即记住我的Cookie,保存时长30天;

  

Java代码  
  1. <!-- rememberMe管理器 -->  
  2. <bean id="rememberMeManager"   
  3. class="org.apache.shiro.web.mgt.CookieRememberMeManager">  
  4.     <property name="cipherKey" value="  
  5. #{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>  
  6.      <property name="cookie" ref="rememberMeCookie"/>  
  7. </bean>   

rememberMe管理器,cipherKey是加密rememberMe Cookie的密钥;默认AES算法;

 

Java代码  
  1. <!-- 安全管理器 -->  
  2. <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">  
  3. ……  
  4.     <property name="rememberMeManager" ref="rememberMeManager"/>  
  5. </bean>   

设置securityManager安全管理器的rememberMeManager; 

 

Java代码  
  1. <bean id="formAuthenticationFilter"   
  2. class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">  
  3.     ……  
  4.     <property name="rememberMeParam" value="rememberMe"/>  
  5. </bean>   

rememberMeParam,即rememberMe请求参数名,请求参数是boolean类型,true表示rememberMe。 

 

Java代码  
  1. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
  2.     ……  
  3.     <property name="filterChainDefinitions">  
  4.         <value>  
  5.             /login.jsp = authc  
  6.             /logout = logout  
  7.             /authenticated.jsp = authc  
  8.             /** = user  
  9.         </value>  
  10.     </property>  
  11. </bean>   

“/authenticated.jsp = authc”表示访问该地址用户必须身份验证通过(Subject. isAuthenticated()==true);而“/** = user”表示访问该地址的用户是身份验证通过或RememberMe登录的都可以。

 

测试:

1、访问http://localhost:8080/chapter13/,会跳转到登录页面,登录成功后会设置会话及rememberMe Cookie;

2、关闭浏览器,此时会话cookie将失效;

3、然后重新打开浏览器访问http://localhost:8080/chapter13/,还是可以访问的;

4、如果此时访问http://localhost:8080/chapter13/authenticated.jsp,会跳转到登录页面重新进行身份验证。

 

如果要自己做RememeberMe,需要在登录之前这样创建Token:UsernamePasswordToken(用户名,密码,是否记住我),如:

Java代码  
  1. Subject subject = SecurityUtils.getSubject();  
  2. UsernamePasswordToken token = new UsernamePasswordToken(username, password);  
  3. token.setRememberMe(true);  
  4. subject.login(token);   

subject.isAuthenticated()表示用户进行了身份验证登录的,即使有Subject.login进行了登录;subject.isRemembered():表示用户是通过记住我登录的,此时可能并不是真正的你(如你的朋友使用你的电脑,或者你的cookie被窃取)在访问的;且两者二选一,即subject.isAuthenticated()==true,则subject.isRemembered()==false;反之一样。

 

另外对于过滤器,一般这样使用:

访问一般网页,如个人在主页之类的,我们使用user拦截器即可,user拦截器只要用户登录(isRemembered()==true or isAuthenticated()==true)过即可访问成功;

访问特殊网页,如我的订单,提交订单页面,我们使用authc拦截器即可,authc拦截器会判断用户是否是通过Subject.login(isAuthenticated()==true)登录的,如果是才放行,否则会跳转到登录页面叫你重新登录。

 

因此RememberMe使用过程中,需要配合相应的拦截器来实现相应的功能,用错了拦截器可能就不能满足你的需求了。

 

posted @ 2022-02-16 15:36  hanease  阅读(40)  评论(0)    收藏  举报