关于CSS服务器的搭建和整合SpringBoot参考:CAS5.3服务器搭建与客户端整合SpringBoot以及踩坑笔记
环境与需求
后端:springboot
前端: vue + element UI
在登录后之后登录状态在系统中自主控制。
问题
当接口在CAS过滤器中时候,CAS Client会检测是否登录(检测内容下面写),若没有登录会像前端发送请求重定向的请求。当前端和后端在不同电脑时候,前端不能够自动跳转。
注:未深究其中缘由,在同台电脑,上面element ui + spring boot就可以直接完成到cas服务器登录页面的跳转。应该是跨域或者其他未知的原因。
粗略的解决方法
如果最后项目是部署在同一台服务上面的,可以在其他都完成的情况下,最后再放到同一台电脑上面进行开发。
简单看cas表层原理。
在自己浏览器中打开接口,后端拦截,指引页面重定向到cas服务器的登录页(接口在自己电脑运行就行),以上操作等同于地址栏直接输入:
http://localhost:8332/cas/login?service=http://localhost:8888/接口名
cas client拦截下来就是在验证未通过后,拼凑了一下service里面的内容。
在登录成功后,本地cookie会存入一条JSESSIONID
为键,值为B5ABB0EB00A770036F284D65453B2593
的记录。
在之后每次访问接口时候会在头中带上。
CAS客户端是在每次登录成功后都会从请求头中拿到JSESSIONID去找服务器验证。成功的话就会把数据返回来。如果未成功(可能过期或者空)且当前接口在CAS过滤器authentication-url-patterns
中配置了,就会重定向到CAS登录页。
所以,我们Cookie做键,值内容一起复制到postman中,在header中添加,同样得到了登录的效果。
以上得出:
不通过CAS Client的重定向,我们直接写url也可以获取登录成功的JSESSIONID。
JSESSIONID在所有浏览器中都可以公用的。
注:经测试,在登录时候service的并不仅仅起到登录成功后的跳转作用,如果使用的service是8888接口,用得到的jsessionid去8877配置了同样CAS Client的接口中访问时,会报错“未能识别xxxxxxx的票根”
网上学到一个方案。(参考链接放在最后)
粗略过程
?url
的参数,意为登录成功后跳转到的前端页面。详细过程:
前端访问登录,
后端进行拦截验证(第一次没有),本来是会给前端302重定向,由于前端是ajax无法识别302重定向,只会拦截下来,未做进一步的处理。我们修改这部分代码,返回json对象,code码定为401,data域中写入需要重定向的url且拼上登录成功需要跳转的接口。
前端处理接口返回内容,成功了就是登录成功了。若失败了并非是401错误时候,则会取data的数据进行重定向。
这时候问题来了,前端手动重定向肯定是可以的,然后登录成功跳转到接口,可是接下来呢,跳转到的那个接口中总不能将数据直接返回去吧,这样就直接显示页面上面了。
需要认识到的时候,现在是接口直接与浏览器打交道的,如果我们可以通过一种方式让浏览器重定向,可是重定向的网址呢,那就在重定向到cas服务器登录页之前就拼上去。
然后再在后面添加?url=http://www.labalaba.com/login
,意为登录成功后需要跳转的前端的页面。
跳转到cas服务器登录页,登录成功后跳转到参数service的地址(是我们后端的接口),接口验证内容,拿到用户消息,生成token或者个人信息,附到url后面。
接下来就是跳转到页面,上面也提到了,现在接口面对的是浏览器,并不是前端,不是ajax,所以我们可以使用下面这种方法进行重定向。
@Controller
public class UserController {
/**
* 在cas登录页登录成功进入此接口
*
* @param url 登录成功后要跳入的页面
* @return 重定向,并在url后面拼接登录信息,如token等
*/
@GetMapping("/redirect")
public String redirect(@RequestParam String url) {
return "redirect:" + url;
}
}
重定向到页面后,前端只需要在每次页面启动时候,检测地址栏中是否有想要的参数即可。
经过观察得出的结论:
登录后cas server会返回一个ticket
,然后在cas client的接口(service后面跟的)上验证成功后,生成一个JSESSIONID,存入浏览器的cookie中,且之后的接口中会放在Header中。所以service后面必须是接口。
并非配置了拦截的接口才会去找cas server做验证,只要携带了,都会去做验证,且放在request中。所以只需要拦截/login
(需要结合项目对于单点登录的需求),/redirect
接口也回去找cas server做验证。
开始尝试, 修改过滤器AuthenticationFilter
中的doFilter
方法
覆盖的方法,也很简单,就只需要在/src/main/java/
下创建AuthenticationFilter
一样的包,然后创建同名类,将AuthenticationFilter
中的内容全部覆盖,修改doFilter
方法。
将最后一行this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);
注释掉,不让它重定向,返回json数据。
// add start // -------------------------------------------------
PrintWriter out = response.getWriter();
response.setContentType("application/json; charset=UTF-8");
String http = "";
String redirectUrl = serviceUrl;
if (redirectUrl.contains("https://")) {
http += "https://";
redirectUrl = redirectUrl.substring(8);
} else if (redirectUrl.contains("http://")) {
http += "http://";
redirectUrl = redirectUrl.substring(7);
}
int index = redirectUrl.indexOf("/");
String host = redirectUrl.substring(0, index + 1);
String finalUrl = casServerLoginUrl+"?service="+http+host+"redirect";
System.err.println("拦截了 -- direct url: " + finalUrl);
out.println(JSON.toJSON(new HashMap<String, Object>() {{
put("code", "401");
put("data", finalUrl);
}}));
out.flush();
out.close();
// add end // -------------------------------------------------
分别是login接口和redirect接口。
@ResponseBody
@PostMapping("/login")
public Object login(HttpServletRequest request) {
AttributePrincipal principal2 = (AttributePrincipal)request.getUserPrincipal();
if (principal2 != null) {
Map<String, Object> attrs = principal2.getAttributes();
log.info("login8888: " + attrs);
return new HashMap<String, Object>() {{
put("code", "200");
put("msg", "8888登录成功");
put("date", new HashMap<String, String>() {{
put("accessToken", "-->wei_tiao_zhuan_suc");
}});
}};
}
log.error("login8888: null");
return "401, redirect to the cas server login url. by 8888";
}
redirect接口:
@GetMapping("/redirect")
public String redirect(@RequestParam String url, HttpServletRequest request) {
AttributePrincipal principal2 = (AttributePrincipal)request.getUserPrincipal();
// 模拟jwt或者uuid生成token。
if (principal2 != null) {
Map<String, Object> attrs = principal2.getAttributes();
log.info("登录成功,正在生成token");
return "redirect:" + url + "?accessToken=-->tiao_zhuan_hou_suc";
}
// 登录失败不返回token
log.error("登录失败");
return "redirect:" + url;
}
测试过程均是在两才电脑上,且登录接口是在element ui项目中访问的。
序号 | 内容 |
---|---|
1 | 1个前端1个后端 |
2 | 2个前端1个后端 |
3 | 2个前端2个后端 |
以上都为正常的,当测试到微服务☁️时候(参考资料中有位大佬也提到了):
参考(方案具体是参考的最后两个)
上个月我负责的系统SSO升级,对接京东ERP系统,这也让我想起了之前我做过一个单点登录的项目。想来单点登录有很多实现方案,不过最主流的还是基于CAS的方案,所以我也就分享一下我的CAS实践之路。