美文网首页javaWeb学习我爱编程Javaweb
SSO单点登录------系统搭建

SSO单点登录------系统搭建

作者: Ferrari1001 | 来源:发表于2018-03-25 19:16 被阅读11次

    3 搭建过程

    3.1 搭建环境

      本文搭建过程以cas-server-4.2.7,cas-client-3.4.1为例。构建环境为Idea 2017.1, Gradle3.5。

    3.2 服务端搭建

    3.2.1 安装配置Gradle
    3.2.1.1 下载安装gradle

    cas-server-4.2.7所使用构建工具为Gradle,所以需要先安装好Gradle。

    3.2.1.2 配置gradle本地仓库

      配置环境变量GRADLE_USER_HOME,并指向你的一个本地目录,用来保存Gradle下载的依赖包。


    image.png
    3.2.1.3 远程仓库配置

      一般Gradle、maven从中央仓库mavenCentral下载依赖包,但是在国内下载速度巨慢,我们只能使用国内的镜像。 我们可以在cas-server项目的根目录下,对build.gradle做如下配置:

    repositories {
        maven {
            url 'http://maven.aliyun.com/nexus/content/groups/public/'
        }
        mavenCentral()
    }
    
    3.2.2构建项目

      将下载好的cas-4.2.7解压到cas-server目录,在Idea中打开项目。


    image.png

    选择使用本地Gradle版本,接下来就是漫长的等待项目加载过程。

    3.2.3 实现自定义用户登录

      cas原始登陆验证十分简单,只需在cas.properties中配置accept.authn.users字段即可,初始登录名密码为casuser,Mellon。为了实现真正的登陆验证,我们需要实现自己的登录验证流程。

    3.2.3.1 建立自定义账号密码处理类:

      在cas-server-core-authentication模块,org.jasig.cas.authentication包下新建自定义账号密码处理类。以建立sunlandsAuthenticationHandler为例:

     @Component("sunlandsAuthenticationHandler")public class SunlandsAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {
        /** The default separator in the file. */
        private static final String DEFAULT_SEPARATOR = "::";
        private static final Pattern USERS_PASSWORDS_SPLITTER_PATTERN = Pattern.compile(DEFAULT_SEPARATOR);
    
        /** The list of users we will accept. */
        private Map<String, String> users;
    
        @Value("${accept.authn.users:}")
        private String acceptedUsers;
    
        @Autowired
        private SdfPasswordValidatorImpl sdfPasswordValidatorImpl;  //验证账号密码的实现类
        @Override
        protected HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential) throws GeneralSecurityException, PreventedException {
            if (users == null || users.isEmpty()) {
                throw new FailedLoginException("No user can be accepted because none is defined");
            }
            final String username = credential.getUsername();
            final String password = credential.getPassword();
    
            try {
                boolean isValidUser = sdfPasswordValidatorImpl.authenticate(username,
                        password);  //返回true则验证通过
                if (!isValidUser) {
                    throw new FailedLoginException("Password does not match value on record.");
                }
            } catch (Exception e) {
                throw new FailedLoginException("login failed.");
            }
            return createHandlerResult(credential, this.principalFactory.createPrincipal(username), null);
    
        }
    
        /**
         * @param users The users to set.
         */
        public final void setUsers(@NotNull final Map<String, String> users) {
            this.users = Collections.unmodifiableMap(users);
        }
        @PostConstruct
        public void init() {
            if (StringUtils.isNotBlank(this.acceptedUsers) && this.users == null) {
                final Set<String> usersPasswords = org.springframework.util.StringUtils.commaDelimitedListToSet(this.acceptedUsers);
                final Map<String, String> parsedUsers = new HashMap<>();
                for (final String usersPassword : usersPasswords) {
                    final String[] splitArray = USERS_PASSWORDS_SPLITTER_PATTERN.split(usersPassword);
                    parsedUsers.put(splitArray[0], splitArray[1]);
                }
                setUsers(parsedUsers);
            }
        }
    }
    

      在authenticateUsernamePasswordInternal()方法中实现账号和密码的验证逻辑, 我这里是引入并调用了公司原有系统的密码账号验证类SdfPasswordValidatorImpl。
      在类SdfPasswordValidatorImpl 的authenticate()方法中,用户自定义账号密码验证逻辑, 返回true则验证通过。

    3.2.3.2配置自定义验证处理类:

    在deployerConfigContext中将
    ···
    <alias name="acceptUsersAuthenticationHandler" alias="primaryAuthenticationHandler" />
    ···
    替换为
    ···
    <alias name="sunlandsAuthenticationHandler"
    alias="primaryAuthenticationHandler" />
    ···
    3.2.4 自定义登录页面

    3.2.5 实现ST与TGC存入redis

    3.2.6 实现单点登录跳转地址限制

      从应用系统跳转到cas登录页面时,会在登录地址后附带应用系统的地址,如
    <u>http://</u><u>www.casserver.com</u><u>/serviceValidate?service=http://</u><u>www.client1.com</u><u>/</u><u>index</u>,登陆之后就会跳转回应用系统页面<u>http://</u><u>www.client1.com</u><u>/</u><u>index。</u>
    Service地址在cas服务器后端并没有进行验证, service为任意网址均能跳转。在遭遇网络劫持或其他情况下,可能会从cas登录页面跳转到非公司网址,造成安全隐患,因此需要对service后的跳转地址进行验证。在service地址不符合要求的情况下,跳转到登陆成功的主页,而不是service地址。

    3.2.6.1 修改cas.properties

      在cas.properties文件中新增允许跳转的service地址变量,用 “| ”符号分隔,支持正则形式。
    ···
    //# 允许跳转的service地址
    service.allowed=xxx.cn|yyy.com
    ···

    3.2.6.2 修改GenerateServiceTicketAction

    修改GenerateServiceTicketAction类:
    添加私有属性 serviceAllowed以及是否可以跳转标记常量

     private static final Integer REGISTERED_SERVICE_FLAG = 1;
        private static final Integer UNREGISTERED_SERVICE_FLAG = 0; 
    @Value("${service.allowed}")
        private String serviceAllowed;
    

    修改doExecute方法

     final ServiceTicket serviceTicketId = this.centralAuthenticationService
                        .grantServiceTicket(ticketGrantingTicket, service, authenticationContext);
                WebUtils.putServiceTicketInRequestScope(context, serviceTicketId);
    // 新增开始
                //判断service是否注册
                Integer serviceFlag = UNREGISTERED_SERVICE_FLAG;
                if(serviceAllowed !=null && !serviceAllowed.isEmpty()){
                    try {
                        String url = new URL(service.toString()).getHost();
                        String[] services = serviceAllowed.split("\\|");
    for(String ser : services){
                            Pattern pattern = Pattern.compile(ser);
                            if (!pattern.matcher(url).matches()) continue;
                            serviceFlag = REGISTERED_SERVICE_FLAG;
                        }
                        if(serviceFlag==0) return new Event(this,"unregisteredService");
                    } catch (Exception e) {
                        logger.error("识别service是否注册失败",e);
                        return new Event(this,"unregisteredService");
                    }
                }
               //新增结束
                return success();
    
    3.6.2.3修改log-webflow.xml
     <action-state id="generateServiceTicket">
            <evaluate expression="generateServiceTicketAction"/>
            <transition on="success" to="warn"/>
    <!--  新增开始     -->
            <transition on="unregisteredService" to="viewGenericLoginSuccess"/>
    <!--  新增结束     -->
            <transition on="authenticationFailure" to="handleAuthenticationFailure"/>
            <transition on="error" to="initializeLogin"/>
            <transition on="gateway" to="gatewayServicesManagementCheck"/>
        </action-state>
    

    3.3 应用系统端搭建

    3.3.1 应用系统接入单点登录配置

      若对应用系统端没有个性化需求, 应用系统直接引用cas-client jar包。并进行配置即可实现单点登录。

    3.3.1.1 引入cas-client统一登录jar包

    maven项目引入:
      在pom文件中添加以下代码

    <dependency>
        <groupId>org.jasig.cas.client</groupId>
        <artifactId>cas-client-core</artifactId>
        <version>3.4.1</version>
    </dependency>
    <!--如果原本没有引入slf4j, 还需引入slf4j-->
    <dependency>
       <groupId>org.slf4j</groupId>
       <artifactId>slf4j-api</artifactId>
       <version>1.7.10</version>
    </dependency>
    
    3.3.1.2 配置文件

    修改web.xml
      在项目web.xml 中引入以下代码。引入内容应在系统原有字符串过滤filter之后,以免造成系统原有字符串过滤filter不能使用。

    <listener>
      <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:casFilter.xml</param-value>
    </context-param>
    
    <listener>
        <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
    </listener>
    <!-- 单点登出 -->
    <filter>
        <filter-name>CAS Single Sign Out Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>singleSignOutFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Single Sign Out Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!--用户认证过滤器-->
    <filter>
        <filter-name>CAS Authentication Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
      <init-param>
        <param-name>targetFilterLifecycle</param-name>
        <param-value>true</param-value>
      </init-param>
      <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>authenticationFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Authentication Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <!-- Ticket的校验过滤器 -->
    <filter>
        <filter-name>CAS Validation Filter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>targetBeanName</param-name>
            <param-value>ticketValidationFilter</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>CAS Validation Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    
    <filter>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <filter-class>
            org.jasig.cas.client.util.HttpServletRequestWrapperFilter
        </filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <filter>
        <filter-name>CAS Assertion Thread Local Filter</filter-name>
        <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>CAS Assertion Thread Local Filter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    

    添加文件
      在项目/src/main/resources/下添加cas-client.properties及casFilter.xml文件.
    cas-client.properties

    #线上cas服务器
    
    #casServer=http://login.xxx.com
    
    #测试cas服务器, 不验证263密码, 任何密码可登陆
    
    casServer=[http://172.16.116.136:9091/cas](http://172.16.116.136:9091/cas/login)
    
    #本应用的服务地址,需修改  serverName=http://172.16.103.226:9000
    
    #设置不被不需要被拦截的地址,支持正则  ignoreAddress=/a.do|/*.html
    

    casFilter.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    
        <context:property-placeholder location="classpath:/cas-client.properties"/>
    
        <bean name="singleSignOutFilter" class="org.jasig.cas.client.session.SingleSignOutFilter">
            <property name="casServerUrlPrefix" value="${casServer}"/>
        </bean>
    
        <bean name="authenticationFilter"
              class="org.jasig.cas.client.authentication.AuthenticationFilter">
            <property name="casServerLoginUrl" value="${casServer}/login"/>
            <property name="serverName" value="${serverName}"/>
            <property name="ignorePattern" value="${ignoreAddress}"/>
        </bean>
    
        <bean name="ticketValidationFilter"
              class="org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter">
            <property name="serverName" value="${serverName}" />
          <property name="casServerUrlPrefix" value="${casServer}"/>
        </bean>
    </beans>
    

    相关文章

      网友评论

        本文标题:SSO单点登录------系统搭建

        本文链接:https://www.haomeiwen.com/subject/gyqkcftx.html