本文转载自微信公众号《Java极客技术》,作者鸭血范。转载本文请联系Java极客技术公众号。1.介绍昨天介绍了API接口设计token认证方案。事实上,令牌认证的最佳实践场景是在单点登录系统上。在企业发展初期,后台管理系统还比较少,一两个。以电子商务系统为例。在初期,可能只有一套商城订单系统和一套后台系统用于产品和库存管理。随着业务量的增加,此时的业务系统会越来越复杂,项目会分成多个组,每个组负责自己的领域,例如:A组负责开发商城系统,B组负责支付系统开发,C组负责库存系统开发,D组负责物流追踪系统开发,E组负责每日业绩报表统计开发。。ETC。随着规模变大,人员数量会逐渐增加。对于研发部门来说,人员大致分为几类:研发人员、测试人员、运维人员、产品经理、技术支持等,他们会经常登录各自的后台业务系统,然后进行工作。至此,我们可以想象一下,如果每个组自己开发一套后台管理系统的登录,如果有10个这样的系统,一个新同事需要为每个系统开通一个权限,那么我们可能需要为他开10个账户。随着业务规模的扩大,较大的公司可能有100多个业务系统。配置100多个账号,让人去做这种操作,岂不是很不合理?面对这种繁琐低效的工作,IT大佬们想出了一个解决方案,那就是开发一个登录系统,所有业务系统都认可,然后就可以实现只需要一次登录就可以访问其他相互信任的应用系统。这个登录系统,我们称之为:单点登录系统。好了,废话不多说,下面从两个方面来介绍一下单点登录系统的实现。方案设计项目实践2.方案设计2.1.单体后台系统登录在传统的单体后台系统中,我们通常都是这样玩的,为了简单的操作。用户使用账号和密码登录后,服务器会给当前用户创建一个会话session,同时也会生成一个cookie,最后返回给前端。当用户访问其他后端服务时,我们只需要检查当前用户的session是否有效,如果不有效,则重新跳转到登录页面;如果有效,则进入业务流程。但是如果访问不同的域名系统,这个cookie是无效的,所以不能跨系统访问,也不支持在集群环境下共享。针对单点登录的场景,我们需要重新设计一个新的方案。2.2.单点登录系统登录首先来张图吧!该流程图是单点登录系统与应用系统的交互图。当用户登录应用系统时,应用系统会调用单点登录系统的接口,用客户端传入的token验证token的有效性。如果不合法,则跳转到单点登录系统的登录页面;如果合法,直接进入首页。进入登录页面后,会要求用户输入用户名和密码进行登录验证。如果验证成功,则返回一个有效的token,然后客户端根据服务端返回的参数链接,跳回需要访问的应用系统。.然后,应用系统将再次验证令牌的合法性。如果合法,则进入首页,流程结束。引入单点登录系统后,接入的应用系统无需与用户登录相关,只需要对客户端的token进行合法性认证即可。单点登录系统只需要完成用户的登录过程和身份验证,并返回一个安全令牌给客户端。在一些项目中,生成的token会保存在客户端的cookie中。这样做的目的是为了避免每次调用接口时都在url中带上token。但浏览器只允许共享同一域名下的cookies。对于不同的域名系统,cookie不能共享。这种情况下,我们可以先将token放入url链接中,类似于上面流程图中跳转的思路,对于同一个应用系统,我们可以将token放入cookie中,对于不同的应用系统,我们可以通过传递url链接来实现token传输。3.项目实践在实践中,token的存储有两种选择:存储在服务器,如果是分布式环境,一般会存储在redis和存储在客户端,由服务器做校验,自然supportsdistributed3.1andstoredinRedis存储在redis中,这是一种比较常见的处理方式,也是一开始的处理方式。当用户登录成功后,会以用户的信息为value,uuid为key,存储在redis中,各个服务集群共享用户信息。代码练习也很简单。用户登录后,将用户信息存储在redis中,并返回一个有效的token给客户端。@RequestMapping(value="/login",method=RequestMethod.POST,produces={"application/json;charset=UTF-8"})publicTokenVOlogin(@RequestBodyLoginDTOloginDTO){//...参数合法性验证//来自数据库获取用户信息UserdbUser=userService.selectByUserNo(loginDTO.getUserNo);//...用户,密码验证//创建tokenStringtoken=UUID.randomUUID();//将token和用户信息存储到redis,并设置有效期2小时redisUtil.save(token,dbUser,2*60*60);//定义返回结果TokenVOresult=newTokenVO();//封装tokenresult.setToken(token);//封装应用系统访问地址result.setRedirectURL(loginDTO.getRedirectURL());returnresult;}客户端收到登录成功后,会根据参数组合跳转到对应的应用系统。跳转示例如下:http://xxx.com/page.html?token=xxxxxx对于每个应用系统,只需要写一个过滤器TokenFilter来验证拦截token参数,就可以实现连接.Thecodeisasfollows:@OverridepublicvoiddoFilter(ServletRequestservletRequest,ServletResponseservletResponse,FilterChainfilterChain)throwsServletException,IOException,SecurityException{HttpServletRequestrequest=(HttpServletRequest)servletRequest;HttpServletResponseresponse=(HttpServletResponse)servletResponse;StringrequestUri=request.getRequestURI();StringcontextPath=request.getContextPath();StringserviceName=request.getServerName();//加入白名单的URL被释放String[]excludeUrls={"(?:/images/|/css/|/js/|/template/|/static/|/web/|/constant/).+$","/user/login","/user/createImage"};for(Stringurl:excludeUrls){if(requestUri.matches(contextPath+url)||(serviceName.matches(url))){filterChain.doFilter(request,response);return;}}//运行跨域检测if(RequestMethod.OPTIONS.name().equals(request.getMethod())){filterChain.doFilter(request,response);return;}//检查token是否有效finalStringtoken=request.getHeader("token");if(StringUtils.isEmpty(token)||!redisUtil.exist(token)){ResultMsg