问卷/试卷服务
成长值服务
短信服务

疑问点

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客户端依赖

参考 《单点登录cas版》中 1.使用单点认证登录页面

第二步、编写Cas经典拦截器的配置类

@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的客户端整合就算是完成了。

第三步、编写一个CasController,专门负责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服务器的单点登录页面了。