前后端分离系统接入CAS单点登录说明(十五)

前后端分离说明

目前越来越多的系统采用前后端分离技术来进行构建,这类系统需要接入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包下。我们一起来看一下具体的代码实现

  1. 通过(用户名|密码)获取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(""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
            fmt<span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"%s %s"</span><span class="token punctuation">,</span> HttpStatus<span class="token punctuation">.</span>CREATED<span class="token punctuation">,</span> HttpStatus<span class="token punctuation">.</span>CREATED<span class="token punctuation">.</span><span class="token function">getReasonPhrase</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span>
                    <span class="token punctuation">.</span><span class="token function">format</span><span class="token punctuation">(</span><span class="token string">"

TGT Created

, ticketReference.toString()) .format("\" method=\"POST\">Service:") .format("
"
); //将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中,为后续的访问提供参数。

  1. 通过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,并以次来保存与后端的交互。

  1. 删除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的地址以及参数请求头的要求,上面的代码中都有可参考

你可能感兴趣的:(CAS单点登录)