SpringBoot集成CAS实现SSO(single sign on)单点登录
什么是单点登录
单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
CAS(中央认证服务)
CAS是Central Authentication Service的缩写,中央认证服务,一种独立开放指令协议。CAS 是 耶鲁大学(Yale University)发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登录方法。
协议特点
- 开源的企业级单点登录解决方案。
- CAS Server 需要独立部署的 Web 应用。
- CAS Client 支持非常多的客户端(这里指单点登录系统中的各个 Web 应用),包括 Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等语言编写的各种web应用。
- CAS属于Apache 2.0许可证,允许代码修改,再发布(作为开源或商业软件)。
原理和协议
从结构上看,CAS 包含两个部分: CAS Server 和 CAS Client。CAS Server 需要独立部署,主要负责对用户的认证工作;CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。下图是 CAS 最基本的协议过程:
- 访问服务:sso客户端发送请求访问应用系统提供的服务资源。
- 定向认证:sso客户端会重定向用户请求到sso服务器。
- 用户认证:用户身份认证。
- 发放票据:sso服务器会产生一个随机的service ticket。
- 验证票据:sso服务器验证票据service ticket的合法性,验证通过后,允许客户端访问服务。
- 传输用户信息:sso服务器验证票据通过后,传输用户认证结果信息给客户端。
CAS服务端
基本部署
1、git地址:https://github.com/apereo/cas-overlay-template/tree/5.3
2、解压下载的zip压缩包,我放到:E:\wspace\cas-overlay-template-5.3
3、解压后使用maven命令打包:mvn package(该命令执行时间比较长,耐心等待)
4、把target下生成的war包重命名为cas.war放到tomcat下
5、启动tomcat
6、修改配置文件
1)由于cas默认使用的是基于https协议,需要改为兼容使用http协议,打开对应你的目录文件:${tomcat目录}\webapps\cas\web-inf\classes\application.properties
修改application.properties文件,添加下面配置,使用http
# Service Registry(服务注册)
# 开启识别Json文件,默认false,这里就是开启识别 services 中的json文件的
# 注意属性是驼峰的,网上有些文章是小写的,误人子弟
cas.serviceRegistry.initFromJson=true
# 保存tgc,去掉https协议,使用http协议。
cas.tgc.secure=false
#由于https协议默认使用的端口为8443,还需我们修改为tomcat的8080端口
server.port=8080
2)修改HTTPSandIMAPS-10000001.json文件:${tomcat目录}\webapps\cas\web-inf\classes\services目录下的HTTPSandIMAPS-10000001.json
把原来的serviceid内容改成如下:
"serviceId" : "^(https|http|imaps)://.*",
兼容http修改完毕。
3)修改配置中的登录用户名密码(application.properties)
cas.authn.accept.users=lhw::123456
cas服务器端搭建完毕,重启tomcat 进行测试,在浏览器中输入下面地址,进行访问:http://localhost:8080/cas/login
输出我们刚才配置的用户名和密码(lhw/123456)
服务端就已经搭好了,并且可以通过登录退出了。
CAS客户端
客户端搭建
在新建的springboot项目的pom.xml添加如下依赖(匹配对应的版本):
<dependency>
<groupId>net.unicon.cas</groupId>
<artifactId>cas-client-autoconfig-support</artifactId>
<version>2.3.0-GA</version>
</dependency>
在resources下新建application.yml
server:
port: 9999
# cas配置
cas:
#cas服务端的地址
server-url-prefix: http://localhost:8080/cas
#cas服务端的登录地址
server-login-url: http://localhost:8080/cas/login
#当前服务器的地址(客户端)
client-host-url: http://localhost:${server.port}
#ticket校验器使用cas30proxyreceivingticketvalidationfilter
validation-type: cas3
application启动类上添加注解
import net.unicon.cas.client.configuration.EnableCasClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//启用cas
@EnableCasClient
@SpringBootApplication
public class CasOneApp {
public static void main(String[] args) {
SpringApplication.run(CasOneApp.class, args);
}
}
第一个客户端的controller
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
import static org.jasig.cas.client.util.AbstractCasFilter.CONST_CAS_ASSERTION;
@RestController
public class SsoClientOneController {
@RequestMapping("/sso-test1")
public String test1(HttpSession session) {
Assertion assertion = (Assertion) session.getAttribute(CONST_CAS_ASSERTION);
AttributePrincipal principal = assertion.getPrincipal();
String loginName = principal.getName();
return "sso-test1,当前登录账户" + loginName;
}
}
一个客户端就添加好了,我们再添加另一个客户端,除端口其他基本也是一样。
第二个客户端的controller
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
import static org.jasig.cas.client.util.AbstractCasFilter.CONST_CAS_ASSERTION;
@RestController
public class SsoClientTwoController {
@RequestMapping("/sso-test2")
public String test1(HttpSession session) {
Assertion assertion = (Assertion) session.getAttribute(CONST_CAS_ASSERTION);
AttributePrincipal principal = assertion.getPrincipal();
String loginName = principal.getName();
return "test222222,当前登录账户" + loginName;
}
}
在没有登录的情况下访问 http://localhost:9999/sso-test1
直接跳到了登录界面,并且把回调地址也带上了。
访问第二个客户端 http://localhost:8888/sso-test2
跟第一个也是一样,这次我们随便登录一个。
登录后,执行了回调接口,刷新一下另外一个客户端的地址
也登录成功了。
配置统一登出
添加登出接口。
@Controller
public class LogoutController {
/**
* 退出 后自动重定向自定义接口
*
* @param request
* @return
*/
@RequestMapping("/system/logout1")
public String logout1(HttpServletRequest request) {
HttpSession session = request.getSession();
session.invalidate();
//http://localhost:8080/cas/logout是cas的登出链接
//http://localhost:9999/system/logOutSuccess 自己的客户端的退出请求链接
return "redirect:http://localhost:8080/cas/logout?service=http://localhost:9999/system/logOutSuccess";
}
/**
* 退出成功页
*
* @return
*/
@RequestMapping("/system/logOutSuccess")
@ResponseBody
public String logOutSuccess() {
return "test1成功退出!";
}
}
设置cas认证中心允许重定向跳转
打开你的cas认证中心里的 application.properties 文件,添加如下配置:
#配置允许登出后跳转到指定页面
cas.logout.followServiceRedirects=true
新建Java配置文件:
@Configuration
public class CasConfig {
@Autowired
private Environment environment;
/**
* description: 登录过滤器
*
* @param: []
* @return: org.springframework.boot.web.servlet.FilterRegistrationBean
*/
@Bean
public FilterRegistrationBean filterSingleRegistrationBean() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new SingleSignOutFilter());
// 设定匹配的路径
registration.addUrlPatterns("/*");
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("casServerUrlPrefix", environment.getProperty("cas.server-url-prefix"));
registration.setInitParameters(initParameters);
// 设定加载的顺序
registration.setOrder(1);
return registration;
}
/**
* description:过滤验证器
* * @param: []
*
* @return: org.springframework.boot.web.servlet.FilterRegistrationBean
*/
@Bean
public FilterRegistrationBean filterValidationRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new Cas30ProxyReceivingTicketValidationFilter());
// 设定匹配的路径
registration.addUrlPatterns("/*");
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("casServerUrlPrefix", environment.getProperty("cas.server-url-prefix"));
initParameters.put("serverName", environment.getProperty("cas.client-host-url"));
initParameters.put("useSession", "true");
registration.setInitParameters(initParameters);
// 设定加载的顺序
registration.setOrder(1);
return registration;
}
/**
* description:授权过滤器
*
* @param: []
* @return: org.springframework.boot.web.servlet.FilterRegistrationBean
*/
@Bean
public FilterRegistrationBean filterAuthenticationRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new AuthenticationFilter());
// 设定匹配的路径
registration.addUrlPatterns("/*");
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("casServerUrlPrefix", environment.getProperty("cas.server-url-prefix"));
initParameters.put("serverName", environment.getProperty("cas.client-host-url"));
//设置忽略 退出登录不用登录
initParameters.put("ignorePattern", "/system/*");
registration.setInitParameters(initParameters);
// 设定加载的顺序
registration.setOrder(1);
return registration;
}
/**
* wrapper过滤器
*
* @return
*/
@Bean
public FilterRegistrationBean filterWrapperRegistration() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setFilter(new HttpServletRequestWrapperFilter());
// 设定匹配的路径
registration.addUrlPatterns("/*");
// 设定加载的顺序
registration.setOrder(1);
return registration;
}
/**
* 添加监听器
*
* @return
*/
@Bean
public ServletListenerRegistrationBean<EventListener> singleSignOutListenerRegistration() {
ServletListenerRegistrationBean<EventListener> registrationBean = new ServletListenerRegistrationBean<EventListener>();
registrationBean.setListener(new SingleSignOutHttpSessionListener());
registrationBean.setOrder(1);
return registrationBean;
}
}
客户端2跟客户端1的大似相同,这样就可以实现登出一个系统,所有系统全部登出。