SSO单点登陆基于Session简单实现

SSO单点登陆基于Session简单实现

单点登陆活动图

SSO单点登陆基于Session简单实现

单点登陆活动图

废话不多说直接上代码咯,什么好处,实现原理相关资料baidu去,基本都一样,就不复制了

1 服务端代码

1.1 登陆跳转注销代码

controller层代码


package com.ffcs.sso.controller;


import javax.servlet.http.HttpSession;


import org.apache.commons.lang.StringUtils;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Controller;

import org.springframework.ui.Model;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RequestMethod;

import org.springframework.web.bind.annotation.RequestParam;

import org.springframework.web.servlet.ModelAndView;


import com.ffcs.sso.pojo.User;

import com.ffcs.sso.service.LogoutManagerService;

import com.ffcs.sso.service.TicketManagerService;

import com.ffcs.sso.service.UserManagerService;

import com.ffcs.sso.utils.Constant;

/**

*

* @author damon

*

*/

@Controller

public class SSOController {


private final static Logger LOGGER=LoggerFactory.getLogger(SSOController.class);


@Autowired

private TicketManagerService ticketManagerService;


@Autowired

private UserManagerService userManagerService;


@Autowired

private LogoutManagerService logoutManagerService;


/**

* 主页

* @return

*/

@RequestMapping(value="manager/index",method=RequestMethod.GET)

public String index(){


return "index";

}

/**

* 登陆首页

* @return

*/

@RequestMapping(value="login",method=RequestMethod.GET)

public ModelAndView loginIndex(String queryParams,String targetUrl,ModelAndView modelAndView){


modelAndView.addObject("queryParams", queryParams);


modelAndView.addObject("targetUrl", targetUrl);


modelAndView.setViewName("login");


return modelAndView;

}


@RequestMapping(value="login",method=RequestMethod.POST)

public String login(@RequestParam(required = true) String account, @RequestParam(required = true) String password,


String targetUrl, String queryParams, Model model, HttpSession session) {


User user = userManagerService.getUserInfo(account, password);


if (user != null) {


String homeCookieId = session.getId();


session.setAttribute(Constant.SSO_IS_LOGIN, true);


session.setAttribute(Constant.SSO_LOGIG_INFO, account);


logoutManagerService.saveLoginUserInfo(homeCookieId, user);


if (StringUtils.isNotBlank(targetUrl)) {


// 生成targetURL对应的票


String ticket = ticketManagerService.generateTicket(targetUrl, homeCookieId);


String params = StringUtils.isNotBlank(queryParams) ? "&" + queryParams : "";


LOGGER.info("################### 用户账号:[{}] 对应主站cookieId:[{}] 登陆系统 :[{}]", account, homeCookieId, targetUrl);


return "redirect:" + targetUrl + "?" + Constant.SSO_TICKET + "=" + ticket + params;


}

return "redirect:manager/index";


} else {


model.addAttribute("error", "用户名或密码错误!");


if (StringUtils.isNotBlank(targetUrl)) {


model.addAttribute("targetUrl", targetUrl);

}

return "login";

}

}


/**

* 重定向到子系统并生成票

* @param targetUrl

* @param queryParams

* @param modelAndView

* @param session

* @return

*/

@RequestMapping(value="redirect",method=RequestMethod.GET)

public ModelAndView redirect(@RequestParam(value="targetUrl",required=true)String targetUrl,


String queryParams,ModelAndView modelAndView,HttpSession session) {


if(session.getAttribute(Constant.SSO_IS_LOGIN)==null){


modelAndView.setViewName("redirect:login");


modelAndView.addObject("targetUrl", targetUrl);


modelAndView.addObject("queryParams", queryParams);


//重定向到login方法,带上目标网页地址


}else{


String homeCookieId=session.getId();


String account=(String) session.getAttribute(Constant.SSO_LOGIG_INFO);


//生成targetURL对应的票


String ticket=ticketManagerService.generateTicket(targetUrl,homeCookieId);


String params=StringUtils.isNotBlank(queryParams)?"&"+queryParams:"";


modelAndView.setViewName("redirect:" + targetUrl + "?" + Constant.SSO_TICKET + "=" + ticket + params);


LOGGER.info("############### 用户账号:[{}] 主站cookieId:[{}] 重定向到系统:[{}] 对应ticket:[{}]", account, homeCookieId, targetUrl, ticket);

}


return modelAndView;

}


/**

* 单点注销

* @param session

* @return

*/

@RequestMapping(value="logout",method=RequestMethod.GET)

public String logout(HttpSession session){


if(session.getAttribute(Constant.SSO_IS_LOGIN)!=null){


String cookieId=session.getId();


String account=(String) session.getAttribute(Constant.SSO_LOGIG_INFO);


logoutManagerService.logout(cookieId);


session.invalidate();


LOGGER.info("########### 单点退出用户账号:[{}] 对应主站cookieId为:[{}] ",account,cookieId);

}

return "redirect:login";

}


}


1.2 生成票管理接口代码(对外restful webservice发布)

package com.ffcs.sso.service;


import java.util.UUID;


import net.rubyeye.xmemcached.MemcachedClient;


import org.apache.commons.lang.StringUtils;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.stereotype.Service;


import com.ffcs.sso.exception.SSOException;

import com.ffcs.sso.pojo.TicketInfo;

import com.ffcs.sso.pojo.TicketResponseInfo;

import com.ffcs.sso.pojo.User;

import com.ffcs.sso.utils.Constant;

/**

*

* 票管理

*

*

* damon

*

*/

@Service

public class TicketManagerServiceImpl implements TicketManagerService{


@Autowired

@Qualifier(value="memcachedSSOClient")

private MemcachedClient memcachedClient;

@Autowired

private LogoutManagerService logoutManagerService;


@Override

public String generateTicket(String target,String homeCookieId){


if(StringUtils.isBlank(target)){


throw new IllegalArgumentException("target不可以为空!");


}


if(StringUtils.isBlank(homeCookieId)){


throw new IllegalArgumentException("homeCookieId不可以为空!");


}


String ticket=UUID.randomUUID().toString();


try {


memcachedClient.set(ticket, Constant.MAX_TICKET_INACTIVE_INTERVAL, new TicketInfo(ticket,target,homeCookieId));


return ticket;


} catch (Exception e) {


throw new SSOException(e);

}

}

@Override

public TicketResponseInfo validateTicket(String ticket,String target,

String subCookieId,String subCookieName,String subLogoutPath){

if(StringUtils.isBlank(ticket)){

throw new IllegalArgumentException("ticket不可以为空!");

}

if(StringUtils.isBlank(target)){

throw new IllegalArgumentException("target不可以为空!");

}

if(StringUtils.isBlank(subCookieId)){

throw new IllegalArgumentException("subCookieId不可以为空!");

}

if(StringUtils.isBlank(subCookieName)){

throw new IllegalArgumentException("subCookieName不可以为空!");

}

if(StringUtils.isBlank(subLogoutPath)){

throw new IllegalArgumentException("subLogoutPath不可以为空!");

}

try {

TicketInfo ticketInfo = memcachedClient.get(ticket);

if(ticketInfo==null||!target.equals(ticketInfo.getTargetUrl())){

//返回空验证不通过

return new TicketResponseInfo(false);

}

//删除票保存的临时信息


memcachedClient.delete(ticket);


String homeCookieId=ticketInfo.getHomeCookieId();


//验证后保存登出信息(原本验证和登出信息分开提交,一并提交减少访问次数)


logoutManagerService.saveSubWebsiteLogouInfo(homeCookieId, subLogoutPath, subCookieId, subCookieName);

User user= logoutManagerService.getLoginUserInfo(homeCookieId);

return new TicketResponseInfo(true,user.getAccount(),homeCookieId);

} catch (Exception e) {

throw new SSOException(e);

}

}

}

1.3 单点退出接口(对外restful webservice发布)

package com.ffcs.sso.service;

import java.util.Set;

import net.rubyeye.xmemcached.MemcachedClient;

import org.apache.commons.lang.StringUtils;

import org.apache.http.HttpResponse;

import org.apache.http.client.HttpClient;

import org.apache.http.client.methods.HttpPost;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.beans.factory.annotation.Qualifier;

import org.springframework.stereotype.Service;

import com.ffcs.sso.exception.SSOException;

import com.ffcs.sso.pojo.ActivationInfo;

import com.ffcs.sso.pojo.LogoutInfo;

import com.ffcs.sso.pojo.User;

import com.ffcs.sso.utils.Constant;

/**

* 单点退出

*

* damon

*/


@Service

public class LogoutManagerServiceImpl implements LogoutManagerService {

private static final Logger LOGGER=LoggerFactory.getLogger(LogoutManagerServiceImpl.class);

private final String LOGOUT_PREFIX="LOGOUT_";

@Autowired

@Qualifier("memcachedSSOClient")

private MemcachedClient memcachedClient;

@Autowired

private HttpClient httpClient;

@Override

public boolean saveSubWebsiteLogouInfo(String homeCookieId, String logoutPath,String subCookieId,String subCookieName) {

if(StringUtils.isBlank(homeCookieId)){

throw new IllegalArgumentException("homeCookieId 不可以为空!");

}

if(StringUtils.isBlank(logoutPath)){

throw new IllegalArgumentException("logoutPath 不可以为空!");

}

if(StringUtils.isBlank(subCookieId)){

throw new IllegalArgumentException("subWebSite 不可以为空!");

}

ActivationInfo info=null;

Set<logoutinfo> logoutInfos=null; /<logoutinfo>

try {

info=memcachedClient.get(LOGOUT_PREFIX + homeCookieId);

if(info==null){

info=new ActivationInfo();

}

logoutInfos=info.getLogoutInfo();

logoutInfos.add(new LogoutInfo(logoutPath,subCookieId,subCookieName));

info.setLogoutInfo(logoutInfos);

memcachedClient.set(LOGOUT_PREFIX + homeCookieId, Constant.MAX_USERINFO_INACTIVE_INTERVAL, info);

LOGGER.debug("############### 保存子站登出信息 ,子站登出地址 url:{}, 子站cookie:{}, 主站cookie:{}", logoutPath, subCookieId, homeCookieId);

return true;

} catch (Exception e) {

throw new SSOException(e);

}

}

@Override

public void logout(String homeCookieId) {

if(StringUtils.isBlank(homeCookieId)){

throw new IllegalArgumentException("cookieId 不可以为空!");

}

ActivationInfo info=null;

Set<logoutinfo> logoutInfos=null; /<logoutinfo>

try {

info = memcachedClient.get(LOGOUT_PREFIX+homeCookieId);

memcachedClient.delete(LOGOUT_PREFIX+homeCookieId);

if(info==null|| (logoutInfos=info.getLogoutInfo())==null){

LOGGER.debug("############## 用户 cookieId:[{}] 未登陆任何系统 !",homeCookieId);

return;

}

} catch (Exception e) {

LOGGER.error("############### Memcached获取单点登出信息失败", e);

return;

}

for (LogoutInfo logoutInfo:logoutInfos) {

HttpPost post=null;

try {

post = new HttpPost(logoutInfo.getLogoutPath());

post.setHeader("charset", "UTF-8");

post.setHeader("Connection", "close");

//添加cookie模拟回调时候子系统能找到session

post.setHeader("Cookie",logoutInfo.getSubCookieName()+"="+logoutInfo.getSubCookieId());

HttpResponse response = httpClient.execute(post);

LOGGER.debug("########## 登出子站 :[{}] 主站cookie:[{}] 子站cookie:[{}] 登出返回状态码:[{}] ",

logoutInfo.getLogoutPath(), homeCookieId, logoutInfo.getSubCookieId(), response.getStatusLine().getStatusCode());

} catch (Exception e) {

LOGGER.error("########## 注销子系统失败,子系统信息:{}",logoutInfo.toString(),e);

} finally {

if(post!=null){

post.releaseConnection();

}

}

}

}

@Override

public void saveLoginUserInfo(String homeCookieId,User userInfo){

try {

ActivationInfo info=memcachedClient.get(LOGOUT_PREFIX+homeCookieId);

if(info==null){

info=new ActivationInfo();

}

info.setUserInfo(userInfo);

memcachedClient.set(LOGOUT_PREFIX+homeCookieId, Constant.MAX_USERINFO_INACTIVE_INTERVAL, info);

} catch (Exception e) {

throw new SSOException("############# 保存用户登陆信息失败 ",e);

}


}

@Override

public User getLoginUserInfo(String homeCookieId){

ActivationInfo info = null;

try {

info=memcachedClient.get(LOGOUT_PREFIX+homeCookieId);

if(info!=null){

return info.getUserInfo();

}

throw new RuntimeException("找不到该cookie:["+homeCookieId+"]的用户信息");

} catch (Exception e) {

throw new SSOException("############# 获取登陆用户信息失败 ####",e);

}

}

@Override

public boolean updateUserInfoTimeout(String homeCookieId) {

if(StringUtils.isBlank(homeCookieId)){

throw new IllegalArgumentException("homeCookieId 不可以为空!");

}

try {

return memcachedClient.touch(LOGOUT_PREFIX+homeCookieId, Constant.MAX_USERINFO_INACTIVE_INTERVAL);

} catch (Exception e) {

LOGGER.error("############ 定时更新主站登陆用户信息失败 ",e);

}

return false;

}

}

2 客户端代码

2.1 过滤器代码

package com.ffcs.sso.filter;

import java.io.IOException;

import java.net.URLEncoder;

import java.util.Iterator;

import java.util.Map;

import java.util.Map.Entry;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import javax.servlet.http.HttpSession;

import org.apache.commons.lang.StringUtils;

import org.apache.http.client.methods.HttpGet;

import org.apache.http.client.methods.HttpPut;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import com.ffcs.sso.exception.HttpRequestException;

import com.ffcs.sso.pojo.TicketResponseInfo;

import com.ffcs.sso.utils.Constant;

import com.ffcs.sso.utils.HttpClientUtils;

import com.ffcs.sso.utils.JsonUtil;

public class SSOFilter implements Filter {

private final static Logger LOGGER=LoggerFactory.getLogger(SSOFilter.class);

private String ticketValidateURL;

private String redirectLoginURL;

private String updateUserInfoTimeOutURL;

@Override

public void destroy() {

}

@Override

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;

HttpServletResponse response = (HttpServletResponse) res;

HttpSession session=request.getSession();

if(session.getAttribute(Constant.SSO_IS_LOGIN)==null){

String targetUrl = request.getRequestURL().toString();

String ticket = request.getParameter(Constant.SSO_TICKET);

//子站单点退出路径

String subLogoutPath = request.getScheme()+"://"+request.getServerName()+":"

+request.getServerPort()+ request.getContextPath() +"/"+Constant.SSO_LOGOUT_SUFFIX;

TicketResponseInfo responseInfo=new TicketResponseInfo(false);

if(StringUtils.isNotBlank(ticket)){

//校验票如果校验通过同时保存子站的登出信息(减少提交次数,原本验证成功后在提交登出信息),并返回主站的cookieId和主站登陆的账号

responseInfo=validateTicketInfo(ticket,targetUrl,Constant.SSO_SUB_COOKIE_NAME,session.getId(),subLogoutPath);

}

if(!responseInfo.isSuccess()){

String queryParams =this.getQureyParams(request);

String params=StringUtils.isNotBlank(queryParams)?"&queryParams="+URLEncoder.encode(queryParams,"UTF-8"):"";

//重定向到主站判断系统是否登陆过

response.sendRedirect(redirectLoginURL + "?targetUrl=" + targetUrl + params);

LOGGER.debug("############## 重定向到主系统:" + redirectLoginURL + "?targetUrl=" + targetUrl + params);

return;

}

session.setAttribute(Constant.SSO_IS_LOGIN, true);

session.setAttribute(Constant.SSO_ACCOUNT, responseInfo.getAccount());

session.setAttribute(Constant.SSO_HOME_COOKIEID, responseInfo.getHomeCookieId());

}

Long updateInterval = (System.currentTimeMillis() - session.getLastAccessedTime()) / 1000;

if( updateInterval > Constant.SSO_HOME_USERINFO_UPDATE_TIMEOUT){

//更新主站用户信息超时时间

updateUserInfoTimeOut((String)session.getAttribute(Constant.SSO_HOME_COOKIEID));

LOGGER.debug("############## 更新主站用户信息超时时间,间隔{}秒 ########",Constant.SSO_HOME_USERINFO_UPDATE_TIMEOUT);

}

chain.doFilter(request, response);

return;

}

private String getQureyParams(HttpServletRequest request) {

StringBuilder queryParams=new StringBuilder();

Map<string> map=request.getParameterMap(); /<string>

Iterator<entry>> params=map.entrySet().iterator(); /<entry>

boolean tag=false;

while (params.hasNext()) {

Map.Entry<string> entry = params.next(); /<string>

if(!entry.getKey().equals(Constant.SSO_TICKET)){

String[] values=entry.getValue();

for(String value:values){

if(tag){

queryParams.append("&");

}

queryParams.append(entry.getKey()).append("=").append(value);

}

tag=true;

}

}

return queryParams.toString();

}

private void updateUserInfoTimeOut(String homeCookieId){

HttpPut put=new HttpPut(updateUserInfoTimeOutURL);

//添加cookie主站那边能够找到session

put.setHeader("Cookie",Constant.SSO_HOME_COOKIE_NAME+"="+homeCookieId);

put.setHeader("accept", "text/plain; charset=UTF-8");

try {

HttpClientUtils.getResponse(put);

} catch (HttpRequestException e) {

LOGGER.error("###################### 定时更新主站用户信息失败 ",e);

}

}

/**

* 根据ticket验证是否正确,验证通过返回主站的cookieId

*

* @param ticket

*

* @param target

*

* @return 返回验证空失败 成功cookieId

*/

private TicketResponseInfo validateTicketInfo(String ticket, String target,String subCookieName,

String subCookieId, String subLogoutPath) {

HttpGet httpget = new HttpGet(ticketValidateURL + "/"+ ticket + "?target=" + target

+"&subCookieId="+subCookieId+"&subCookieName="+subCookieName+"&subLogoutPath="+subLogoutPath);

httpget.setHeader("accept", "application/json; charset=UTF-8");

String result;

try {

result = HttpClientUtils.getResponse(httpget);

return JsonUtil.fromJson(result, TicketResponseInfo.class);

} catch (HttpRequestException e) {

throw new RuntimeException("############### 子站验证ticket失败 ",e);

}


}

@Override

public void init(FilterConfig filterConfig) {

if (filterConfig != null) {

LOGGER.info("####### SSOFilter:Initializing filter");

}

this.ticketValidateURL = filterConfig.getInitParameter("ticketValidateURL");

this.redirectLoginURL=filterConfig.getInitParameter("redirectLoginURL");

this.updateUserInfoTimeOutURL=filterConfig.getInitParameter("updateUserInfoTimeOutURL");

}

}

2.2 登出过滤器代码

比较简单,服务端通过httpclient回调这个路口进行退出,服务端必须把客户端登陆的cookie返回过来,不然找不到session

package com.ffcs.sso.filter;

import java.io.IOException;

import javax.servlet.Filter;

import javax.servlet.FilterChain;

import javax.servlet.FilterConfig;

import javax.servlet.ServletException;

import javax.servlet.ServletRequest;

import javax.servlet.ServletResponse;

import javax.servlet.http.HttpServletRequest;

public class LogoutFilter implements Filter{

@Override

public void destroy() {


}

@Override

public void doFilter(ServletRequest req, ServletResponse res,FilterChain chain) throws IOException, ServletException {

HttpServletRequest request = (HttpServletRequest) req;

request.getSession().invalidate();

return;

}

@Override

public void init(FilterConfig filterConfig) {

}

}

2.3 客户端web.xml配置

<web-app>

xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee"

xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"

version="3.0">

<filter> /<filter>

<filter-name>DisableUrlSessionFilter/<filter-name>

<filter-class>com.ffcs.sso.filter.DisableUrlSessionFilter/<filter-class>

<filter-mapping> /<filter-mapping>

<filter-name>DisableUrlSessionFilter/<filter-name>

<url-pattern>/*/<url-pattern>

<filter> /<filter>

<filter-name>sessionFilter/<filter-name>

<filter-class>org.springframework.web.filter.DelegatingFilterProxy/<filter-class>

<filter-mapping> /<filter-mapping>

<filter-name>sessionFilter/<filter-name>

<url-pattern>/*/<url-pattern>

<filter> /<filter>

<filter-name>LogoutFilter/<filter-name>

<filter-class>com.ffcs.sso.filter.LogoutFilter/<filter-class>

<filter-mapping> /<filter-mapping>

<filter-name>LogoutFilter/<filter-name>

<url-pattern>/SSO_LOGOUT/<url-pattern>

<listener> /<listener>

<description>spring监听器/<description>

<listener-class>org.springframework.web.context.ContextLoaderListener/<listener-class>

<listener> /<listener>

<listener-class>org.springframework.web.util.IntrospectorCleanupListener/<listener-class>

<context-param> /<context-param>

<param-name>contextConfigLocation/<param-name>

<param-value>classpath:spring.xml/<param-value>

<filter> /<filter>

<description>字符集过滤器/<description>

<filter-name>encodingFilter/<filter-name>

<filter-class>org.springframework.web.filter.CharacterEncodingFilter/<filter-class>

<init-param> /<init-param>

<description>字符集编码/<description>

<param-name>encoding/<param-name>

<param-value>UTF-8/<param-value>

<filter-mapping> /<filter-mapping>

<filter-name>encodingFilter/<filter-name>

<url-pattern>/*/<url-pattern>

<filter> /<filter>

<filter-name>SSOFilter/<filter-name>

<filter-class>com.ffcs.sso.filter.SSOFilter/<filter-class>

<init-param> /<init-param>

<param-name>ticketValidateURL/<param-name>

<param-value>http://127.0.0.1:8080/SSO/webservice/ticket/<param-value>

<init-param> /<init-param>

<param-name>updateUserInfoTimeOutURL/<param-name>

<param-value>http://127.0.0.1:8080/SSO/webservice/userinfo/timeout/<param-value>

<init-param> /<init-param>

<param-name>redirectLoginURL/<param-name>

<param-value>http://127.0.0.1:8080/SSO/redirect/<param-value>

<filter-mapping> /<filter-mapping>

<filter-name>SSOFilter/<filter-name>

<url-pattern>/*/<url-pattern>

<servlet> /<servlet>

<servlet-name>springMvc/<servlet-name>

<servlet-class>org.springframework.web.servlet.DispatcherServlet/<servlet-class>

<init-param> /<init-param>

<param-name>contextConfigLocation/<param-name>

<param-value>classpath:spring-mvc.xml/<param-value>

<servlet-mapping> /<servlet-mapping>

<servlet-name>springMvc/<servlet-name>

<url-pattern>//<url-pattern>

<filter> /<filter>

<filter-name>HiddenHttpMethodFilter/<filter-name>

<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter/<filter-class>

<filter-mapping> /<filter-mapping>

<filter-name>HiddenHttpMethodFilter/<filter-name>

<servlet-name>springMvc/<servlet-name>

<error-page> /<error-page>

<error-code>404/<error-code>

<location>/error/404.jsp/<location>

<error-page> /<error-page>

<error-code>500/<error-code>

<location>/error/500.jsp/<location>

/<web-app>

<servlet-mapping> /<servlet-mapping>

<servlet-name>springMvc/<servlet-name>

<url-pattern>//<url-pattern>

<filter> /<filter>

<filter-name>HiddenHttpMethodFilter/<filter-name>

<filter-class>org.springframework.web.filter.HiddenHttpMethodFilter/<filter-class>

<filter-mapping> /<filter-mapping>

<filter-name>HiddenHttpMethodFilter/<filter-name>

<servlet-name>springMvc/<servlet-name>

<error-page> /<error-page>

<error-code>404/<error-code>

<location>/error/404.jsp/<location>

<error-page> /<error-page>

<error-code>500/<error-code>

<location>/error/500.jsp/<location>

CSDN:https://blog.csdn.net/Sadlay/article/details/100041408


分享到:


相關文章: