idea基于springboot+redis+springSession+jsp实现简易SSO单点登录

2018-11-30 14:31:07220人围观
现象:web系统早已从久远单系统过度到现在的多系统,面对多系统时难道要用户一个一个的登录,如下图:系统的负责性由系统自己负责,而不是需要用户负责,无论多复杂的系统的对用户而言就是一个整体,也就是说用户

现象:

web系统早已从久远单系统过度到现在的多系统,面对多系统时难道要用户一个一个的登录,如下图:

系统的负责性由系统自己负责,而不是需要用户负责,无论多复杂的系统的对用户而言就是一个整体,也就是说用户访问web应用群要像单系统一样一次登录,一次注销就ok,如图:

  

单系统登录的解决方案虽然完美,单系统登录的核心是cookie,cookie会携带会话id在浏览器与web服务器保持会话,但是有一定限制就是cookie域(一般与网站的域名所对应),http访问网站是会携带与这个网站域说匹配的cookie,而不是所有cookie,早期的单点登录就是基于cookie共享,但是可行性不好,首先这种方式要保证群应用的顶级域名一致,其次要保证服务器开发技术要一致不然cookie的key值(tomcat为sessionid)不一致,无法保证会话,共享cookie不支持跨语言的技术平台登录,如java、php、.net,还有cookie本身就是不安全的  所以需要一种更好的解决方案也就是单点登录 名词解释: 单点登录:单点登录全称Single Sign On(以下简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录 相对于单系统登录,sso提供一个认证中心,只有认证中心接受用户名/或密码等安全信息,子系统不提供登录入口,只接受认证中心的间接授权,间接授权使用令牌实现(token),当用户登录某一个子系统,会转到认证中心,认证中心会验证用户名/密码等安全信息,验证通过生成令牌(token),生成的令牌会作为参数发送给各个子系统,子系统根据拿到的令牌(token)访问受保护的资源,当操作子系统其它资源时都会拿这个令牌(token)到认证中心验证,这个过程就是单点登录,如下图:

上图描述: 

 1、当用户通过浏览器访问系统1,拦截器发现用户并没有登录,跳转到认证中心,并将请求地址作为参数 

 2、sso认证中心首先会检查是否携带token,携带token跳转到token验证,没有就跳转到认证中心登陆界面 

 3、认证通过后生产t oken,产生全局会话,并拼接url(加上token)重定向到系统1地址

 4、系统1地址会携带token,之后再重新执行token验证方法,验证通过直接跳转到系统1首页

 5、访问系统2时,将携带token,此时执行token验证方法,验证通过后放行 

以下就一步一步实现单点登录,使用开发工具idea、使用技术springBoot+redis+springSession+jsp实现 

 1、新建项目使用spring.io提供的模板

2、首先要解决springBoot访问jsp问题,springBoot官方建议使用 theamleaf,如果使用想要使用jsp就要添加如下几个依赖

  

3、加入spring-redis主键依赖

  

工程搭建完毕,编码实现,首先是ssoserver(因为是简易版的所以sso-client与sso-server组合在一起)

 sso-server:     

1、拦截子系统的未等录的请求,跳转至认证中心     

2、校验携带的token是否有效    

 3、拦截子系统注销请求,销毁会话    

 4、验证用户登陆信息    

 5、创建令牌    

 6、创建与子系统间的会话 

 下面来实现ssoserver吧

ssoserver的工程构成:

1、springSession配置

2、ssoserver作用就是拦截子系统的请求所以使用filter实现:

public class SsoFilter implements Filter {
 //sso认证 
 private final String SSO_SERVER_URL = "http://localhost:8080/sso/auth"; 
 //sso token 验证 
 private final String SSO_VERIFI_URL="http://localhost:8080/sso/verifi";
 //sso注销 
 private final String SSO_LOGINOUT_URL="http://localhost:8080/sso/logout"; 
 @Override public void init(FilterConfig filterConfig) throws ServletException { } 
 @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { 
 //检查是否携带token.
 HttpServletRequest httpServletRequest =(HttpServletRequest) servletRequest; 
 HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
 String token = httpServletRequest.getParameter("token"); 
 Map<String,Object> info = new HashMap<>();
 //token不为空就验证
 if(token != null){ 
 boolean flag = verifi(servletRequest,SSO_VERIFI_URL,token); 
 if (flag){ filterChain.doFilter(servletRequest,servletResponse);
 return; 
 }else{ 
 info.put("info",token+"无效"); 
 info.put("code","10010");
 httpServletResponse.getWriter().write((Json2String(info))); return;
 }
 } 
 //如果有效并且已登录就放行到下个过滤器 
 HttpSession session=httpServletRequest.getSession();
 if (session.getAttribute("flag")!=null && (boolean)session.getAttribute("flag") == true){ 
 filterChain.doFilter(servletRequest,servletResponse);
 return; } 
 //获取注销的标记销毁全局会话 
 String loginOut = httpServletRequest.getParameter("logout"); 
 if (loginOut == "true"){ 
 httpServletResponse.sendRedirect(SSO_LOGINOUT_URL);
 }
 //没有token就跳转到认证中心 
 //当前请求地址 
 String callBackUrl = httpServletRequest.getRequestURL().toString(); StringBuilder authUrl = new StringBuilder(); authUrl.append(SSO_SERVER_URL).append("?callBackUrl=").append(callBackUrl); httpServletResponse.sendRedirect(authUrl.toString());
httpServletResponse.sendRedirect(authUrl.toString());
 }
 @Override public void destroy() { } 
/验证token方法 private boolean verifi(ServletRequest servletRequest,String url,String token){
 StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append(url).append("?token=").append(token); 
 //封装的ResTemplateUtil工具类发送get请求
 String result = RestTemplateUtil.get(servletRequest,stringBuilder.toString(),null);
 JSONObject jsonObject=JSONObject.parseObject(result); 
 if ( jsonObject.getString("code").equals("200")){
 return true; }else
 return false;
 } }
 private String Json2String(Object obj){
 if (obj == null){ 
 return null; 
 }
 String jsonString= JSONObject.toJSONString(obj); return jsonString;
 }

3、restTemplateutil封装,RestTemplate、 ResponseEntity、HttpEntity等用法自行百度:

public class RestTemplateUtil { 
 private static RestTemplate restTemplate = new RestTemplate();
 public static String get(ServletRequest request,String url,Map<String,?> params){
 ResponseEntity<String> responseEntity = request(request,url,HttpMethod.GET,params);
 return responseEntity.getBody(); 
 } 
 public static String post(ServletRequest request,String url,Map<String,?> params){ 
 ResponseEntity<String> responseEntity = request(request,url,HttpMethod.POST,params); 
 return responseEntity.getBody(); }
 private static ResponseEntity<String> request(ServletRequest request, String url, HttpMethod method,Map<String, ?> parmas){
 HttpServletRequest httpServletRequest = (HttpServletRequest) request; 
 HttpHeaders httpHeaders = new HttpHeaders(); 
 Enumeration<String> enumerations = httpServletRequest.getHeaderNames();
 while (enumerations.hasMoreElements()){
 String key =(String)enumerations.nextElement(); 
 String value = httpServletRequest.getHeader(key);
 httpHeaders.add(key,value); }
 HttpEntity<String> httpEntity = new HttpEntity<String>(parmas == null?null: JSONObject.toJSONString(parmas),httpHeaders); 
 ResponseEntity<String> res = restTemplate.exchange(url,method,httpEntity,String.class); 
 return res; 
 } 
}  

4、认证与验证中心:

public class AuthController { 
 //认证并生成令牌(token) 
 @RequestMapping("/auth") 
 public String auth(String userName, String password,String callBackUrl, Model model, HttpSession session,HttpServletRequest request){ 
 //生成token 
 String token = UUID.randomUUID().toString().substring(0,16); 
 if (userName == null&&password == null){
return "login";
 }
 if ("admin".equals(userName) && "admin".equals(password)){
 //记录登录状态 session.setAttribute("flag",true); 
 session.setAttribute("token",token);
 return "index"; }else{ 
 model.addAttribute("error","用户名或密码错误"); return "login";
 }
 } //验证令牌(token) @RequestMapping("/verifi") @ResponseBody 
 public JSONObject vifer(HttpServletRequest request,HttpSession session){
 //获取token String authToken = request.getParameter("token"); 
 String token = (String) session.getAttribute("token");
 JSONObject jsonObject = new JSONObject(); 
 if (token.equals(authToken) && token !=null){ 
 jsonObject.put("code","200"); 
 jsonObject.put("info","token认证成功");
 }else{
 jsonObject.put("code","400"); 
 jsonObject.put("info","token认证失败");
 }
 return jsonObject;
 } 
 //注销 @RequestMapping("/logout") @ResponseBody 
 public JSONObject logOut(HttpServletRequest request){ 
 JSONObject jsonObject = new JSONObject(); 
 HttpSession session = request.getSession();
 if (session == null){ 
 }else { 
 session.invalidate(); 
 jsonObject.put("info","注销成功");
 jsonObject.put("code", HttpStatus.OK);
 } 
 return jsonObject;
 }  

子系统需要使用ssoserver中核心的过滤器类,配置此过滤拦截请求

注册过滤器 

 @Configuration 
public class SSoFilterConfig { 
 @Bean
 public FilterRegistrationBean filterRegistrationBean(){
 FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); 
 filterRegistrationBean.setName("ssoFilter");
 filterRegistrationBean.addUrlPatterns("/*"); 
 filterRegistrationBean.addInitParameter("paramName", "paramValue"); 
 filterRegistrationBean.setFilter(ssoFilter());
 return filterRegistrationBean; 
 } 
 @Bean public SsoFilter ssoFilter(){
 return new SsoFilter();
 }
 } 

其他子系统配置与此一致

另外

使用springBoot建立能访问jsp的多模块工程还需要注意两个地方的配置,如图:




到此sso单点登陆项目搭建完成,代码已上传到码云,地址:https://gitee.com/TangThomas/sso

参考文章:https://www.cnblogs.com/ywlaker/p/6113927.html                   

                http://tengj.top/2017/03/13/springboot5/ 


分享到: