SpringBoot2+SpringSecurity+CAS 安全认证整合项目
1. 写在前面
如何你能看到这边文章,那我觉得我也不需要多废话,文章里面的东西你自然可以看懂。此篇文章只介绍整合过程,不介绍原理,适合懂原理想快速搭建环境的人儿们。
介绍一下开发环境,CAS服务端使用的是CAS5.3;SpringBoot使用的是2.0.1
注:CAS服务端的安装如果不会,可以看我其他文章 docker快速搭建CAS
先晒一下项目目录:
2. pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<dependencies>
<!-- spring security cas-->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-cas</artifactId>
</dependency>
<!-- spring security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- spring web-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.8</version>
</dependency>
</dependencies>
3. application.yml
server:
port: 7200
#cas配置
cas:
#秘钥
key: 123
server:
host:
#cas服务端地址 这是我的cas服务端地址 需要修改成你们的cas服务端地址
url: http://192.168.16.122:8080/cas
#cas服务端登录地址
login_url: ${cas.server.host.url}/login
#cas服务端登出地址 service参数后面跟就是需要跳转的页面/接口 这里指定的是cas客户端登录接口
logout_url: ${cas.server.host.url}/logout?service=${cas.service.host.url}${cas.service.host.login_url}
service:
host:
#cas客户端地址
url: http://localhost:${server.port}
#cas客户端地址登录地址
login_url: /login
#cas客户端地址登出地址
logout_url: /logout
4. 创建配置类,映射yml中的变量
/**
* @author : LCheng
* @date : 2020-11-20 14:21
* description : cas配置项
*/
@Data
@Component
public class CasProperties {
/**
* 秘钥
*/
@Value("${cas.key}")
private String casKey;
/**
* cas服务端地址
*/
@Value("${cas.server.host.url}")
private String casServerUrl;
/**
* cas服务端登录地址
*/
@Value("${cas.server.host.login_url}")
private String casServerLoginUrl;
/**
* cas服务端登出地址 并回跳到制定页面
*/
@Value("${cas.server.host.logout_url}")
private String casServerLogoutUrl;
/**
* cas客户端地址
*/
@Value("${cas.service.host.url}")
private String casServiceUrl;
/**
* cas客户端地址登录地址
*/
@Value("${cas.service.host.login_url}")
private String casServiceLoginUrl;
/**
* cas客户端地址登出地址
*/
@Value("${cas.service.host.logout_url}")
private String casServiceLogoutUrl;
}
5. 创建CasUserDetailService
/**
* 用户登录操作
*
* @author lCheng
*/
@Component
@Slf4j
public class CasUserDetailService implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken> {
@Override
public UserDetails loadUserDetails(CasAssertionAuthenticationToken casAssertionAuthenticationToken) throws UsernameNotFoundException {
log.info("登陆用户名: " + casAssertionAuthenticationToken.getName());
//由于登录交于cas服务端管理,如果进入这里代表登录成功,可不做任何操作,也可以根据自身系统业务开发相关功能代码
return new User(casAssertionAuthenticationToken.getName(), "", AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
}
}
6. 创建cas 配置类
/**
* @author : LCheng
* @date : 2020-11-20 18:06
* description : cas 配置中心
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class CasSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private CasProperties casProperties;
@Autowired
private AuthenticationUserDetailsService casUserDetailService;
/**
* 设置AuthenticationProvider
*/
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(casAuthenticationProvider());
}
/**
* security安全策略
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry config = http.authorizeRequests()
// 放行OPTIONS请求
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll();
//剩下的所有请求都需要验证
config.anyRequest().authenticated();
http
.logout()
.permitAll()//放行logout
.and()
.formLogin();//表单登录
http.exceptionHandling().authenticationEntryPoint(casAuthenticationEntryPoint())
.and()
.addFilter(casAuthenticationFilter())
.addFilterBefore(logoutFilter(), LogoutFilter.class)
.addFilterBefore(singleSignOutFilter(), CasAuthenticationFilter.class);
http.csrf().disable(); //禁用CSRF
}
/**
* cas认证入口
*/
@Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
//设置cas 服务端登录url
casAuthenticationEntryPoint.setLoginUrl(casProperties.getCasServerLoginUrl());
//设置cas 客户端信息
casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
return casAuthenticationEntryPoint;
}
/**
* cas客户端信息
*/
@Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
//设置cas客户端登录完整的url
serviceProperties.setService(casProperties.getCasServiceUrl() + casProperties.getCasServiceLoginUrl());
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}
/**
* cas认证过滤器
* casAuthenticationFilter.setFilterProcessesUrl 必须要设置完整路径 不然会无限重定向
*/
@Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
casAuthenticationFilter.setFilterProcessesUrl(casProperties.getCasServiceUrl() + casProperties.getCasServiceLoginUrl());
casAuthenticationFilter.setServiceProperties(serviceProperties());
return casAuthenticationFilter;
}
/**
* cas认证Provider
*/
@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
//设置UserDetailService 登录成功后 会进入loadUserDetails方法中
casAuthenticationProvider.setAuthenticationUserDetailsService(casUserDetailService);
//设置cas 客户端信息
casAuthenticationProvider.setServiceProperties(serviceProperties());
//设置cas 票证验证器
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
//设置cas 秘钥
casAuthenticationProvider.setKey(casProperties.getCasKey());
return casAuthenticationProvider;
}
/**
* cas 票证验证器
*/
@Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
return new Cas20ServiceTicketValidator(casProperties.getCasServerUrl());
}
/**
* 单点注销过滤器
* 用于接收cas服务端的注销请求
*/
@Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setCasServerUrlPrefix(casProperties.getCasServerUrl());
singleSignOutFilter.setIgnoreInitConfiguration(true);
return singleSignOutFilter;
}
/**
* 单点退出过滤器
* 用于跳转到cas服务端
*/
@Bean
public LogoutFilter logoutFilter() {
LogoutFilter logoutFilter = new LogoutFilter(casProperties.getCasServerLogoutUrl(), new SecurityContextLogoutHandler());
logoutFilter.setFilterProcessesUrl(casProperties.getCasServiceLogoutUrl());
return logoutFilter;
}
}
7.最后一步,配置一个测试控制器
/**
* @author : LCheng
* @date : 2020-11-26 17:44
* description : 测试控制器
*/
@Slf4j
@RestController
public class TestController {
@SneakyThrows
@GetMapping(value = "/login")
public String login(HttpServletRequest request, HttpServletResponse response) {
//只是为了跳转到cas服务端的登录页面 登录成功后会跳回此页
//可根据自身系统进行代码增强
return "登录成功了";
}
@GetMapping(value = "/logout")
public void logout() {
//只是为了跳转到cas服务端的登出页面
//可根据自身系统进行代码增强
}
@GetMapping(value = "/hello")
public String hello() {
//登录成功后,访问此接口,如果正常返回hello word,表示已授权成功
return "hello word";
}
}
8.最最后一步,实践检验真理的最后一步
下面的git图片展示了整个测试过程:
- 访问http://localhost:7200/,由于没有授权,会跳到cas服务端登录页
- cas服务端登录页进行登录操作,登录成功后跳转回http://localhost:7200/login,提示登录成功
- 访问http://localhost:7200/logout,进行登出操作
- 成功登出后,由于我们配置了回调url,会再次引导跳转回http://localhost:7200//login,而此时已经退出登录,系统会再次引导我们进入cas服务端登录页
注:application.yml中配置了登出回调url - 重复步骤2的操作,再次登录成功
- 访问http://localhost:7200/hello,由于已经登录成功,不会跳转到cas服务端登录页,而是正常返回 hello word。
结束语
至此所有整合过程已经完毕。
源码已上传至gitee 地址为:https://gitee.com/lonecheng/springboot-springsecurity-cas