背景
這個問題也花了一兩天解決,寫到這里,記錄下來,同時也希望可以幫到別人,如有疑問或許改正的地方,還望不吝賜教!
項目背景是公司有多套后台管理系統,都是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");
}