热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

单点登录实现demo!

文章目录前言一、CAS是什么?二、搭建客户端系统引入CAS客户端后端搭建总结前言什么是单点登录?单点登录全称SingleSignOn(以下

文章目录

  • 前言

  • 一、CAS是什么?

  • 二、搭建客户端系统

  1. 引入CAS

  2. 客户端后端搭建

总结

前言

什么是单点登录?单点登录全称Single Sign On(以下简称SSO),是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分,如图(不标准,只是方便理解)。

daa40c55a8c8470a64f0bb442d2c70de.png

一、CAS是什么?

CAS 是 Yale 大学发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登录方法,CAS 在 2004 年 12 月正式成为 JA-SIG 的一个项目。CAS 具有以下特点:

  • 开源的企业级单点登录解决方案。

  • CAS Server 为需要独立部署的 Web 应用。

  • CAS Client 支持非常多的客户端(这里指单点登录系统中的各个 Web 应用),包括 Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等。

二、搭建客户端系统

1.引入CAS

参考:https://www.bilibili.com/video/BV1xy4y1r7BU?t=666&p=8

注意其中将证书导入jdk中,一定要注意精确到cacerts这个文件下,不然一直报拒绝写入,另外最好用管理员下的命令窗口

2.客户端后端搭建

1.添加依赖

org.jasig.cas.clientcas-client-core3.3.2

joda-timejoda-time2.10.5
org.springframework.securityspring-security-cas
org.springframework.securityspring-security-taglibs

2.配置客户端

server:port: 1234

3.添加config(filter)文件

地址全为ip,如果用hosts映射地址,可能会出现问题

package com.casclient1.cas.config;import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.util.HttpServletRequestWrapperFilter;
import org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.web.filter.CharacterEncodingFilter;import javax.servlet.Filter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;@Configuration
public class FilterConfig implements Serializable, InitializingBean {private static final Logger LOGGER &#61; LoggerFactory.getLogger(FilterConfig.class);public static final String CAS_SIGNOUT_FILTER_NAME &#61; "CAS Single Sign Out Filter";public static final String CAS_AUTH_FILTER_NAME &#61; "CAS Filter";public static final String CAS_IGNOREL_SSL_FILTER_NAME &#61; "CAS Ignore SSL Filter";public static final String CAS_FILTER_NAME &#61; "CAS Validation Filter";public static final String CAS_WRAPPER_NAME &#61; "CAS HttpServletRequest Wrapper Filter";public static final String CAS_ASSERTION_NAME &#61; "CAS Assertion Thread Local Filter";public static final String CHARACTER_ENCODING_NAME &#61; "Character encoding Filter";//CAS服务器退出地址private static String casSigntouServerUrlPrefix &#61; "https://127.0.0.1:8443/cas/logout";//CAS服务器登录地址private static String casServerLoginUrl &#61; "https://127.0.0.1:8443/cas/login";//客户端地址private static String clienthosturl&#61;"http://127.0.0.1:1234";//CAS服务器地址private static String casValidationServerUrlPrefix &#61; "https://127.0.0.1:8443/cas";public FilterConfig() {}/*** 单点登出功能,放在其他filter之前* casSigntouServerUrlPrefix为登出前缀:https://123.207.122.156:8081/cas/logout** &#64;return*/&#64;Bean&#64;Order(0)public FilterRegistrationBean getCasSignoutFilterRegistrationBean() {FilterRegistrationBean registration &#61; new FilterRegistrationBean();registration.setFilter(getCasSignoutFilter());registration.addUrlPatterns("/*", "*.html");registration.addInitParameter("casServerUrlPrefix", casSigntouServerUrlPrefix);registration.setName(CAS_SIGNOUT_FILTER_NAME);registration.setEnabled(true);return registration;}&#64;Bean(name &#61; CAS_SIGNOUT_FILTER_NAME)public Filter getCasSignoutFilter() {return new SingleSignOutFilter();}/*** 忽略SSL认证** &#64;return*/&#64;Bean&#64;Order(1)public FilterRegistrationBean getCasSkipSSLValidationFilterRegistrationBean() {FilterRegistrationBean registration &#61; new FilterRegistrationBean();registration.setFilter(getCasSkipSSLValidationFilter());registration.addUrlPatterns("/*", "*.html");registration.setName(CAS_IGNOREL_SSL_FILTER_NAME);registration.setEnabled(true);return registration;}&#64;Bean(name &#61; CAS_IGNOREL_SSL_FILTER_NAME)public Filter getCasSkipSSLValidationFilter() {return new IgnoreSSLValidateFilter();}/*** 负责用户的认证* casServerLoginUrl&#xff1a;https://123.207.122.156:8081/cas/login* casServerName&#xff1a;https://123.207.122.156:8080/tdw/alerts/** &#64;return*/&#64;Bean&#64;Order(2)public FilterRegistrationBean getCasAuthFilterRegistrationBean() {FilterRegistrationBean registration &#61; new FilterRegistrationBean();final Filter casAuthFilter &#61; getCasAuthFilter();registration.setFilter(casAuthFilter);registration.addUrlPatterns("/*", "*.html");registration.addInitParameter("casServerLoginUrl", casServerLoginUrl);registration.addInitParameter("serverName", clienthosturl);registration.setName(CAS_AUTH_FILTER_NAME);registration.setEnabled(true);return registration;}&#64;Bean(name &#61; CAS_AUTH_FILTER_NAME)public Filter getCasAuthFilter() {return new MyAuthenticationFilter();}/*** 对Ticket进行校验* casValidationServerUrlPrefix要用内网ip* casValidationServerUrlPrefix&#xff1a;https://123.207.122.156:8081/cas* casServerName&#xff1a;https://123.207.122.156:8080/tdw/alerts/** &#64;return*/&#64;Bean&#64;Order(3)public FilterRegistrationBean getCasValidationFilterRegistrationBean() {FilterRegistrationBean registration &#61; new FilterRegistrationBean();final Filter casValidationFilter &#61; getCasValidationFilter();registration.setFilter(casValidationFilter);registration.addUrlPatterns("/*", "*.html");registration.addInitParameter("casServerUrlPrefix", casValidationServerUrlPrefix);registration.addInitParameter("serverName", clienthosturl);registration.setName(CAS_FILTER_NAME);registration.setEnabled(true);return registration;}&#64;Bean(name &#61; CAS_FILTER_NAME)public Filter getCasValidationFilter() {return new Cas20ProxyReceivingTicketValidationFilter();}/*** 设置response的默认编码方式&#xff1a;UTF-8。** &#64;return*/&#64;Bean&#64;Order(4)public FilterRegistrationBean getCharacterEncodingFilterRegistrationBean() {FilterRegistrationBean registration &#61; new FilterRegistrationBean();registration.setFilter(getCharacterEncodingFilter());registration.addUrlPatterns("/*", "*.html");registration.setName(CHARACTER_ENCODING_NAME);registration.setEnabled(true);return registration;}&#64;Bean(name &#61; CHARACTER_ENCODING_NAME)public Filter getCharacterEncodingFilter() {CharacterEncodingFilter characterEncodingFilter &#61; new CharacterEncodingFilter();characterEncodingFilter.setEncoding("UTF-8");return characterEncodingFilter;}&#64;Beanpublic FilterRegistrationBean casHttpServletRequestWrapperFilter(){FilterRegistrationBean authenticationFilter &#61; new FilterRegistrationBean();authenticationFilter.setFilter(new HttpServletRequestWrapperFilter());authenticationFilter.setOrder(6);List urlPatterns &#61; new ArrayList<>();urlPatterns.add("/*");authenticationFilter.setUrlPatterns(urlPatterns);return authenticationFilter;}&#64;Overridepublic void afterPropertiesSet() throws Exception {}
}

4.filter类中的MyAuthenticationFilter是重写cas jar包中的AuthenticationFilter&#xff0c;原因是CAS源码无法认证直接重定向&#xff0c;而ajax请求又不能直接重定向&#xff0c;导致前端302&#xff0c;而302vue response拦截器是拦截不到的。所以就想到不让cas给我重定向&#xff0c;给我返回状态码&#xff0c;告诉前端认证失败&#xff0c;让前端直接跳转cas服务器登录地址。

修改cas源码过滤器&#xff0c;复制源码AuthenticationFilter这个过滤器&#xff0c;重写他&#xff0c;其实这里只改了重定向的代码其他都一样。上MyAuthenticationFilter代码

package com.casclient1.cas.config;import org.jasig.cas.client.authentication.*;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.util.ReflectUtils;
import org.jasig.cas.client.validation.Assertion;import javax.servlet.FilterConfig;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;public class MyAuthenticationFilter extends AbstractCasFilter {private String casServerLoginUrl;private boolean renew &#61; false;private boolean gateway &#61; false;private GatewayResolver gatewayStorage &#61; new DefaultGatewayResolverImpl();private AuthenticationRedirectStrategy authenticationRedirectStrategy &#61; new DefaultAuthenticationRedirectStrategy();private UrlPatternMatcherStrategy ignoreUrlPatternMatcherStrategyClass &#61; null;private static final Map> PATTERN_MATCHER_TYPES &#61; new HashMap();public MyAuthenticationFilter() {}&#64;Overrideprotected void initInternal(FilterConfig filterConfig) throws ServletException {if (!this.isIgnoreInitConfiguration()) {super.initInternal(filterConfig);this.setCasServerLoginUrl(this.getPropertyFromInitParams(filterConfig, "casServerLoginUrl", (String)null));this.logger.trace("Loaded CasServerLoginUrl parameter: {}", this.casServerLoginUrl);this.setRenew(this.parseBoolean(this.getPropertyFromInitParams(filterConfig, "renew", "false")));this.logger.trace("Loaded renew parameter: {}", this.renew);this.setGateway(this.parseBoolean(this.getPropertyFromInitParams(filterConfig, "gateway", "false")));this.logger.trace("Loaded gateway parameter: {}", this.gateway);String ignorePattern &#61; this.getPropertyFromInitParams(filterConfig, "ignorePattern", (String)null);this.logger.trace("Loaded ignorePattern parameter: {}", ignorePattern);String ignoreUrlPatternType &#61; this.getPropertyFromInitParams(filterConfig, "ignoreUrlPatternType", "REGEX");this.logger.trace("Loaded ignoreUrlPatternType parameter: {}", ignoreUrlPatternType);if (ignorePattern !&#61; null) {Class ignoreUrlMatcherClass &#61; (Class)PATTERN_MATCHER_TYPES.get(ignoreUrlPatternType);if (ignoreUrlMatcherClass !&#61; null) {this.ignoreUrlPatternMatcherStrategyClass &#61; (UrlPatternMatcherStrategy) ReflectUtils.newInstance(ignoreUrlMatcherClass.getName(), new Object[0]);} else {try {this.logger.trace("Assuming {} is a qualified class name...", ignoreUrlPatternType);this.ignoreUrlPatternMatcherStrategyClass &#61; (UrlPatternMatcherStrategy)ReflectUtils.newInstance(ignoreUrlPatternType, new Object[0]);} catch (IllegalArgumentException var6) {this.logger.error("Could not instantiate class [{}]", ignoreUrlPatternType, var6);}}if (this.ignoreUrlPatternMatcherStrategyClass !&#61; null) {this.ignoreUrlPatternMatcherStrategyClass.setPattern(ignorePattern);}}String gatewayStorageClass &#61; this.getPropertyFromInitParams(filterConfig, "gatewayStorageClass", (String)null);if (gatewayStorageClass !&#61; null) {this.gatewayStorage &#61; (GatewayResolver)ReflectUtils.newInstance(gatewayStorageClass, new Object[0]);}String authenticationRedirectStrategyClass &#61; this.getPropertyFromInitParams(filterConfig, "authenticationRedirectStrategyClass", (String)null);if (authenticationRedirectStrategyClass !&#61; null) {this.authenticationRedirectStrategy &#61; (AuthenticationRedirectStrategy)ReflectUtils.newInstance(authenticationRedirectStrategyClass, new Object[0]);}}}&#64;Overridepublic void init() {super.init();CommonUtils.assertNotNull(this.casServerLoginUrl, "casServerLoginUrl cannot be null.");}&#64;Overridepublic final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request &#61; (HttpServletRequest)servletRequest;HttpServletResponse response &#61; (HttpServletResponse)servletResponse;if (this.isRequestUrlExcluded(request)) {this.logger.debug("Request is ignored.");filterChain.doFilter(request, response);} else {HttpSession session &#61; request.getSession(false);Assertion assertion &#61; session !&#61; null ? (Assertion)session.getAttribute("_const_cas_assertion_") : null;if (assertion !&#61; null) {filterChain.doFilter(request, response);} else {String serviceUrl &#61; this.constructServiceUrl(request, response);String ticket &#61; this.retrieveTicketFromRequest(request);boolean wasGatewayed &#61; this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);if (!CommonUtils.isNotBlank(ticket) && !wasGatewayed) {this.logger.debug("no ticket and no assertion found");String modifiedServiceUrl;if (this.gateway) {this.logger.debug("setting gateway attribute in session");modifiedServiceUrl &#61; this.gatewayStorage.storeGatewayInformation(request, serviceUrl);} else {modifiedServiceUrl &#61; serviceUrl;}this.logger.debug("Constructed service url: {}", modifiedServiceUrl);String xRequested &#61;request.getHeader("x-requested-with");if("XMLHttpRequest".equals(xRequested)){response.getWriter().write("{\"code\":202, \"msg\":\"no ticket and no assertion found\"}");}else{String urlToRedirectTo &#61; CommonUtils.constructRedirectUrl(this.casServerLoginUrl, this.getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);this.logger.debug("redirecting to \"{}\"", urlToRedirectTo);this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);}} else {filterChain.doFilter(request, response);}}}}public final void setRenew(boolean renew) {this.renew &#61; renew;}public final void setGateway(boolean gateway) {this.gateway &#61; gateway;}public final void setCasServerLoginUrl(String casServerLoginUrl) {this.casServerLoginUrl &#61; casServerLoginUrl;}public final void setGatewayStorage(GatewayResolver gatewayStorage) {this.gatewayStorage &#61; gatewayStorage;}private boolean isRequestUrlExcluded(HttpServletRequest request) {if (this.ignoreUrlPatternMatcherStrategyClass &#61;&#61; null) {return false;} else {StringBuffer urlBuffer &#61; request.getRequestURL();if (request.getQueryString() !&#61; null) {urlBuffer.append("?").append(request.getQueryString());}String requestUri &#61; urlBuffer.toString();return this.ignoreUrlPatternMatcherStrategyClass.matches(requestUri);}}static {PATTERN_MATCHER_TYPES.put("CONTAINS", ContainsPatternUrlPatternMatcherStrategy.class);PATTERN_MATCHER_TYPES.put("REGEX", RegexUrlPatternMatcherStrategy.class);PATTERN_MATCHER_TYPES.put("EXACT", ExactUrlPatternMatcherStrategy.class);}
}

测试Controller

package com.casclient1.cas.controller;import com.casclient1.cas.domain.UserDomain;
import com.casclient1.cas.tools.Result;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.Console;
import java.io.IOException;&#64;Controller
public class TestController {/*** 测试* &#64;return*/&#64;GetMapping("/test")&#64;ResponseBodypublic Result  login(HttpServletRequest httpServletRequest){System.out.println("sss");return new Result<>(new UserDomain(httpServletRequest.getRemoteUser()));}&#64;GetMapping("/checkTicket")public void index(HttpServletResponse response) throws IOException {// 前端页面地址response.sendRedirect("http://127.0.0.1:8088/Home");}/*** 注销* &#64;return*/&#64;RequestMapping("/logout")public String logout(){return "redirect:https://127.0.0.1:8443/cas/logout";}}

3.前端

客户端2验证:{{name}}安全退出




5.效果

未登录:

0ab5b5ef17048c665b368da4de0f2b81.png

点击客户端1超链接

e32e4e4ca5fe099dce7e40f29cb44dc1.png

登录成功

dad2b082b73777fdcad025d163e62e0c.png

点击客户端2超链接&#xff0c;直接进入&#xff0c;无需登录

2086295b8f79e5db05a4a5f5c34c56d2.png

退出

3b9e461a784e02f1a5291b6871a94081.png

总结

网上有很多CAS单点登录的demo&#xff0c;但是对于前后端分离讲的比较详细的很少&#xff0c;前后端分离&#xff0c;必定会出现跨域&#xff0c;导致CAS登录无法重定向等等原因&#xff0c;结合和网上一些想法和部门代码后&#xff0c;大致做了一个比较完善&#xff0c;但很基础的单点登录系统&#xff0c;当然单点登录不光有CAS&#xff0c;还有JWT(1.所有服务靠约定来生成token&#xff0c;2.要么集中生成集中判断,所有服务都能生成都认这个&#xff0c;要么一个服务管控全局)&#xff0c;OAuth2等等。

来源&#xff1a;blog.csdn.net/weixin_43483911/article/details/117811270



推荐阅读
  • centos安装Mysql的方法及步骤详解
    本文介绍了centos安装Mysql的两种方式:rpm方式和绿色方式安装,详细介绍了安装所需的软件包以及安装过程中的注意事项,包括检查是否安装成功的方法。通过本文,读者可以了解到在centos系统上如何正确安装Mysql。 ... [详细]
  • 安装mysqlclient失败解决办法
    本文介绍了在MAC系统中,使用django使用mysql数据库报错的解决办法。通过源码安装mysqlclient或将mysql_config添加到系统环境变量中,可以解决安装mysqlclient失败的问题。同时,还介绍了查看mysql安装路径和使配置文件生效的方法。 ... [详细]
  • Nginx使用AWStats日志分析的步骤及注意事项
    本文介绍了在Centos7操作系统上使用Nginx和AWStats进行日志分析的步骤和注意事项。通过AWStats可以统计网站的访问量、IP地址、操作系统、浏览器等信息,并提供精确到每月、每日、每小时的数据。在部署AWStats之前需要确认服务器上已经安装了Perl环境,并进行DNS解析。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 本文介绍了Android 7的学习笔记总结,包括最新的移动架构视频、大厂安卓面试真题和项目实战源码讲义。同时还分享了开源的完整内容,并提醒读者在使用FileProvider适配时要注意不同模块的AndroidManfiest.xml中配置的xml文件名必须不同,否则会出现问题。 ... [详细]
  • 解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法
    本文介绍了解决nginx启动报错epoll_wait() reported that client prematurely closed connection的方法,包括检查location配置是否正确、pass_proxy是否需要加“/”等。同时,还介绍了修改nginx的error.log日志级别为debug,以便查看详细日志信息。 ... [详细]
  • 欢乐的票圈重构之旅——RecyclerView的头尾布局增加
    项目重构的Git地址:https:github.comrazerdpFriendCircletreemain-dev项目同步更新的文集:http:www.jianshu.comno ... [详细]
  • 如何去除Win7快捷方式的箭头
    本文介绍了如何去除Win7快捷方式的箭头的方法,通过生成一个透明的ico图标并将其命名为Empty.ico,将图标复制到windows目录下,并导入注册表,即可去除箭头。这样做可以改善默认快捷方式的外观,提升桌面整洁度。 ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • 本文介绍了Perl的测试框架Test::Base,它是一个数据驱动的测试框架,可以自动进行单元测试,省去手工编写测试程序的麻烦。与Test::More完全兼容,使用方法简单。以plural函数为例,展示了Test::Base的使用方法。 ... [详细]
  • FeatureRequestIsyourfeaturerequestrelatedtoaproblem?Please ... [详细]
  • 图像因存在错误而无法显示 ... [详细]
  • 本文探讨了在设置了HTTP客户端超时时间后,向HTTP服务器发送请求时出现两个请求的情况。其中一个请求正常,另一个请求无法获取请求参数。文章分析了可能导致此问题的原因,并提供了解决方案。 ... [详细]
author-avatar
行玲于諭淑臻
这个家伙很懒,什么也没留下!
Tags | 热门标签
RankList | 热门文章
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有