天天看点

shiro 集成cas实现单点登录

        之前写了security 集成cas实现单点登录的文章,有小伙伴私聊我问有没有基于shiro实现的,我研究了一下搞了个shiro版本,相关业务背景就不说了,可以看我 spring security 集成cas实现单点登录 这篇文章,废话不多说直接上代码

首先兴建一个 cas-shiro-boot-starter的spring boot 项目具体目录如下

shiro 集成cas实现单点登录

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.7.RELEASE</version>
        <relativePath/>
    </parent>

    <groupId>com.rxsk.cas</groupId>
    <artifactId>cas-shiro-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>

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

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

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

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

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-cas</artifactId>
            <version>1.3.0</version>
        </dependency>


        <!-- fastjson -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.9</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.2</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-lang/commons-lang -->
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>


        <!--缓存依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>


        <!--JWT(Json Web Token)登录支持-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

    </dependencies>

    <distributionManagement>
        <repository>
            <id>trj-releases</id>
            <name>trj Release Maven Repository Group</name>
            <url>http://172.18.10.33:8081/repository/maven-releases/</url>
        </repository>
        <snapshotRepository>
            <id>trjcn-snapshots</id>
            <name>trj Snapshot Maven Repository Group</name>
            <url>http://172.18.10.33:8081/repository/maven-snapshots/</url>
        </snapshotRepository>
    </distributionManagement>


</project>
           
CasShiroConfig 是 shiro与cas的配置类      
package com.rxsk.cas.config;

import com.rxsk.cas.filter.ShiroLoginFilter;
import com.rxsk.cas.properties.CasProperties;
import com.rxsk.cas.service.ShiroCasRealmService;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;

import javax.annotation.Resource;
import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
@EnableConfigurationProperties(CasProperties.class)
public class CasShiroConfig {

    private static final String CAS_FILTER = "casFilter";

    @Resource
    private CasProperties casProperties;

    @Resource
    private ShiroCasRealmService shiroCasRealmService;

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置realm.
        securityManager.setRealm(shiroCasRealmService);

        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);

        return securityManager;
    }


    @Bean
    @ConditionalOnMissingBean
    public ShiroFilterFactoryBean shiroFilter() {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 必须设置 SecurityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();

        filters.put("authc", new ShiroLoginFilter());

        shiroFilterFactoryBean.setLoginUrl(casProperties.getClientLoginUrl());
        // filters.put("casFilter", getCasFilter());

        // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        // shiroFilterFactoryBean.setLoginUrl("/login");

        // 登录成功后要跳转的链接
        // shiroFilterFactoryBean.setSuccessUrl("/success");

        // 未授权界面;
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");

        // 拦截器.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();

        // 配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put(casProperties.getIgnoredUrl()[0], "anon");

        // 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put(casProperties.getServerLogoutUrl(), "logout");

        // <!-- 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了;
        // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->

        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }



    @Bean(name = "lifecycleBeanPostProcessor")
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /*
     * 开启shiro aop注解支持.
     * 使用代理方式;所以需要开启代码支持;
     **/
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }
}
           
CasProperties cas的配置文件:      
package com.rxsk.cas.properties;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "cas")
public class CasProperties {

    private String serverUrlPrefix;

    private String serverLoginUrl;

    private String serverLogoutUrl;

    private String filterUrlPattern;

    private String clientUrlPrefix;

    private String clientLoginUrl;

    private String ossLoginUserAccount;

    private String ossLoginUserPassword;

    private String[] ignoredUrl;

    private String jwtTokenHead;

    private String jwtSecretKey;

    private Long jwtExpired;

    private String jwtTokenPrefix;


    public String getServerUrlPrefix() {
        return serverUrlPrefix;
    }

    public void setServerUrlPrefix(String serverUrlPrefix) {
        this.serverUrlPrefix = serverUrlPrefix;
    }

    public String getServerLoginUrl() {
        return serverLoginUrl;
    }

    public void setServerLoginUrl(String serverLoginUrl) {
        this.serverLoginUrl = serverLoginUrl;
    }

    public String getServerLogoutUrl() {
        return serverLogoutUrl;
    }

    public void setServerLogoutUrl(String serverLogoutUrl) {
        this.serverLogoutUrl = serverLogoutUrl;
    }

    public String getFilterUrlPattern() {
        return filterUrlPattern;
    }

    public void setFilterUrlPattern(String filterUrlPattern) {
        this.filterUrlPattern = filterUrlPattern;
    }

    public String getClientUrlPrefix() {
        return clientUrlPrefix;
    }

    public void setClientUrlPrefix(String clientUrlPrefix) {
        this.clientUrlPrefix = clientUrlPrefix;
    }

    public String getClientLoginUrl() {
        return clientLoginUrl;
    }

    public void setClientLoginUrl(String clientLoginUrl) {
        this.clientLoginUrl = clientLoginUrl;
    }

    public String getOssLoginUserAccount() {
        return ossLoginUserAccount;
    }

    public void setOssLoginUserAccount(String ossLoginUserAccount) {
        this.ossLoginUserAccount = ossLoginUserAccount;
    }

    public String getOssLoginUserPassword() {
        return ossLoginUserPassword;
    }

    public void setOssLoginUserPassword(String ossLoginUserPassword) {
        this.ossLoginUserPassword = ossLoginUserPassword;
    }

    public String getJwtSecretKey() {
        return jwtSecretKey;
    }

    public void setJwtSecretKey(String jwtSecretKey) {
        this.jwtSecretKey = jwtSecretKey;
    }

    public Long getJwtExpired() {
        return jwtExpired;
    }

    public void setJwtExpired(Long jwtExpired) {
        this.jwtExpired = jwtExpired;
    }

    public String getJwtTokenPrefix() {
        return jwtTokenPrefix;
    }

    public void setJwtTokenPrefix(String jwtTokenPrefix) {
        this.jwtTokenPrefix = jwtTokenPrefix;
    }

    public String getJwtTokenHead() {
        return jwtTokenHead;
    }

    public void setJwtTokenHead(String jwtTokenHead) {
        this.jwtTokenHead = jwtTokenHead;
    }

    public String[] getIgnoredUrl() {
        return ignoredUrl;
    }

    public void setIgnoredUrl(String[] ignoredUrl) {
        this.ignoredUrl = ignoredUrl;
    }
}
           

最终将此项目打包成jar并推送到你的私服中,在需要cas集成的业务系统pom.xml 中加入相关的jar

<dependency>
    <groupId>com.rxsk.cas</groupId>
    <artifactId>cas-spring-security-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
 
           

在你的项目配置文件中加入 cas的相关配置

# oss单点登录相关配置
cas:
  server-url-prefix: http://10.100.3.8:8080/cas
  server-login-url: http://10.100.3.8:8080/cas/login
  server-logout-url: http://10.100.3.8:8080/cas/logout
  filter-url-pattern:
  client-url-prefix: http://10.100.12.44:8085/passport
  ignored-url: /login/ticket-login
  client-login-url: ${cas.server-login-url}?service=${cas.client-url-prefix}${cas.filter-url-pattern}
  oss-login-user-account: admin
  oss-login-user-password: 123456
  jwt-secret-key: 12345678
  jwt-expired: 2592000
  jwt-token-prefix: Bearer
  jwt-token-head: Authorization
           

因为有些接口是需要当前登录用户是否有配置某些权限才能访问,因此需要实现 

com.rxsk.cas.service.ShiroCasRealmService 这个抽象类里面的相关方法      
package com.rxsk.business.service;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.rxsk.business.vo.req.TicketLoginReqVO;
import com.rxsk.business.vo.resp.TicketLoginRespVO;
import com.rxsk.cas.properties.CasProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.cas.CasToken;
import org.apache.shiro.subject.PrincipalCollection;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.jasig.cas.client.validation.TicketValidationException;
import org.jasig.cas.client.validation.TicketValidator;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;

@Slf4j
@Service
public class ShiroCasRealmService extends com.rxsk.cas.service.ShiroCasRealmService {

    @Resource
    private CasProperties casProperties;

    @PostConstruct
    public void initProperty(){
        setCasServerUrlPrefix(casProperties.getServerUrlPrefix());
        setCasService(casProperties.getClientUrlPrefix() + casProperties.getFilterUrlPattern());
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addStringPermission("role");
        return authorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        TicketLoginRespVO ticketLoginRespVO = new TicketLoginRespVO();
        String credentials = null;
        if(token instanceof CasToken){
            CasToken casToken = (CasToken) token;
            TicketLoginReqVO ticketLoginReqVO = JSON.parseObject((String)casToken.getCredentials(), TicketLoginReqVO.class);
            String casServerURL = ticketLoginReqVO.getSsoURL();
            setCasServerUrlPrefix(casServerURL);
            TicketValidator ticketValidator = createTicketValidator();
            Assertion casAssertion = null;
            try {
                casAssertion = ticketValidator.validate(ticketLoginReqVO.getTicket(), ticketLoginReqVO.getService());
            } catch (TicketValidationException e) {
                e.printStackTrace();
                log.error("票据校验异常", e);
                throw new AuthenticationException("登录失败,票据校验异常:" + e.getMessage());
            }
            AttributePrincipal casPrincipal = casAssertion.getPrincipal();
            credentials = (String)casToken.getCredentials();
        }
        return new SimpleAuthenticationInfo(ticketLoginRespVO, credentials, getName());
    }


    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof AuthenticationToken;
    }

}
           

业务系统需要定义 /login/ticket-login 这个接口来验证ticket与返回token

@RequestMapping("/ticket-login")
public Response<TicketLoginRespVO> ticketLogin(@RequestBody TicketLoginReqVO req){

    TicketLoginRespVO ticketLoginRespVO = new TicketLoginRespVO();

    TicketValidator ticketValidator = new Cas20ProxyTicketValidator(casProperties.getServerUrlPrefix());
    Assertion casAssertion = null;
    try {
        casAssertion = ticketValidator.validate(req.getTicket(), req.getService());
    } catch (TicketValidationException e) {
        e.printStackTrace();
        log.error("票据校验异常", e);
    }

    AttributePrincipal casPrincipal = casAssertion.getPrincipal();

    SysUserDO sysUserDO = loginService.loginByAccountPassword(casProperties.getOssLoginUserAccount(),
            casProperties.getOssLoginUserPassword());

    //生成token,记录到redis
    LoginRespVO loginRespVO = super.createTokenVO(sysUserDO, true);

    ticketLoginRespVO.setToken(loginRespVO.getToken());

    return Response.builder(ticketLoginRespVO);
}
           

cas-shiro-spring-boot-starter 已提交到码云,有需要的小伙伴可以找我要或者在之前的文章中有相关的链接地址

有问题可加微信 

补充一句,加微信别老是您您您的,都是打工人,不必这么客气,我也才18啊哈哈

请大家关注下博客谢谢 请大家关注下博客谢谢 请大家关注下博客谢谢 重要的事情说三遍