CAS(中央认证服务)
从结构上看,CAS 包含两个部分: CAS Server 和 CAS Client。CAS Server 需要独立部署,主要负责对用户的认证工作;CAS Client 负责处理对客户端受保护资源的访问请求,需要登录时,重定向到 CAS Server。图1 是 CAS 最基本的协议过程:
CAS Client 与受保护的客户端应用部署在一起,以 Filter 方式保护受保护的资源。对于访问受保护资源的每个 Web 请求,CAS Client 会分析该请求的 Http 请求中是否包含 Service Ticket,如果没有,则说明当前用户尚未登录,于是将请求重定向到指定好的 CAS Server 登录地址,并传递 Service (也就是要访问的目的资源地址),以便登录成功过后转回该地址。用户在第 3 步中输入认证信息,如果登录成功,CAS Server 随机产生一个相当长度、唯一、不可伪造的 Service Ticket,并缓存以待将来验证,之后系统自动重定向到 Service 所在地址,并为客户端浏览器设置一个 Ticket Granted Cookie(TGC),CAS Client 在拿到 Service 和新产生的 Ticket 过后,在第 5,6 步中与 CAS Server 进行身份核实,以确保 Service Ticket 的合法性
在该协议中,所有与 CAS 的交互均采用 SSL 协议,确保,ST 和 TGC 的安全性。协议工作过程中会有 2 次重定向的过程,但是 CAS Client 与 CAS Server 之间进行 Ticket 验证的过程对于用户是透明的
另外,CAS 协议中还提供了 Proxy (代理)模式,以适应更加高级、复杂的应用场景,具体介绍可以参考 CAS 官方网站上的相关文档
以上摘自百度百科
数据库设计
建表主要以下几张表,不过也可以添加一些其他表,比如保存前端模块的表module,保存前端菜单的表menu,至于表中字段根据自己需求来设计,学习也是需要自己来参与的。
相关依赖
主要依赖版本:
springboot-2.3.0
spring-security-casspring-security-cas 5.2.2
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-cas</artifactId>
<version>5.2.2.RELEASE</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.11</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
</dependency>
</dependencies>
配置文件
对于CAS服务端搭建就省略了,可以去官网 下载,实际就是一个war包,放到tomcat的webapps下就可以了,具体安装流程可以自行Google,资源有很多。
server:
servlet:
context-path: "/test"
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/cas?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
username: root
password: 123456
#客户端地址
app:
server:
host:
url: http://${host:localhost}:8080/test
login:
url: /login
logout:
url: /logout
#cas认证中心地址
cas:
server:
host: http://localhost:8081/cas
login_url: http://localhost:8081/cas/login
logout_url: http://localhost:8081/cas/logout?service=http://${host:localhost}:8080/test/app/login
#logging:
# level:
# root: debug
相关配置类
声明一下主要展示spring security如何配置CAS,其他基本操作就不一一列举。
首先新建一个YmlProperties类来读取application.yml的相关配置
@Component
@Data
public class YmlProperties {
@Value("${app.server.host.url}")
private String appServerUrl;
@Value("${app.login.url}")
private String appLoginUrl;
@Value("${app.logout.url}")
private String appLogoutUrl;
@Value("${cas.server.host}")
private String casServerUrl;
@Value("${cas.server.login_url}")
private String casServerLoginUrl;
@Value("${cas.server.logout_url}")
private String casServerLogoutUrl;
}
新建UserDetail类封装我们需要的用户信息
注意如果没有相关需求,此类可以不建,直接使用spring security提供的org.springframework.security.core.userdetails.User类。
public class UserDetail extends User implements UserDetails, CredentialsContainer {
private Long userId;
public UserDetail(String username,String password, Collection<? extends GrantedAuthority> authorities){
super(username,password,true,true,true,true,authorities);
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
}
新建MyUserDetailsService类实现AuthenticationUserDetailsService接口
涉及的查询语句可以自己完成,一个是根据用户名查询用户信息,一个是根据用户id查询角色权限,根据上述建的中间表关联查询,都是一些基础的查询语句,这里就不贴出来了。
@Service
public class MyUserDetailsService implements AuthenticationUserDetailsService<CasAssertionAuthenticationToken>{
@Autowired
private UserService userService;
@Autowired
private PermissionMapper permissionMapper;
@Override
public UserDetails loadUserDetails(CasAssertionAuthenticationToken token)
throws UsernameNotFoundException {
User user = userService.findByName(token.getName());
if (Objects.isNull(user)) {
throw new RuntimeException("用户不存在");
}
List<String> roleCodes = permissionMapper.getPermissionByUser(user.getId());
if (CollectionUtils.isEmpty(roleCodes)) {
throw new RuntimeException("账户[" + user.getUsername() + "]未绑定角色");
}
Set<GrantedAuthority> authorities = new HashSet<>();
for (String roleCode : roleCodes) {
authorities.add(new SimpleGrantedAuthority(roleCode));
}
UserDetail userDetail=new UserDetail(user.getUsername(),user.getPassword(),authorities);
userDetail.setUserId(user.getId());
return userDetail;
}
}
新建SecurityConfiguration类实现WebSecurityConfigurerAdapter接口
如果想了解每个Bean的相关作用和spring security是如何和CAS交互的,参考spring官网说明 ,官网详细介绍了整个流程,一定要自己理解。
不管是spring security还是其他技术,个人建议还是首先去看官网给的文档,他可以解决大部分问题,而后再去Google。
其实以下这些配置就是根据官网一步一步配置的,官网给的是xml配置,可能有的小伙伴一看到就直接放弃不看去Google了,如今都用springboot谁还学习xml如何配置,不过如果静下心去看的话,完全可以根据xml配置自己转化成配置类。
@Slf4j
@EnableWebSecurity
//开启@Secured和@PreAuthorize
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private YmlProperties ymlProperties;
@Autowired
private MyUserDetailsService myUserDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //配置安全策略
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.csrf().disable()
.logout().permitAll() //logout不需要验证
.and()
.headers().frameOptions().disable()
.and()
.cors()
.and().formLogin(); //使用form表单登录
http.exceptionHandling().authenticationEntryPoint(myCasAuthenticationEntryPoint)
.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());
}
/**
* 主要配置的是ServiceProperties的service属性,它指定的是cas回调的地址
*/
@Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService(ymlProperties.getAppServerUrl() + ymlProperties.getAppLoginUrl());
serviceProperties.setSendRenew(false);
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}
/**
* 认证的入口,指定cas服务器的登录地址,指定ServiceProperties(主要是获取回调地址)
*/
@Bean
public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
casAuthenticationEntryPoint
.setLoginUrl(ymlProperties.getCasServerLoginUrl());
casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
return casAuthenticationEntryPoint;
}
@Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setServiceProperties(serviceProperties()); casAuthenticationFilter.setFilterProcessesUrl(ymlProperties.getAppLoginUrl());
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
//指定登录成功后跳转页面,也可以使用SavedRequestAwareAuthenticationSuccessHandler
// SavedRequestAwareAuthenticationSuccessHandler handler=new SavedRequestAwareAuthenticationSuccessHandler();
// handler.setRedirectStrategy(new MyRedirectStrategy());
// casAuthenticationFilter.setAuthenticationSuccessHandler(handler);
casAuthenticationFilter.setAuthenticationSuccessHandler(
new SimpleUrlAuthenticationSuccessHandler(
ymlProperties.getAppServerUrl() + "/hello"));
casAuthenticationFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
return casAuthenticationFilter;
}
// class MyRedirectStrategy extends DefaultRedirectStrategy {
//
// @Override
// public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url)
// throws IOException {
// String redirectUrl = calculateRedirectUrl(request.getContextPath(), url);
// redirectUrl = response.encodeRedirectURL(redirectUrl);
// log.info(redirectUrl);
// if(redirectUrl.startsWith("http://")){
// if(redirectUrl.contains("/app/login")){
// redirectUrl="http://localhost:8080/test/index";
// }
// }
// response.sendRedirect(redirectUrl);
// }
// }
@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setServiceProperties(serviceProperties());
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider
.setAuthenticationUserDetailsService(myUserDetailsService);
casAuthenticationProvider.setKey("casAuthenticationProviderKey");
return casAuthenticationProvider;
}
/**
* 验证ticker,向cas服务器发送验证请求
*/
@Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
//转Https请求
// HttpURLConnectionFactory httpURLConnectionFactory=new HttpURLConnectionFactory() {
// @Override
// public HttpURLConnection buildHttpURLConnection(URLConnection urlConnection) {
// SSLContext sslContext=null;
// try {
// sslContext = SSLContext.getInstance("SSL");
// sslContext.init(new KeyManager[]{},new TrustManager[]{new X509TrustManager() {
// @Override
// public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
// throws CertificateException {
//
// }
//
// @Override
// public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
// throws CertificateException {
//
// }
//
// @Override
// public X509Certificate[] getAcceptedIssuers() {
// return new X509Certificate[0];
// }
// }},null);
// SSLSocketFactory socketFactory=sslContext.getSocketFactory();
//
// if(urlConnection instanceof HttpsURLConnection){
// HttpsURLConnection httpsURLConnection=(HttpsURLConnection)urlConnection;
// httpsURLConnection.setSSLSocketFactory(socketFactory);
// httpsURLConnection.setHostnameVerifier((s,l)->true);
// }
//
// return (HttpURLConnection)urlConnection;
//
// }catch (Exception e){
// throw new RuntimeException(e);
// }
// }
// };
Cas20ServiceTicketValidator cas20ServiceTicketValidator = new Cas20ServiceTicketValidator(
ymlProperties.getCasServerUrl());
cas20ServiceTicketValidator.setEncoding("UTF-8");
return cas20ServiceTicketValidator;
}
@Bean
public SessionAuthenticationStrategy sessionAuthenticationStrategy() {
SessionAuthenticationStrategy sessionAuthenticationStrategy = new SessionFixationProtectionStrategy();
return sessionAuthenticationStrategy;
}
/**
* 此过滤器向cas发送登出请求
*/
@Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setCasServerUrlPrefix(ymlProperties.getCasServerUrl());
singleSignOutFilter.setIgnoreInitConfiguration(true);
return singleSignOutFilter;
}
/**
* 此过滤器拦截客户端的logout请求,发现logout请求后向cas服务器发送登出请求
*/
@Bean
public LogoutFilter casLogoutFilter() {
LogoutFilter logoutFilter = new LogoutFilter(ymlProperties.getCasServerLogoutUrl(),
new SecurityContextLogoutHandler());
logoutFilter.setFilterProcessesUrl(ymlProperties.getAppLogoutUrl());
return logoutFilter;
}
/**
* 去除@Secured的前缀 "ROLE_"
* @return
*/
@Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("");
}
}
新建HelloController
@Slf4j
@RestController
public class HelloController {
@Secured("security_index")
//@PreAuthorize("hasAnyAuthority('security_index')")
@GetMapping("/hello")
public String hello() {
return "首页";
}
}
浏览器访问http://localhost:8080/test/login 如果未登录会跳转到CAS服务端登录页。
前后端分离项目遇到的问题
对于前后端分离面临的问题是后端不干涉前端页面跳转,在退出登录后,访问前端页面仍可以访问,前端向后端发送请求后端拦截重定向到CAS服务端地址,但是前端页面跳转失败。虽说页面数据是不会加载,但是这不符合我们希望实现的。
目前想到通过实现AuthenticationEntryPoint并重定向到指定接口,返回页面
CAS服务端跳转地址,前端通过拦截器在每个页面访问时都会向后端指定接口发送请求,如果没有登录就会返回CAS服务端地址,登录后直接放行,具体判断我们通过返回状态码来实现。
新建AuthController
方法中Result类是自定义的统一返回json格式,我会在下面贴出来。
@Slf4j
@RestController
public class HelloController {
@Autowired
private YmlProperties ymlProperties;
/**
* 适用前后端分离
* 当未登录时重定向到此请求,返回给前端CAS服务器登录地址,通过前端跳转
* @return
*/
@GetMapping("/send")
public Result send() {
String url =
ymlProperties.getCasServerLoginUrl() + "?service=" + ymlProperties.getAppServerUrl()
+ ymlProperties.getAppLoginUrl();
return Result.failed().setCode(444).setData(url).setMessage("未登录").setSuccess(false);
}
/**
* 适用前后端分离
* 当登录成功后返回前端数据
* @return
*/
@GetMapping("/login")
public Result login(){
return Result.success(null,"已登录");
}
}
Result统一返回json格式
对于Result类中静态方法可以自己扩展。
public interface IErrorCode {
long getCode();
String getMessage();
}
public enum ResultCode implements IErrorCode {
FAILED(500,"操作失败"),
SUCCESS(200,"操作成功");
private long code;
private String message;
private ResultCode(long code,String message){
this.code=code;
this.message=message;
}
@Override
public long getCode() {
return code;
}
@Override
public String getMessage() {
return message;
}
}
@Data
@Accessors(chain = true)
public class Result<T> {
private boolean success;
private long code;
private String message;
private T data;
private Result() {
}
private Result(boolean success,long code,String message,T data){
this.success=success;
this.code=code;
this.message=message;
this.data=data;
}
/**
* 成功返回结果
* @param data 获取的数据
*/
public static <T> Result<T> success(T data){
return new Result<T>(true,ResultCode.SUCCESS.getCode(),ResultCode.SUCCESS.getMessage(),data);
}
/**
* 成功返回结果
* @param data 获取的数据
* @param message 提示信息
*/
public static <T> Result<T> success(T data,String message){
return new Result<>(true,ResultCode.SUCCESS.getCode(),message,data);
}
/**
* 失败返回结果
* @param <T>
* @return
*/
public static <T> Result<T> failed(){
return new Result<>(false,ResultCode.FAILED.getCode(),ResultCode.FAILED.getMessage(),null);
}
/**
* 失败返回结果
* @param message 提示信息
*/
public static <T> Result<T> failed(String message){
return new Result<>(false,ResultCode.FAILED.getCode(),message,null);
}
}
新建MyCasAuthenticationEntryPoint类实现AuthenticationEntryPoint
@Component
public class MyCasAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Autowired
private YmlProperties ymlProperties;
@Override
public void commence(HttpServletRequest request, HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.sendRedirect(ymlProperties.getAppServerUrl()+"/send");
}
}
重写SecurityConfiguration配置类
@Slf4j
@EnableWebSecurity
//开启@Secured和@PreAuthorize
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
private YmlProperties ymlProperties;
@Autowired
private MyCasAuthenticationEntryPoint myCasAuthenticationEntryPoint;
@Autowired
private MyUserDetailsService myUserDetailsService;
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //配置安全策略
.antMatchers("/login","/send").permitAll()
.anyRequest().authenticated() //所有请求都要验证
.and()
.csrf().disable()
.logout().permitAll() //logout不需要验证
.and()
.headers().frameOptions().disable()
.and()
.cors()
.and().formLogin(); //使用form表单登录
http.exceptionHandling().authenticationEntryPoint(myCasAuthenticationEntryPoint)
.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());
}
/**
* 主要配置的是ServiceProperties的service属性,它指定的是cas回调的地址
*/
@Bean
public ServiceProperties serviceProperties() {
ServiceProperties serviceProperties = new ServiceProperties();
serviceProperties.setService(ymlProperties.getAppServerUrl() + ymlProperties.getAppLoginUrl());
serviceProperties.setSendRenew(false);
serviceProperties.setAuthenticateAllArtifacts(true);
return serviceProperties;
}
/**
* 认证的入口,指定cas服务器的登录地址,指定ServiceProperties(主要是获取回调地址)
*/
// @Bean
// public CasAuthenticationEntryPoint casAuthenticationEntryPoint() {
// CasAuthenticationEntryPoint casAuthenticationEntryPoint = new CasAuthenticationEntryPoint();
// casAuthenticationEntryPoint
// .setLoginUrl(casProperties.getCasServerLoginUrl());
// casAuthenticationEntryPoint.setServiceProperties(serviceProperties());
// return casAuthenticationEntryPoint;
// }
@Bean
public CasAuthenticationFilter casAuthenticationFilter() throws Exception {
CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
casAuthenticationFilter.setServiceProperties(serviceProperties());
casAuthenticationFilter.setFilterProcessesUrl(ymlProperties.getAppLoginUrl());
casAuthenticationFilter.setAuthenticationManager(authenticationManager());
//指定登录成功后跳转页面,也可以使用SavedRequestAwareAuthenticationSuccessHandler
// SavedRequestAwareAuthenticationSuccessHandler handler=new SavedRequestAwareAuthenticationSuccessHandler();
// handler.setRedirectStrategy(new MyRedirectStrategy());
// casAuthenticationFilter.setAuthenticationSuccessHandler(handler);
casAuthenticationFilter.setAuthenticationSuccessHandler(
new SimpleUrlAuthenticationSuccessHandler(
ymlProperties.getAppServerUrl() + "/hello"));
casAuthenticationFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy());
return casAuthenticationFilter;
}
// class MyRedirectStrategy extends DefaultRedirectStrategy {
//
// @Override
// public void sendRedirect(HttpServletRequest request, HttpServletResponse response, String url)
// throws IOException {
// String redirectUrl = calculateRedirectUrl(request.getContextPath(), url);
// redirectUrl = response.encodeRedirectURL(redirectUrl);
// log.info(redirectUrl);
// if(redirectUrl.startsWith("http://")){
// if(redirectUrl.contains("/app/login")){
// redirectUrl="http://10.11.36.21:8080/test/hello";
// }
// }
// response.sendRedirect(redirectUrl);
// }
// }
@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
CasAuthenticationProvider casAuthenticationProvider = new CasAuthenticationProvider();
casAuthenticationProvider.setServiceProperties(serviceProperties());
casAuthenticationProvider.setTicketValidator(cas20ServiceTicketValidator());
casAuthenticationProvider
.setAuthenticationUserDetailsService(myUserDetailsService);
casAuthenticationProvider.setKey("casAuthenticationProviderKey");
return casAuthenticationProvider;
}
/**
* 验证ticker,向cas服务器发送验证请求
*/
@Bean
public Cas20ServiceTicketValidator cas20ServiceTicketValidator() {
//转Https请求
// HttpURLConnectionFactory httpURLConnectionFactory=new HttpURLConnectionFactory() {
// @Override
// public HttpURLConnection buildHttpURLConnection(URLConnection urlConnection) {
// SSLContext sslContext=null;
// try {
// sslContext = SSLContext.getInstance("SSL");
// sslContext.init(new KeyManager[]{},new TrustManager[]{new X509TrustManager() {
// @Override
// public void checkClientTrusted(X509Certificate[] x509Certificates, String s)
// throws CertificateException {
//
// }
//
// @Override
// public void checkServerTrusted(X509Certificate[] x509Certificates, String s)
// throws CertificateException {
//
// }
//
// @Override
// public X509Certificate[] getAcceptedIssuers() {
// return new X509Certificate[0];
// }
// }},null);
// SSLSocketFactory socketFactory=sslContext.getSocketFactory();
//
// if(urlConnection instanceof HttpsURLConnection){
// HttpsURLConnection httpsURLConnection=(HttpsURLConnection)urlConnection;
// httpsURLConnection.setSSLSocketFactory(socketFactory);
// httpsURLConnection.setHostnameVerifier((s,l)->true);
// }
//
// return (HttpURLConnection)urlConnection;
//
// }catch (Exception e){
// throw new RuntimeException(e);
// }
// }
// };
Cas20ServiceTicketValidator cas20ServiceTicketValidator = new Cas20ServiceTicketValidator(
ymlProperties.getCasServerUrl());
cas20ServiceTicketValidator.setEncoding("UTF-8");
return cas20ServiceTicketValidator;
}
@Bean
public SessionAuthenticationStrategy sessionAuthenticationStrategy() {
SessionAuthenticationStrategy sessionAuthenticationStrategy = new SessionFixationProtectionStrategy();
return sessionAuthenticationStrategy;
}
/**
* 此过滤器向cas发送登出请求
*/
@Bean
public SingleSignOutFilter singleSignOutFilter() {
SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
singleSignOutFilter.setCasServerUrlPrefix(ymlProperties.getCasServerUrl());
singleSignOutFilter.setIgnoreInitConfiguration(true);
return singleSignOutFilter;
}
/**
* 此过滤器拦截客户端的logout请求,发现logout请求后向cas服务器发送登出请求
*/
@Bean
public LogoutFilter casLogoutFilter() {
LogoutFilter logoutFilter = new LogoutFilter(ymlProperties.getCasServerLogoutUrl(),
new SecurityContextLogoutHandler());
logoutFilter.setFilterProcessesUrl(ymlProperties.getAppLogoutUrl());
return logoutFilter;
}
/**
* 取出@Secured的前缀 "ROLE_"
* @return
*/
@Bean
public GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("");
}
}
前端只需要访问/login
请求,登录就会返回200状态码和“已登录”提示信息,未登录后端就会重定向/send
请求,并返回444状态码和CAS服务端地址,前端根据444状态码拿到CAS服务端地址并跳转页面。
注意未登录时后端会重定向一次,所以想一想前端如何拿到重定向的数据。
对于以上有什么建议,请在评论区留下宝贵意见!