前后端分离说明
目前越来越多的系统采用前后端分离技术来进行构建,这类系统需要接入CAS单点登录系统,是否有很好的解决方案呢?肯定有,下面会进行相关接口说明。
前后端分离之后,前端如何保存数据呢?一般前端的本地存储方式有:localStorage、sessionStorage、cookie、webSQL、indexedDB等。这些技术有什么差异和区别,这个不在此展开。目前情况下一般都会采用localStorage、sessionStorage和cookie。具体还要看项目使用场景,建议使用localStorage或是sessionStorage。
CAS服务端提供的REST
其实CAS很早就已经考虑了相关的rest接口,下面我们看一下CAS服务端为我们提供了什么样的rest。具体的实现类为org.jasig.cas.support.rest.TicketsResource,在cas-server-support-rest包下。我们一起来看一下具体的代码实现
- 通过(用户名|密码)获取TGT票据
前端的账号密码可以直接提交到CAS单点登录的服务端,具体的rest接口代码如下所示:
@RequestMapping(value = "/v1/tickets", method = RequestMethod.POST, consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public final ResponseEntity<String> createTicketGrantingTicket(@RequestBody final MultiValueMap<String, String> requestBody,
final HttpServletRequest request) throws JsonProcessingException {
try (Formatter fmt = new Formatter()) {
//解析入参,用户名和密码
final Credential credential = this.credentialFactory.fromRequestBody(requestBody);
final AuthenticationContextBuilder builder = new DefaultAuthenticationContextBuilder(
this.authenticationSystemSupport.getPrincipalElectionStrategy());
final AuthenticationTransaction transaction =
AuthenticationTransaction.wrap(credential);
this.authenticationSystemSupport.getAuthenticationTransactionManager().handle(transaction, builder);
final AuthenticationContext authenticationContext = builder.build();
//产生tgt
final TicketGrantingTicket tgtId = this.centralAuthenticationService.createTicketGrantingTicket(authenticationContext);
final URI ticketReference = new URI(request.getRequestURL().toString() + '/' + tgtId.getId());
final HttpHeaders headers = new HttpHeaders();
headers.setLocation(ticketReference);
headers.setContentType(MediaType.TEXT_HTML);
//响应数据进行格式化
fmt.format("<!DOCTYPE HTML PUBLIC \\\"-//IETF//DTD HTML 2.0//EN\\\"><html><head><title>");
fmt.format("%s %s", HttpStatus.CREATED, HttpStatus.CREATED.getReasonPhrase())
.format("</title></head><body><h1>TGT Created</h1><form action=\"%s", ticketReference.toString())
.format("\" method=\"POST\">Service:<input type=\"text\" name=\"service\" value=\"\">")
.format("<br><input type=\"submit\" value=\"Submit\"></form></body></html>");
//将tgt返回给客户端
return new ResponseEntity<>(fmt.toString(), headers, HttpStatus.CREATED);
}
可以做个简单的测试,看返回的结果是什么。
curl -v -X POST "http://127.0.0.1:8080/cas/v1/tickets" -d "username=cas&password=mellon"
这时响应到前端的是TGT,这个就是我们一直需要的,TGT信息存储在localStorage中,为后续的访问提供参数。
- 通过TGT获取ST
前端现在已经把获取到的TGT存储到本地,那么接下来就是要获取ST了。具体代码如下:
@RequestMapping(value = "/v1/tickets/{tgtId:.+}", method = RequestMethod.POST, consumes = MediaType
.APPLICATION_FORM_URLENCODED_VALUE)
public final ResponseEntity<String> createServiceTicket(@RequestBody final MultiValueMap<String, String> requestBody,
@PathVariable("tgtId") final String tgtId) {
try {
//获取入参service
final String serviceId = requestBody.getFirst(CasProtocolConstants.PARAMETER_SERVICE);
final AuthenticationContextBuilder builder = new DefaultAuthenticationContextBuilder(
this.authenticationSystemSupport.getPrincipalElectionStrategy());
//通过service创建对象
final Service service = this.webApplicationServiceFactory.createService(serviceId);
final AuthenticationContext authenticationContext =
builder.collect(this.ticketRegistrySupport.getAuthenticationFrom(tgtId)).build(service);
//通过TGT产生对应ST
final ServiceTicket serviceTicketId = this.centralAuthenticationService.grantServiceTicket(tgtId,
service, authenticationContext);
return new ResponseEntity<>(serviceTicketId.getId(), HttpStatus.OK);
进行一个简单URL请求测试:
curl -X POST -v "http://127.0.0.1:8080/cas/v1/tickets/TGT-1-xxxxxxx" -d "service=http://server.test.com"
注意这里的service参数,在请求的时候需要encode。同时建议这里的service地址为后端的地址,这个后端地址是受保护的可以保证后端能够产生session会话信息并响应到前端。
当通过TGT获取到ST之后,就可以直接访问service地址并带带上ST。
http://server.test.com?ticket=ST-1-xxxxx
这个地址其实是前端发起的一个异步请求用于获取到session,可以存入localStorage,并以次来保存与后端的交互。
- 删除TGT(注销登录)
前后端分离之后,如何进行退出,其实只要清除了TGT即可,因为CAS服务端会去通知各个对应的service进行退出,也即会话失效。
具体rest代码如下:
@RequestMapping(value = "/v1/tickets/{tgtId:.+}", method = RequestMethod.DELETE)
public final ResponseEntity<String> deleteTicketGrantingTicket(@PathVariable("tgtId") final String tgtId) {
this.centralAuthenticationService.destroyTicketGrantingTicket(tgtId);
return new ResponseEntity<>(tgtId, HttpStatus.OK);
}
发起一下请求,请求成功会响应你传递的tgtId。
curl -X DELETE -v "http://127.0.0.1:8080/cas/v1/tickets/TGT-1-xxxxx"
总结
1、前端如何存储TGT以及如何应用后端保持联系
建议使用localStorage或是sessionStorage。
2、如何传递参数
只要注意service的地址以及参数请求头的要求,上面的代码中都有可参考