SpringBoot + Spring Security 整合CAS5.1

使用CAS 5.1搭建(先用的其他版本5.3,6.2都有导包的问题,最后用的5.1才可以),SpringBoot2.3.0,Spring Security5.3

CAS服务端、客户端搭建,参考文章:

江南一点雨系列(SpringBoot + Spring Security 整合CAS):


史上最全的Cas学习整理-yellowcong
spring security集成cas

CAS实现单点登录SSO执行原理探究(终于明白了)
CAS(一)搭建CAS - server服务器
CAS单点登录-自定义登录页、修改编辑登录页
不使用域名,直接使用ip地址生成 https 证书:IP地址开启https

我的CAS服务端代码:

项目结构:

在这里插入图片描述

application.properties 配置:

##
# CAS Server Context Configuration
#
server.context-path=/cas
server.port=8443

cas.serviceRegistry.json.location=classpath:/services
cas.serviceRegistry.initFromJson=true
#as.tgc.secure=false

server.ssl.key-store=classpath:keystore
server.ssl.key-store-password=123456
server.ssl.key-password=123456

# server.ssl.ciphers=
# server.ssl.client-auth=
# server.ssl.enabled=
# server.ssl.key-alias=
# server.ssl.key-store-provider=
# server.ssl.key-store-type=
# server.ssl.protocol=
# server.ssl.trust-store=
# server.ssl.trust-store-password=
# server.ssl.trust-store-provider=
# server.ssl.trust-store-type=

server.max-http-header-size=2097152
server.use-forward-headers=true
server.connection-timeout=20000
server.error.include-stacktrace=ALWAYS

server.tomcat.max-http-post-size=2097152
server.tomcat.basedir=build/tomcat
server.tomcat.accesslog.enabled=true
server.tomcat.accesslog.pattern=%t %a "%r" %s (%D ms)
server.tomcat.accesslog.suffix=.log
server.tomcat.max-threads=10
server.tomcat.port-header=X-Forwarded-Port
server.tomcat.protocol-header=X-Forwarded-Proto
server.tomcat.protocol-header-https-value=https
server.tomcat.remote-ip-header=X-FORWARDED-FOR
server.tomcat.uri-encoding=UTF-8
   
spring.http.encoding.charset=UTF-8
spring.http.encoding.enabled=true
spring.http.encoding.force=true

##
# CAS Cloud Bus Configuration
#
spring.cloud.bus.enabled=false
# spring.cloud.bus.refresh.enabled=true
# spring.cloud.bus.env.enabled=true
# spring.cloud.bus.destination=CasCloudBus
# spring.cloud.bus.ack.enabled=true

endpoints.enabled=false
endpoints.sensitive=true

endpoints.restart.enabled=false
endpoints.shutdown.enabled=false

management.security.enabled=true
management.security.roles=ACTUATOR,ADMIN
management.security.sessions=if_required
management.context-path=/status
management.add-application-context-header=false

security.basic.authorize-mode=role
security.basic.enabled=false
security.basic.path=/cas/status/**

##
# CAS Web Application Session Configuration
#
server.session.timeout=300
server.session.cookie.http-only=true
server.session.tracking-modes=COOKIE

##
# CAS Thymeleaf View Configuration
#
spring.thymeleaf.encoding=UTF-8
#spring.thymeleaf.cache=true
spring.thymeleaf.cache=false
spring.thymeleaf.mode=HTML
##
# CAS Log4j Configuration
#
# logging.config=file:/etc/cas/log4j2.xml
server.context-parameters.isLog4jAutoInitializationDisabled=true

##
# CAS AspectJ Configuration
#
spring.aop.auto=true
spring.aop.proxy-target-class=true

##
# CAS Authentication Credentials
#
#cas.authn.accept.users=admin::1234

# 数据库连接
cas.authn.jdbc.query[0].url=jdbc:mysql://数据库IP:数据库端口/数据库名?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
# 数据库用户名
cas.authn.jdbc.query[0].user=数据库用户名
# 数据库用户密码
cas.authn.jdbc.query[0].password=数据库用户密码
# 查询账号密码SQL,必须包含密码字段
cas.authn.jdbc.query[0].sql=select * from g_user where username=?
cas.authn.jdbc.query[0].fieldPassword=password
# 数据库驱动
cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver
# 数据库dialect配置
cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQLDialect


# Rest配置
#cas.authn.rest.uri=http://127.0.01:8088/user/login
# cas.authn.rest.name=
# cas.authn.rest.passwordEncoder.type=NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2|com.example.CustomPasswordEncoder
# cas.authn.rest.passwordEncoder.characterEncoding=UTF-8
# cas.authn.rest.passwordEncoder.encodingAlgorithm=
# cas.authn.rest.passwordEncoder.secret=
# cas.authn.rest.passwordEncoder.strength=16


#cas.theme.defaultThemeName=mylogin

#配置允许登出后跳转到指定页面(使用场景:退出CAS并跳转到首页: https://localhost:8443/cas/logout?service=http://localhost:8081/index)
cas.logout.followServiceRedirects=true
#跳转到指定页面需要的参数名为 service
cas.logout.redirectParameter=service
#登出后需要跳转到的地址,如果配置该参数,service将无效。
#cas.logout.redirectUrl=http://localhost:8081/index
#在退出时是否需要 确认退出提示   true弹出确认提示框  false直接退出
#cas.logout.confirmLogout=true
#是否移除子系统的票据
#cas.logout.removeDescendantTickets=true
#禁用单点登出,默认是false不禁止
#cas.slo.disabled=true
#默认异步通知客户端,清除session
#cas.slo.asynchronous=true

#默认配置(负数为永不过期)
#TGT的最大生存时间,默认28800秒,也就是八小时;
cas.ticket.tgt.maxTimeToLiveInSeconds=-1
#在用户没有对系统进行任何操作的情况下,默认7200秒之后,也就是两个小时之后TGT会过期
cas.ticket.tgt.timeToKillInSeconds=-1

#Remember Me配置
# cas.ticket.tgt.rememberMe.enabled=true
# cas.ticket.tgt.rememberMe.timeToKillInSeconds=28800

#Timeout(应用于TGTs的过期策略提供了最近使用的过期策略,类似于Web服务器会话超时)
# cas.ticket.tgt.timeout.maxTimeToLiveInSeconds=28800

#Throttled Timeout(节流超时策略通过节流的概念扩展了超时策略,其中最多每N秒使用一个票据。)
# cas.ticket.tgt.throttledTimeout.timeToKillInSeconds=28800
# cas.ticket.tgt.throttledTimeout.timeInBetweenUsesInSeconds=5

#Hard Timeout(硬超时策略提供了从创建时开始计算的有限票据生命周期。)
# cas.ticket.tgt.hardTimeout.timeToKillInSeconds=28800


#当你访问一个应用系统时,cas server签发了一张票据,你需要在十秒钟之内拿着这种ST去server进行校验,过了20秒钟就过期了,系统也就访问不了;
cas.ticket.st.timeToKillInSeconds=20
#ST可以用几次才过期,默认是用过一次就过期。
cas.ticket.st.numberOfUses=1

我的CAS客户端代码:

项目结构:

在这里插入图片描述

pom.xml 需要引入的:

<!--Spring Security依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-test</artifactId>
    <scope>test</scope>
</dependency>

<!-- security 对CAS支持 -->
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-cas</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

<!--Mysql数据库驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.47</version>
</dependency>

<!-- Mybatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.0.0</version>
</dependency>

application.properties 需要配置的:


# CAS服务地址
cas.server.prefix=https://localhost:8443/cas
# CAS Server 登录地址
cas.server.login=${cas.server.prefix}/login
# CAS Server 登出地址
cas.server.logout=${cas.server.prefix}/logout

#应用访问地址
cas.client.prefix=http://localhost:8081
# CAS Client 登录地址
cas.client.login=${cas.client.prefix}/login/cas
# CAS Client 登出地址
cas.client.logoutRelative=/logout/cas   
cas.client.logout=${cas.client.prefix}${cas.client.logoutRelative}

#SpringSecurity session超时时间设置(28800秒=8小时)
server.servlet.session.timeout=28800

CASClientProperties:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "cas.client")
public class CASClientProperties {
    private String prefix;
    private String login;
    private String logoutRelative;
    private String logout;
	/** 此处省略getter、setter */
}

CASServerProperties:

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@ConfigurationProperties(prefix = "cas.server")
public class CASServerProperties {
    private String prefix;
    private String login;
    private String logout;
    /** 此处省略getter、setter */
}

CasSecurityConfig(Security整合CAS的配置文件) :

import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.validation.Cas20ProxyTicketValidator;
import org.jasig.cas.client.validation.TicketValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.cas.ServiceProperties;
import org.springframework.security.cas.authentication.CasAuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationEntryPoint;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;

@Configuration
public class CasSecurityConfig {

    @Autowired
    CASClientProperties casClientProperties;

    @Autowired
    CASServerProperties casServerProperties;

    @Autowired
    UserDetailsService userDetailService;

    /**
     * 1.处理 CAS 验证逻辑
     * @return
     */
    @Bean
    CasAuthenticationProvider casAuthenticationProvider() {
        CasAuthenticationProvider provider = new CasAuthenticationProvider();
        provider.setServiceProperties(serviceProperties());
        provider.setTicketValidator(ticketValidator());
        provider.setUserDetailsService(userDetailService);
        provider.setKey("aaaaa");	//不知道具体作用,目前看来使用上没有影响
        return provider;
    }

    /**
     * 2.配置一下 Client 的登录地址;
     * 在 CAS Server 上登录成功后,重定向的地址
     * @return
     */
    @Bean
    ServiceProperties serviceProperties() {
        ServiceProperties serviceProperties = new ServiceProperties();
        serviceProperties.setService(casClientProperties.getLogin());   //配置 Client 的登录地址
        return serviceProperties;
    }


    /**
     * 3.配置 ticket 校验地址,CAS Client 拿到 ticket 要去 CAS Server 上校验,
     * 默认校验地址是:${cas.server.prefix}/proxyValidate?ticket=xxx
     * @return
     */
    @Bean
    TicketValidator ticketValidator() {
        return new Cas20ProxyTicketValidator(casServerProperties.getPrefix());
    }


    /**
     * 4.CAS 认证的过滤器,过滤器将请求拦截下来之后,交由 CasAuthenticationProvider 来做具体处理
     * @param authenticationProvider
     * @return
     */
    @Bean
    CasAuthenticationFilter casAuthenticationFilter(AuthenticationProvider authenticationProvider) {
        CasAuthenticationFilter filter = new CasAuthenticationFilter();
        filter.setServiceProperties(serviceProperties());
        filter.setAuthenticationManager(new ProviderManager(authenticationProvider));
        return filter;
    }

    /**
     * 5.表示接受 CAS Server 发出的注销请求,所有的注销请求都将从 CAS Client 转发到 CAS Server,CAS Server 处理完后,会通知所有的 CAS Client 注销登录。
     * @return
     */
    @Bean
    SingleSignOutFilter singleSignOutFilter() {
        SingleSignOutFilter sign = new SingleSignOutFilter();
        sign.setIgnoreInitConfiguration(true);
        return sign;
    }

    /**
     * 6.配置将注销请求转发到 CAS Server。
     * @return
     */
    @Bean
    LogoutFilter logoutFilter() {
        LogoutFilter filter = new LogoutFilter(casServerProperties.getLogout(), new SecurityContextLogoutHandler());
        filter.setFilterProcessesUrl(casClientProperties.getLogoutRelative());
        return filter;
    }

    /**
     * 7.CAS 验证的入口
     * @return
     */
    @Bean
    @Primary
    AuthenticationEntryPoint authenticationEntryPoint() {
        CasAuthenticationEntryPoint entryPoint = new CasAuthenticationEntryPoint();
        entryPoint.setLoginUrl(casServerProperties.getLogin()); //设置 CAS Server 的登录地址
        entryPoint.setServiceProperties(serviceProperties());

        return entryPoint;
    }
}

SecurityConfig(Security配置文件):

import org.jasig.cas.client.session.SingleSignOutFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.time.Duration;
import java.util.Arrays;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    AuthenticationEntryPoint authenticationEntryPoint;
    @Autowired
    AuthenticationProvider authenticationProvider;
    @Autowired
    SingleSignOutFilter singleSignOutFilter;
    @Autowired
    LogoutFilter logoutFilter;
    @Autowired
    CasAuthenticationFilter casAuthenticationFilter;

    /**
     * 8.
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(authenticationProvider);
    }

    /**
     * 9.配置安全策略: /user/** 格式的路径需要有 "user" 角色才能访问;
     *
     * requestMatchers() 配置一个request Mather数组,参数为RequestMatcher 对象,其match 规则自定义,需要的时候放在最前面,对需要匹配的的规则进行自定义与过滤
     * authorizeRequests() URL权限配置
     * antMatchers() 配置一个request Mather 的 string数组,参数为 ant 路径格式, 直接匹配url
     * anyRequest 匹配任意url,无参 ,最好放在最后面
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.headers().frameOptions().disable();    //解决浏览器报错:项目中用到iframe嵌入网页,然后用到springsecurity就被拦截了 浏览器报错  x-frame-options deny;原因是因为springSecurty使用X-Frame-Options防止网页被Frame

        http
            .authorizeRequests()
                .antMatchers("/user/**").hasRole("user")
                .antMatchers("/admin/**").hasRole("admin")  //hasRole(String role): 限制单个角色访问,角色将被增加 “ROLE_” .所以”ADMIN” 将和 “ROLE_ADMIN”进行比较. 另一个方法是hasAuthority(String authority)
                .antMatchers("/admin/**").hasAnyRole("admin","user")  //hasAnyRole(String... roles): 限制多个角色访问,admin和user角色都可以访问
                .antMatchers("/hello").permitAll()	//permitAll(): 指定URL无需保护
                .antMatchers("/login/cas").permitAll()
            .anyRequest()   //匹配任意url
                .authenticated()    //表示剩下的任何请求需要验证之后才可以访问
            .and()
            .exceptionHandling()
            .authenticationEntryPoint(authenticationEntryPoint)
            .and()
            //.addFilter(casAuthenticationFilter)
            .addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class)
            .addFilterBefore(logoutFilter, LogoutFilter.class)
            
            .logout()
            .logoutUrl("/signout") // 退出登录的url
            .logoutSuccessUrl("/cas_logout") // 退出登录成功跳转的url
            .deleteCookies("JSESSIONID") // 删除名为"JSESSIONID"的cookie
   
   			.and()
            .cors()
            //.configurationSource(corsConfigurationSource())
            .and()

            .csrf()
            .disable();
            
            // //只允许一个用户登录,如果同一个账户两次登录,那么第一个账户将被踢下线,跳转到登录页面
            // http.sessionManagement().maximumSessions(1).expiredUrl("/login");

    }

    /**
     * 解决security跨域
     */
    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowCredentials(true);
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setMaxAge(Duration.ofHours(1));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**",configuration);
        return source;
    }
}

UserDetailsServiceImpl (角色处理):

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Primary;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;

@Service
@Primary
public class UserDetailsServiceImpl implements UserDetailsService{

    @Autowired
    private UserDao userDao;
    
    /**
     *
     * @param s 用户虽然在 CAS Server 上登录,但是,登录成功之后,CAS Client 还是要获取用户的基本信息、角色等,
     *          以便做进一步的权限控制,所以,这里的 loadUserByUsername 方法中的参数,实际上就是你从 CAS Server 上登录成功后获取到的用户名,
     *          拿着这个用户名,去数据库中查询用户的相关信息并返回,方便 CAS Client 在后续的鉴权中做进一步的使用。
     *
     *          当用户在 CAS Server 上登录成功之后,拿着用户名回到 CAS Client,然后我们再去数据库中根据用户名获取用户的详细信息,包括用户的角色等,进而在后面的鉴权中用上角色;
     * @return
     * @throws UsernameNotFoundException
     */
    //@Override
    //public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    //    return new User(s, "1234", true, true, true, true,
    //            AuthorityUtils.createAuthorityList("ROLE_user"));
    //}

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userDao.findByUsername(username);
        List<Role> roles = new ArrayList<>();
        roles.add(userDao.getRole(user.getId()));
        user.setRoles(roles);

        if (user == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        return user;
    }
}

Role :

public class Role {
    private Long id;    //角色 id
    private String name;    //角色名称(英文)
    private String nameZh;  //角色名称(中文)
    
    /** 此处省略getter、setter */
}    

User:

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class User implements UserDetails {
    private Long id;
    private String username;
    private String password;
    private boolean accountNonExpired;  //账户是否没有过期
    private boolean accountNonLocked;   //账户是否没有被锁定
    private boolean credentialsNonExpired;  //密码是否没有过期
    private boolean enabled;    //账户是否可用

    private List<Role> roles;   //用户的角色

    //用户的角色信息
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (Role role : getRoles()) {
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }
    
	/** 此处省略getter、setter */
}

UserDao:

@Repository
public interface UserDao {

    @Select("SELECT u.id, u.account_non_expired as accountnonexpired, u.account_non_locked as accountnonlocked, u.credentials_non_expired as credentialsnonexpired, u.enabled, u.password, u.username, r.name as roles, r.name_zh FROM g_user u \n" +
            "LEFT JOIN g_user_roles ur on ur.t_user_id = u.id \n" +
            "LEFT JOIN g_role r on ur.roles_id = r.id \n" +
            "where u.username= #{username};")
    User findByUsername(String username);

    @Select("SELECT r.* FROM g_user u \n" +
            "LEFT JOIN g_user_roles ur on ur.t_user_id = u.id \n" +
            "LEFT JOIN g_role r on ur.roles_id = r.id \n" +
            "where u.id= #{id};")
    Role getRole(Long id);

    @Select("SELECT id, account_non_expired as accountnonexpired, account_non_locked as accountnonlocked, credentials_non_expired as credentialsnonexpired, enabled, password, username FROM g_user where username = #{username} and password = #{password};")
    User findByUsernameAndPassword(String username, String password);

}

IndexController :

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class IndexController {

    private static final Logger logger = LoggerFactory.getLogger(IndexController.class);

    @CrossOrigin
    @GetMapping("/hello")
    public String hello() {
        return "hello";
    }

    @CrossOrigin
    @GetMapping("/admin/hello")
    public String admin_hello() {
        return "admin_hello";
    }

    @GetMapping("/user/hello")
    public String user_hello() {
        return "user_hello";
    }

}
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值