点击关注公众号,利用碎片时间学习

文章目录

  • 前言

  • 一、CAS是什么?

  • 二、搭建客户端系统

  1. 引入CAS

  2. 客户端后端搭建

总结

前言

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

一、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.添加依赖

<dependency><groupId>org.jasig.cas.client</groupId><artifactId>cas-client-core</artifactId><version>3.3.2</version>
</dependency>
<dependency><groupId>joda-time</groupId><artifactId>joda-time</artifactId><version>2.10.5</version>
</dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-cas</artifactId>
</dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-taglibs</artifactId>
</dependency>

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

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

修改cas源码过滤器,复制源码AuthenticationFilter这个过滤器,重写他,其实这里只改了重定向的代码其他都一样。上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 = false;private boolean gateway = false;private GatewayResolver gatewayStorage = new DefaultGatewayResolverImpl();private AuthenticationRedirectStrategy authenticationRedirectStrategy = new DefaultAuthenticationRedirectStrategy();private UrlPatternMatcherStrategy ignoreUrlPatternMatcherStrategyClass = null;private static final Map<String, Class<? extends UrlPatternMatcherStrategy>> PATTERN_MATCHER_TYPES = new HashMap();public MyAuthenticationFilter() {}@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 = this.getPropertyFromInitParams(filterConfig, "ignorePattern", (String)null);this.logger.trace("Loaded ignorePattern parameter: {}", ignorePattern);String ignoreUrlPatternType = this.getPropertyFromInitParams(filterConfig, "ignoreUrlPatternType", "REGEX");this.logger.trace("Loaded ignoreUrlPatternType parameter: {}", ignoreUrlPatternType);if (ignorePattern != null) {Class<? extends UrlPatternMatcherStrategy> ignoreUrlMatcherClass = (Class)PATTERN_MATCHER_TYPES.get(ignoreUrlPatternType);if (ignoreUrlMatcherClass != null) {this.ignoreUrlPatternMatcherStrategyClass = (UrlPatternMatcherStrategy) ReflectUtils.newInstance(ignoreUrlMatcherClass.getName(), new Object[0]);} else {try {this.logger.trace("Assuming {} is a qualified class name...", ignoreUrlPatternType);this.ignoreUrlPatternMatcherStrategyClass = (UrlPatternMatcherStrategy)ReflectUtils.newInstance(ignoreUrlPatternType, new Object[0]);} catch (IllegalArgumentException var6) {this.logger.error("Could not instantiate class [{}]", ignoreUrlPatternType, var6);}}if (this.ignoreUrlPatternMatcherStrategyClass != null) {this.ignoreUrlPatternMatcherStrategyClass.setPattern(ignorePattern);}}String gatewayStorageClass = this.getPropertyFromInitParams(filterConfig, "gatewayStorageClass", (String)null);if (gatewayStorageClass != null) {this.gatewayStorage = (GatewayResolver)ReflectUtils.newInstance(gatewayStorageClass, new Object[0]);}String authenticationRedirectStrategyClass = this.getPropertyFromInitParams(filterConfig, "authenticationRedirectStrategyClass", (String)null);if (authenticationRedirectStrategyClass != null) {this.authenticationRedirectStrategy = (AuthenticationRedirectStrategy)ReflectUtils.newInstance(authenticationRedirectStrategyClass, new Object[0]);}}}@Overridepublic void init() {super.init();CommonUtils.assertNotNull(this.casServerLoginUrl, "casServerLoginUrl cannot be null.");}@Overridepublic final void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {HttpServletRequest request = (HttpServletRequest)servletRequest;HttpServletResponse response = (HttpServletResponse)servletResponse;if (this.isRequestUrlExcluded(request)) {this.logger.debug("Request is ignored.");filterChain.doFilter(request, response);} else {HttpSession session = request.getSession(false);Assertion assertion = session != null ? (Assertion)session.getAttribute("_const_cas_assertion_") : null;if (assertion != null) {filterChain.doFilter(request, response);} else {String serviceUrl = this.constructServiceUrl(request, response);String ticket = this.retrieveTicketFromRequest(request);boolean wasGatewayed = 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 = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);} else {modifiedServiceUrl = serviceUrl;}this.logger.debug("Constructed service url: {}", modifiedServiceUrl);String xRequested =request.getHeader("x-requested-with");if("XMLHttpRequest".equals(xRequested)){response.getWriter().write("{\"code\":202, \"msg\":\"no ticket and no assertion found\"}");}else{String urlToRedirectTo = 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 = renew;}public final void setGateway(boolean gateway) {this.gateway = gateway;}public final void setCasServerLoginUrl(String casServerLoginUrl) {this.casServerLoginUrl = casServerLoginUrl;}public final void setGatewayStorage(GatewayResolver gatewayStorage) {this.gatewayStorage = gatewayStorage;}private boolean isRequestUrlExcluded(HttpServletRequest request) {if (this.ignoreUrlPatternMatcherStrategyClass == null) {return false;} else {StringBuffer urlBuffer = request.getRequestURL();if (request.getQueryString() != null) {urlBuffer.append("?").append(request.getQueryString());}String requestUri = 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;@Controller
public class TestController {/*** 测试* @return*/@GetMapping("/test")@ResponseBodypublic Result<UserDomain>  login(HttpServletRequest httpServletRequest){System.out.println("sss");return new Result<>(new UserDomain(httpServletRequest.getRemoteUser()));}@GetMapping("/checkTicket")public void index(HttpServletResponse response) throws IOException {// 前端页面地址response.sendRedirect("http://127.0.0.1:8088/Home");}/*** 注销* @return*/@RequestMapping("/logout")public String logout(){return "redirect:https://127.0.0.1:8443/cas/logout";}}

3.前端

<template xmlns="http://www.w3.org/1999/html"><div ><header style="height: 60px"><span>客户端2验证:{{name}}</span><button @click="logout">安全退出</button></header><router-view></router-view><!--<my-vue v-bind:lineID="lineID"></my-vue>--></div>
</template>
<style lang="scss">
</style>
<script type="text/ecmascript-6">export default {data() {return {name:'ss'}},
mounted(){var _this = this;this.$http.get('/test', {headers: {'x-requested-with': 'XMLHttpRequest'}}).then(function (response) {console.log("sss");if (response.data.code === 202) {debuggerconsole.log("sss");window.location.href = "http://127.0.0.1:1235/checkTicket"} else if (response.data.code === 200) {console.log("sss");_this.name = response.data.data.name}console.log(response);}).catch(function (error) {console.log(error);});},methods: {logout() {window.location.href = "http://127.0.0.1:1234/logout"},}}
</script>

5.效果

未登录:

点击客户端1超链接

登录成功

点击客户端2超链接,直接进入,无需登录

退出

总结

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

来源:blog.csdn.net/weixin_43483911/article/

details/117811270

推荐:

最全的java面试题库

PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!

SpringBoot+Vue+CAS 前后端分离实现单点登录方案相关推荐

  1. 视频教程-springboot+Vue整合前后端分离权限后台管理系统-Java

    springboot+Vue整合前后端分离权限后台管理系统 拥有八年的Java项目开发经验,擅长Java.vue.SpringBoot.springCloud.spring.springmvc.myb ...

  2. 基于SSM+SpringBoot+Vue+ElementUI前后端分离的校园岗位招聘就业管理系统

    运行视频 基于SSM+SpringBoot+Vue+ElementUI前后端分离的校园岗位招聘就业管理系统 项目运行截图 学生管理 添加学生 学生信息 教师管理 教师信息 实习基地 公告信息 公司管理 ...

  3. shiro+php,一套基于SpringBoot+Vue+Shiro 前后端分离 开发的代码生成器

    一.前言 最近花了一个月时间完成了一套基于Spring Boot+Vue+Shiro前后端分离的代码生成器,目前项目代码已基本完成 止步传统CRUD,进阶代码优化: 该项目可根据数据库字段动态生成 c ...

  4. springboot+vue的前后端分离与合并方案

    pringboot和vue结合的方案网络上的主要有以下两种: 1. [不推荐]在html中直接使用script标签引入vue和一些常用的组件,这种方式和以前传统的开发是一样的,只是可以很爽的使用vue ...

  5. 基于springboot+vue的前后端分离商城系统

    springboot前后端分离商城 介绍 springboot前后端分离商城 本项目由本人根据教程实现的一个springboot项目,基本已实现项目,但是本人希望加入自己的小功能, 请期待下一次的更新 ...

  6. SpringBoot + Vue 开发前后端分离的旅游管理系统

    旅游管理系统 项目简介 项目演示 数据库建表 环境搭建 引入依赖(pom.xml) 配置文件(application.properties) href="javascript:;" ...

  7. SpringBoot+Vue搭建前后端分离的轻博客

    之前用SpringBoot+Thymeleaf写了个人博客,这次又来炒冷饭,用新的技术来重构个人博客,并把它变成开放式的,目前水平有限,欢迎大家一起讨论提建议 2020-04-07:因为期间在找工作, ...

  8. 基于Springboot+vue实现前后端分离二手图书交易

    作者主页:编程指南针 作者简介:Java领域优质创作者.CSDN博客专家 .掘金特邀作者.多年架构师设计经验.腾讯课堂常驻讲师 主要内容:Java项目.毕业设计.简历模板.学习资料.面试题库.技术互助 ...

  9. 基于Springboot+Vue实现前后端分离商城管理系统

    项目编号:BS-SC-030 一,项目简介 新新商城,一款基于 Springboot+Vue 的电商项目,前后端分离项目.完整的实现了一个商城系统应有的基本功能,包括但不限于以下主要功能模块: 前端商 ...

最新文章

  1. Lambda表达式(简单解析)
  2. Bootstrap中模态框多层嵌套时滚动条问题
  3. like左匹配索引失效_导致MySQL索引失效的一些常见写法总结
  4. VB API 之 第十一课 绘制矩形
  5. oracle11g nid,Oracle工具之nid命令的使用
  6. Maven项目错误解决小结
  7. lasso回归_一文读懂线性回归、岭回归和Lasso回归
  8. poj3279 反转 挑战程序设计竞赛
  9. 一年超20万人次在抖音志愿登记器官捐献
  10. cmd java转jar包,cmd 打包java成jar可执行文件
  11. 查看pytorch和匹配的CUDA版本
  12. matlab 16qam误码率图,16QAM理论误码率与实际误码率MATLAB仿真程序(最新整理)
  13. JS 特效代码 400例
  14. 道客巴巴 文档免积分保存方法
  15. 基于RFID的物联网系统
  16. jQ UI 后台管理系统基础UI
  17. ssh远程安全访问路由器
  18. 【刘一哥2021总结】不负韶华,收获丰硕;不忘使命,砥砺前行
  19. AR涂涂乐项目之识别图制作制作地球仪线框一
  20. 2016团体程序设计天梯赛-决赛-部分题解

热门文章

  1. [python]-日志记录之logging
  2. 【代码模板004】VBA快速删除工作表
  3. 2022年骨传导耳机哪个好、骨传导耳机品牌推荐
  4. 十年打磨,青云四“新”战略奔赴未来
  5. QT-FFMPEG录制屏幕和声音的完整程序(包含音视频同步)
  6. L学姐美团金融测开面经
  7. python timedelta对象_python timedelta函数是什么?
  8. java学习所需软件
  9. vue3 watch
  10. Linux ❉ vimrc文件详解