CAS(Central Authentication Service) 是 Yale 大学发起的一个开源项目,是单点登录的一种现方式
官网地址:https://www.apereo.org/
Github地址:https://github.com/apereo/cas
分为CAS Server服务端和CAS Client客户端:
CAS Server:
CAS Server 负责完成对用户的认证工作, CAS Server 需要独立部署,有不止一种 CAS Server 的实现, Yale CAS Server 和 ESUP CAS Server 都是很不错的选择。
CAS Server 会处理用户名 / 密码等凭证 (Credentials) ,它可能会到数据库检索一条用户帐号信息,也可能在 XML 文件中检索用户密码,对这种方式, CAS 均提供一种灵活但同一的接口 / 实现分离的方式, CAS 究竟是用何种认证方式,跟 CAS 协议是分离的,也就是,这个认证的实现细节可以自己定制和扩展.
CAS Client:
CAS Client 负责部署在客户端(注意,我是指 Web 应用),原则上, CAS Client 的部署意味着,当有对本地 Web 应用的受保护资源的访问请求,并且需要对请求方进行身份认证, Web 应用不再接受任何的用户名密码等类似的 Credentials ,而是重定向到 CAS Server进行认证。
目前, CAS Client 支持(某些在完善中)非常多的客户端,包括 Java 、 .Net 、 ISAPI 、 Php 、 Perl 、 uPortal 、 Acegi 、 Ruby 、VBScript 等客户端,几乎可以这样说, CAS 协议能够适合任何语言编写的客户端应用。
因为CAS要求所有的请求都要在一个安全的通道,所以要配置服务端的https。
keytool -genkey -alias wjx_cas -keyalg RSA -keysize 2048 -validity 36500 -keystore C:/opt/keys/wjx_cas.keystore
参数说明:
-genkey 生成密钥
-keyalg 指定密钥算法,这时指定RSA,
-keysize 指定密钥长度,默认是1024位,这里指定2048,长一点,比较难破解,
-validity 指定证书有效期,这里指定36500天.
-alias 指定别名,这里是 wjx_cas
-keystore 指定密钥库存储位置,这里存在 C:/opt/keys/目录下
注意:您的名字与姓氏 www.sso.com 是CAS服务器使用的域名,不是随便乱定的,其他的随意.
在cmd中找到当前目录,输入命令
输入结果:
keytool -export -alias wjx_cas -storepass 123456 -file C:/opt/keys/wjx_cas.cer -keystore C:/opt/keys/wjx_cas.keystore
参数说明:
-alias指定别名为 wjx_cas;
-storepass指定私钥为123456;
-file指定导出证书的文件名为 wjx_cas.cer;
-keystore指定之前生成的密钥文件的文件名。
注意:-alias和-storepass必须为生成wolfcode .keystore密钥文件时所指定的别名和密码,否则证书导出失败.
当前目录下面的文件。
查看当前JDK版本,必须是 JDK.1.8 以上
找到当前的jdk安装的目录下面,C:/Program Files/Java/jdk1.8.0_191/jre/lib/security/
执行下面命令:
keytool -import -alias wolfcode -keystore C:/"Program Files"/Java/jdk1.8.0_191/jre/lib/security/cacerts -file C:/opt/keys/wjx_cas.cer -trustcacerts
注意:
1.原来的 $JAVA_HOME/jre/lib/security/cacerts 文件要先删掉,否则会报出java.io.IOException: Keystore was tampered with, or password was incorrect错误.
2.如果路径有空格或特殊字符,像我上面一样加上引号.
正确截图:
什么是maven的overlay?
overlay可以把多个项目war合并成为一个项目,并且如果项目存在同名文件,那么主项目中的文件将覆盖掉其他项目的同名文件。
apereo提供了一个基于层结构的框架,可以帮助开发者快速引入cas server的代码,然后实现自由配置或代码覆盖,打包方式也非常简单。
Github地址: https://github.com/apereo/cas-overlay-template
使用步骤:
1.下载或者克隆cas-overlay-template(version:5.2)项目到本地,用Intellji Idea打开项目,下载依赖时间较长,需耐心等待,建议使用国内的镜像.
有个依赖下载很慢或者下载不了cas-server-webapp-tomcat,建议直接下载之后放到本地仓库对应的目录会快点.
http://mvnrepository.com/artifact/org.apereo.cas/cas-server-webapp-tomcat
环境要求:JDB1.8+
打开项目之后。
2.把pom.xml中配置的仓库注释/删除,因为仓库在国外,所以下载速度会比较慢,建议换成国内的镜像.
true
maven2-release
http://uk.maven.org/maven2/
//如果在setting.xml文件中已经配置了阿里镜像也是可以的.
3.在项目中添加src/main/java和src/main/resources目录,并将src/main/java设置为代码文件根目录,将src/main/resources设置为资源文件根目录。
4.将overlays目录下的WEB-INF/classes/目录中的application.properties文件复制到src/main/resources中
5.将 证书 C:/opt/keys/wjx_cas.keystore 拷贝到resources 目录
6.修改application.properties文件.
7.打开命令行,进入项目所在目录,运行内置的命令: build.cmd run ,执行时间较长,请耐心等待
CAS is configured to accept a static list of credentials for authentication. While this is generally useful for demo purposes, it is STRONGLY recommended that
这个只是警告,不是错误,意思是现在使用的是静态的验证,不够安全.
没关系,我们后续会换成数据库的验证.
这个下面还有一个,出现这个信息,表示CAS服务端已经启动了。
8.配置host
www.sso.com 这个域名,为当前CAS的服务端。必须和之前 生成证书的 名字与姓氏 一致。
9.访问CAS服务器
输入地址:https://www.sso.com:8443/cas/login
不用管,那是因为我们的证书是通过JDK生成的,不是认证机构发布的。
第一次进去会有点慢。跳转完成之后界面。
输入,用户密码:wjx/123456
导入maven依赖
net.unicon.cas
cas-client-autoconfig-support
1.5.0-GA
项目结构:聚会项目,里面有三个服务,公共模块、会员服务、订单服务、支付服务
package com.wjx.config;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.util.AbstractCasFilter;
import org.jasig.cas.client.validation.Assertion;
import javax.servlet.http.HttpServletRequest;
/**
* cas client常用工具类
*
* @author wjx
*/
public class CASUtil {
/**
* 从cas中获取用户名
*
* @param request
* @return
*/
public static String getAccountNameFromCas(HttpServletRequest request) {
Assertion assertion = (Assertion) request.getSession().getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
if(assertion!= null){
AttributePrincipal principal = assertion.getPrincipal();
return principal.getName();
}else return null;
}
}
我发现不加 SingleSignOutFilter 类过滤,单点退出之后,依然可以访问,加上这个就可以了,原因不清楚
package com.wjx.config;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @Description:
* @Auther: wjx
* @Date: 2019/3/5 16:02
*/
@Configuration
public class FilterConfig {
@Value("${cas.server-url-prefix}")
private String CAS_URL;
@Value("${cas.client-host-url}")
private String APP_URL;
/**
* 单点登录退出
*
* @return
*/
@Bean
public FilterRegistrationBean singleSignOutFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new SingleSignOutFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.addInitParameter("casServerUrlPrefix", CAS_URL);
registrationBean.setName("CAS Single Sign Out Filter");
registrationBean.setOrder(1);
return registrationBean;
}
/**
* 获取当前用户
*
* @return
*/
@Bean
public FilterRegistrationBean registrationBean() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
registrationBean.setFilter(new LocalUserInfoFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setName("localUserInfoFilter");
registrationBean.setOrder(2);
return registrationBean;
}
}
package com.wjx.config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import javax.servlet.*;
import javax.servlet.FilterConfig;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
/**
* 本地用户信息过滤器
*
* @author wjx
* @Date 2019/3/5 16:02
*/
public class LocalUserInfoFilter implements Filter {
Logger logger = LoggerFactory.getLogger(LocalUserInfoFilter.class);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request_ = (HttpServletRequest) request;
String loginName = CASUtil.getAccountNameFromCas(request_);
if (!StringUtils.isEmpty(loginName)) {
logger.info("访问者 :" + loginName);
request_.getSession().setAttribute("loginName", loginName);
}
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}
package com.wjx.web;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description:
* @Auther: wjx
* @Date: 2019/3/5 12:03
*/
@RestController
public class MemberController {
@Value("${server.port}")
private String serverPort;
@GetMapping("/")
public String hello() {
return "我是会员服务:" + serverPort;
}
}
package com.wjx;
import net.unicon.cas.client.configuration.EnableCasClient;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @Description:
* @Auther: wjx
* @Date: 2019/3/5 11:29
*/
@SpringBootApplication
@EnableCasClient
public class AppMember{
public static void main(String[] args) {
SpringApplication.run(MemberApp.class);
}
}
要想使客户端生效,要加上注解@EnableCasClient
# 项目端口
server.port=8090
# 填CAS服务器的前缀
cas.server-url-prefix=https://www.sso.com:8443/cas
# 填CAS服务器的登录地址
cas.server-login-url=https://www.sso.com:8443/cas/login
# 填客户端的访问前缀 www.member.com是在host文件中配置的映射,映射到127.0.0.1
cas.client-host-url=http://www.member.com:8090
cas.validation-type=CAS
可以发现,客户端已经跳转到服务端的登录界面。但是出现了未认证授权的服务,这个需要对服务代码做一些修改。
解决方法:
解决办法: 服务端开启http,默认只开始https和imaps。
操作步骤(1):将overlays目录下面的service拷贝到src下面进行修改,项目启动sr目录代码会覆盖overlays代码
操作步骤(2): 修改services\HTTPSandIMAPS-10000001.json文件
"serviceId" : "^(https|imaps)://.*"
改为==>
"serviceId" : "^(https|http|imaps)://.*",
操作步骤(3): 在application.properties文件中添加:
cas.tgc.secure=false
cas.serviceRegistry.initFromJson=true
重启cas服务端。
解决了。
其他几个服务按照member配置,注意修改端口号就可以了。
步骤一:在服务端cas-overlay-template-5.2的pom添加
org.apereo.cas cas-server-support-jdbc ${cas.version} mysql mysql-connector-java 5.1.21
步骤二:导入数据库sql脚本
/*
SQLyog Ultimate v12.09 (64 bit)
MySQL - 5.7.21 : Database - cas
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`cas` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `cas`;
/*Table structure for table `sys_user` */
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`expired` int(11) DEFAULT NULL,
`disabled` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
/*Data for the table `sys_user` */
insert into `sys_user`(`id`,`username`,`password`,`expired`,`disabled`) values (1,'test','202cb962ac59075b964b07152d234b70',1,0),(2,'admin','202cb962ac59075b964b07152d234b70',0,0),(3,'root','202cb962ac59075b964b07152d234b70',0,0),(4,'wjx','202cb962ac59075b964b07152d234b70',0,0),(5,'zhangsan','202cb962ac59075b964b07152d234b70',0,1);
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
密码都是:123
步骤三:对密码进行MD5加密,application.properties将cas.authn.accept.users注释,配置文件添加
#Encode Database Authentication 开始
#加密次数
cas.authn.jdbc.encode[0].numberOfIterations=2
#该列名的值可替代上面的值,但对密码加密时必须取该值进行处理
#cas.authn.jdbc.encode[0].numberOfIterationsFieldName=
# 盐值固定列
cas.authn.jdbc.encode[0].saltFieldName=username
#静态盐值
cas.authn.jdbc.encode[0].staticSalt=.
cas.authn.jdbc.encode[0].sql=select * from sys_user_encode where username=?
#对处理盐值后的算法
cas.authn.jdbc.encode[0].algorithmName=MD5
cas.authn.jdbc.encode[0].passwordFieldName=password
cas.authn.jdbc.encode[0].expiredFieldName=expired
cas.authn.jdbc.encode[0].disabledFieldName=disabled
cas.authn.jdbc.encode[0].url=jdbc:hsqldb:mem:cas-hsql-database
cas.authn.jdbc.encode[0].dialect=jdbc:mysql://localhost:3306/cas?useUnicode=true&characterEncoding=UTF-8
cas.authn.jdbc.encode[0].user=root
cas.authn.jdbc.encode[0].password=admin
cas.authn.jdbc.encode[0].driverClass=com.mysql.jdbc.Driver
#Encode Database Authentication 结束
重启服务器。因为要连接数据库,第一次会慢一些。
发现没有了警告,输入数据库的用户名:admin/123
会员服务登录成功之后,订单服务就不需要登录了。
输入地址:https://www.sso.com:8443/cas/logout
再去访问:http://www.member.com:8090/ 地址
完成,测试订单服务也是跳转到登录服务界面。
由于跳转到注销成功界面,不好,我们需要跳转到指定路径
解决方法:
服务端的application.properties添加
###允许退出之后跳转到登录页面,并且可以携带service参数到指定页面
cas.logout.followServiceRedirects=true
重启服务器。
输入地址:https://www.sso.com:8443/cas/logout?service=http://www.member.com:8090/
界面变成下面:发现把登录地址自动带在后边。
至此配置完成。