目录
最近在网上搜索CAS 单点登录及第三方授权登录的功能,网上一搜一大堆,使用到处都是坑,那么多抄袭的,我也只能苦笑。。。
自己动手丰衣足食,所有还是把自己的正确功能记录一下,给大家做个参考
一:Cas server 搭建
CAS提供了模板 Overlay Template,方便使用 https://github.com/apereo/cas-overlay-template,当前版本是5.3.16,JDK 1.8+
IDEA导入下载的模板,暂时不修改任何熟悉,添加Tomcat
启动Cas Server,打开http://localhost:8080/cas/login
输入用户名: casuser 密码:Mellon
静态的用户名和密码在application.properties里配置
因为使用静态的用户和http连接,左侧有提示
黄色内容提示我们没有使用https,本地暂时使用的是http
蓝色内容提示我们使用了静态的用户登录,当然我们会在后面添加数据库支持
二:证书配置
1:生成证书
HTTPS需要证书的支持,所有我们借用JDK提供的工具keytool生成自己使用的证书
keytool -genkey -alias caskeystore -keypass cas123 -keyalg RSA -validity 36500 -keystore D:/Java/keys/cas.keystore
名字与姓氏时为为具体路由地址,就是待会CAS认证服务器,sso.test.com,时间设置的比较长,36500单位是天
2:导出数字证书
keytool -export -file D:/Java/keys/cas.crt -alias caskeystore -keystore D:/Java/keys/cas.keystore
3:数字证书导入到JDK中
keytool -import -keystore D:/Java/jdk1.8.0_161/jre/lib/security/cacerts -file D:/Java/keys/cas.crt -alias caskeystore -storepass changeit
因为我们没有这个域名,所有将本地的host文件修改下,添加以下
127.0.0.1 sso.test.com
4:配置tomcat
在server.xml中
<Connector port="443" protocol="org.apache.coyote.http11.Http11AprProtocol" maxThreads="150" SSLEnabled="true" >
<UpgradeProtocol className="org.apache.coyote.http2.Http2Protocol" />
<SSLHostConfig>
<Certificate certificateKeystoreFile="D:/Java/keys/cas.keystore" certificateKeystorePassword="cas123" type="RSA" />
</SSLHostConfig>
</Connector>
访问 https://sso.test.com/cas/login
已经可以正常访问了。
三:添加数据库支持
1:注释掉静态用户名和密码
#cas.authn.accept.users=casuser::Mellon
2:添加新配置
#以下为本地的数据库配置信息
cas.authn.jdbc.query[0].url=jdbc:mysql://localhost:3306/user?serverTimezone=UTC&allowMultiQueries=true
cas.authn.jdbc.query[0].user=root
cas.authn.jdbc.query[0].password=123456
cas.authn.jdbc.query[0].sql=select password from data_user where user_name = ?
cas.authn.jdbc.query[0].fieldPassword=password
cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver
cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQLDialect
cas.authn.jdbc.query[0].passwordEncoder.type=DEFAULT
cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8
cas.authn.jdbc.query[0].passwordEncoder.encodingAlgorithm=MD5
#指定过期字段,1为过期,若过期不可用(可选)
#cas.authn.jdbc.query[0].fieldExpired=expired
#为不可用字段段,1为不可用,需要修改密码(可选)
#cas.authn.jdbc.query[0].fieldDisabled=disabled
3:在pom.xml中添加配置
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-jdbc</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.18</version>
</dependency>
mysql版本根数据库版本一致就行
建表语句
CREATE TABLE DATA_USER(
ID VARCHAR(32) COMMENT '编号' ,
USER_NAME VARCHAR(60) COMMENT '用户名' ,
PASSWORD VARCHAR(60) COMMENT '密码' ,
CREATED_BY VARCHAR(32) COMMENT '创建人' ,
CREATED_TIME DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间' ,
UPDATED_BY VARCHAR(32) COMMENT '更新人' ,
UPDATED_TIME DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间'
) COMMENT = '用户表';
三:客户端搭建
1:搭建个spring boot项目
以下是pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.5</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>client1</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>client1</name>
<description>client1</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 新增的引用 -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-cas</artifactId>
<version>5.5.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
新建application.yml
cas:
server:
host: https://sso.test.com/cas
login: ${cas.server.host}/login
logout: ${cas.server.host}/logout
app:
server:
host: http://localhost:${server.port}${server.servlet.context-path}
login: /login/cas
logout: /logout
server:
# address: 127.0.0.1
port: 9998
servlet:
context-path: /client1
# 为了方便调试可以打开
#logging.level.org.springframework.security: debug
#logging.level.web: debug
2:配置Security
添加SecurityConfig
@Configuration
@EnableWebSecurity //启用web权限
@EnableGlobalMethodSecurity(prePostEnabled = true) //启用方法验证
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final String CAS_AUTHENTICATION_PROVIDER_KEY = "casAuthenticationProviderKey";
@Autowired
private CasProperties casProperties;
/**定义安全策略*/
@Override
protected void configure(HttpSecurity http) throws Exception {
//禁用csrf保护机制
http.csrf().disable();
//禁用cors保护机制
http.cors().disable();
//禁用form表单登录
http.formLogin().disable();
//增加自定义过滤器
http.authorizeRequests()//配置安全策略
// .antMatchers("/**").permitAll()
.antMatchers("/common/**").hasAnyRole("USER")
// .anyRequest().authenticated()//其余的所有请求都需要验证
.and()
.logout()
.permitAll();//定义logout不需要验证
http.exceptionHandling()
.authenticationEntryPoint(casAuthenticationEntryPoint())
.and()
.addFilter(casAuthenticationFilter())
.addFilterBefore(casLogoutFilter(), LogoutFilter.class)
.addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
auth.authenticationProvider(casAuthenticationProvider());
}
/**
* CAS认证入口点开始 =============================================================================
*/
//即跳转至服务端的cas地址
@Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
casAuthenticationEntryPoint.setLoginUrl(casProperties.getCasServerLogin());
casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
return casAuthenticationEntryPoint;
}
// 设置客户端service的属性
@Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
// 设置回调的service路径,此为主页路径
// Cas Server认证成功后的跳转地址,这里要跳转到我们的Spring Security应用,
// service 配置自身工程的根地址+/login/cas,表示是通过自身工程跳转到cas的登录页面,/login/cas 这个是security集成cas的固定写法
serviceProperties.setService(casProperties.getAppServerHost() + casProperties.getAppServerLogin());
// 对所有的未拥有ticket的访问均需要验证
serviceProperties.setAuthenticateAllArtifacts(true);
// sendRenew默认是false,true则意味着不允许单点登录,用户需要重新输入用户名密码以验证
// 在安全级别要求比较高的情况下可以使用
// serviceProperties.setSendRenew(false);
return serviceProperties;
}
/**
* CAS认证入口点结束 =============================================================================
*/
/**
* CAS认证过滤器开始 =============================================================================
*/
@Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setFilterProcessesUrl(casProperties.getAppServerLogin());
return casAuthenticationFilter;
}
/**
* 创建CAS校验类
* Notes:TicketValidator、AuthenticationUserDetailsService属性必须设置;
* serviceProperties属性主要应用于ticketValidator用于去cas服务端检验ticket
*/
@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setAuthenticationUserDetailsService(customUserDetailsService());
casAuthenticationProvider.setServiceProperties(serviceProperties());
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider.setKey(CAS_AUTHENTICATION_PROVIDER_KEY);
return casAuthenticationProvider;
}
/**
* 用户自定义的AuthenticationUserDetailsService
*/
@Bean
public AuthenticationUserDetailsService<CasAssertionAuthenticationToken> customUserDetailsService() {
return new CustomUserDetailsService();
}
/**
* 验证ticker,向cas服务器发送验证请求
*/
@Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
// 配置上服务端的校验ticket地址
return new Cas20ServiceTicketValidator(casProperties.getCasServerHost());
}
/**
* CAS认证过滤器结束 =============================================================================
*/
/**
* CAS登出过滤器开始 =============================================================================
*/
/**
* 请求单点退出过滤器
* @return
*/
@Bean
public LogoutFilter casLogoutFilter() {
LogoutFilter logoutFilter =
new LogoutFilter(casProperties.getCasServerLogout(), new SecurityContextLogoutHandler());
logoutFilter.setFilterProcessesUrl(casProperties.getAppServerLogout());
return logoutFilter;
}
@Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setIgnoreInitConfiguration(true);
return singleSignOutFilter;
}
/**
* CAS登出过滤器结束 =============================================================================
*/
}
配置读取文件CasProperties
@Component
@Data
public class CasProperties {
@Value("${cas.server.host}")
private String casServerHost;
@Value("${cas.server.login}")
private String casServerLogin;
@Value("${cas.server.logout}")
private String casServerLogout;
@Value("${app.server.host}")
private String appServerHost;
@Value("${app.server.login}")
private String appServerLogin;
@Value("${app.server.logout}")
private String appServerLogout;
}
自定义授权CustomUserDetailsService
public class CustomUserDetailsService implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {
/**
* @param token The pre-authenticated authentication token
* @return UserDetails for the given authentication token, never null.
* @throws UsernameNotFoundException if no user details can be found for the given
* authentication token
*/
@Override
public UserDetails loadUserDetails(CasAssertionAuthenticationToken token) throws UsernameNotFoundException {
System.out.println("人员信息");
List<GrantedAuthority> authorities = new ArrayList();
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return new User(token.getName(), "", authorities);
}
}
3:添加接口
@RestController
@AllArgsConstructor
@RequestMapping("/common")
public class Common {
@GetMapping( "/test")
public String test() {
return "客户端1";
}
}
client2和client1配置基本相同,自己操作即可
启动clien项目,分别访问http://localhost:9998/client1/common/test和http://localhost:9999/client2/common/test
发现以下错误
4:未认证授权的服务
出现以上问题是因为Cas Server未开启http协议
在服务端的WEB-INF/classess/application.properties添加以下内容
#设置安全为false
cas.tgc.secure=false
#开启识别json文件,默认false
cas.serviceRegistry.initFromJson=true
# 配置允许登出后跳转到指定页面
cas.logout.followServiceRedirects=true
# 跳转到指定页面需要的参数名为 service
cas.logout.redirectParameter=service
同样修改WEB-INF\classes\services\HTTPSandIMAPS-10000001.json,在serviceId的属性中添加http后重启服务端即可
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^(https|http|imaps)://.*",
"name" : "HTTPS and IMAPS",
"id" : 10000001,
"description" : "This service definition authorizes all application urls that support HTTPS and IMAPS protocols.",
"evaluationOrder" : 10000
}
现在重新打开http://localhost:9998/client1/common/test
输入用户名和密码,登录
访问 http://localhost:9999/client2/common/test,直接显示内容
单点登录到这里就完成了