Bootstrap

不同业务场景Cas客户端(Java业务系统)接入

概述

场景一、网站部分文章需要Cas认证(登录平台)通过后才能访问

1、通过拦截器,把文章访问路径进行拦截,判断当前文章是否需要cas认证

2、如果需要认证,那么调用CAS服务端进行认证,成功后回调地址

3、根据回调地址,返回文章内容

场景二、通过登录平台Cas认证(登录平台)后,不需要再登录就能访问业务系统

1、登录平台系统

2、通过平台,点击业务系统(一般平台上看到业务系统图片链接)

3、进入cas服务端认证中心

4、认证通过后,回调地址(调用业务系统)并返回用户username等信息

5、业务系统根据返回用户,校验信息并返回前端需要的内容(如:菜单、cookie、token等等)

说明:把自己登录页面换成平台登录登录页面,不是真正意义上单点登录,通过哪个登录入口都可以。

cas原理

来自百度百科一张图片,其实也是官网一张图片,其实百度百科有解释,这里我用自己的理解再复述一遍。

1、用户访问CAS Client请求资源

        比如我们配置的路径:cas.server-login-url=http://XXX:8989/cas/login

2、客户端程序做了重定向,重定向到CAS Server

        我们在网址上看到路径:http://XXX/cas?service=http://localhost:8888/xxx/

        http://localhost:8888是业务系统网址(一般客户端配置在业务系统上,在类上使用@EnableCasClient标识为cas客户端)

3、CAS Server会对请求做认证,验证是否有TGC(Ticket Granted Cookie,有TGC说明已经登录过,不需要再登录,没有就返回登录页面

4、认证通过后会生成一个Service Ticket返回Cas Client,客户端进行Ticket缓存,一般放在Cookie里,我们称之为TGC(Ticket Granted Cookie)

5、然后Cas Client就带着Ticket再次访问Cas Server,CAS Server进行Ticket验证

6、CAS Server对Ticket进行验证,回调业务系统地址并返回用户信息

        回调地址:前面第二步service后面的地址--》http://localhost:8888/xxx

 

cas客户端配置

包引进代码-》:

<dependency>
			<groupId>net.unicon.cas</groupId>
			<artifactId>cas-client-autoconfig-support</artifactId>
			<version>2.3.0-GA</version>
		</dependency>

一、网站部分文章需要Cas认证通过后才能访问

配置代码-》

// 1、配置文件的内容
cas:
  server-url-prefix: http://cas.XXX:8989/cas
  server-login-url: http://cas.XXX:8989/cas/login
  client-host-url: http://localhost:8888
  validation-type: cas
  authentication-url-patterns:
    /cas_authentication
// 备注:authentication-url-patterns 是正则表达式,在重定向路径有这个(/cas_authentication),cas会拦截并进行认证

// 2、配置cas客户端以及服务地址与客户端访问地址
@Configuration @EnableCasClient
public class CasAuthConfig extends CasClientConfigurerAdapter {
    @Value("${cas.server-url-prefix}") private String casServerUrlPrefix;
    @Value("${cas.client-host-url}") private String casClientHostUrl;

    @Override
    public void configureAuthenticationFilter(FilterRegistrationBean authenticationFilter) {
        // 设定匹配的路径
//        authenticationFilter.addUrlPatterns(API_CAS_AUTH);
        Map<String, String> initParameters = new HashMap<>();
        initParameters.put(ConfigurationKeys.CAS_SERVER_LOGIN_URL.getName(), this.casServerUrlPrefix);
        initParameters.put(ConfigurationKeys.SERVER_NAME.getName(), this.casClientHostUrl);
        authenticationFilter.setInitParameters(initParameters);
    }
}

// 3、拦截器,重定向到cas服务端认证后,会判断是否登录,如果没有登录,则先跳转到cas登录框(看1配置server-login-url)
@Component
public class ArchiveInterceptor extends HandlerInterceptorAdapter {
    private static final Logger logger = LoggerUtils.getPlatformLogger();

    @Autowired
    private ArchivesService archivesService;

    @Value("${cas.client-host-url}") private String casClientHostUrl;
    @Value("${cas.authentication-url-patterns}") private String casAuth;

    private static final String INTERCEPTOR_URL = "/article/";

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        if (request.getRequestURI().contains(INTERCEPTOR_URL)) {
            logger.info("拦截路径 uri = {}", request.getRequestURI());
            String id = StringUtils.substringAfter(request.getRequestURI(), INTERCEPTOR_URL);
            if (StringUtils.isEmpty(id)) {
                throw new TemplateNotFoundException(
                        ExceptionEnum.HTTP_BAD_REQUEST.getCode(),
                        ExceptionEnum.HTTP_BAD_REQUEST.getMessage(),
                        "转入的路径不正确");
            }
            Archives archives = archivesService.selectByPrimaryKey(id);

            //判断下文章是否需要登录
            if (!Objects.isNull(archives.getIsCheckLogin()) && archives.getIsCheckLogin() == 1) {
                String pathUrl = casClientHostUrl + casAuth + "?redirectUrl=" + request.getRequestURI();
                logger.info("重定网址:{}", pathUrl);
                response.sendRedirect(pathUrl);
            }
        }

        return Boolean.TRUE;
    }
}

// 控制器(文章路径)
@RequestMapping("/article/{id}")
    public void article(@PathVariable String id) throws CmsException {
        //返回文章内容
    }

@RequestMapping("/cas_authentication")
    public void redirect(@RequestParam(value = "redirectUrl") String redirectUrl) throws CmsException {
        if (Strings.isEmpty(redirectUrl)) return;

       //返回文章内容
    }

备注:为什么cas认证成功后访问文章路径不一样?
         这里主要是避免拦截器死循环,重定向都同一个路径,会不断判断并进入死循环。
     那为什么不对返回的用户(或token)进行判断,有用户了不就证明登录成功了吗,不用重定向了?
         因为文章访问原本就没有用户,所有无法判断。

二、通过登录平台Cas认证后,不需要再登录就能访问业务系统

供配置代码-》

// 1、配置cas
cas.server-url-prefix=http://XXX/cas
cas.server-login-url=http://XXX/cas/login
cas.client-host-url=http://localdev
cas.validation-url-patterns=/cas_authentication

// 2、cas客户端配置
@Slf4j
@Configuration @EnableCasClient @Controller
public class CasControlFacade extends CasClientConfigurerAdapter {

    private static final int COOKIE_MAX_AGE = 30 * 60 * 24;

    @Value("${cas.server-url-prefix}") @Getter private String casServerUrlPrefix;
    @Value("${cas.client-host-url}") @Getter private String casClientHostUrl;

    @Resource private SessionUserApp userApp;


    /**
     * 受cas保护的路径, 回跳时会先使用filter进行验证Ticket,进入控制器后已经是合法用户
     */
    public String casAuth(Model model, HttpServletRequest request, HttpServletResponse response) {

        model.addAttribute("hasError", false);
        Principal principal = request.getUserPrincipal();
        if (Objects.isNull(principal) || !(principal instanceof AttributePrincipal)) {
            model.addAttribute("error", buildErrorMsg("Invalid signin request"));
            model.addAttribute("hasError", true);
            return "index";
        }

        AttributePrincipal loginResult= (AttributePrincipal) principal;

        log.debug("cas联合登录成功, username = {}, userPrincipal = {}", loginResult.username, loginResult.principal);

        CheckUserExistOutput isUserExist = userApp.isUserExist(new CheckUserExistInput(loginResult.username));
        if (!isUserExist.getIsExist()) {
            log.warn("cas回传参数异常, 本地用户不存在, username = {}, userPrincipal = {}", loginResult.username, loginResult.principal);
            model.addAttribute("error", buildErrorMsg("用户[" + loginResult.username + "]不存在"));
            model.addAttribute("hasError", true);
            return "index";
        }

        // 直接使用uid快捷登录, 前面的检测保证到这里肯定成功
        SigninOutput result = userApp.signin(DirectSigninInput.of(loginResult.username));
        response.addCookie(generateCookie(result.getToken()));
        response.addCookie(generateLoginFlagCookie());

        model.addAttribute("payload", result);

        return "index";
    }

    @Override
    public void configureAuthenticationFilter(FilterRegistrationBean authenticationFilter) {
        // 设定匹配的路径
        authenticationFilter.addUrlPatterns(API_CAS_AUTH);
        Map<String, String> initParameters = Maps.newHashMap();
        log.debug("[CAS] initial CAS with loginUrl = {}", this.casServerUrlPrefix);
        log.debug("[CAS] initial CAS with serverName = {}", this.casClientHostUrl);
        initParameters.put(ConfigurationKeys.CAS_SERVER_LOGIN_URL.getName(), this.casServerUrlPrefix);
        initParameters.put(ConfigurationKeys.SERVER_NAME.getName(), this.casClientHostUrl);

        authenticationFilter.setInitParameters(initParameters);
    }
}

;