CAS单点登录开源框架解读(十)--CAS单点登录客户端认证之客户端接收服务端认证信息

客户端如何接收服务端认证信息

上一章节我们分析了CAS服务端是如何产生认证信息的,那么这一章节我们将解析客户端是如何接收服务端的认证信息。这里所说的客户端指的是接入CAS单点登录系统的应用,是一个独立的应用。客户端的源码分析都是基于CAS服务端提供的集成包cas-client-core。

1. 返回到CommonUtils.getResponseFromServer()

在我们分析客户端如何接收服务端认证信息时,可以回头去看看第八章节我们所分析到的最终其实是通过后端发起http请求获取到cas服务端的数据。那么我们回到这里继续分析客户端是如何解析服务端的认证信息。

*CommonUtils.java*

public final class CommonUtils {
public static String getResponseFromServer(final URL constructedUrl, final HttpURLConnectionFactory factory,
            final String encoding) {

        HttpURLConnection conn = null;
        InputStreamReader in = null;
        try {
            //创建http请求链接
            conn = factory.buildHttpURLConnection(constructedUrl.openConnection());
            //获取响应流
            if (CommonUtils.isEmpty(encoding)) {
                in = new InputStreamReader(conn.getInputStream());
            } else {
                in = new InputStreamReader(conn.getInputStream(), encoding);
            }

            final StringBuilder builder = new StringBuilder(255);
            int byteRead;
            //从流中获取数据
            while ((byteRead = in.read()) != -1) {
                builder.append((char) byteRead);
            }
            //返回响应结果
            return builder.toString();
        } catch (final Exception e) {
            LOGGER.error(e.getMessage(), e);
            throw new RuntimeException(e);
        } finally {
            closeQuietly(in);
            if (conn != null) {
                conn.disconnect();
            }
        }
    }
}

in为从CAS单点登录服务端返回的结果(见上一章节),返回xml用户认证信息。

2. 返回到retrieveResponseFromServer()

这里其实只是一个抽象类的调用,返回结果同上一小节xml用户认证信息是一样的。最后还是要看后续的结果是如何解析。

AbstractCasProtocolUrlBasedTicketValidator.java

public abstract class AbstractCasProtocolUrlBasedTicketValidator extends AbstractUrlBasedTicketValidator {

protected final String retrieveResponseFromServer(final URL validationUrl, final String ticket) {
        return CommonUtils.getResponseFromServer(validationUrl, getURLConnectionFactory(), getEncoding());
}
}

3. 返回到ticketValidator.validate()

*AbstractUrlBasedTicketValidator.java*

public abstract class AbstractUrlBasedTicketValidator implements TicketValidator {
   …………
   public final Assertion validate(final String ticket, final String service) throws TicketValidationException {
        //计算需要请求到CAS服务端的url地址(注意这里是普通登录模式,所以校验地址为://serviceValidate)
        final String validationUrl = constructValidationUrl(ticket, service);
        logger.debug("Constructing validation url: {}", validationUrl);

        try {
            logger.debug("Retrieving response from server.");
            //到CAS单点登录服务进行校验,获取返回结果,也就是我们上文一直分析的内容
            final String serverResponse = retrieveResponseFromServer(new URL(validationUrl), ticket);

            if (serverResponse == null) {
                throw new TicketValidationException("The CAS server returned no response.");
            }

            logger.debug("Server response: {}", serverResponse);
            //解析从服务端获取到的信息
            return parseResponseFromServer(serverResponse);
        } catch (final MalformedURLException e) {
            throw new TicketValidationException(e);
        }
}
…………
}

parseResponseFromServer:解析返回值,也就是解析xml格式的响应信息。这是最重要的一点,从响应中解析出对应的数据信息,在上一章节中已经分析了服务端响应的
XML的格式,因此这里就会解析出服务端响应数据的值。

4. 返回到客户端第3个filter

*AbstractTicketValidationFilter.java*

public abstract class AbstractTicketValidationFilter extends AbstractCasFilter {
    public final void doFilter(
            ......
               try {
                //通过ticket和service去校验ticket,并根据服务端响应结果构建assertion用户认证信息
                final Assertion assertion = this.ticketValidator.validate(ticket,
                        constructServiceUrl(request, response));

                logger.debug("Successfully authenticated user: {}", assertion.getPrincipal().getName());

                request.setAttribute(CONST_CAS_ASSERTION, assertion);

                if (this.useSession) {
                    //如果需要使用session,则把获取到的assertion认证信息放入会话
                    request.getSession().setAttribute(CONST_CAS_ASSERTION, assertion);
                }
                onSuccessfulValidation(request, response, assertion);

                if (this.redirectAfterValidation) {
                    //如果需要重定向,认证成功后重定向到客户端的service地址
                    logger.debug("Redirecting after successful ticket validation.");
                    response.sendRedirect(constructServiceUrl(request, response));
                    return;
                }
            } catch (final TicketValidationException e) {
                logger.debug(e.getMessage(), e);

                onFailedValidation(request, response);

                if (this.exceptionOnValidationFailure) {
                    throw new ServletException(e);
                }
				//认证失败响应错误码403到前端
                response.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());

                return;
            }
        }
        filterChain.doFilter(request, response);

}
…… ……
}

此抽象类的功能是将用户认证信息存入request的Attribute和request.getSession(),便于客户端应用获取。设置认证完成后的重定向地址response.sendRedirect(constructServiceUrl(request, response));重定向地址为:http://localhost:8088/cas-client/。

5. 重定向后再次调用第1个filter

重定向后,重新进入我们配置的filters。首先进入第1个filter,因为没有请求退出,没做什么就进入下一个filter。第2个filter,因session里assertion存在,走到下面代码后,进入第3个认证的filter中。

*AuthenticationFilter.java*

public class AuthenticationFilter extends AbstractCasFilter {

  final HttpSession session = request.getSession(false);
        final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;
        //此时由于在第4节中已经设置了对应的值,因此assertion不为空,进入下一个过滤器
        if (assertion != null) {
            filterChain.doFilter(request, response);
            return;
        }

第3个filter,因为重定向后,此时ticket==null,省略了校验ticket的过程,相当于什么都没做,进入第4个filter。

6. 调用第4个filter

第四个过滤器在客户端的org.jasig.cas.client.util包中。

*HttpServletRequestWrapperFilter.java*

public final class HttpServletRequestWrapperFilter extends AbstractConfigurationFilter {
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
            final FilterChain filterChain) throws IOException, ServletException {
        //从session中获取principal
        final AttributePrincipal principal = retrievePrincipalFromSessionOrRequest(servletRequest);
        //包装request请求
        filterChain.doFilter(new CasHttpServletRequestWrapper((HttpServletRequest) servletRequest, principal),
                servletResponse);
    }

retrievePrincipalFromSessionOrRequest获取Principal,代码如下表:

protected AttributePrincipal retrievePrincipalFromSessionOrRequest(final ServletRequest servletRequest) {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpSession session = request.getSession(false);
        //先判断session是否为空,不为空从session中获取assertion,否则从request中获取
        final Assertion assertion = (Assertion) (session == null ? request
                .getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION) : session
                .getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION));
        //如果assertion认证信息不为空就获取principal
        return assertion == null ? null : assertion.getPrincipal();
    }

简单总结就是从request或session里获取assertion,再从assertion里获取Principal。通过new CasHttpServletRequestWrapper((HttpServletRequest) servletRequest, principal)向request请求中添加principal属性,重新包装servletRequest,用于继续下一个filter。

*HttpServletRequestWrapperFilter.java的内部类*

final class CasHttpServletRequestWrapper extends HttpServletRequestWrapper {

        private final AttributePrincipal principal;

        CasHttpServletRequestWrapper(final HttpServletRequest request, final AttributePrincipal principal) {
            super(request);
            this.principal = principal;
        }

        public Principal getUserPrincipal() {
            return this.principal;
        }

        public String getRemoteUser() {
            return principal != null ? this.principal.getName() : null;
        }

…… ……
}

说白了这里的request请求的warp封装,就是方便客户端应用可以通过getRemoteUser()得到登录用户名。

7. 调用第5个filter

AssertionThreadLocalFilter.java

public final class AssertionThreadLocalFilter implements Filter {

    public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
            final FilterChain filterChain) throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpSession session = request.getSession(false);
        //获取assertion
        final Assertion assertion = (Assertion) (session == null ? request
                .getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION) : session
                .getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION));

        try {
            //将assertion认证信息放入到线程中
            AssertionHolder.setAssertion(assertion);
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            AssertionHolder.clear();
        }
    }

    public void destroy() {
        // nothing to do
    }
}

这个过滤器通过代码分析可以看到AssertionHolder.setAssertion(assertion) 将Assertion绑定到本地线程ThreadLocal,那么后面的Fileter和Servlet就可以在本地线程中取得Assertion对象。

8. 获取用户认证信息并展示

最终当所有filter完成过滤后,通过我们自己的测试代码中spring找到“/”的映射。

@Controller
public class IndexController {
  @RequestMapping("/")
  @ResponseBody
  public String index(HttpServletRequest request, HttpServletResponse response) {
	String result = "execute test method</br>";
	result +="request.sessionId: " + request.getSession().getId()+"</br>";
	result +="request.getRemoteUser():" + request.getRemoteUser()	+"</br>";
	result +="request.getUserPrincipal():" + request.getUserPrincipal() +"</br>";
	return result;
}

最终我们在自己的controller中可通过request.getRemoteUser()或者request.getUserPrincipal()获取当前登录认证后的用户信息。当然你要从会话中取对应的认证信息也是可行的。那么我们到此是真正的完成了所以的单独登录认证过程,重新回到了我们客户端应用系统中,那么剩下的就是客户端中的一系列的操作了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

行走的一只鞋

你的肯定是对我最大赞赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值