对于大型企业,内部有各类各样的办公OA、业务系统,员工使用起来要记住不一样系统的帐号密码,很是不方便,所以就催生出一种统一管理帐号的认证系统,即单点登陆系统。html
其做用是,某位员工,在其中一个系统进行登陆验证后,再打开其余系统时,就不须要再次登陆,直接进入系统,十分方便快捷。java
那怎么才能作到呢?web
一、单点登陆系统数据库
首先,咱们要有一个单点登陆系统,对帐号进行统一管理,咱们能够将它做为一个微服务进行部署。session
在这个系统中,记录了员工的帐号信息,如:工号、姓名、电话等等。app
二、业务系统ide
在业务系统中,也要有对应的用户帐户,由于业务系统的用户只是单点登陆系统用户的一个子集或者交集而已。微服务
而且,业务系统中有本身的一套角色管理以及受权机制。url
三、单点登陆原理spa
1)在登陆业务系统时,拦截未登陆的请求,重定向到单点登陆扫码认证接口;
2)在单点登陆页面扫码或密码登陆后,单点登陆系统重定向回业务系统,而且在session中携带认证帐号信息;
3)在业务系统经过过滤器,从session中尝试提取帐号信息,若是提取到了,则根据帐号信息[一般是用户的工号或者loginname];
而后根据帐号信息,从业务系统自己的数据库中查询出对应的用户帐户名和登陆密码,若是有,则说明此登陆用户是本系统用户,直接调用本系统所用的登陆校验机制在代码中模拟一遍登陆过程,并把登陆后状态信息保存到session中便可;若是找不到对应用户,则说明此用户在业务系统没有帐号,禁止登入,重定向回系统登陆页面或者一个消息提示页面告诉他没有系统帐户。
如下基于Java进行接入说明。
一、添加依赖
Java的单点登陆功能依赖于 cas_client_core 这个jar包,咱们能够在pom文件中添加依赖:
<!-- https://mvnrepository.com/artifact/org.jasig.cas.client/cas-client-core --> <dependency> <groupId>org.jasig.cas.client</groupId> <artifactId>cas-client-core</artifactId> <version>3.2.0</version> </dependency>
二、配置拦截规则
1)对于非SpringBoot构建的项目,在web.xml中配置如下拦截规则:
<!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置 --> <listener> <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class> </listener> <!-- 该过滤器用于实现单点登出功能,可选配置。 --> <filter> <filter-name>CAS Single Sign Out Filter</filter-name> <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class> </filter> <filter-mapping> <filter-name>CAS Single Sign Out Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器负责用户的认证工做,必须配置 --> <filter> <filter-name>CASFilter</filter-name> <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class> <init-param> <param-name>casServerLoginUrl</param-name> <param-value>单点登陆系统认证接口</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>本业务系统首页地址</param-value> </init-param> </filter> <filter-mapping> <filter-name>CASFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器负责对Ticket的校验工做,必须启用它 --> <filter> <filter-name>CAS Validation Filter</filter-name> <filter-class> org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class> <init-param> <param-name>casServerUrlPrefix</param-name> <param-value>单点登陆系统认证接口</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>本业务系统首页地址</param-value> </init-param> </filter> <filter-mapping> <filter-name>CAS Validation Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器负责实现HttpServletRequest请求的包裹, 好比容许开发者经过HttpServletRequest的getRemoteUser()方法得到SSO登陆用户的登陆名,可选配置。 --> <filter> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <filter-class> org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class> </filter> <filter-mapping> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器使得开发者能够经过org.jasig.cas.client.util.AssertionHolder来获取用户的登陆名。 好比AssertionHolder.getAssertion().getPrincipal().getName()。 --> <filter> <filter-name>CAS Assertion Thread Local Filter</filter-name> <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class> </filter> <filter-mapping> <filter-name>CAS Assertion Thread Local Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
2)对于SpringBoot构建的项目,则需建立一个配置类,在其中经过方法来返回配置bean。
首先,建立一个类,继承 WebMvcConfigurer,并重写两个方法:
而后,定义过滤器配置方法,注意:一个过滤器定义一个方法,该方法返回一个配置bean:
方法中要设置的内容,参考web.xml下配置的内容便可。
三、编写业务系统的登陆过滤器
在业务系统处定义一个过滤器,对全部路径进行拦截校验:若是session中已有登陆状态,则放行;不然,尝试从session中提取cas认证信息,并进行模拟登陆逻辑;若是也没有,则重定向到业务系统登陆页面使用户进行登陆操做。
@Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest re = (HttpServletRequest) request; //获取request HttpServletResponse resp = (HttpServletResponse) response; //获取respinse AttributePrincipal attributePrincipal = (AttributePrincipal) re.getUserPrincipal(); //返回一个java.security.Principal 对象,该对象包含当前受权用户的名称 String cas_account = ""; String username = ""; String userpass = ""; //提早session中的登陆信息 Object object = re.getSession().getAttribute("_const_cas_assertion_"); if (object != null) { Assertion assertion = (Assertion) object; fnumber = assertion.getPrincipal().getName(); } else if (attributePrincipal != null) { cas_account = (String) attributePrincipal.getName(); } Users userLogin = (Users) re.getSession().getAttribute("CURRENT_USER"); if (userLogin == null) { if(cas_account ==null||"".equals(cas_account )){ chain.doFilter(request, response);//本系统经过拦截器进行了登陆状态校验,故此处放行,让后面的拦截器进行未登陆重定向,而不在此处进行。固然,也能够在此处直接重定向回首页 } }else{ chain.doFilter(request, response);//若检测到登陆信息,则直接放行 } //检测到cas信息,可是又未登陆,则在此处模拟登陆过程 try { if (re.getSession().getAttribute("CURRENT_USER") == null) { //代码模拟登陆过程:根据cas传过来的用户信息,查找本系统用户,拿到帐号密码进行登陆认证
模拟过程略......各系统不同,按需实现。
re.getSession().setAttribute("CURRENT_USER", 当前登陆用户); resp.sendRedirect("/index?userId="+当前登陆用户id);//登陆成功,重定向到业务系统首页 }else { if(users.size() == 0){ logger.error("========users match none!========");//根据cas信息,在本系统没找到匹配用户,则返回系统登陆页面或者重定向到一个错误提示页 }else { logger.error("========users match more than one!========");//根据cas信息,在本系匹配到多于一个用户,则返回系统登陆页面或者重定向到一个错误提示页 } } } } else { if(mobiles.size() == 0){ logger.error("========mobile match none!========"); }else { logger.error("========mobile match more than one!========"); } } } } catch (Exception e) { e.printStackTrace(); } chain.doFilter(request, response); }
最后,别忘了将自定义的filter配置到web.xml或者在配置类中书写一个注册方法。