CAS概念
CAS是Central Authentiction Service 的缩写,中央认证服务。CAS是Yale大学发起的开源项目,旨在为Web应用系统提供一种可靠的单点登录方法,CAS在2004年12月正式称为JA-SIG的一个项目。官网地址:https://www.apereo.org/projects/cas。
Spring Security CAS原理
- 用户在浏览器发起请求web系统私有资源 /private;
- SecurityFilterChain过滤器链路到达FilterSecurityInterceptor,并抛出访问被拒绝的异常AccessDeniedException,ExceptionTranslationFilter捕获该异常并通过sendStartAuthentication方法进入CasAuthenticationEntryPoint(AuthenticationEntryPoint的实现类);
- CasAuthenticationEntryPoint设置重定向到CAS Server地址https://my.company.com/cas/login?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/cas。
- 用户在CAS Server登录验证完后,携带ST跳转到客户端https://server3.company.com/webapp/login/cas?ticket=ST-0-ER94xMJmn6pha35CQRoZ
,进入CasAuthenticationFilter; - CasAuthenticationFilter将ST包装成UsernamePasswordAuthenticationToken请求AuthenticationManager进行认证处理;
- AuthenticationManager将认证委托给CasAuthenticationProvider;
- CasAuthenticationProvider使用TicketValidator向CAS Server发起ST校验请求https://server3.company.com/webapp/serviceValidate?service=https%3A%2F%2Fserver3.company.com%2Fwebapp%2Flogin/cas&ticket=ST-0-ER94xMJmn6pha35CQRoZ,成功后获取用户登录信息。
- 最后,CasAuthenticationProvider使用AuthenticationUserDetailsService进行后置处理,一般获取更加详细的用户信息,例如权限等。
Spring Security CAS 实战
- 引入依赖
<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>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
- 修改配置文件application.yml
server:
port: 8080
base:
url: http://localhost:8080
cas:
server:
url: https://cas.test.com/cas
- 相关代码
CasConfig.java
@Configuration
public class CasConfig {
@Value("${cas.server.url}")
private String casServerUrl;
@Value("${base.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.setAuthenticationUserDetailsService(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;
}
}
CustomUserDetailsService.java
public class CustomUserDetailsService extends AbstractCasAssertionUserDetailsService {
@Override
protected UserDetails loadUserDetails(Assertion assertion) {
// 可自定义获取用户信息
String username = assertion.getPrincipal().getName();
Map<String, Object> attributes = assertion.getPrincipal().getAttributes();
return new User("admin", "admin", true, true,
true, true, AuthorityUtils.createAuthorityList("ROLE_ADMIN"));
}
}
WebConfig.java
@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);
}
}
WelcomeController.java
@RequestMapping
@Controller
public class WelcomeController {
@GetMapping("/")
@ResponseBody
public String index() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
return "当前用户信息:" + auth.getPrincipal();
}
}