使用spring实现单点登录(SpringSecurityCAS)
使用spring实现单点登录(SpringSecurityCAS)默认用户名和密码在cas\WEB-INF\classes\application.properties中 用户名:casuser 密码:Mellon把war包放在tomcat中启动下载官方的cas-overlay-template,在此基础上修改打包,运行。 cas-overlay-template已经集成了springboot,可直接运行。build之后会在target目录生成一个cas.war文件编译过程需要下载很多文件,过程有点长,需要耐心等待 如果本机maven环境变量没配置,用idea打开工程编译也可以
版本说明6.0及以上需要jdk11,如果你是jdk8,最高只能用5.3版本
5.3以下版本的是maven工程,6.0以上改成gradle工程了
这里基于5.3版本搭建
原理 一、服务端搭建1、下载源码HTTPS://github.com/apereo/Cas-overlay-template/tree/5.3
下载官方的cas-overlay-template,在此基础上修改打包,运行。 cas-overlay-template已经集成了springboot,可直接运行。
build之后会在target目录生成一个cas.war文件
编译过程需要下载很多文件,过程有点长,需要耐心等待 如果本机maven环境变量没配置,用idea打开工程编译也可以
把war包放在tomcat中启动
默认用户名和密码在cas\WEB-INF\classes\application.properties中 用户名:casuser 密码:Mellon
CAS默认使用的是HTTPS协议,如果使用HTTPS协议需要SSL安全证书(需向特定的机构申请和购买) 。如果对安全要求不高或是在开发测试阶段,可使用HTTP协议。我们这里讲解通过修改配置,让CAS使用HTTP协议。 如果不去除https认证下面整合客户端时会出现未认证授权的服务
2、去掉https配置使用http链接跳转到cas服务器默认会有未认证授权的服务提示
1)修改CAS服务端配置文件 cas\WEB-INF\classes目录的application properties添加如下的内容
cas.tgc.secure=false
cas.serviceRegistry.initFromJson=true
2)cas\WEB-INF\classes\services目录下的HTTPSandIMAPS-10000001.json 修改内容如下 即添加http "serviceId" : "^(https|http|imaps)://.*"
3)重启tomcat
3、tomcat 配置https证书1、生成证书
keytool -genkey -alias sso-server -keystore ./mydestore -keyalg RSA -validity 2000 -storepass 111111 -keypass 111111
# 输入:sso-server.com
keytool -export -alias sso-server -keystore ./mydestore -file server.crt -keypass 111111
# 将证书导入到jdk
keytool -import -alias sso-server -File ./server.crt -keystore "C:/Program Files/Java/jdk1.8.0_161/jre/lib/security/cacerts" -storepass changeit -keypass 111111
# jdk默认密码:changeit
2、配置tomcat
把证书文件mydestore复制到conf目录下
<Connector port="8443" Protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="150" SSLEnabled="true"
scheme="https" secure="true" clientAuth="false" sslProtocol="TLS"
keystoreFile="conf/mydestore" keystorePass="111111"
>
</Connector>
修改本地host文件:
127.0.0.1 sso-server.com
重启tomcat,输入访问地址:https://sso-server.com:8443/cas
4、修改cas-server链接到mysql数据库1)服务端工程引入jar包,重新编译生成cas.war
<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.13</version>
</dependency>
2)修改配置文件: cas/WEB-INF/classes/application.properties
##
# CAS Authentication Credentials
#
# cas.authn.accept.users=casuser::Mellon
cas.authn.jdbc.query[0].url=jdbc:mysql://127.0.0.1:3306/user?serverTimezone=GMT+8&useUnicode=true&characterEncoding=UTF-8&useSSL=false&allowPublicKeyRetrieval=true
cas.authn.jdbc.query[0].user=root
cas.authn.jdbc.query[0].password=password
cas.authn.jdbc.query[0].sql=select * from u_user where nickname = ?
cas.authn.jdbc.query[0].fieldPassword=pswd
cas.authn.jdbc.query[0].driverClass=com.mysql.cj.jdbc.Driver
## md5加密
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
#配置单点登出
#配置允许登出后跳转到指定页面
cas.logout.followServiceRedirects=true
#跳转到指定页面需要的参数名为 service
cas.logout.redirectParameter=service
#登出后需要跳转到的地址 如果配置该参数 service将无效。
# cas.logout.redirectUrl=http://sso-server.com:8088/index
#在退出时是否需要 确认退出提示 true弹出确认提示框 false直接退出
cas.logout.confirmLogout=true
#是否移除子系统的票据
cas.logout.removeDescendantTickets=true
#禁用单点登出 默认是false不禁止
#cas.slo.disabled=true
#默认异步通知客户端 清除session
#cas.slo.asynchronous=true
3)测试数据库脚本
CREATE TABLE `u_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT
`nickname` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户昵称'
`email` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '邮箱|登录帐号'
`pswd` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '密码'
`create_time` datetime(0) NULL DEFAULT NULL COMMENT '创建时间'
`last_login_time` datetime(0) NULL DEFAULT NULL COMMENT '最后登录时间'
`status` bigint(1) NULL DEFAULT 1 COMMENT '1:有效,0:禁止登录'
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
INSERT INTO `u_user` VALUES (1 'admin' 'admin@163.com' md5('1') NOW() NULL 1);
4)将war包copy到tomcat运行
二、客户端配置,配置cas-client(集成Spring-security)客户端就是自己的项目工程
1、引入jar包<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-cas</artifactId>
</dependency>
2、添加配置项
#cas服务端的地址
cas.server-url-prefix=http://localhost:8080/cas
#cas服务端的登录地址
cas.server-login-url=http://localhost:8080/cas/login
#当前服务器的地址(客户端)
cas.client-host-url=http://localhost:8088
3、代码示例
启动类添加注解
@SpringBootApplication
//开启spring security过滤器链
@EnableWebSecurity
public class CasDemoClientApplication {
public static void main(String[] args) {
SpringApplication.run(CasDemoClientApplication.class args);
}
}
CAS配置类
package com.example.casclient.config;
import com.example.casclient.security.CustomUserDetailsService;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.validation.Cas30ServiceTicketValidator;
import org.jasig.cas.client.validation.TicketValidator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
@Configuration
public class CasConfig {
@Value("${cas.server-url-prefix}")
private String casServerUrl;
@Value("${cas.client-host-url}")
private String baseUrl;
@Bean
public AuthenticationEntryPoint authenticationEntryPoint() {
CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
entryPoint.setLoginUrl(casServerUrl "/login");
entryPoint.setServiceProperties(this.serviceProperties());
return entryPoint;
}
@Bean
protected AuthenticationManager authenticationManager() throws Exception {
return new ProviderManager(this.casAuthenticationProvider());
}
@Bean
public CasAuthenticationFilter casAuthenticationFilter(
AuthenticationManager authenticationManager
ServiceProperties serviceProperties) throws Exception {
CasAuthenticationFilter filter = new CasAuthenticationFilter();
filter.setAuthenticationManager(authenticationManager);
filter.setServiceProperties(serviceProperties);
return filter;
}
@Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService(baseUrl "/login/cas");
serviceProperties.setSendRenew(false);
return serviceProperties;
}
@Bean
public TicketValidator ticketValidator() {
return new Cas30ServiceTicketValidator(casServerUrl);
}
@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider provider = new CasAuthenticationProvider();
provider.setServiceProperties(this.serviceProperties());
provider.setTicketValidator(this.ticketValidator());
provider.setUserDetailsService(new CustomUserDetailsService());
provider.setKey("CAS_PROVIDER_LOCALHOST");
return provider;
}
@Bean
public SecurityContextLogoutHandler securityContextLogoutHandler() {
return new SecurityContextLogoutHandler();
}
@Bean
public LogoutFilter logoutFilter() {
LogoutFilter logoutFilter = new LogoutFilter(casServerUrl "/logout?service=" baseUrl
securityContextLogoutHandler());
logoutFilter.setFilterProcessesUrl("/logout/cas");
return logoutFilter;
}
@Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setIgnoreInitConfiguration(true);
return singleSignOutFilter;
}
}
WebConfig配置类
package com.example.casclient.config;
import org.jasig.cas.client.session.SingleSignOutFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutFilter;
@EnableWebSecurity
public class WebConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SingleSignOutFilter singleSignOutFilter;
@Autowired
private LogoutFilter logoutFilter;
@Autowired
private AuthenticationEntryPoint authenticationEntryPoint;
@Autowired
private CasAuthenticationFilter casAuthenticationFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login/cas").permitAll()
.anyRequest().authenticated()
.and().httpBasic().authenticationEntryPoint(authenticationEntryPoint)
.and()
.addFilter(casAuthenticationFilter)
.addFilterBefore(singleSignOutFilter CasAuthenticationFilter.class)
.addFilterBefore(logoutFilter LogoutFilter.class);
}
}
授权后获取用户信息处理类
package com.example.casclient.security;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 可自定义获取用户信息
// 示例代码写死,实际应该从数据库获取用户信息,并加载对应的权限
return new User(username username true true true true
AuthorityUtils.createAuthorityList("ROLE_ADMIN"));
}
}
添加测试Controller类
package com.example.casclient.controller;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import javax.servlet.http.HttpSession;
@Controller
public class IndexController {
@RequestMapping(value = "")
public String index(Model model) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
User user = (User) auth.getPrincipal();
System.out.println("当前用户信息:" user);
model.addAttribute("user" user);
return "index";
}
@RequestMapping(value = "info")
public String info(Model model) {
return "info";
}
}
测试页面代码,templates/index.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout">
<head>
<meta charset="UTF-8">
<title>首页</title>
</head>
<body class="home-body">
<h1>首页信息</h1>
<div>登录用户信息</div>
<div>用户名:<span th:text="${user.username}"></span></div>
</body>
</html>
三、测试
1)在浏览中输入地址(http://sso-server.com:8088)会跳转到cas登录页面
输入指定的用户名/密码:admin/1
2)登录成功跳转到页面
3)注销,输入地址:http://sso-server.com:8088/logout/cas,点击确认退出,页面会回到登录页面
或直接在cas服务端退出:https://sso-server.com:8443/cas/logout
参考地址- https://github.com/apereo/cas
- https://github.com/apereo/cas-overlay-template
- https://www.apereo.org/projects/cas
- https://github.com/apereo/java-cas-client
- https://apereo.github.io/cas/5.3.x/installation/Configuration-Properties.html#query-database-authentication