Springboot和vue前后端分离项目接入cas单点登录,解决跨域
背景
这个问题也花了一两天解决,写到这里,记录下来,同时也希望可以帮到别人,如有疑问或许改正的地方,还望不吝赐教!
项目背景是公司有多套后台管理系统,都是jsp或者thymeleaf前后端代码在一起,接入的cas,实现一处登录多处使用。
后业务发展,需要对其进行前后端分离,前端项目由vue完成,后台不同的管理模块分别写在不同的服务里。同时涉及到,新旧系统并存,jsp平台登录后,前后端分离项目免登录,同时解决跨域问题。
CSDN有几遍介绍前后端分离接入cas的文章,都不尽完善,或者功能没有完美实现。本文主要介绍了cas-server部署成功后,cas-client的接入,引用net.unicon.cas依赖,代码侵入少,无需在filter上进行大量的自定义代码。最后可以达到,用户登录了老的jsp项目后,在新的前后端分离项目中,可以成功请求后台接口,无需重新登录或刷新。同时也可以实现,不同的cas-client之间共享session。
改造需要注意的点
- cookie的携带
- 不同服务之间cookie的共享
- clien-server授权后的页面跳转
- 跨域的配置
普通的cas接入
pom中引入依赖
<dependency>
<groupId>net.unicon.cas</groupId>
<artifactId>cas-client-autoconfig-support</artifactId>
<version>2.3.0-GA</version>
</dependency>
配置cas-server地址,本地地址,validation-type(cas,cas3,sam的区别)
cas.server-url-prefix=https://****/cas
cas.server-login-url=https://****/cas/login
cas.client-host-url=http://****:8080
cas.validation-type=CAS
启动配置注解
import net.unicon.cas.client.configuration.EnableCasClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
@EnableCasClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
如果要配置白名单,两种方式
第一种
需要放行的url,可以继承CasClientConfigurerAdapter类,重写configureAuthenticationFilter
import net.unicon.cas.client.configuration.EnableCasClient;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
@Configuration
public class CasClientConfig extends CasClientConfigurerAdapter {
@Override
public void configureAuthenticationFilter(FilterRegistrationBean authenticationFilter) {
super.configureAuthenticationFilter(authenticationFilter);
Map<String, String> initParameters = authenticationFilter.getInitParameters();
// 配置地址,这里还可以配置很多,例如cas重定向策略等。
initParameters.put("ignorePattern", "/ignoreUrl1/");
}
}
同时启动的SpringBootApplication 也要配置cookie可用
import net.unicon.cas.client.configuration.EnableCasClient;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement
@EnableCasClient
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Bean
public CookieSerializer httpSessionIdResolver() {
DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
cookieSerializer.setCookieName("JSESSIONID");
cookieSerializer.setUseHttpOnlyCookie(false);
cookieSerializer.setSameSite(null);
return cookieSerializer;
}
}
第二种方式
也可以配置需要拦截的url,不在配置内的就自动放行
cas.authentication-url-patterns=/need-filter-url/*,
前后端分离项目
接口接入cas-client,跨域问题及cookie携带需要配置
import net.unicon.cas.client.configuration.CasClientConfigurerAdapter;
import net.unicon.cas.client.configuration.EnableCasClient;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import java.util.Map;
@Configuration
public class CasClientConfig extends CasClientConfigurerAdapter {
@Override
public void configureAuthenticationFilter(FilterRegistrationBean authenticationFilter) {
super.configureAuthenticationFilter(authenticationFilter);
Map<String, String> initParameters = authenticationFilter.getInitParameters();
initParameters.put("authenticationRedirectStrategyClass",
"com.demo.filter.CustomAuthRedirectStrategy");
// 配置地址,这里还可以配置很多,例如cas重定向策略等。
initParameters.put("ignorePattern", "/ignoreUrl1/|/ignoreUrl2/|/ignoreUrl3/");
}
@Bean
public FilterRegistrationBean corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
//前端页面地址,可以配置多个
config.addAllowedOrigin("http://****:8081");
config.addAllowedOrigin("http://****:8081");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/**", config);
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new CorsFilter(source));
//需要拦截的url
registrationBean.addUrlPatterns("/url1/*");
registrationBean.addUrlPatterns("/url2/*");
registrationBean.addUrlPatterns("/url3/*");
registrationBean.setOrder(-2147483648);
return registrationBean;
}
}
CustomAuthRedirectStrategy类
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.jasig.cas.client.authentication.AuthenticationRedirectStrategy;
public class CustomAuthRedirectStrategy implements AuthenticationRedirectStrategy {
@Override
public void redirect(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, String s) throws IOException {
httpServletResponse.setCharacterEncoding("utf-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
}
}
前端代码
http.js
import axios from 'axios'
import helper from './helper'
var qs = require('qs')
const ERR_CODE_LIST = { //常见错误码列表
[400]: "请求错误",
[401]: "登录失效或在其他地方已登录",
[403]: "拒绝访问",
[404]: "请求地址出错",
[408]: "请求超时",
[500]: "服务器内部错误",
[501]: "服务未实现",
[502]: "网关错误",
[503]: "服务不可用",
[504]: "网关超时",
[505]: "HTTP版本不受支持"
}
export function getErrMsg(error) {//通过error处理错误码
if(!error.response) {//无网络时单独处理
return {errCode:null, errMsg:"网络不可用,请刷新重试"}
}
const errCode = error.response.status //错误码
const errMsg = ERR_CODE_LIST[errCode] //错误消息
return {errCode: errCode,errMsg: errMsg ? `${errMsg} [${errCode}]` : error.message}
}
// axios.defaults.withCredentials=true;//让ajax携带cookie
// 引用axios,设置头文件
function apiAxios(method,rootUrl, url, params) {
return axios({
method: method,
url: url,
data:qs.stringify(params),
baseURL:rootUrl,
timeout: 600000,
dataType:"json",
async: true,
crossDomain: true,
withCredentials:true
}).catch(error => {
const {errCode,errMsg} = getErrMsg(error);
if(errCode == 401){
//登录失效 -> 跳转登录页
// http://****/cas/login 为cas-server的地址
// http://******/front/redirect 为后台用于处理跳转的地址
let hurl = 'http://****:8080/front/redirect';
window.location.href = 'http://****/cas/login?service=' + hurl;
}else{
showMsg(errMsg);
}
return Promise.reject(error)
})
}
export default {
get: function (rootUrl,url, params) {
return apiAxios('GET',rootUrl, url, params)
},
post: function (rootUrl,url, params) {
return apiAxios('post',rootUrl, url, params)
},
}
Api.js
import http from "./http";
let root = 'http://****:8080'
export default {
// 用户管理------------------
getUserInfo(data) {
return http.get( root, '/user/userInfo', data)
},
}
user.vue
async getUserInfo() {
let obj = {
param1: value1
};
let userInfo = await this.$api.getUserInfo(obj);
console.log(userInfo, '11');
},
跳转处理地址
@RequestMapping(value = "redirect", method = RequestMethod.GET)
public void redirect(HttpServletRequest request, HttpServletResponse httpServletResponse)
throws IOException {
//重定向到前端页面
httpServletResponse.sendRedirect("http://****:8081/#/demo.html");
}