spring整合shiro
web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> <!-- shiro过虑器,DelegatingFilterProx会从spring容器中找shiroFilter --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>
spring_shiro.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/"/> <property name="filters"> <map> <!--<entry key="authc" value-ref="authorizationFilter"/>--> <entry key="authc" value-ref="authenticationFilter"/> </map> </property> <!-- anon org.apache.shiro.web.filter.authc.AnonymousFilter authc org.apache.shiro.web.filter.authc.FormAuthenticationFilter authcBasic org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter logout org.apache.shiro.web.filter.authc.LogoutFilter noSessionCreation org.apache.shiro.web.filter.session.NoSessionCreationFilter perms org.apache.shiro.web.filter.authz.PermissionAuthorizationFilter 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.authz.UserFilter --> <property name="filterChainDefinitions"> <value> /app/**=anon /css/**=anon /static/**=anon /upload/**=anon /login_manager=anon /isAffirm=anon /getAdviceNoteForId=anon /**=authc </value> </property> </bean> <!-- securityManager安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realms"> <list> <ref bean="shiroRealm"/> </list> </property> <!-- 注入缓存管理器 --> <property name="cacheManager" ref="cacheManager"/> <!-- 注入session管理器 --> <property name="sessionManager" ref="sessionManager"/> <!-- 记住我 --> </bean> <bean id="shiroRealm" class="org.magicabc.pc.shiro.ShiroRealm"> <!-- <property name="credentialsMatcher" ref="credentialsMatcher"/> --> </bean> <bean id="redisSessionDAO" class="org.magicabc.pc.shiro.RedisSessionDao"/> <bean id="cacheManager" class="org.magicabc.pc.shiro.RedisCacheManager"> <property name="redisTemplate" ref="redisTemplate"/> </bean> <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> <property name="hashAlgorithmName" value="md5"/> <property name="hashIterations" value="2"/> </bean> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="globalSessionTimeout" value="1800000"/> <property name="sessionValidationSchedulerEnabled" value="true"/> <property name="sessionDAO" ref="redisSessionDAO"/> <!-- <property name="sessionListeners"> <list> <bean class="org.magicabc.pc.shiro.ShiroSessionListener"/> </list> </property> --> </bean> <!--<bean id="authorizationFilter" class="com.hunt.system.security.shiro.ShiroAuthorizationFilter"/>--> <bean id="authenticationFilter" class="org.magicabc.pc.shiro.ShiroAuthenticationFilter"/> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"> <property name="proxyTargetClass" value="true"></property> </bean> <aop:aspectj-autoproxy proxy-target-class="true"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> </beans>
相关类:

关于这份配置文件和相关类,需要系统的说明一下,也算是对spring集成shiro有一个总结,关于什么是shiro的简介就不说了 ,直接捡关键要点
1.为什么使用shiro
在之前的项目中,如果对用户进行登陆验证,需要自己把用户登陆信息保存到相应的session中,如果不同角色的用户登陆可以操作不同的功能时,需要自己每一次都从数据库中拿数据然后和session中的数据进行比对。
当角色满足功能表对应的条件时,才可以获取到相应的功能列表。当一个功能对应多个角色时,这样的操作时几位不方便的。这个东西说的专业一点,就叫权限操作。shiro,就是为了解决这个问题才被创造的。
2.shiro为什么就能管理权限呢
这个就需要从shiro的内部结构开始说起了,
首先看一幅图,这图是《跟我学shiro》中的shiro结构:
来说说这幅图吧:
首先是subjuect,主体,任何访问的对象都是一个主体。
然后是SecurityManager,这个东西是个大管家,所以主体来的请求,都归它管。
Authenticator,认证用的,什么叫认证,就是看看是不是这个角色。
Authorizer,授权用的,什么叫授权,一把钥匙开一把锁,能打开这把锁就是授权。
Realm,这东西叫域,说的通俗一点,就是个数据源。认证,授权所需要的数据,都在这里边放着呢。这里边的数据哪里来的?从数据库或者配置文件,都行。
SessionManager,会话管理用的。什么是会话?就是session.集中管理session.
SessionDao,这是个把session保存到数据库中的东西,值得一提,这东西可以使用缓存。
CacheManager,上边刚说可以使用缓存,这个东西就来了,这是管理缓存的。
Crytography,解密加密必备趁手工具。
结构说了一大堆,但是shiro怎么就能管理权限了?
看图:

3.shiro的过滤器
在项目中使用shiro,核心是使用shiro的过滤器。

图片来自《跟我学shiro》。上图就是shiro的过滤器类别分布。
在项目中,和shiro相关的类都在这些jar包中

Filter:是一个接口,在javax.servlet包下,其中只有三个方法:
public void init(FilterConfig filterConfig) throws ServletException; public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException; public void destroy();
AbstractFilter,NameableFilter,OncePerRequestFilter,AdviceFilter,AbstractShiroFilter和shiroFilter这几个抽象类,全部都在一个包下

AbstractFilter实现了Filter这个接口,它是一个抽象类,内部定义了一个过滤器配置文件属性,并添加了get/set方法。然后只对Filter的destory和init方法进行了重写
顺便说一句,@Override这个注解写不写都可以,如果实现类和接口中方法构成重写关系,不写也没事,JVM会对这个进行处理,但还是写上为好,如果没有进行方法重写,那么在写上去以后编译器自动报错)
NameableFilter继承自AbstractFilter这个抽象类,这个类就是给过滤器起名的.名字就是配置文件里写的,如果配置文件有,就从配置文件这里获取
protected String getName() { if (this.name == null) { FilterConfig config = getFilterConfig(); if (config != null) { this.name = config.getFilterName(); } } return this.name; }
OncePerRequestFilter这个抽象类继承自NameableFilter,这个过滤器的作用是保证一次请求只走一次过滤器链.
Filter base class that guarantees to be just executed once per request,
on any servlet container. It provides a {@link #doFilterInternal}
method with HttpServletRequest and HttpServletResponse arguments.
在这个类中,它自己重写了doFilter方法,这个就是从Filter这个接口一路下来神秘消失的DoFilter,在这里它被重新定义赋予了新的功能,来看代码
/** * This {@code doFilter} implementation stores a request attribute for * "already filtered", proceeding without filtering again if the * attribute is already there. * * @see #getAlreadyFilteredAttributeName * @see #shouldNotFilter * @see #doFilterInternal */ public final void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws ServletException, IOException { String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); if ( request.getAttribute(alreadyFilteredAttributeName) != null ) { log.trace("Filter '{}' already executed. Proceeding without invoking this filter.", getName()); filterChain.doFilter(request, response); } else //noinspection deprecation if (/* added in 1.2: */ !isEnabled(request, response) || /* retain backwards compatibility: */ shouldNotFilter(request) ) { log.debug("Filter '{}' is not enabled for the current request. Proceeding without invoking this filter.", getName()); filterChain.doFilter(request, response); } else { // Do invoke this filter... log.trace("Filter '{}' not yet executed. Executing now.", getName()); request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); try { doFilterInternal(request, response, filterChain); } finally { // Once the request has finished, we're done and we don't // need to mark as 'already filtered' any more. request.removeAttribute(alreadyFilteredAttributeName); } } }
先看说明:这个Filter的实现类存储了已经通过滤过的请求中的属性,再次访问的时候,如果属性已经在这里存在了,就不会通过这个过滤器了
可以说这个就是OncePerRequestFilter的核心.
方法内部,第一句String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName();需要获取已经通过过滤器的访问属性名,这是另一个方法
protected String getAlreadyFilteredAttributeName() { String name = getName(); if (name == null) { name = getClass().getName(); } return name + ALREADY_FILTERED_SUFFIX; }
getName()就是NameableFilter的getName(),也就是获取配置文件中过滤器的名字.如果从配置文件中获取不到,就通过反射直接从源文件中获取这个过滤器的名字.
在返回时,在这个名字加上后缀+ ALREADY_FILTERED_SUFFIX,这个 ALREADY_FILTERED_SUFFIX实际上是".FILTERED",这个单词代表已经通过的.
好了现在已经获取到已经通过过滤器的访问属性名了,接下来看后边的代码,开始进行比较了,如果请求中包含这个属性,那么久直接去执行过滤器链中后边的过滤器,
然后else if(!isEnabled(request, response) ||shouldNotFilter(request)),这两个条件是什么意思?
先来看第一个,isEnabled(request, response),看看这个请求是否被允许,默认返回的是true,表示当前过滤器要执行,request和response直接进入过滤器链的下一个.
第二个是个过期方法,返回的是个false;
所以这个条件合起来的意思就是在说如果这个过滤器不执行,就直接进入过滤器链的下一个过滤器.
官方注释: log.trace("Filter '{}' already executed. Proceeding without invoking this filter.", getName());已经执行过了,不执行这个过滤器.
剩下的就让没有访问过的走这个过滤器,执行doFilterInternal,最后,使用finally修饰,在请求中一定要移除已经通过过滤器的访问属性名.
AbstractShiroFilter是有一个抽象类,这个类的干了非常多的事,而且是很有必要的。ShiroFilte这个类是Shiro的入口,很多关键的方法都在AbstractShiroFilter中实现。
这个抽象类下有三个重要的属性:securityManager,filterChainResolver,staticSecurityManagerEnabled,并分别添加了getset方法
WebSecurityManager接口继承自SecurityManager,为这个过滤器引入SecurityManager;
FilterChainResolver接口用来判断哪一个过滤器是和请求或相应相匹配的.
staticSecurityManagerEnabled是一个boolean;是否为静态内存创建安全管理器,默认是false;
然后看几个重要的方法:
1:init():初始化过滤器,没有任何实现,等待ShiroFIlter继承
public void init() throws Exception { }
2:createSubject(ServletRequest request, ServletResponse response):创建网络访问的主体
protected WebSubject createSubject(ServletRequest request, ServletResponse response) { return new WebSubject.Builder(getSecurityManager(), request, response).buildWebSubject(); }
3:doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain):执行内部过滤器
protected void doFilterInternal(ServletRequest servletRequest, ServletResponse servletResponse, final FilterChain chain) throws ServletException, IOException { Throwable t = null; try { final ServletRequest request = prepareServletRequest(servletRequest, servletResponse, chain); final ServletResponse response = prepareServletResponse(request, servletResponse, chain); final Subject subject = createSubject(request, response); //noinspection unchecked subject.execute(new Callable() { public Object call() throws Exception { updateSessionLastAccessTime(request, response); executeChain(request, response, chain); return null; } }); } catch (ExecutionException ex) { t = ex.getCause(); } catch (Throwable throwable) { t = throwable; } if (t != null) { if (t instanceof ServletException) { throw (ServletException) t; } if (t instanceof IOException) { throw (IOException) t; } //otherwise it's not one of the two exceptions expected by the filter method signature - wrap it in one: String msg = "Filtered request failed."; throw new ServletException(msg, t); } }
如果还有印象,这个方法就是对OncePerRequestFilter中doFilterInternal方法的重写。这个方法 的核心是这两句:

创建subject,然后由subjuct执行,在匿名内部类中,调用call()方法,首先更新最后一次访问时间的Session,然后再执行过滤器链。
protected void executeChain(ServletRequest request, ServletResponse response, FilterChain origChain) throws IOException, ServletException { FilterChain chain = getExecutionChain(request, response, origChain); chain.doFilter(request, response); } }
这个里边调用了自己的getExecutionChain(request, response, origChain),来看看这个方法说了什么
protected FilterChain getExecutionChain(ServletRequest request, ServletResponse response, FilterChain origChain) { FilterChain chain = origChain; FilterChainResolver resolver = getFilterChainResolver(); if (resolver == null) { log.debug("No FilterChainResolver configured. Returning original FilterChain."); return origChain; } FilterChain resolved = resolver.getChain(request, response, origChain); if (resolved != null) { log.trace("Resolved a configured FilterChain for the current request."); chain = resolved; } else { log.trace("No FilterChain configured for the current request. Using the default."); } return chain; }
从过滤器链解析器获取解析到的结果,然后得到过滤器链,将这个过滤器链返回。
ShiroFilter这个类前边已经说过了,它是FilterShiro的入口,它继承自AbstractShiroFilter,并且不是一个抽象类。在它里边只有一个方法,init()
public void init() throws Exception { WebEnvironment env = WebUtils.getRequiredWebEnvironment(getServletContext()); setSecurityManager(env.getWebSecurityManager()); FilterChainResolver resolver = env.getFilterChainResolver(); if (resolver != null) { setFilterChainResolver(resolver); } }
这个init()方法是对它父类init()方法的重写,来看看它究竟干了什么事。
第一步:通过WebUtils这个工具类获取RequiredWebEnvironment,这些信息到保存在servletContext上下文对象中,所以可以直接获取。
得到一个WebEnvironment对象,这个对象是一个接口,继承自Environment这个接口
public interface WebEnvironment extends Environment { /** * Returns the web application's {@code FilterChainResolver} if one has been configured or {@code null} if one * is not available. * * @return the web application's {@code FilterChainResolver} if one has been configured or {@code null} if one * is not available. */ FilterChainResolver getFilterChainResolver(); /** * Returns the {@code ServletContext} associated with this {@code WebEnvironment} instance. A web application * typically only has a single {@code WebEnvironment} associated with its {@code ServletContext}. * * @return the {@code ServletContext} associated with this {@code WebEnvironment} instance. */ ServletContext getServletContext(); /** * Returns the web application's security manager instance. * * @return the web application's security manager instance. */ WebSecurityManager getWebSecurityManager(); }
public interface Environment { /** * Returns the application's {@code SecurityManager} instance. * * @return the application's {@code SecurityManager} instance. */ SecurityManager getSecurityManager(); }
从WebEnvironment和Environment这两个接口的方法可以知道,这俩其实就是获取SecurityManager,ServletContext、FilterChainResolver
第二步:从WebEnvironment对象中获取WebSecurityManager,并把它设置为SecurityManager。这个setSecurityManager的方法,是AbstractShiroFilter的,于是就有了SecurityManager。
第三步:从WebEnvironment对象中获取FilterChainResolver,如果这个FilterChainResolver不是空,调用setFilterChainResolver方法,这个方法也是AbstractShiroFilter的,于是就有了FilterChainResolver。
既然说到shiroFilter是入口了,它怎么就成了入口了呢?
让我重新看看web.xml
<filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
配置文件中定义了filter的名字是shiroFilter,并且映射到了org.springframework.web.filter.DelegatingFilterProxy。
DelegatingFilterProxy是对servlet filter的代理,它可以直接把shiroFilter交由spring容器管理,那么是怎么做到的呢?来看源码吧。因为这个项目使用的spring版本是4.0.2,所以需要从github找到相对应的版本源码包导入。

这个类继承自GenericFilterBean,然后GenericFilterBean呢?

这个抽象类实现了Filter,所以在servlet初始化之前,是首先调用filter的init()方法,GenericFilterBean的主要作用就是把Filter的初始化参数自动的set到继承于它的的filter中去。
把spring容器中的filter和spring中的bean进行关联。
public final void init(FilterConfig filterConfig) throws ServletException { Assert.notNull(filterConfig, "FilterConfig must not be null"); if (logger.isDebugEnabled()) { logger.debug("Initializing filter '" + filterConfig.getFilterName() + "'"); } this.filterConfig = filterConfig; // Set bean properties from init parameters. try { PropertyValues pvs = new FilterConfigPropertyValues(filterConfig, this.requiredProperties); BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(filterConfig.getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, this.environment)); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { String msg = "Failed to set bean properties on filter '" + filterConfig.getFilterName() + "': " + ex.getMessage(); logger.error(msg, ex); throw new NestedServletException(msg, ex); } // Let subclasses do whatever initialization they like. initFilterBean(); if (logger.isDebugEnabled()) { logger.debug("Filter '" + filterConfig.getFilterName() + "' configured successfully"); } }
所以,在spring_shiro中这样配置,它才可以被识别:
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="/"/> <property name="filters"> <map> <!--<entry key="authc" value-ref="authorizationFilter"/>--> <entry key="authc" value-ref="authenticationFilter"/> </map> </property> </bean>
在这里,可以看到,shiFilter对应的class并不是我们自定义的filter,而是ShiroFilterFactoryBean
这是个工厂类,所以我们可以配置多个自定义的过滤器,组成过滤器链。值得一提的是,
ShiroFilter在继续Servlet容器的过滤器链执行之前,会先走shiro自己的过滤器体系。
好了,接下来我们要继续梳理我们的shiro过滤器了。紧接着ShiroFilter,接下来我们要看的事AdviceFilter这个类
AdviceFilter这个抽象类提供了对AOP风格的支持,这一赖于其中的几个方法:
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { return true; } protected void postHandle(ServletRequest request, ServletResponse response) throws Exception { } public void afterCompletion(ServletRequest request, ServletResponse response, Exception exception) throws Exception { }
---以下摘自《跟我学shiro》第八节。
preHandler:相当于AOP的前置增强,在过滤器执行前先进性执行,如果返回true,继续执行过滤器链,否则终端后续的过滤器链的执行直接返回
,进行预处理(如基于表单的身份验证和授权)。
postHandle:相当于AOP的后置返回增强,在过滤器链执行之后在进行执行,进行后处理(记录执行时间等操作)。
afterCompletion:相当于AOP中的后置最终增强,不论有没有异常都会执行,可以进行清理资源(比如解除Subject和线程的绑定)
PathMatchingFilter提供了基于Ant风格的请求路径匹配功能和拦截器参数解析的功能,如“roles[admin,user]”自动根据“,”分割解析到一个路径参数配置并绑定到相应的路径。

protected boolean pathsMatch(String path, ServletRequest request) { String requestURI = getPathWithinApplication(request); log.trace("Attempting to match pattern '{}' with current requestURI '{}'...", path, requestURI); return pathsMatch(path, requestURI); } protected boolean pathsMatch(String pattern, String path) { return pathMatcher.matches(pattern, path); }
PathMath该方法用于path与请求路径进行匹配的方法,如果匹配返回rue;
protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return true; }
OnPreHandle:默认返回true.在preHandle中,当pathsMatch匹配一个路径后,会调用opPreHandler方法并将路径绑定参数配置传给mappedValue;然后可以在这个方法中进行一些验证(如角色授权),如果验证失败可以返回false中断流程;默认返回true;也就是说子类可以只实现onPreHandle即可,无须实现preHandle。如果没有path与请求路径匹配,默认是通过的(即preHandle返回true)
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception { if (this.appliedPaths == null || this.appliedPaths.isEmpty()) { if (log.isTraceEnabled()) { log.trace("appliedPaths property is null or empty. This Filter will passthrough immediately."); } return true; } for (String path : this.appliedPaths.keySet()) { // If the path does match, then pass on to the subclass implementation for specific checks //(first match 'wins'): if (pathsMatch(path, request)) { log.trace("Current requestURI matches pattern '{}'. Determining filter chain execution...", path); Object config = this.appliedPaths.get(path); return isFilterChainContinued(request, response, path, config); } } //no path matched, allow the request to go through: return true; }
preHandle:从路径配置中获取的对象appliedPaths(Map),如果不是空,就依次获取里边的路径.如果能和path匹配上,就放行.
AccessControlFilter提供了访问控制的基础功能;比如是否允许访问/当访问拒绝时如何处理等.
abstract boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception; boolean onAccessDenied(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception; abstract boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception;
isAccessAllowed:表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回true,否则false;
onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。
onPreHandle会自动调用这两个方法决定是否继续处理:
boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { return isAccessAllowed(request, response, mappedValue) || onAccessDenied(request, response, mappedValue); }
另外AccessControlFilter还提供了如下方法用于处理如登录成功后/重定向到上一个请求:
void setLoginUrl(String loginUrl) //身份验证时使用,默认/login.jsp String getLoginUrl() Subject getSubject(ServletRequest request, ServletResponse response) //获取Subject实例 boolean isLoginRequest(ServletRequest request, ServletResponse response)//当前请求是否是登录请求 void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException //将当前请求保存起来并重定向到登录页面 void saveRequest(ServletRequest request) //将请求保存起来,如登录成功后再重定向回该请求 void redirectToLogin(ServletRequest request, ServletResponse response) //重定向到登录页面

浙公网安备 33010602011771号