Jeesite单点登录集成Cas另加自定义登录验证

Advertisement

Jeesite单点登录集成Cas另加自定义登录验证

JeeSite是基于多个优秀的开源项目,高度整合封装而成的高效,高性能,强安全性的 开源 Java EE快速开发平台.

Cas主要是用来解决多应用之间统一登陆认证,无需用户在同一公司多应用之间重复登陆。例如阿里巴巴中淘宝、天猫,在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.


Cas基础

服务端

服务端cas-server-webapp-4.0.0.war,服务器端程序一般不用我们完成,但需要做一点小小的修改,cas的服务器端程序由spring+Spring web flow+cas写成。全部使用spring配置文件。

默认登录用户名密码


去除https

修改第一处: cas/WEB-INF/deployerConfigContext.xml

<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
p:httpClient-ref="httpClient"/>

增加参数p:requireSecure=”false”,是否需要安全验证,即HTTPS,false为不采用。修改后为:

<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler"
 p:httpClient-ref="httpClient"
 p:requireSecure="false"/>

修改第二处: cas/WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml

<bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
      p:cookieSecure="true"
      p:cookieMaxAge="-1"
      p:cookieName="CASTGC"
      p:cookiePath="/cas" />

参数p:cookieSecure=”true”,同理为HTTPS验证相关,TRUE为采用HTTPS验证,FALSE为不采用https验证。

参数p:cookieMaxAge=”-1”,简单说是COOKIE的最大生命周期,-1为无生命周期,即只在当前打开的IE窗口有效,IE关闭或重新打开其它窗口,仍会要求验证。可以根据需要修改为大于0的数字,比如3600等,意思是在3600秒内,打开任意IE窗口,都不需要验证。
这里把 cookieSecure修改为false就行了

修改客户端应用的web.xml

增加如下filter和mapping(jeesite不修改此处,有其它方式处理)

 <filter>
   <filter-name>CAS Authentication Filter</filter-name>
   <filter-class>
    org.jasig.cas.client.authentication.AuthenticationFilter
   </filter-class>
   <init-param>
    <param-name>casServerLoginUrl</param-name>
    <param-value>
    http://localhost:8080/cas/login
    </param-value>
   </init-param>
   <init-param>
    <param-name>renew</param-name>
    <param-value>false</param-value>
   </init-param>
   <init-param>
    <param-name>gateway</param-name>
    <param-value>false</param-value>
   </init-param>
   <init-param>
    <param-name>serverName</param-name>
    <param-value>http://localhost:8080</param-value>
   </init-param>
</filter>

<filter>
   <filter-name>CAS Validation Filter</filter-name>
   <filter-class>
    org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter
   </filter-class>
   <init-param>
    <param-name>casServerUrlPrefix</param-name>
    <param-value>http://localhost:8080/cas</param-value>
   </init-param>
   <init-param>
    <param-name>serverName</param-name>
    <param-value>http://localhost:8080</param-value>
   </init-param>
   <init-param>
    <param-name>useSession</param-name>
    <param-value>true</param-value>
   </init-param>
   <init-param>
    <param-name>redirectAfterValidation</param-name>
    <param-value>true</param-value>
   </init-param>
</filter>

<filter>
   <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
   <filter-class>
    org.jasig.cas.client.util.HttpServletRequestWrapperFilter
   </filter-class>
</filter>

<filter>
   <filter-name>CAS Assertion Thread Local Filter</filter-name>
   <filter-class>
    org.jasig.cas.client.util.AssertionThreadLocalFilter
   </filter-class>
</filter>
<filter>
        <filter-name>loginFilter</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>
    </filter>
<filter-mapping>
   <filter-name>CAS Authentication Filter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-mapping>
   <filter-name>CAS Validation Filter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-mapping>
   <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-mapping>
   <filter-name>CAS Assertion Thread Local Filter</filter-name>
   <url-pattern>/*</url-pattern>
</filter-mapping>

Jeesite单点登录集成cas

需要写个自己MyCasRealm.java

package com.thinkgem.jeesite.modules.sys.security;

import java.util.Collection;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
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.CasAuthenticationException;
import org.apache.shiro.cas.CasRealm;
import org.apache.shiro.cas.CasToken;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.SimplePrincipalCollection;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.thinkgem.jeesite.common.config.Global;
import com.thinkgem.jeesite.common.utils.SpringContextHolder;
import com.thinkgem.jeesite.common.web.Servlets;
import com.thinkgem.jeesite.modules.sys.entity.Menu;
import com.thinkgem.jeesite.modules.sys.entity.Role;
import com.thinkgem.jeesite.modules.sys.entity.User;
import com.thinkgem.jeesite.modules.sys.security.SystemAuthorizingRealm.Principal;
import com.thinkgem.jeesite.modules.sys.service.SystemService;
import com.thinkgem.jeesite.modules.sys.utils.LogUtils;
import com.thinkgem.jeesite.modules.sys.utils.UserUtils;

public class MyCasRealm extends CasRealm {
    private Logger logger = LoggerFactory.getLogger(getClass());
    private SystemService systemService;

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//      return super.doGetAuthenticationInfo(token);
        CasToken casToken = (CasToken) token;
        if (token == null) {
            return null;
        }
        //获取ticket
        String ticket = (String)casToken.getCredentials();
        if (!org.apache.shiro.util.StringUtils.hasText(ticket)) {
            return null;
        }

        TicketValidator ticketValidator = ensureTicketValidator();

        try {
            //回传ticket到服务端验证,验证通过就进入下一行,可以获取登录后的相关信息,否则直接抛异常,即验证不通过
            Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
            AttributePrincipal casPrincipal = casAssertion.getPrincipal();
            String userId = casPrincipal.getName();
            User user = getSystemService().getUserByLoginName(userId);
            if (user != null) {
                Principal p = new  Principal(user, false);
                PrincipalCollection principalCollection = new SimplePrincipalCollection(p, getName());
                return new SimpleAuthenticationInfo(principalCollection, ticket);
            } else {
                return null;
            }

        } catch (TicketValidationException e) {
            throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e);
        }

    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Principal principal = (Principal) getAvailablePrincipal(principals);
        // 获取当前已登录的用户
        if (!Global.TRUE.equals(Global.getConfig("user.multiAccountLogin"))){
            Collection<Session> sessions = getSystemService().getSessionDao().getActiveSessions(true, principal, UserUtils.getSession());
            if (sessions.size() > 0){
                // 如果是登录进来的,则踢出已在线用户
                if (UserUtils.getSubject().isAuthenticated()){
                    for (Session session : sessions){
                        getSystemService().getSessionDao().delete(session);
                    }
                }
                // 记住我进来的,并且当前用户已登录,则退出当前用户提示信息。
                else{
                    UserUtils.getSubject().logout();
                    throw new AuthenticationException("msg:账号已在其它地方登录,请重新登录。");
                }
            }
        }
        User user = getSystemService().getUserByLoginName(principal.getLoginName());
        if (user != null) {
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            List<Menu> list = UserUtils.getMenuList();
            for (Menu menu : list){
                if (StringUtils.isNotBlank(menu.getPermission())){
                    // 添加基于Permission的权限信息
                    for (String permission : StringUtils.split(menu.getPermission(),",")){
                        info.addStringPermission(permission);
                    }
                }
            }
            // 添加用户权限
            info.addStringPermission("user");
            // 添加用户角色信息
            for (Role role : user.getRoleList()){
                info.addRole(role.getEnname());
            }
            // 更新登录IP和时间
            getSystemService().updateUserLoginInfo(user);
            // 记录登录日志
            LogUtils.saveLog(Servlets.getRequest(), "系统登录");
            return info;
        } else {
            return null;
        }
    }

    /**
     * 获取系统业务对象
     */
    public SystemService getSystemService() {
        if (systemService == null){
            systemService = SpringContextHolder.getBean(SystemService.class);
        }
        return systemService;
    }

}

修改spring-context-shiro.xml配置文件

原来的:

<property name="loginUrl" value="${adminPath}/login" />

改为:

<property name="loginUrl" value="${cas.server.url}?service=${cas.project.url}${adminPath}/cas" />

其实就是把注释放开

新增配置bean : casRealm

<bean id="casRealm" class="com.thinkgem.jeesite.modules.sys.security.MyCasRealm">
        <property name="casServerUrlPrefix" value="${cas.server.url}"/>
        <!-- 客户端的回调地址设置,必须和下面的shiro-cas过滤器拦截的地址一致 -->
        <property name="casService" value="${cas.project.url}${adminPath}/cas"/>
</bean>

修改Shiro安全管理配置的realm属性

原来Shiro安全管理配置的realm属性:

<property name="realm" ref="systemAuthorizingRealm" />

改为:<property name="realm" ref="casRealm" />


Jeesite其它

其他的基本没有什么修改的,要改的话就是:jeesite.properties里面的cas.project.url和cas.server.url

Cas通过查询数据库验证用户名、密码正确性(密码非复杂加密,可如:MD5,SHA-1等)

jar包准备

MySQL jdbc驱动:mysql-connector-Java-5.1.13-bin.jar
Cas jdbc支持:cas-server-support-jdbc-4.0.0.jar

编辑:WEB-INF\deployerConfigContext.xml,加入数据源:

Cas服务端二次开发

把cas-server-webapp-4.0.0.war转换成Eclipse项目

自定义登录验证(加密规则)

复制jeesite的3个java文件

作用是在Cas服务端实现jeesite的密码加密方式

jar包

开发自己的MyQueryDatabaseAuthenticationHandler.java


package com.jinfonet.developer.portal;

import java.security.GeneralSecurityException;

import org.jasig.cas.adaptors.jdbc.AbstractJdbcUsernamePasswordAuthenticationHandler;
import org.jasig.cas.authentication.HandlerResult;
import org.jasig.cas.authentication.PreventedException;
import org.jasig.cas.authentication.UsernamePasswordCredential;
import org.jasig.cas.authentication.principal.SimplePrincipal;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.IncorrectResultSizeDataAccessException;

import com.thinkgem.jeesite.common.security.Digests;
import com.thinkgem.jeesite.common.utils.Encodes;

import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.FailedLoginException;
import javax.validation.constraints.NotNull;

/**
 * 20170309gch
 */
public class MyQueryDatabaseAuthenticationHandler extends AbstractJdbcUsernamePasswordAuthenticationHandler {
    public static final String HASH_ALGORITHM = "SHA-1";
    public static final int HASH_INTERATIONS = 1024;
    public static final int SALT_SIZE = 8;
    @NotNull
    private String sql;

    /** {@inheritDoc} */
    @Override
    protected final HandlerResult authenticateUsernamePasswordInternal(final UsernamePasswordCredential credential)
            throws GeneralSecurityException, PreventedException {

        final String username = credential.getUsername();
        final String encryptedPassword = this.getPasswordEncoder().encode(credential.getPassword());
        try {
            final String dbPassword = getJdbcTemplate().queryForObject(this.sql, String.class, username);

//          String plain = Encodes.unescapeHtml(credential.getPassword());
//          byte[] salt = Digests.generateSalt(SALT_SIZE);
//          byte[] hashPassword = Digests.sha1(plain.getBytes(), salt, HASH_INTERATIONS);
//          String s=Encodes.encodeHex(salt)+Encodes.encodeHex(hashPassword);
//          boolean ss= dbPassword.equals(s);

            String plainPassword="123456";
            plainPassword=credential.getPassword();
            String password="9ec51dc31c730f4b6a719842d1c5a6d2034e653f4fbdafa45ed60104";
            password=dbPassword;
            String plain1 = Encodes.unescapeHtml(plainPassword);
            byte[] salt1 = Encodes.decodeHex(password.substring(0,16));
            byte[] hashPassword1 = Digests.sha1(plain1.getBytes(), salt1, HASH_INTERATIONS);
            boolean ss1= password.equals(Encodes.encodeHex(salt1)+Encodes.encodeHex(hashPassword1));
            if (!ss1) {
//            if (!dbPassword.equals(encryptedPassword)) {
                throw new FailedLoginException("Password does not match value on record.");
            }
        } catch (final IncorrectResultSizeDataAccessException e) {
            if (e.getActualSize() == 0) {
                throw new AccountNotFoundException(username + " not found with SQL query");
            } else {
                throw new FailedLoginException("Multiple records found for " + username);
            }
        } catch (final DataAccessException e) {
            throw new PreventedException("SQL exception while executing query for " + username, e);
        }
        return createHandlerResult(credential, new SimplePrincipal(username), null);
    }

    /**
     * @param sql The sql to set.
     */
    public void setSql(final String sql) {
        this.sql = sql;
    }
}

修改deployerConfigContext.xml文件

至此完成Jeesite单点登录集成Cas和自定义登录验证


jeesite加密研究

如果直接给密码散列,黑客可以通过查散列值字典(例如MD5密码破解网站),得到某用户的密码。加上salt后就会难上很多,即便是你获得了其中的salt和最终密文,破解也是相当麻烦的。


盐(Salt),在密码学中,是指通过在密码任意固定位置插入特定的字符串,让散列后的结果和使用原始密码的散列结果不相符,这种过程称之为“加盐”。
安全因素
通常情况下,当字段经过散列处理(如MD5),会生成一段散列值,而散列后的值一般是无法通过特定算法得到原始字段的。但是某些情况,比如一个大型的彩虹表,通过在表中搜索该MD5值,很有可能在极短的时间内找到该散列值对应的真实字段内容。

加盐后的散列值,可以极大的降低由于用户数据被盗而带来的密码泄漏风险,即使通过彩虹表寻找到了散列后的数值所对应的原始内容,但是由于经过了加盐,插入的字符串扰乱了真正的密码,使得获得真实密码的概率大大降低。

实现原理
加盐的实现过程通常是在需要散列的字段的特定位置增加特定的字符,打乱原始的字符串,使其生成的散列结果产生变化。比如,用户使用了一个密码:

x7faqgjw
经过MD5散列后,可以得出结果:

455e0e5c2bc109deae749e7ce0cdd397
但是由于用户密码位数不足,短密码的散列结果很容易被彩虹表破解,因此,在用户的密码末尾添加特定字符串(粗体下划线为加盐的字段):

x7faqgjwabcdefghijklmnopqrstuvwxyz

因此,加盐后的密码位数更长了,散列的结果也发生了变化:

4a1690d5eb6c126ef68606dda68c2f79
以上就是加盐过程的简单描述,在实际使用过程中,还需要通过特定位数插入、倒序或多种方法对原始密码进行固定的加盐处理,使得散列的结果更加不容易被破解或轻易得到原始密码,比如(绿色字体为加盐字符串):

其它方式实现单点登录

使用Cookie解决单点登录 技术点:
1、设置Cookie的路径为setPath(“/”) .即Tomcat的目录下都有效
2、设置Cookie的域setDomain(“.gch.com”);即bbs.gch.com,或是mail.gch.com有效。即跨域。
3、设置Cookie的时间。即使用户不选择在几天内自动登录,也应该保存Cookie以保存在当前浏览器没有关闭的情况下有效。
4、使用Filter自动登录。

网上查询到的,未测试,个人觉得可行。

Similar Posts:

  • [置顶] 单点登录之CAS SSO从入门到精通(第三天)

    开场白 各位新年好,上海的新年好冷,冷到我手发抖. 做好准备全身心投入到新的学习和工作中去了吗?因为今天开始的教程很"变态"啊,我们要完成下面几件事: 自定义CAS SSO登录界面 在CAS SSO登录界面增加我们自定义的登录用元素 使用LDAP带出登录用户在LDAP内存储的更多的信息 实现CAS SSO支持多租户登录的功能 好,开始正文! 正文 上次我们说到了CAS SSO的一些基本用法如:连数据库怎么用,连LDAP怎么用,这次我们要来讲一个网上几乎没有人去过多涉及到的一个问题即:在

  • java单点登录系统CAS的简单使用

    转:http://blog.csdn.net/yunye114105/article/details/7997041 背景 有几个相对独立的java的web应用系统, 各自有自己的登陆验证功能,用户在使用不同的系统的时候,需要登陆不同的系统.现在需要提供一个统一的登陆/登出界面, 而不修改各个系统原来的登陆验证机制.于是采用单点登录系统CAS. 使用步骤 要使用单点登录,需要部署CAS系统, CAS服务端可以直接部署在tomcat下运行, 对于CAS服务端来说,所有要集成单点登录的web应用都是

  • 单点登录系统CAS搭建及取得更多用户信息的实现

    一. 单点登录简介 单点登录(Single sign-on,简称为 SSO),是目前比较流行的企业业务整合的解决方案之一.其简单定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统.类似的,用户只需要执行一次退出操作就可以终止对所有相关应用系统的访问. 本文主旨在介绍如何使用CAS实现单点登录时取得更多的用户信息,单点登录的原理将不作阐述. 二. CAS简介 CAS是CentralAuthentication Service即中央认证服务的简称,它是由耶鲁大学发起的开源项目

  • FineReport和泛微OA(Ecology)的单点登录集成方案

    最近出现了很多关于帆软报表和泛微OA的集成问题,均出现在"单点登录"上.直接也有相关的文章介绍一些FineReport和泛微集成的背景.价值等,以及FineReport和OA的深度集成的方案,但是并没有提到单点登录的集成方案,今天就简单介绍下FineReport和OA单点登录集成方式. 同步用户信息 单点登录的基础,就是用户信息的同步,FineReport的决策系统中有一个很强大的功能,可以自动帮助用户实现用户信息同步.具体菜单:决策系统-管理系统-用户管理.点击用户管理菜单后,右侧页

  • 使用单点登录(sso cas )整合dz论坛

    自己的网站使用单点登录(cas)进行控制,现在想要增加一个discuz论坛也用单点登录进行控制,为了进行整合dz费尽心思,把dz的登录流程研究了一遍才算大致明白了怎么去做,因为dz有自己的cookie和session机制,不是用的php的原生的,所以这点也着实让我头疼了好久,现在终于搞定了.具体实现的代码如下: $this->casLogin(); /* * 单点登录的调用,成功后返回用户名,然后再设置dz的用户登录状态 */ public function casLogin(){ requir

  • Spring Security集成Cas后页面跳转问题

    问题描述:在集成cas后,如果在A应用里面直接调用B应用的某个页面,第一次点击的时候总是会跳转到B应用设置的默认页面,然后再点击的时候,才能跳转到正确的页面. 后来通过查看源码,发现 类:org.springframework.security.web.authentication.AbstractAuthenticationTargetUrlRequestHandler protected String determineTargetUrl(HttpServletRequest request

  • myeclipse、tomcat集成CAS

    Myeclipse6.0.1 下Tomcat集成CAS (说明:附件中有此文档的图片版,写博客时图片没上传上来) 1. 安装JDK 安装jdk1.5到D:\Program Files\jdk1.5 2.安装Tomcat6.0.20 安装tomcat6.0.20到D:\Program Files\Tomcat 6.0,其中注意jre路径选择jdk目录下的: 3.安装myeclipse6.0 1)安装myeclipse6.0到D:\Program Files\MyEclipse 6.0 2)打开安装

  • discuz(dz) SSO(单点,同歩,异步)登录 --转

    原文地址:http://fc-lamp.blog.163.com/blog/static/1745666872012762520123/ discuz(dz) SSO(单点,同歩,异步)登录 一般流程: 1 发起请求: 1 )登录请求到 localhost/member.php 初始化一些设置,然后调用source/module/member/member_logging.php(37行). 2)在member_logging.php里实例化登录控制类(new logging_ctl 20行),

  • 整合Spring Security自定义登录的简便方法

    安全开源框架这块咱们基本上没什么选择,Spring Security是最常用的了,虽然它功能强大,但是还是过于复杂了.虽然3.0提供了auto config,但本质上它的复杂度没有降低,只是简化了一下配置而已.我始终认为它应该弄一个简化版,不要集成那么多的认证支撑,让人一看就懂的那种,个人意见哈. 我们最常用的基于用户名和密码的认证,SS提供了UsernamePasswordAuthenticationFilter,要求我们通过POST提交j_username/j_password两个参数来完成

  • 【cas】基础模式登录流程详解

    在前几篇博客中通过对bbs和news两个系统进行单点登录的入门介绍. 这里就登录这两个系统的流程画一个流程图来解析他们的登录过程. 一.两次登录bbs流程 二.第一次登录news流程 三.解析 我们每次在登录系统时,主要判断的是登录的浏览器访问的地址中有没有带着cas分发的ST, 如果有,需要去cas服务器进行验证一下看看是不是cas服务器签发的ST,如果没有,直接转发到cas去登录或者获取ST. 对于cas来说,主要看访问的地址中有没有cookie,如果有并且有效,直接签发ST,如果没有,需要

Tags: