程序员L札记

V1

2022/03/02阅读:65主题:橙心

Spring Security 整合 Cas Client 实战篇

前言

本篇介绍下Spring Security 整合 Cas Client相关内容,实现单点登录。以下代码,也是在生产环境中实际使用的代码。请大家结合自己实际需求,自行调整。废话不多说,直接上干货。

开发

引入依赖

<dependency>
     <groupId>org.springframework.security</groupId>
     <artifactId>spring-security-cas</artifactId>
     <version>5.1.5.RELEASE</version>
 </dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

配置

#应用访问地址(需要能被认证中心正常访问)
cas:
  enable: false
  server-name: http://192.168.43.160:8005
#认证中心服务地址
  cas-server-url-prefix: http://192.168.43.48:8088/CASS
#认证中心登录地址
  cas-server-login-url: ${cas.cas-server-url-prefix}/login
#认证中心登出地址
  cas-server-logout-url: ${cas.cas-server-url-prefix}/logout

自定义属性配置

/**
 * cas服务的配置信息
 */

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
@ConfigurationProperties(prefix = "cas")
@ConditionalOnProperty(prefix = "cas", name = "enable", havingValue = "true")
@Component
public class CasServerProperties {

    /**
     * cas服务地址
     */

    private String casServerUrlPrefix;

    /**
     * cas登陆地址
     */

    private String casServerLoginUrl;

    /**
     * cas登出地址
     **/

    private String casServerLogoutUrl;

    /**
     * 本应用的地址,该地址需要能被cas服务器访问到
     */

    private String serverName;

    /**
     * 开始session
     */

    private boolean useSession = true;

    /**
     * 重定向后验证
     */

    private boolean redirectAfterValidation = true;

    /**
     * 忽略的地址
     */

    private String ignorePattern = "\\.(js|img|css)(\\?.*)?$";

    /**
     * 忽略地址表达式的类型
     */

    private String ignoreUrlPatternType = "REGEX";

    /**
     * 编码方式
     */

    private String encoding = "UTF-8";

}

java bean

/**
 * * cas与sercurity整合,需要配置的对象
 * <p>
 * 在cas与security整合中, 首先需要做的是将应用的登录认证入口改为使用CasAuthenticationEntryPoint。
 * 所以首先我们需要配置一个CasAuthenticationEntryPoint对应的bean,
 * 然后指定需要进行登录认证时使用该AuthenticationEntryPoint。
 * 配置CasAuthenticationEntryPoint时需要指定一个ServiceProperties,
 * 该对象主要用来描述service(Cas概念)相关的属性,主要是指定在Cas Server认证成功后将要跳转的地址。
 * <p>
 * <p>
 * CasAuthenticationFilter认真过滤器,负责认证跳转和票据验证
 *
 * @author lvyongqiang
 * @date 2020/05/30
 */

@Configuration
@ConditionalOnBean(CasServerProperties.class)
@AutoConfigureAfter(CasServerProperties.class)
public class SecurityConfiguration 
{

    @Autowired
    private CasServerProperties casServerProperties;

    @Autowired
    private JwtUserDetailsServiceImpl jwtUserDetailsService;


    @Bean
    public AuthenticationManager authenticationManager(CasAuthenticationProvider provider) {

        List<AuthenticationProvider> providers = new ArrayList<>();
        providers.add(provider);

        ProviderManager providerManager = new ProviderManager(providers);

        return providerManager;

    }


    /**
     * * 我们自己应用的配置信息,该对象主要用于构建CasAuthenticationEntryPoint。
     *
     * @return
     * @date 2018年12月10日 上午11:21:16
     */

    @Bean
    public ServiceProperties serviceProperties() {
        ServiceProperties serviceProperties = new ServiceProperties();
        //设置默认的cas登陆后回跳地址
        serviceProperties.setService(casServerProperties.getServerName() + "/");
        //设置我们应用是否敏感
        serviceProperties.setSendRenew(false);
        //设置是否对未拥有ticket的访问均需要验证
        serviceProperties.setAuthenticateAllArtifacts(true);
        return serviceProperties;
    }

    /**
     * * CAS认证过滤器,主要实现票据认证和认证成功后的跳转。
     *
     * @return
     * @date 2018年12月10日 上午11:25:56
     */

    @Bean
    public CasAuthenticationFilter casAuthenticationFilter(AuthenticationManager auth, ServiceProperties serviceProperties) {
        CasAuthenticationFilter casAuthenticationFilter = new CasAuthenticationFilter();
        //给过滤器设置我们应用的基本配置
        casAuthenticationFilter.setServiceProperties(serviceProperties);
        //给过滤器设置认证管理器
        casAuthenticationFilter.setAuthenticationManager(auth);
        //设置过滤器到cas server认证的地址
        casAuthenticationFilter.setFilterProcessesUrl(casServerProperties.getCasServerLoginUrl());
        //设置是否继续执行其他过滤器,在完成认证前
        casAuthenticationFilter.setContinueChainBeforeSuccessfulAuthentication(false);
        //设置认证成功后的处理handler
        casAuthenticationFilter.setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler("/"));

  casAuthenticationFilter.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/"));
  casAuthenticationFilter.setProxyAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/"));
        return casAuthenticationFilter;
    }

    /**
     * * 认证的入口,即跳转至服务端的cas地址
     * security框架整合cas认证的入口,也就是security不再走自己的认证入口,而是cas的,该对象就是cas的认证入口
     *
     * @return
     * @date 2018年12月10日 上午11:41:54
     */

    @Bean
    public CustomCasAuthenticationEntryPoint casAuthenticationEntryPoint(ServiceProperties serviceProperties) {

        CustomCasAuthenticationEntryPoint casAuthenticationEntryPoint = new CustomCasAuthenticationEntryPoint();
        //security框架整合cas认证的入口,也就是security不再走自己的认证入口,而是cas的,该对象就是cas的认证入口
        casAuthenticationEntryPoint.setServiceProperties(serviceProperties);
        casAuthenticationEntryPoint.setLoginUrl(casServerProperties.getCasServerLoginUrl());
        return casAuthenticationEntryPoint;
    }


    /**
     * * 配置TicketValidator在登录认证成功后验证ticket
     * 该对象就是一个ticket校验器
     *
     * @return
     * @date 2018年12月10日 上午11:47:36
     */

    @Bean
    public Cas30ServiceTicketValidator cas30ServiceTicketValidator() {
        //需要设置cas server的前缀,也就是根路径
        Cas30ServiceTicketValidator cas30ServiceTicketValidator = new Cas30ServiceTicketValidator(casServerProperties.getCasServerUrlPrefix());
        return cas30ServiceTicketValidator;
    }

    /**
     * * 该对象为cas校验对象,TicketValidator、AuthenticationUserDetailService属性必须设置;
     * serviceProperties属性主要应用于ticketValidator用于去cas服务端检验ticket
     *
     * @param serviceProperties
     * @param ticketValidator
     * @return
     * @date 2018年12月10日 下午3:45:13
     */

    @Bean("casProvider")
    public CasAuthenticationProvider casAuthenticationProvider(ServiceProperties serviceProperties, Cas20ServiceTicketValidator ticketValidator) {

        CasAuthenticationProvider provider = new CasAuthenticationProvider();
        provider.setKey("casProvider");
        provider.setServiceProperties(serviceProperties);
        provider.setTicketValidator(ticketValidator);
        provider.setAuthenticationUserDetailsService(jwtUserDetailsService);
        return provider;
    }


    /**
     * 注销过滤器(用于单点登出)
     *
     * @return {@link LogoutFilter}
     */

    @Bean
    public LogoutFilter logoutFilter() {
        String logoutRedirectPath = casServerProperties.getCasServerLogoutUrl() + "?service=" + casServerProperties.getServerName();
        LogoutFilter logoutFilter = new LogoutFilter(logoutRedirectPath, new SecurityContextLogoutHandler());
        //单点登出的拦截路径
        logoutFilter.setFilterProcessesUrl("/logout/cas");
        return logoutFilter;
    }

    /**
     * 1.单点注销的过滤器,必须配置在SpringSecurity的过滤器链中,如果直接配置在Web容器中,貌似是不起作用的
     * 2.响应Cas Server单点登出时的回调
     * 3.访问Cas Server的logout地址(如:https://cas-server:8443/cas/logout)进行登出
     * 4.官方推荐:在Cas Client应用进行登出操作时,不是直接访问Cas Server的logout,
     *   而是先登出本应用,然后告诉用户其当前登出的只是本应用,再提供一个对应Cas Server的链接,使其可以进行真正的单点登出
     * 5.配置LogoutFilter,先进行退出spring security应用,再登出cas server的
     */

    @Bean
    public SingleSignOutFilter singleSignOutFilter(){
        SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
        singleSignOutFilter.setCasServerUrlPrefix(casServerProperties.getCasServerUrlPrefix());
        singleSignOutFilter.setIgnoreInitConfiguration(true);
        return singleSignOutFilter;
    }



}

spring security整合

/**
 * cas安全配置
 *
 * @author lvyongqiang
 * @date 2020/05/29
 */

@Configuration
@EnableWebSecurity
@Order(SecurityProperties.BASIC_AUTH_ORDER)
@EnableGlobalMethodSecurity(prePostEnabled = true)
@ConditionalOnMissingBean(WebSecurityConfig.class)
public class CasWebSecurityConfiguration extends WebSecurityConfigurerAdapter 
{

    @Autowired
    private CustomCasAuthenticationEntryPoint casAuthenticationEntryPoint;

    @Autowired
    private CasAuthenticationProvider casAuthenticationProvider;

    @Autowired
    private CasAuthenticationFilter casAuthenticationFilter;

    @Autowired
    private LogoutFilter logoutFilter;

    @Autowired
    private JwtUserDetailsServiceImpl jwtUserDetailsService;

    @Autowired
    private SingleSignOutFilter singleSignOutFilter;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //开启跨域
        http.csrf().disable()
                .headers()
                .frameOptions().sameOrigin()
                .xssProtection()
                .block(false);

        http.formLogin().disable()
                .headers()
                .cacheControl().disable()
                .contentTypeOptions().disable()
                .httpStrictTransportSecurity();
        http

                //禁用匿名用户
                //.anonymous().disable()

//                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()

                // 全局不创建session
//                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)

//                .and()

                .logout().disable()

                .authorizeRequests()

                .requestMatchers(CorsUtils::isPreFlightRequest).permitAll()

                .antMatchers("/assets/**").permitAll()

                //获取验证码
                .antMatchers("/kaptcha").permitAll()


                // 登录接口放开过滤
                .antMatchers("/login").permitAll()

                // session登录失效之后的跳转
                .antMatchers("/global/sessionError").permitAll()

                // 图片预览 头像
                .antMatchers("/system/preview/*").permitAll()

                // 文档预览
                .antMatchers("/rest/api/document/preview/*").permitAll()

                .antMatchers("/ureport/**").permitAll()

                // 错误页面的接口
                .antMatchers("/error").permitAll()
                .antMatchers("/global/error").permitAll()
                .antMatchers("/rest/api/receiveOrgAndAccount").permitAll()
                .antMatchers("/external/roles").permitAll()

                .anyRequest().authenticated()

                .and()

                .exceptionHandling()

                .defaultAuthenticationEntryPointFor(casAuthenticationEntryPoint, new AntPathRequestMatcher("/*"))

                .and()

//                .addFilterBefore(myFilter, CasAuthenticationFilter.class)

                .addFilter(casAuthenticationFilter)

                .addFilterBefore(logoutFilter, LogoutFilter.class)

                .addFilterBefore(singleSignOutFilterCasAuthenticationFilter.class)
;

//                .antMatcher("/*");
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(casAuthenticationProvider);
        auth.userDetailsService(jwtUserDetailsService);
//        SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
//        smsCodeAuthenticationProvider.setUserDetailsService(userNamePwdDetails);
//        auth.authenticationProvider(smsCodeAuthenticationProvider);
    }

    /**
     * 单点登出http会话侦听器
     * SingleSignOutHttpSessionListener用于在Session过期时删除SingleSignOutFilter存放的对应信息
     *
     * @return {@link ServletListenerRegistrationBean<SingleSignOutHttpSessionListener>}
     */

    @Bean
    public ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> singleSignOutHttpSessionListener() {
        ServletListenerRegistrationBean<SingleSignOutHttpSessionListener> servletListenerRegistrationBean = new ServletListenerRegistrationBean<>();
        servletListenerRegistrationBean.setListener(new SingleSignOutHttpSessionListener());
        return servletListenerRegistrationBean;
    }

    @Override
    public void configure(WebSecurity web) {
        web
                .ignoring()
                .antMatchers(HttpMethod.POST, "/login")

                // 静态资源放开过滤
                .and()
                .ignoring()
                .antMatchers(HttpMethod.GET, "/assets/**""/favicon.ico""/activiti-editor/**""/ureport-asserts/**""/bpmn/**""/excel/**""/file/**");
    }


}

总结

以上就是今天要分享的主要内容,代码可直接用于生产环境中,结合自身情况做微调即可。

欢迎关注我的公众号:程序员L札记

更多原创文章,请扫码关注我的微信公众号
更多原创文章,请扫码关注我的微信公众号

分类:

后端

标签:

Java

作者介绍

程序员L札记
V1