UNIAUTH README.MD

<a id='about'/>

一. 关于UNIAUTH

1.1.uniauth干什么的

它是一个统一登录+鉴权+权限管理的综合系统;它适用于各系统需要通过统一认证/授权,并能根据既定的角色和权限来规范业务操作的多子系统环境。

1.2.uniauth技术选型

基于jdk7,使用spring mvc4.2+mybatis3+spring security4+cas+cxf作为基础框架,数据库为mysql5.7,配置管理使用zookeeper3.4redis 作为可选项用于保存ticket和session信息,前端使用angularJS 1.x

<a id='quickstart'/>

二. 快速开始

2.1.编译打包

在源码目录执行命令

gradle warUniAuthServer、 gradle warCas、 gradle warTechopsWebsite, 

分别可以在uniauth-server,cas,techops项目的build路径下得到uniauth.warcas.wartechops.war

2.2.初始化数据库

在mysql中create database uniauth,然后按rt顺序执行uniauth-server/src/script/sql 中所有的脚本;
管理员账号:shuanggui.fan@dianrong.com,密码: $1234qweR

2.3.配置tomcat7

  • 添加环境参数
    修改tomcat bin目录中catalina.sh文件,在正文首行后加入下面环境参数:
JAVA_OPTS="$JAVA_OPTS -Dcatalina.logs=/var/lib/tomcat/logs"  # log output
DR_CFG_ZOOKEEPER_ENV_URL="10.8.12.85:2181,10.8.12.85:2182,10.8.12.85:2183"  # zookeeper cluster address
export DR_CFG_ZOOKEEPER_ENV_URL 
  • 修改端口
    将上面tomcat复制粘贴三份,分别叫做 tomcat_uniauth、 tomcat_cas、 tomcat_techops,分别修改server.xml中监听端口,假定分别是:8080,8081,8082,
    注意:同时修改其他shutdown,ajp端口
  • uniauth添加jndi数据源
    在tomcat_uniauth的context.xml
<Resource name="jdbc/uniauth" auth="Container" type="javax.sql.DataSource" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/uniauth?useUnicode=true&characterEncoding=utf8" username="root" password="root" maxActive="50" maxIdle="20" maxWait="200" />

2.4.配置zookeeper

在zokeeper中create下面节点数据

/com/dianrong/cfg/1.0.0/uniauth/cas_server.iscookiesecure false   // 线上环境一定是true
/com/dianrong/cfg/1.0.0/uniauth/cas_server http://localhost:8081/cas // cas服务器的部署base地址
/com/dianrong/cfg/1.0.0/uniauth/domains.techops http://localhost:8082/techops // techops的部署base地址
/com/dianrong/cfg/1.0.0/uniauth/uniauth_ws_endpoint http://localhost:8080/uniauth/ws/rs  // uniauth的webservice endpoint地址, base+/ws/rs

zookeeper的配置根路径为/com/dianrong/cfg/1.0.0, 该默认路径可通过在catalina.sh中 export DR_CFG_ZOOKEEPER_BASE_PATH变量来更改

2.5.部署启动

按下面顺序启动

  • 1.启动tomcat_uniauth
    uniauth.war部署到tomcat_uniauth 的webapps目录,启动tomcat
  • 2.启动tomcat_cas
    cas.war部署到tomcat_cas 的webapps目录,启动tomcat
  • 3.启动tomcat_techops
    techops.war部署到tomcat_techops 的webapps目录,启动tomcat

访问管理控制台techops
<a id='architecture' />

三. 系统结构

3.1. 模块组件依赖

uniauth模块依赖图.png

如上图,uniauth的核心模块:uniauth-servercommonshare-rwss-client,cas,techops

  • 三个独立应用:

(1)uniauth-server是提供数据访问的REST服务;用户,资源,权限数据通过它进行访问;
(2)techops是web界面的管理控制台;通过techops录入资源,角色,做权限分配;
(3)cas是单点登录系统;各个subsystem通过cas做sso认证;

  • 三个关键模块:

(4)common定义了uniauth-server的接口,接口的数据模型以及cxf方式的访问实现;由它定义了数据接口,因此各个模块必须依赖它;
(5)share-rw提供对uniauth-server功能的访问接口定义 ,cxf方式访问实现,由于牵涉到对数据的修改,因此只有管理系统才能依赖这个模块,如techops
(6)ss-client是uniauth提供的sdk,各子系统依赖它后只需要少量的配置就可以完成sso,authentication 和 authorization的业务
(7)subsystem代表各个子系统,也是uniauth的客户端,各个需要用到sso,authentication和authorization的系统,比如techops

3.2.系统架构

组件调用关系

上图中分两条线

  1. step 1-7, include sso and authentication

Between step 7 and 8,the uniauth client send request to cas server for authentication user with st (get from step 6) then the client can get credentials(* include username *) from response data 。

  1. step 8 , include authorization

After got credentials, uniauth client call uniauth-server to get user details and all permissions.

3.3. 数据结构

data-model.png

如上图,数据模型涵盖了通用的数据结构:user,role,group,user-group,user-role,group-group,group-role,permission,role-permission,另外还有跨系统的domain

<a id='configuration'/>

四. 参数配置

配置都是在zookeeper中添加节点数据

4.1. 核心配置

(1) cas本身的配置

  /com/dianrong/cfg/1.0.0/uniauth/cas_server.iscookiesecure true #是否采用https的形式写cookie,生产为true
  /com/dianrong/cfg/1.0.0/uniauth/cas_server     http://localhost:8081/cas  #cas的地址 。比如:http://localhost:8080/cas
  /com/dianrong/cfg/1.0.0/uniauth/cas.st_use_times  2  #配置cas的service ticket可验证的次数,现在默认是2次。其实正常情况下只验证一次是最安全的。
  /com/dianrong/cfg/1.0.0/uniauth/tenancyIdentity.check.switch   true #配置true 或 false。 用于指定是否强制的验证访问uniauth-server请求是否带有租户的验证信息
  /com/dianrong/cfg/1.0.0/uniauth/uniauth_ws_endpoint    http://localhost:8090/uniauth/ws/rs #配置的uniauth-server的地址。比如:http://localhost:8090/uniauth/ws/rs

( 2 ) 邮箱配置
邮箱主要是为了验证码发送,密码重置

   /com/dianrong/cfg/1.0.0/uniauth/internal.mail.smtp.host    邮箱服务器host,比如:smtp-dev.sl.com(默认值)
   /com/dianrong/cfg/1.0.0/uniauth/internal.mail.smtp.port    邮箱服务器port,比如:[2](http://smtp-dev.sl.com/)5(默认值)
   /com/dianrong/cfg/1.0.0/uniauth/internal.mail.smtp.femail   uniauth系统邮件的发送者,比如:TechOps-Notification<noreply@[dianrong.com](http://dianrong.com/)>(默认值)

4.2. redis配置

 a 普通模式
  /com/dianrong/cfg/1.0.0/uniauth/redis.host  localhost    #cas采用的redis的host
  /com/dianrong/cfg/1.0.0/uniauth/redis.port   6379        #redis的port
 b 哨兵模式
  /com/dianrong/cfg/1.0.0/uniauth/redis.database 0     #redis的数据库index
  /com/dianrong/cfg/1.0.0/uniauth/cas.iscluster true   #true or false  cas是否采用redis存储登陆的ticket
  /com/dianrong/cfg/1.0.0/uniauth/redis.password ''    #指定redis密码
  /com/dianrong/cfg/1.0.0/uniauth/redis.master  mymaster  #指定master节点name
  /com/dianrong/cfg/1.0.0/uniauth/redis.sentinels  10.18.19.51:5000,10.18.19.101:5000    #指定 redis集群节点列表,例如:10.18.19.67:5000,10.18.19.51:5000,10.18.19.101:5000

4.3. 集成系统配置

下面的xxx就是定义好的domain,如techops,crm,pms,cms,etc

  /com/dianrong/cfg/1.0.0/uniauth/domains.xxx    http://localhost:8100/techops/  #xxx系统的地址。比如:http://localhost:8100/techops/
  /com/dianrong/cfg/1.0.0/uniauth/domainx.xxx.loginPage  http://localhost:8100/techops/login.jsp #如果xxx系统采用自定义登陆页面,则此处配置其自定义登陆页面地址。
  /com/dianrong/cfg/1.0.0/uniauth/domainx.xxx.auth_fail_url    http://localhost:8100/techops/login_fail.jsp  #如果xxx系统在进行st认证失败的时候跳转到失败页面(需要客户端集成做一些配置)。
  /com/dianrong/cfg/1.0.0/uniauth/domains.xxx.showInHomePage true  #明确指定是否在uniauth登陆页能跳转到xxx系统的登陆页(是否出现选项框中),例如:true

<a id='integration'/>

五. 系统接入指南

integration.png

上图是各业务系统集成uniauth图,uniatuh客户端以spring系统为例

5.1.Spring系统

客户端系统需要使用spring security,引入ss-client模块的jar包
下面以techops项目为例

    1. web.xml
<!-- spring session publisher -->
    <listener> 
      <listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class> 
    </listener>
    <!-- In generally  Spring Security Filter order first before other filters  -->
    <filter>
      <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    <filter-mapping>
      <filter-name>springSecurityFilterChain</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>
    1. applicationContext.xml
      <font color=#ff0000 size=3> 不要把下面这一段配置放到SpringMVC对应的xml当中,必须放置到Spring Bean的xml当中 </font>
    <import resource="classpath:ss-uniauth-client.xml" /> //导入ss-client中的Spring配置文件
    <bean id="domainDefine" class="com.dianrong.common.uniauth.common.client.DomainDefine">      
      <property name="domainCode" value="techops">
      </property> //定义自身域编码,和Uniauth的zookper(有domains.前缀),TechOps中的域定义保持一致      
<!-- <property name="userInfoClass" value="com.dianrong.common.techops.sscustom.TechOpsUserExtInfo"></property> --> 
//定义UserDetails扩展属性类,该类从UserExtInfo扩展,如果不需要则可以不配置
      //如果设置成true,则如果当前请求的url没有找到任何匹配的url定义,就会拒绝访问
      //如果设置成true,需要去掉<sec:intercept-url pattern="/**" access="isAuthenticated()"/>,因为这条定义是涵盖任何url的默认定义
      <property name="rejectPublicInvocations" value="false"></property>
      //在访问业务域的某个url时,如果session失效或从未登录过,这个url地址会被业务域服务器记下来,然后转向到cas登录页;
      //登录成功后会再转向回来到这个url,如果需要复写这个url,则在此处定制,如果不做定制默认就是转向先前记下的url地址
      <property name="customiedSavedRequestUrl" value="/jump/jump.html"></property>
    </bean>
    <sec:http pattern="/views/**" security="none" /> //某些url资源不需要被Spring Security保护
    <sec:http entry-point-ref="casAuthEntryPoint" use-expressions="true" request-matcher="ant">      
    <sec:custom-filter ref="casAuthenticationFilter" position="CAS_FILTER"/>
    <sec:custom-filter after="EXCEPTION_TRANSLATION_FILTER" ref="exceptionTranslationFilter" />
    <sec:custom-filter ref="singleLogoutFilter" before="CAS_FILTER" />      
    <sec:custom-filter  ref="requestSingleLogoutFilter" before="LOGOUT_FILTER" />
    <sec:access-denied-handler error-page="/errors/403.jsp"/> //随需定制自己的403 Forbidden页      
    <sec:csrf disabled="true"/>      
    <sec:custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />      
    <sec:session-management session-authentication-strategy-ref="sas" invalid-session-url="#{uniauthConfig['cas_server']}/logout?dupsession=true"/>      
    <sec:logout delete-cookies="JSESSIONID" />      
  <!--         在此处可以添加业务系统常用的稳定的intercept-url定义      -->
       // 重要:如果你的系统基于url_pattern进行权限判断,应尽可能地将需要保护的url进行分解和登记,
       //否则就会退而求其次地选用这条规则,因为这条定义是涵盖任何url的默认定义,即登录用户能访问所有url。      
    <sec:intercept-url pattern="/**" access="isAuthenticated()"/>    
</sec:http>
 <bean id="exceptionTranslationFilter" class="com.dianrong.common.uniauth.client.custom.SSExceptionTranslationFilter"> 
<constructor-arg name="authenticationEntryPoint" ref="casAuthEntryPoint"></constructor-arg>
     <property name="accessDeniedHandler"> 
        <bean class="org.springframework.security.web.access.AccessDeniedHandlerImpl">       
          <property name="errorPage" value="/errors/403.jsp" /> 
        </bean> 
</property> 
</bean>
    <!-- Session并发控制: start -->    
<bean id="concurrencyFilter" class="org.springframework.security.web.session.ConcurrentSessionFilter">   
    <constructor-arg name="sessionRegistry" ref="sessionRegistry">
    </constructor-arg>      
    <constructor-arg name="expiredUrl" value="#{uniauthConfig['cas_server']}/logout?dupsession=true">
    </constructor-arg>    
</bean>   
 <bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />    
<bean id="sas" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy">     
     <constructor-arg>        
        <list>          
            <bean class="org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy">           
             <constructor-arg ref="sessionRegistry"/>          
            </bean>          
            <bean class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">            
              <constructor-arg ref="sessionRegistry"/>           
                 <property name="maximumSessions" value="1" />            
                <property name="exceptionIfMaximumExceeded" value="false" />          
            </bean>        
        </list>      
      </constructor-arg>    
</bean>    
<bean id="casAuthenticationFilter" class="org.springframework.security.cas.web.CasAuthenticationFilter">     
    <property name="authenticationManager" ref="authenticationManager"/>    
    <property name="filterProcessesUrl" value="/login/cas"></property>    
    <property name="authenticationSuccessHandler" ref="ssAuthenticationSuccessHandler"></property>    
    <property name="sessionAuthenticationStrategy" ref="sas" />    </bean>   
 <!-- Session并发控制: end -->    
<!--
    注册PermissionEvaluator实现,以便支持hasPermission表达式,如果不使用则不用配置
    注意:ID必须为uniauthPermissionEvaluator,且primary="true"
     -->    
<bean id="uniauthPermissionEvaluator" class="com.dianrong.common.techops.sscustom.TechOpsPermissionEvaluator" primary="true"></bean>
  • 3)获取当前登录用户
    可以通过调用UniClientCommonService这个Spring Bean类的getLoginUserInfo()方法获取当前登录用户对象
 SecurityContextHolder.getContext().getAuthentication().getPrincipal();

5.2.非Spring系统

5.2.1 与CAS集成

    1. 引入Cas jar包
      jar包:'org.jasig.cas.client:cas-client-core:3.4.1'
    1. 添加filter
      说明:需要添加三个filter,SingleSignOutFilter(统一登出filter), AuthenticationFilter(统一认证) ,Cas20ProxyReceivingTicketValidationFilter(ticket验证filter)
      使用方式举例(以jetty内置服务器为例,其他tomcat容器的配置类似)
// first
FilterHolder singleSignOutFilter = new FilterHolder(SingleSignOutFilter.class);
singleSignOutFilter.setInitParameter("casServerUrlPrefix", "http://localhost:8080/cas"); // 配置cas的统一地址cas_server
root.addFilter(singleSignOutFilter, "/*", Handler.DEFAULT);
// second
FilterHolder authenticationFilter = new FilterHolder(AuthenticationFilter.class);
authenticationFilter.setInitParameter("casServerLoginUrl", "http://localhost:8080/cas/login");// 配置cas的登陆页面: cas_server/login
authenticationFilter.setInitParameter("gateway", Boolean.FALSE.toString());// 默认采用false
authenticationFilter.setInitParameter("service", "http://localhost:8120/login/cas"); // 配置业务系统的地址:custom_url/login/cas.  后缀/login/cas 必须要,为了与统一登陆页面兼容。
root.addFilter(authenticationFilter, "/*", Handler.DEFAULT);
// third
FilterHolder ticketValidationFilter = new FilterHolder(Cas20ProxyReceivingTicketValidationFilter.class);
ticketValidationFilter.setInitParameter("service", "http://localhost:8120/login/cas"); // 配置业务系统的地址:custom_url/login/cas.  后缀/login/cas 必须要,为了与统一登陆页面兼容。如: https://passport-dev.dianrong.com
ticketValidationFilter.setInitParameter("casServerUrlPrefix", "http://localhost:8080/cas");//  配置cas的统一地址cas_server
root.addFilter(ticketValidationFilter, "/*", Handler.DEFAULT);

注意:可以注意到添加filter的顺序,SingleSignOutFilter -> AuthenticationFilter -> ticketValidationFilter

5.2.2经过上面的Filter后,可从session中 获取登陆人Principal

Assertion assertion = (Assertion)session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION);
AttributePrincipal principal = assertion.getPrincipal(); // principal中有登陆人的相关信息,邮箱,service等。

说明:其中CONST_CAS_ASSERTION为字符串“const_cas_assertion”,可以直接使用AbstractCasFilter.CONST_CAS_ASSERTION获取。

5.2.3登陆用户信息获取

    1. 首先通过第2.3步得到的AttributePrincipal对象
    1. 获取principal的attribute属性,如:principal.getAttributes().get("tenancyid"),返回租户的id
    1. call uniauth的api获取用户信息:
// 构造登陆参数
LoginParam loginParam = new LoginParam();
// 用户
emailloginParam.setAccount(userName);
// 设置租户
idloginParam.setTenancyId(tenancyId);// 设置租户id(用于uniauth统计调用日志)
CxfHeaderHolder.TENANCYID.set(tenancyId);
Response<UserDetailDto> response = uniClientFacade.getUserResource().getUserDetailInfo(loginParam);// 清空租户
idCxfHeaderHolder.TENANCYID.set(null);

<a id='sdk'/>

六. SDK使用

common模块针对uniauth-server提供的Rest API进行了客户端封装,客户系统引入com.dianrong.common.uniauth.common.client.UniClientFacade即可获取人员,组,域,角色,权限等信息,具体参UniClientFacade看如下:

public static void main(String args[]) {
 //这个构造函数中的字符串是uniauth-server的webservice endpoint
    UniClientFacade uniClientFacade = new UniClientFacade("http://localhost:8080/ws/rs"); 
    System.out.println(uniClientFacade.getConfigResource().getAllCfgTypes().getData());
}

<a id='attention'/>

七. 系统说明

7.1 Uniauth内置的3种PermissionType说明

  1. URI_PATTERN

效果和Spring Security配置文件中的<sec:intercept-url pattern="/xxx" access="hasAnyRole('ROLE_XXX','ROLE_YYY','ROLE_ZZZ')" />一样,主要用于Web形式的URL资源定义。可用于保护真实URL访问地址,或者用于<sec:authorize url="/xxx">的标签中。
需要尤其指明的是,如果你的多个role共享了一个role_code,则使用URL_PATTERN可能会有影响。
注意:无论在Spring Security中还是在db中都可以定义url pattern的允许访问的http method,如GET,POST等等,如果不指定则该url默认支持所有类型的http method。比如在配置文件中:<sec:intercept-url pattern="/admin/**" access="hasRole('ROLE_ADMIN')" **method="GET"**/>在使用method定义时,这里面有一个注意点,就是Spring Security默认会优先比较请求的http method和定义中的是否匹配,如果不匹配则该条定义就直接被忽略掉了,哪怕url本身是匹配的。所以如果你要使用http method保护你的url,则应该把controller中需要受保护的url模式和可访问方法进行完整定义(在配置文件或db中),这样才能起到应有的作用。

  1. DOMAIN

TechOps使用的专有Permission Type,其他业务系统可以忽略此种类型的Permission。

  1. PRIVILEGE

各个业务系统可以根据自己的需要定义权限控制点列表,这些权限用于保护任何需要保护的资源,比如通过SpEL表达式principal.hasPrivilege('xxx')。注意:不像URI_PATTERN,这种权限类型不参与Spring Security的内部权限控制机制,适用于纯业务级别权限控制点的定义。

上述3种权限类型已经基本涵盖了大部分业务需要,如果不满足,可以和我们联系,双方讨论后可以依需添加新的权限类型。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,612评论 5 471
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,345评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,625评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,022评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,974评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,227评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,688评论 3 392
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,358评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,490评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,402评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,446评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,126评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,721评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,802评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,013评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,504评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,080评论 2 341

推荐阅读更多精彩内容