1.前后端分离项目调用是否会存在跨域问题?
答:前端直接调起接口。不会因为IP或者域名不一致而导致跨域。(如果直接调用接口觉得还是会存在跨域 可以考虑增加 nginx 代理域名来做)
2.云版该如何调用?
答:如果为单体项目,则参考第一种对接方式,项目中加入jar包、拦截器即可;如果为前后端分离,则采用第三种直接调用接口的方式(接口调用完返回用户信息即表示登录成功)
3.前后端分离会跨域。
使用域名https://tcc-dev.interlib.com.cn/api/uumlogin
4.前后端分离调用第一个接口状态码返回201,表示还未执行完成,继续调用第二、第三个接口
5.统一认证的状态码均为http请求的状态码,不要与接口返回的状态码混淆
一、整合前后端分离遇到的问题 前后端系统遇到最多的问题肯定就是302重定向的问题了,即使已经进行了单点登录,请求也会被Cas客户端的过滤器所拦截,这是由于Cas无法感知到已经登录所造成的。 前后端分离架构中通常使用Ajax请求,而Ajax请求对于tomcat来说都是一个新的请求,就都会创建一个新的JSESSIONID,但我们的用户信息是存储在第一次登录成功的CAS客户端的session上的,这样Cas客户端自然就不能Ajax请求的JSESSIONID查找到用户信息了,就自然被拦截了。 这时有人会说一个集成了CAS客户端的新的应用系统第一次访问不也是一个新的JSESSIONID吗,那怎么也能实现单点登录,其实这是上述所说的CASTGC这个cookie发挥的巨大作用,当新的应用系统第一次访问时,CAS客户端会将这个cookie拿到CAS服务器获取ticket票据返回,然后CAS客户端再拿着ticket票据到CAS服务器进行校验,校验通过则获得用户信息并将用户信息存储到session中,这样新的应用系统也能实现单点登录了,测试方法是当cookie有CASTGC这个参数时,可以将应用系统的cookie下的JSESSIONID删除,再刷新被CAS客户端拦截的请求,就可以发现上面的流程被执行了。 很可惜的是Ajax请求同样不具备这个能力,因此请求自然就被CAS客户端拦截,然后重定向到CAS服务端的单点登录界面了。
二、CAS整合前后端分离项目实战 整合CAS应用系统通常是为了对接第三方提供的CAS服务器,因此应用系统很可能已经有了一套自己的登录逻辑,而整合CAS的目的也明确,就是为了获取CAS服务器上用户的用户名等信息,从而在自己的应用系统上实现登录,为了应用系统的登录逻辑保持不变,二次登录是更好的选择,做法是先CAS服务器上的用户名等信息然后再调用应用系统的登录逻辑完成登录。下面就来真正整合一下项目,我这里采用的是springboot项目。
参考 《单点登录cas版》中 1.使用单点认证登录页面
@Configuration
public class CasConfig {
/**
* 用于实现单点登出功能
*/
@Bean
public FilterRegistrationBean<SingleSignOutFilter> logOutFilter() {
FilterRegistrationBean<SingleSignOutFilter> authenticationFilter = new FilterRegistrationBean();
authenticationFilter.setFilter(new SingleSignOutFilter());
authenticationFilter.addUrlPatterns("/*");
authenticationFilter.setOrder(1);
return authenticationFilter;
}
//配置认证Filter
@Bean
public FilterRegistrationBean authenticationFilterRegistrationBean() {
FilterRegistrationBean authenticationFilter = new FilterRegistrationBean();
authenticationFilter.setFilter(new AuthenticationFilter());
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("casServerLoginUrl", "https://183.62.61.218:8123/uumlogin/login");
initParameters.put("serverName", "http://183.62.61.218:8123/");
//CAS过滤器白名单的设置,不同版本名称不同,可点进AuthenticationFilter进行查看
initParameters.put("casWhiteUrl","/casMy/*.*,/casLogout");
authenticationFilter.setInitParameters(initParameters);
authenticationFilter.setOrder(2);
List<String> urlPatterns = new ArrayList<String>();
urlPatterns.add("/*");// 设置匹配的url
authenticationFilter.setUrlPatterns(urlPatterns);
return authenticationFilter;
}
//配置ticket验证Filter
@Bean
public FilterRegistrationBean ValidationFilterRegistrationBean(){
FilterRegistrationBean authenticationFilter = new FilterRegistrationBean();
authenticationFilter.setFilter(new Cas20ProxyReceivingTicketValidationFilter());
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("casServerUrlPrefix", "https://183.62.61.218:8123/uumlogin");
initParameters.put("serverName", "https://183.62.61.218:8123/");
authenticationFilter.setInitParameters(initParameters);
authenticationFilter.setOrder(1);
List<String> urlPatterns = new ArrayList<String>();
urlPatterns.add("/*");// 设置匹配的url
authenticationFilter.setUrlPatterns(urlPatterns);
return authenticationFilter;
}
//配置获取用户信息的Filter
//request.getRemoteUser()
@Bean
public FilterRegistrationBean casHttpServletRequestWrapperFilter(){
FilterRegistrationBean authenticationFilter = new FilterRegistrationBean();
authenticationFilter.setFilter(new HttpServletRequestWrapperFilter());
authenticationFilter.setOrder(3);
List<String> urlPatterns = new ArrayList<String>();
urlPatterns.add("/*");// 设置匹配的url
authenticationFilter.setUrlPatterns(urlPatterns);
return authenticationFilter;
}
}
到这里为止,CAS的客户端整合就算是完成了。
第一步、编写cas登录方法
@RequestMapping("/casLogin")
public void casLogin(HttpServletRequest request,HttpServletResponse response) throws IOException {
String username = request.getRemoteUser();
String sessionId = request.getSession().getId();
//返回给前端,这里先采用百度进行测试,然后前端还是走以前的登录逻辑,只不过是变成自动登录了,让前端将sessionId放到cookie中带过来即可
response.sendRedirect("http://www.baidu.com?username="+username+"&sessionId="+sessionId);
}
为了进行登录,用户必须首先访问/casLogin这个地址,然后被Cas的认证拦截器拦截,重定向到Cas服务器的单点登录界面完成登录,登录完成后回到/casLogin这个Controller,获取到用户名以及存储了用户信息的sessionId,最后重定向给前端的登录界面,用户名和sessionId是必须带上的,这里先用百度进行测试。
这样登录完成后前端就可以获取到用户名和sessionId了,同时CASTGC也会被设置到cookie中。
第二步、测试cas整合是否成功 因为还没有整合前端,也用spring提供的resttemplates来发送Ajax请求,和前端发送Ajax请求效果是一样的。
@RestController
public class TestController {
@RequestMapping("/test")
public Result test(){
return new Result(200,"123","456");
}
@RequestMapping("/test2")
public Result test2(HttpServletRequest request, HttpSession session){
System.out.println("进入test2方法");
RestTemplate restTemplate = new RestTemplate();
// 构建请求头
HttpHeaders headers = new HttpHeaders();
//携带JSESSIONID的cookie
List<String> cookies = new ArrayList<>();
cookies.add("JSESSIONID=" + request.getSession().getId());
headers.put(HttpHeaders.COOKIE,cookies);
String url = "http://localhost:2638/test";
//输出session的值
Enumeration<String> attributeNames = session.getAttributeNames();
while (attributeNames.hasMoreElements()){
String element = attributeNames.nextElement();
System.out.println(element+": "+session.getAttribute(element));
}
//由于用户信息存储在session中,因此可以从session中获取到存储用户信息的AssertionImpl对象从而获取用户信息
AssertionImpl assertion= (AssertionImpl) session.getAttribute("_const_cas_assertion_");
AttributePrincipal principal = assertion.getPrincipal();
String name = principal.getName();
Map<String, Object> attributes = principal.getAttributes();
System.out.println(name);
attributes.forEach((k,v)-> System.out.println(k+": "+v));
HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<Result> response = restTemplate.exchange(
url,
HttpMethod.GET,
entity,
Result.class);
return new Result(200,response.getBody(),"456");
}
}
我是采用了自定义的Result来测试的,这个成功的测试Controller,在浏览器访问/test2路径,因为带了JSESSIONID,Cas客户端就能找到对应的用户信息,判断已经登录,允许访问。
接下来在test2方法中注释掉携带JSESSIONID的代码,重启项目,再次访问/test2路径
//携带JSESSIONID的cookie
/*List `<String>` cookies = new ArrayList<>();
cookies.add("JSESSIONID=" + request.getSession().getId());
headers.put(HttpHeaders.COOKIE,cookies);*/
此时浏览器会报500错误,并且控制台也会说没有合适的转换器将Html页面转换为Result,原因是test2方法中的restTemplate请求已经被Cas客户端拦截了,因为Cas客户端识别为未登录状态,因此Ajax请求携带上JSESSIONID就能成功请求了。
第三步、接收前端传来的值,真正开始应用系统的登录逻辑方法
@RequestMapping("/login")
public Result login(@RequestBody(required = false) LoginDto loginDto,HttpSession session) throws IOException {
//由于用户信息存储在session中,因此可以从session中获取到存储用户信息的AssertionImpl对象从而获取用户信息
AssertionImpl assertion= (AssertionImpl) session.getAttribute("_const_cas_assertion_");
AttributePrincipal principal = assertion.getPrincipal();
String username = principal.getName();
Map<String, Object> attributes = principal.getAttributes();
System.out.println(username);
attributes.forEach((k,v)-> System.out.println(k+": "+v));
//安全判断,传过来的username和session中的username相同才认为用户已经到CAS服务器上进行登录了
if (!loginDto.getUsername().equals(username)){
return new Result(HttpServletResponse.SC_FORBIDDEN,null,"需要先到Cas服务器进行登录");
}
//获取到前端传来的用户账号等信息后,就可以执行原先应用系统的登录逻辑了
RestTemplate restTemplate = new RestTemplate();
String url = "http://localhost:2640/login/casLogin";
ResponseEntity<Result> result = restTemplate.postForEntity(url, loginDto, Result.class);
//处理返回结果
if (result.getStatusCode()!= HttpStatus.OK || !result.getBody().getCode().equals(HttpStatus.OK.value())){
return new Result(HttpServletResponse.SC_UNAUTHORIZED,null,"登录失败");
}
//将原本系统登录成功的返回值返回给前端就实现了二次登录了
return result.getBody();
}
第四步、进行完整测试 首先,用户在浏览器访问应用系统的casLogin方法,然后进入cas登录界面,并且url后面会自动带上回调地址,就是/casLogin。
在Cas服务器的单点登录界面输入用户名和密码进行登录,登录成功,就会执行casLogin方法的逻辑,还是采用百度来模拟前端接收用户数据。
这样就模拟前端接收到了username以及sessionId,接下来前端带着username以及sessionId来访问login方法,执行原先系统的登录逻辑,这里使用postman进行演示。
发送请求
这样前端就获取到原先系统的登录信息了,需要注意的是如果只有一个后端项目tomcat的话,前端一直带着sessionId,那么Cas就一直能认定是登录状态了,Cas单点登录就算是完成了。
单点退出功能
@RequestMapping("/casLogout")
public void casLogout(HttpSession session,HttpServletResponse response) throws IOException {
//先执行业务系统的退出功能,这里省略了
//再执行Cas的单点退出功能
//注销session
session.invalidate();
// ids的退出地址,ids6.wisedu.com为ids的域名 authserver为ids的上下文,logout为固定值
String casLogoutURL = "https://183.62.61.218:8123/uumlogin/logout";
// service后面带的参数为应用的访问地址,需要使用URLEncoder进行编码
String redirectURL = casLogoutURL + "?service=" + URLEncoder.encode("https://183.62.61.218:8123/casLogin");
response.sendRedirect(casLogoutURL);
}
在浏览器直接访问Cas应用系统的/casLogout地址就可以了,退出成功就会回调到casLogin方法,Cas过滤器发现没有登录,就又会跳转到Cas服务器的单点登录页面了。