SpringBoot+Vue+CAS 前后端分离实现单点登录方案
点击关注公众号,利用碎片时间学习
文章目录
前言
一、CAS是什么?
二、搭建客户端系统
引入CAS
客户端后端搭建
总结
前言
什么是单点登录?单点登录全称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 前后端分离实现单点登录方案相关推荐
- 视频教程-springboot+Vue整合前后端分离权限后台管理系统-Java
springboot+Vue整合前后端分离权限后台管理系统 拥有八年的Java项目开发经验,擅长Java.vue.SpringBoot.springCloud.spring.springmvc.myb ...
- 基于SSM+SpringBoot+Vue+ElementUI前后端分离的校园岗位招聘就业管理系统
运行视频 基于SSM+SpringBoot+Vue+ElementUI前后端分离的校园岗位招聘就业管理系统 项目运行截图 学生管理 添加学生 学生信息 教师管理 教师信息 实习基地 公告信息 公司管理 ...
- shiro+php,一套基于SpringBoot+Vue+Shiro 前后端分离 开发的代码生成器
一.前言 最近花了一个月时间完成了一套基于Spring Boot+Vue+Shiro前后端分离的代码生成器,目前项目代码已基本完成 止步传统CRUD,进阶代码优化: 该项目可根据数据库字段动态生成 c ...
- springboot+vue的前后端分离与合并方案
pringboot和vue结合的方案网络上的主要有以下两种: 1. [不推荐]在html中直接使用script标签引入vue和一些常用的组件,这种方式和以前传统的开发是一样的,只是可以很爽的使用vue ...
- 基于springboot+vue的前后端分离商城系统
springboot前后端分离商城 介绍 springboot前后端分离商城 本项目由本人根据教程实现的一个springboot项目,基本已实现项目,但是本人希望加入自己的小功能, 请期待下一次的更新 ...
- SpringBoot + Vue 开发前后端分离的旅游管理系统
旅游管理系统 项目简介 项目演示 数据库建表 环境搭建 引入依赖(pom.xml) 配置文件(application.properties) href="javascript:;" ...
- SpringBoot+Vue搭建前后端分离的轻博客
之前用SpringBoot+Thymeleaf写了个人博客,这次又来炒冷饭,用新的技术来重构个人博客,并把它变成开放式的,目前水平有限,欢迎大家一起讨论提建议 2020-04-07:因为期间在找工作, ...
- 基于Springboot+vue实现前后端分离二手图书交易
作者主页:编程指南针 作者简介:Java领域优质创作者.CSDN博客专家 .掘金特邀作者.多年架构师设计经验.腾讯课堂常驻讲师 主要内容:Java项目.毕业设计.简历模板.学习资料.面试题库.技术互助 ...
- 基于Springboot+Vue实现前后端分离商城管理系统
项目编号:BS-SC-030 一,项目简介 新新商城,一款基于 Springboot+Vue 的电商项目,前后端分离项目.完整的实现了一个商城系统应有的基本功能,包括但不限于以下主要功能模块: 前端商 ...
最新文章
- Lambda表达式(简单解析)
- Bootstrap中模态框多层嵌套时滚动条问题
- like左匹配索引失效_导致MySQL索引失效的一些常见写法总结
- VB API 之 第十一课 绘制矩形
- oracle11g nid,Oracle工具之nid命令的使用
- Maven项目错误解决小结
- lasso回归_一文读懂线性回归、岭回归和Lasso回归
- poj3279 反转 挑战程序设计竞赛
- 一年超20万人次在抖音志愿登记器官捐献
- cmd java转jar包,cmd 打包java成jar可执行文件
- 查看pytorch和匹配的CUDA版本
- matlab 16qam误码率图,16QAM理论误码率与实际误码率MATLAB仿真程序(最新整理)
- JS 特效代码 400例
- 道客巴巴 文档免积分保存方法
- 基于RFID的物联网系统
- jQ UI 后台管理系统基础UI
- ssh远程安全访问路由器
- 【刘一哥2021总结】不负韶华,收获丰硕;不忘使命,砥砺前行
- AR涂涂乐项目之识别图制作制作地球仪线框一
- 2016团体程序设计天梯赛-决赛-部分题解