CAS单点登录客户端实现
客户端介绍
基本架构
cas客户端是一个SpringBoot微应用,引入cas客户端相关依赖,实现请求过滤器,
从而实现通过CAS单点登录服务器进行用户登录的功能。
目录结构
1 | |- cas-client |
引入依赖
1 | <dependency> |
编写代码
启动代码
com.github.johnsonmoon.casclient.Main1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59package com.github.johnsonmoon.casclient;
import com.github.johnsonmoon.casclient.filter.CASAuthFilter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import java.util.EnumSet;
/**
* Create by johnsonmoon at 2019/1/30 09:24.
*/
@ServletComponentScan
@SpringBootApplication
public class Main implements ServletContextInitializer {
//TODO modified
public static final String serverName = "http://127.0.0.1:8500";
public static final String casServerLoginUrl = "https://example.com/cas/login";
public static final String casServerUrlPrefix = "https://example.com/cas";
public static final String casLogoutUrl = "https://example.com/cas/logout";
public static void main(String... args) {
SpringApplication.run(Main.class, args);
}
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
// 用于单点退出
servletContext.addListener(org.jasig.cas.client.session.SingleSignOutHttpSessionListener.class.getName());
FilterRegistration.Dynamic CASSignOutFilter = servletContext.addFilter("CASSignOutFilter", org.jasig.cas.client.session.SingleSignOutFilter.class.getName());
CASSignOutFilter.setInitParameter("casServerUrlPrefix", casServerUrlPrefix);
CASSignOutFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
// 该过滤器负责用户的认证工作,必须启用它,因业务需求,重新实现CasAuthFilter
FilterRegistration.Dynamic CASFilter = servletContext.addFilter("CASFilter", CASAuthFilter.class.getName());
CASFilter.setInitParameter("casServerLoginUrl", casServerLoginUrl);
CASFilter.setInitParameter("serverName", serverName);
CASFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
// 该过滤器负责对Ticket的校验工作,必须启用它
FilterRegistration.Dynamic CASValiFilter = servletContext.addFilter("CASValiFilter", org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter.class.getName());
CASValiFilter.setInitParameter("casServerUrlPrefix", casServerUrlPrefix);
CASValiFilter.setInitParameter("serverName", serverName);
CASValiFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
/*
* 该过滤器负责实现HttpServletRequest请求的包裹,比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。
*/
FilterRegistration.Dynamic CASWrapperFilter = servletContext.addFilter("CASWrapperFilter", org.jasig.cas.client.util.HttpServletRequestWrapperFilter.class.getName());
CASWrapperFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST), false, "/*");
}
}
注意:
- 由于客户端工程为SpringBoot应用,因此通过实现 ServletContextInitializer 接口的 onStartup 方法,注册cas客户端需要的各个组件其中包括必要的监听器、web过滤器等,以及需要开发者自己实现的一个过滤器。
- 启动代码的几个常量配置是必须的,配置了CAS服务端的URL等,开发者自己编写客户端时候可以通过读取配置文件的形式进行配置。
启动配置文件
1 | spring: |
说明:
配置了客户端启动的端口、前端资源路径等
自定义过滤器
com.github.johnsonmoon.casclient.filter.CASAuthFilter1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78package com.github.johnsonmoon.casclient.filter;
import com.github.johnsonmoon.casclient.Main;
import org.jasig.cas.client.Protocol;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.util.CommonUtils;
import org.jasig.cas.client.validation.Assertion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
/**
* Create by johnsonmoon at 2019/1/30 09:48.
*/
public class CASAuthFilter extends AbstractCasFilter {
private static Logger logger = LoggerFactory.getLogger(CASAuthFilter.class);
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) servletRequest;
final HttpServletResponse response = (HttpServletResponse) servletResponse;
final HttpSession session = request.getSession(false);
final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;
/**
* TODO
* Check whether there has already been a user session of this request.
* If there has been, then pass the request to other filters in {@code FilterChain}
*/
if (assertion != null) {
filterChain.doFilter(request, response);
return;
}
/**
* TODO
* If there is no user session, while the request contains a {@code ticket} parameter,
* it means that a user has already loggedIn in the CAS-Server, redirect back to our application,
* then pass the request to other filters in {@code FilterChain}, in order to verify
* the ticket or so.
*/
final String ticket = retrieveTicketFromRequest(request);
if (CommonUtils.isNotBlank(ticket)) {
filterChain.doFilter(request, response);
return;
}
/**
* TODO
* If there is no user session, then get the redirectUrl ready,
* and redirect the request to CAS-Server login page.
*/
final String serviceUrl = constructServiceUrl(request, response);
logger.info("Service url: {}", serviceUrl);
final String redirectUrl = CommonUtils.constructRedirectUrl(
Main.casServerLoginUrl,
getProtocol().getServiceParameterName(),
serviceUrl, false, false);
logger.info("Redirecting to: \"{}\"", redirectUrl);
response.sendRedirect(redirectUrl);
}
public CASAuthFilter(Protocol protocol) {
super(protocol);
}
public CASAuthFilter() {
this(Protocol.CAS2);
}
}
注意:
自定义过滤器中,需要开发者实现逻辑为:
(1)判断请求是否带有有效的用户会话,若有,则说明用户成功登陆CAS服务器,对该请求放行。
(2)若请求中午有效的用户会话,而有带有票据参数(ticket),则将请求放行给其他过滤器进行校验等一系列操作。
(3)若请求中既没有有效的用户会话,也没有票据参数,则将请求重定向到CAS服务器用户登录页面。
基础服务Controller
com.github.johnsonmoon.casclient.controller.BaseController1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57package com.github.johnsonmoon.casclient.controller;
import com.github.johnsonmoon.casclient.Main;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;
import java.util.Map;
/**
* Create by johnsonmoon at 2019/1/30 10:19.
*/
@RestController
@RequestMapping("/client/api/v1/base")
public class BaseController {
private static Logger logger = LoggerFactory.getLogger(BaseController.class);
@GetMapping("/logout")
public void logout(HttpServletResponse response) {
String casLogoutUrl = Main.casLogoutUrl;
try {
if (response != null) {
response.sendRedirect(casLogoutUrl);
}
} catch (Exception e) {
logger.warn(e.getMessage(), e);
}
}
@GetMapping("/gene_data")
public Map<String, Object> generateData(HttpServletRequest request) {
/**
* TODO get loggedIn user information
*/
String userName = null;
AttributePrincipal attributePrincipal = (AttributePrincipal) request.getUserPrincipal();
if (attributePrincipal != null) {
userName = attributePrincipal.getName();
logger.info(String.format("User name: [%s]", userName));
//Map<String, Object> map = attributePrincipal.getAttributes();
}
Map<String, Object> result = new HashMap<>();
long time = System.currentTimeMillis();
result.put("time", time);
result.put("name", (userName == null ? "johnson_" : userName));
result.put("desc", "Hello!__" + time);
return result;
}
}
说明:
该类提供了两个前端接口:
(1)获取CAS服务用户登出URL
(2)获取数据接口,获取的数据包含从请求中获取已登录用户信息的数据。
启动客户端并检验单点登录
启动客户端
IDE启动
- 配置启动项
需要添加jvm参数 -Dwork.home= 指向cas-client工程根目录。
- 启动程序
构建后通过脚本启动
- 进入 cas-example 目录,运行maven命令构建
1 | mvn clean package |
- 构建完成,进入 cas-client/target/cas-client/cas-client/ 目录,运行启动脚本
1 | ./startup.sh |
检验单点登录
- 登录客户端首页 http://127.0.0.1:8500
页面自动跳转至CAS登录页,如下:
- 输入用户名密码点击登录,页面自动跳转回客户端,客户端定时获取用户数据(用户名)输出到页面中,如下:
页面定时将已登录的用户名信息输出到表格中,说明单点登录成功、客户端从单点登录CAS服务器获取用户信息成功。
实例工程
实例工程包含CAS服务器war包打包模块、客户端maven工程代码等,工程仓库地址: