CAS概念
CAS是Central Authentiction Service 的縮寫,中央認證服務。CAS是Yale大學發起的開源項目,旨在為Web應用系統提供一種可靠的單點登錄方法,CAS在2004年12月正式稱為JA-SIG的一個項目。官網地址:https://www.apereo.org/projects/cas。
Spring Security CAS原理
image.png
- 用戶在瀏覽器發起請求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。
image.png
- 用戶在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();
}
}