JASIG CAS学习(五): 客户端集成SHIRO

shiro作为一个安全框架,在它的 1.2 版本开始就已经有了对 cas 的支持,所以用shiro来做客户端的安全框架对于我们的cas来说是非常方便的。

准备工作做好,首先新建一个Springboot 工程添加以下依赖项。

<?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.3.RELEASE</version>
        <!--<version>1.5.8.RELEASE</version>-->
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.xc</groupId>
    <artifactId>cas-shiro-client1</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>cas-shiro-client1</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <shiro.version>1.2.3</shiro.version>
    </properties>

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

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

        <dependency>
            <groupId>net.sf.ehcache</groupId>
            <artifactId>ehcache-core</artifactId>
            <version>2.6.11</version>
        </dependency>

        <!-- Shiro begin -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-cas</artifactId>
            <version>${shiro.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <!-- Shiro end -->


    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

依赖项配置完成之后,就可以开始添加对Shiro以及单点登出过滤器的配置;首先先将要准备的配置属性都添加到配置文件中 application.properties 中 , 另外 我在hosts文件中添加了 127.0.0.1 shiro1.cas.com

server.servlet.context-path=/shirocas
server.port=8081

cas.server-url-prefix=http://server.cas.com:8080/cas
cas.server-login-url=http://server.cas.com:8080/cas/login
cas.client-host-url=http://shiro1.cas.com:8081
cas.logout.url=http://server.cas.com:8080/cas/logout?service=http://shiro1.cas.com:8081/shirocas
cas.validation-type=CAS

cas.shiro.login-url=http://server.cas.com:8080/cas/login?service=http://shiro1.cas.com:8081/shirocas/cas
csa.shiro.logout-url=http://server.cas.com:8080/cas/logout?service=http://shiro1.cas.com:8081/shirocas
cas.shiro.cas-service=http://shiro1.cas.com:8081/shirocas/cas
cas.shiro.role-attr=roles
cas.shiro.permission-attr=permissions

添加一个配置类用于访问该配置文件

package com.xc.cas.config;

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

import javax.validation.constraints.NotNull;

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

    /**
     * CAS server URL E.g. https://example.com/cas or https://cas.example. Required.
     * CAS 服务端 url 不能为空
     */
    @NotNull
    private String serverUrlPrefix;

    /**
     * CAS server login URL E.g. https://example.com/cas/login or https://cas.example/login. Required.
     * CAS 服务端登录地址  上面的连接 加上/login 该参数不能为空
     */
    @NotNull
    private String serverLoginUrl;

    /**
     * CAS-protected client application host URL E.g. https://myclient.example.com Required.
     * 当前客户端的地址
     */
    @NotNull
    private String clientHostUrl;

    /**
     * 忽略规则,访问那些地址 不需要登录
     */
    private String ignorePattern;

    /**
     * 自定义UrlPatternMatcherStrategy验证
     */
    private String ignoreUrlPatternType;

    private Shiro shiro;

    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 getClientHostUrl() {
        return clientHostUrl;
    }

    public void setClientHostUrl(String clientHostUrl) {
        this.clientHostUrl = clientHostUrl;
    }

    public String getIgnorePattern() {
        return ignorePattern;
    }

    public void setIgnorePattern(String ignorePattern) {
        this.ignorePattern = ignorePattern;
    }

    public String getIgnoreUrlPatternType() {
        return ignoreUrlPatternType;
    }

    public void setIgnoreUrlPatternType(String ignoreUrlPatternType) {
        this.ignoreUrlPatternType = ignoreUrlPatternType;
    }

    public Shiro getShiro() {
        return shiro;
    }

    public void setShiro(Shiro shiro) {
        this.shiro = shiro;
    }

    public static class Shiro{

        @NotNull
        private String loginUrl;

        @NotNull
        private String logoutUrl;

        @NotNull
        private String casService;

        private String roleAttr;

        private String permissionAttr;

        public String getLoginUrl() {
            return loginUrl;
        }

        public void setLoginUrl(String loginUrl) {
            this.loginUrl = loginUrl;
        }

        public String getLogoutUrl() {
            return logoutUrl;
        }

        public void setLogoutUrl(String logoutUrl) {
            this.logoutUrl = logoutUrl;
        }

        public String getRoleAttr() {
            return roleAttr;
        }

        public void setRoleAttr(String roleAttr) {
            this.roleAttr = roleAttr;
        }

        public String getPermissionAttr() {
            return permissionAttr;
        }

        public void setPermissionAttr(String permissionAttr) {
            this.permissionAttr = permissionAttr;
        }

        public String getCasService() {
            return casService;
        }

        public void setCasService(String casService) {
            this.casService = casService;
        }
    }
}

配置属性添加好之后,现在开始着手对Shiro的配置,都是一些常规的配置。这里配置了一个CasRealm,用于验证对于CAS客户端传过来的ticket进行验证,其中我添加了roleAttributeName和permissionAttributeName这两个属性,他们的作用是将CAS 服务端传过来与之对应的属性,解析为这个用户的角色和权限,多个角色和权限默认是以逗号分隔。

package com.xc.cas.config;

import com.xc.cas.app.CustomCasRealm;
import org.apache.shiro.cache.ehcache.EhCacheManager;
import org.apache.shiro.cas.CasFilter;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.cas.CasSubjectFactory;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.filter.authc.LogoutFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

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

    @Autowired
    private CasProperties casProperties;

    /**
     * security manager
     * @return
     */
    @Bean("securityManager")
    public DefaultWebSecurityManager securityManager(@Qualifier("shiroCacheManager")EhCacheManager shiroCacheManager,@Qualifier("casRealm")Realm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        securityManager.setCacheManager(shiroCacheManager);
        securityManager.setSubjectFactory(casSubjectFactory());
        return securityManager;
    }

    /**
     * cas subject factory
     * @return
     */
    @Bean
    public CasSubjectFactory casSubjectFactory(){
        return new CasSubjectFactory();
    }
    /**
     * shiro ehcache manager
     * @return
     */
    @Bean("shiroCacheManager")
    public EhCacheManager shiroCacheManager(@Qualifier("ehCacheManagerFactoryBean") EhCacheManagerFactoryBean ehCacheManagerFactoryBean){
        EhCacheManager cacheManager = new EhCacheManager();
        cacheManager.setCacheManager(ehCacheManagerFactoryBean.getObject());
        return cacheManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        shiroFilter.setLoginUrl(casProperties.getShiro().getLoginUrl());
        shiroFilter.setSuccessUrl("/");
        Map<String, Filter> filters = new LinkedHashMap<>();
        filters.put("cas",casFilter());
        Map<String, String> filterChainDefinitions = new LinkedHashMap<>();
        filterChainDefinitions.put("/login/failure","anon");
        filterChainDefinitions.put("/cas","cas");
        filterChainDefinitions.put("/**","user");
        shiroFilter.setFilters(filters);
        shiroFilter.setFilterChainDefinitionMap(filterChainDefinitions);
        return shiroFilter;
    }

    @Bean
    public CasFilter casFilter(){
        CasFilter filter = new CasFilter();
        filter.setFailureUrl("/login/failure");
        return filter;
    }


    @Bean("casRealm")
    public CasRealm casRealm(){
        CasRealm realm = new CasRealm();
        realm.setName("casRealm");
     realm.setPermissionAttributeNames(casProperties.getShiro().getPermissionAttr());
        realm.setRoleAttributeNames(casProperties.getShiro().getRoleAttr());
        realm.setCasServerUrlPrefix(casProperties.getServerUrlPrefix());
        realm.setCasService(casProperties.getShiro().getCasService());
        return realm;
    }


    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager")DefaultWebSecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

}

还有三个bean的配置我放到了另外一个配置文件中去了,第一个bean用于装载ehcache,后两个用于开始shiro的权限注解

package com.xc.cas.config;

import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

@Configuration
public class Config {

    /**
     * ehcache
     * @return
     */
    @Bean("ehCacheManagerFactoryBean")
    public EhCacheManagerFactoryBean ehCacheManagerFactoryBean(){
        EhCacheManagerFactoryBean cacheManager = new EhCacheManagerFactoryBean();
        cacheManager.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:ehcache.xml"));
        return cacheManager;
    }


    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
        creator.setProxyTargetClass(true);
        return creator;
    }

}

接下来配置单点登出的过滤器

package com.xc.cas.config;

import org.jasig.cas.client.session.SingleSignOutFilter;
import org.jasig.cas.client.session.SingleSignOutHttpSessionListener;
import org.jasig.cas.client.util.HttpServletRequestWrapperFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.EventListener;
import java.util.HashMap;
import java.util.Map;

@Configuration
@EnableConfigurationProperties(CasProperties.class)
public class CasLogoutConfig implements WebMvcConfigurer {

    @Autowired
    private CasProperties configProps;

    /**
     * 配置登出过滤器
     * @return
     */
    @Bean
    public FilterRegistrationBean filterSingleRegistration() {
        final FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new SingleSignOutFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        Map<String,String> initParameters = new HashMap<String, String>();
        initParameters.put("casServerUrlPrefix", configProps.getServerUrlPrefix());
        registration.setInitParameters(initParameters);
        // 设定加载的顺序
        registration.setOrder(1);
        return registration;
    }


    /**
     * request wraper过滤器
     * @return
     */
    @Bean
    public FilterRegistrationBean filterWrapperRegistration() {
        final FilterRegistrationBean registration = new FilterRegistrationBean();
        registration.setFilter(new HttpServletRequestWrapperFilter());
        // 设定匹配的路径
        registration.addUrlPatterns("/*");
        // 设定加载的顺序
        registration.setOrder(4);
        return registration;
    }

    /**
     * 添加监听器
     * @return
     */
    @Bean
    public ServletListenerRegistrationBean<EventListener> singleSignOutListenerRegistration(){
        ServletListenerRegistrationBean<EventListener> registrationBean = new ServletListenerRegistrationBean<EventListener>();
        registrationBean.setListener(new SingleSignOutHttpSessionListener());
        registrationBean.setOrder(1);
        return registrationBean;
    }

}

之后再建立一个Controller进行测试即可

    @GetMapping("/")
    @ResponseBody
    public String index(){
        return "shiro cas client inde1";
    }

    @RequiresPermissions("admin:view")
    @GetMapping("/index")
    @ResponseBody
    public String index2(){
        return "shiro cas client index2";
    }

    @GetMapping("/login/failure")
    @ResponseBody
    public String loginFailure(){
        return "login fail....";
    }

    @GetMapping("/logout")
    public String logout(){
        Subject subject = SecurityUtils.getSubject();
        subject.logout();
        return "redirect:http://server.cas.com:8080/cas/logout?service=http://shiro1.cas.com:8081/shirocas";
    }

项目启动之后,可能打印以下  Bean 'beanName' of type [package.className] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)信息,我也是弄了老半天都不知道为什么,后来发现其实对项目没有什么影响,所以就不管了。

项目成功启动之后,跳转到CAS服务端进行登录,登录生成ticket之后会进入 CasRealm的  doGetAuthenticationInfo 方法,认证通过则跳转到你 service 参数的接口 。之前,我在CasRealm中设置了roleAtributeName和permissionAttributeName属性,此时CAS 服务端并没有将这些信息返回(因为服务端还没改),而且一般各个客户端都有本身应用下的角色和权限,所以还需要自身去进行授权。自定义授权也很简单,就是新建一个Realm继承CasRealm,然后重写doGetAuthorizationInfo方法,将其添加到securityManager中即可。

问题:不进入doGetAuthorizationInfo方法?

 在执行完doGetAuthenticationInfo 方法之后并没有进入doGetAuthorizationInfo方法,就直接跳转到service的接口去了。对此研究了很久,我一直以为我是lifecycleBeanPostProcessor 和 defaultAdvisorAutoProxyCreator这两个bean没加导致的,后来发现并不是。shiro是在你的客户端程序中需要用到角色或者权限时,才会进入doGetAuthorizationInfo方法进行授权,比如遇到 subject.hasRole 等方法或者 @RequireRoles 等注解的时候,才会进行授权。

问题:登出为什么不直接采用logout过滤器?

我之前也是直接使用的logout过滤器,但是会出一些莫名其妙的问题,比如无法成功登录的问题。所以我才直接将登出写成接口。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值