Shiro实现多域名登录界面


title: Shiro实现多域名登录界面 tags:

  • shiro
  • dns
  • 多域名
  • domain categories: shiro date: 2017-08-21 18:18:52

背景

目前开发接到需求如下,希望根据不同用户实现自定义域名登录(前台ui等需要略微区分,配色,皮肤等)

现状

目前系统中使用shiro作为授权权限框架,当用户没有登录时将会默认返回未授权页

比如

    <!-- 配置shiroFilter-->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="${wxb.url}"/>
        <property name="successUrl" value="/kzf6/page/index/index.jsp" />
        <property name="unauthorizedUrl" value="/kzf6/page/error/403.jsp" />
        <property name="filters">
            <map>
                <entry key="kickout" value-ref="kickoutSessionControlFilter"/>
            </map>
        </property>
        <property name="filterChainDefinitions">
            <value>
               
                /mlogin/login.json = anon
                <!-- 除了上面定义的url和资源,都需要配认证后才可以访问 -->
                /** = kickout,authc
            </value>
        </property>
    </bean>
复制代码

上述配置可以导致未登录用户来自动重定向到${wxb.url}

那么我们现在需要自定义多个(目前是已知url,可以在发布时配置死)

方案

为了尽量减少部署成本(因此可以将新的域名CNAME到原来域名或者A记录)

一、A记录、CNAME和URL区别

它们间区别如下:

  • A记录 —— 映射域名到一个或多个IP。
  • CNAME——映射域名到另一个域名(子域名)。
  • URL转发——重定向一个域名到另一个URL地址,使用HTTP 301状态码。

A记录、CNAME解析时都将先解析到IP地址。而URL则只是重定向转发。CNAME可以随意设,但URL转发在一些缺少网络自由的国家是被禁止的,因为URL转发还分显示和隐式,很容易造成误解。

注意,无论是A记录、CNAME、URL转发,在实际使用时是全部可以设置多条记录的。比如:

  • ftp.example.com A记录到 IP1,而mail.example.com则A记录到IP2
  • ftp.example.com CNAME到  ftp.abc.com,而mail.example.com则CNAME到mail.abc.com
  • ftp.example.com 转发到 ftp.abc.com,而mail.example.com则A记录到mail.abc.com

二、A记录、CNAME、URL适用范围

了解以上区别,在应用方面:

  • A记录——适应于独立主机、有固定IP地址
  • CNAME——适应于虚拟主机、变动IP地址主机
  • URL转发——适应于更换域名又不想抛弃老用户

因此实质上和原先服务器完全相同,唯一区别是用户访问到服务器上获取的servername发生了变化

由此我们可以根据servername来做用户的划分(提供多套ui)

问题

  1. 解决多域名登录跳转
  2. 解决登录后用户强制退出跳转页面

解决方案

扩展AuthFIlter

    package com.air.tqb.shiro.filter;
     
    import com.air.tqb.common.LoginDomain;
    import com.air.tqb.mapper.base.MenuMapper;
    import com.air.tqb.utils.WxbStatic;
    import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
     
    import java.util.Map;
     
    public class DomainAuthenticationFilter extends FormAuthenticationFilter {
        private Map<LoginDomain, String> loginUrlMap;
     
        @Override
        public String getLoginUrl() {
            LoginDomain domain = WxbStatic.getLoginDomain();
            if (domain == null || loginUrlMap.get(domain) == null) {
                return super.getLoginUrl();
            } else {
                return loginUrlMap.get(domain);
            }
        }
     
        public Map<LoginDomain, String> getLoginUrlMap() {
            return loginUrlMap;
        }
     
        public void setLoginUrlMap(Map<LoginDomain, String> loginUrlMap) {
            this.loginUrlMap = loginUrlMap;
        }
    }
复制代码

该接口作为第一层过滤未登录用户的filter

    public enum DefaultFilter {
        anon(AnonymousFilter.class),
        authc(FormAuthenticationFilter.class),
        authcBasic(BasicHttpAuthenticationFilter.class),
        logout(LogoutFilter.class),
        noSessionCreation(NoSessionCreationFilter.class),
        perms(PermissionsAuthorizationFilter.class),
        port(PortFilter.class),
        rest(HttpMethodPermissionFilter.class),
        roles(RolesAuthorizationFilter.class),
        ssl(SslFilter.class),
        user(UserFilter.class);
    }
复制代码

如上 可以看到authc,因此我们复写了之后需要覆盖默认的authc filter

shiro也很贴心的提供了可以覆盖的filters的map

比如

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="${wxb.url}"/>
        <property name="successUrl" value="/kzf6/page/index/index.jsp" />
        <property name="unauthorizedUrl" value="/kzf6/page/error/403.jsp" />
        <property name="filters">
            <map>
                <entry key="kickout" value-ref="kickoutSessionControlFilter"/>
                <entry key="authc" value-ref="domainAuthenticationFilter"/>
            </map>
        </property>
复制代码

此时authc就会被domainAuthenticationFilter 给覆盖

那么当出现用户未授权登录的时候将会根据条件返回指定的登录url(此处如果需要可以动态,比如所有用户分配二级域名,然后用户自动重定向到二级域名登录)

    package com.air.tqb.utils;
    
    
    import com.air.tqb.common.Channel;
    import com.air.tqb.common.LoginDomain;
    import sun.rmi.runtime.Log;
    
    import javax.servlet.http.HttpServletRequest;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Set;
    
    /**
     * Created by qixiaobo on 16/8/17.
     */
    public class WxbStatic {
        private static final ThreadLocal<List<String>> SQL_LIST_TL = new ThreadLocal<>();
        private static final ThreadLocal<String> IP_TL = new ThreadLocal<>();
        private static final ThreadLocal<String> USER_TL = new ThreadLocal<>();
        private static final ThreadLocal<String> ORG_TL = new ThreadLocal<>();
        private static final ThreadLocal<Set<String>> IDS_OWN_ORG_TL = new ThreadLocal<>();
        private static final ThreadLocal<Set<String>> PERMISSION_IDS_OWN_ORG_TL = new ThreadLocal<>();
        private static final ThreadLocal<String> DATASOURCE_ROUTING_TL = new ThreadLocal<>();
        private static final ThreadLocal<String> ACTION_TL = new ThreadLocal<>();
        private static final ThreadLocal<ActionType> TYPE_TL = new ThreadLocal<>();
        private static final ThreadLocal<String> CUSTOMER_FOR_SUPPLIER_TL = new ThreadLocal<>();
        private static final ThreadLocal<Channel> CHANNEL_TL = new ThreadLocal<>();
        private static final List<ThreadLocal> THREAD_LOCAL_LIST = new ArrayList<>();
        private static final ThreadLocal<Boolean> SECURITY_ENABLE_TL = new ThreadLocal<>();
        private static final ThreadLocal<LoginDomain> LOGIN_DOMAIN_TL=new ThreadLocal<>();
    
    
        static {
            THREAD_LOCAL_LIST.add(SQL_LIST_TL);
            THREAD_LOCAL_LIST.add(IP_TL);
            THREAD_LOCAL_LIST.add(USER_TL);
            THREAD_LOCAL_LIST.add(ORG_TL);
            THREAD_LOCAL_LIST.add(IDS_OWN_ORG_TL);
            THREAD_LOCAL_LIST.add(PERMISSION_IDS_OWN_ORG_TL);
            THREAD_LOCAL_LIST.add(DATASOURCE_ROUTING_TL);
            THREAD_LOCAL_LIST.add(ACTION_TL);
            THREAD_LOCAL_LIST.add(TYPE_TL);
            THREAD_LOCAL_LIST.add(CUSTOMER_FOR_SUPPLIER_TL);
            THREAD_LOCAL_LIST.add(CHANNEL_TL);
            THREAD_LOCAL_LIST.add(SECURITY_ENABLE_TL);
            THREAD_LOCAL_LIST.add(LOGIN_DOMAIN_TL);
        }
    
        public static List<String> getSqlList() {
            return SQL_LIST_TL.get();
        }
    
        public static void addSql(String sql) {
            if (SQL_LIST_TL.get() == null) {
                SQL_LIST_TL.set(new ArrayList<String>());
            }
            SQL_LIST_TL.get().add(sql);
        }
    
        public static void clearSql() {
            SQL_LIST_TL.remove();
        }
    
        public static String getIp() {
            return IP_TL.get();
        }
    
        public static void setIp(String ip) {
            IP_TL.set(ip);
        }
    
        public static void clearIp() {
            IP_TL.remove();
        }
    
        public static Set<String> getIdsOwnOrg() {
            return IDS_OWN_ORG_TL.get();
        }
    
        public static void setIdsOwnOrg(Set<String> IdsOwnOrg) {
            IDS_OWN_ORG_TL.set(IdsOwnOrg);
        }
    
        public static Set<String> getPermissionIdsOwnOrg() {
            return PERMISSION_IDS_OWN_ORG_TL.get();
        }
    
        public static void setPermissionIdsOwnOrg(Set<String> idsOwnOrg) {
            PERMISSION_IDS_OWN_ORG_TL.set(idsOwnOrg);
        }
    
        public static void clearIdsOwnOrg() {
            IDS_OWN_ORG_TL.remove();
        }
    
        public static String getUser() {
            return USER_TL.get();
        }
    
        public static void setUser(String user) {
            USER_TL.set(user);
        }
    
        public static void clearUser() {
            USER_TL.remove();
        }
    
        public static String getOrg() {
            return ORG_TL.get();
        }
    
        public static void setOrg(String org) {
            ORG_TL.set(org);
        }
    
        public static void clearOrg() {
            ORG_TL.remove();
        }
    
        public static String getDataSourceRouting() {
            return DATASOURCE_ROUTING_TL.get();
        }
    
        public static void setDataSourceRouting(String routingKey) {
            DATASOURCE_ROUTING_TL.set(routingKey);
        }
    
        public static void clearDataSourceRouting() {
            DATASOURCE_ROUTING_TL.remove();
        }
    
        public static String getAction() {
            return ACTION_TL.get();
        }
    
        public static void setAction(String action) {
            ACTION_TL.set(action);
        }
    
        public static ActionType getType() {
            return TYPE_TL.get();
        }
    
        public static void setChannel(Channel channel) {
            CHANNEL_TL.set(channel);
        }
    
        public static Channel getChannel() {
            return CHANNEL_TL.get();
        }
    
        public static void setType(ActionType type) {
            TYPE_TL.set(type);
        }
    
        public static void clearAction() {
            ACTION_TL.remove();
        }
    
        public static void clearType() {
            TYPE_TL.remove();
        }
    
        public static String getCustomerForSupplier() {
            return CUSTOMER_FOR_SUPPLIER_TL.get();
        }
    
        public static void setCustomerForSupplier(String idCustomer) {
            CUSTOMER_FOR_SUPPLIER_TL.set(idCustomer);
        }
    
        public static void clearCustomerForSupplier() {
            CUSTOMER_FOR_SUPPLIER_TL.remove();
        }
    
        public static void setSecurityEnable(boolean enable) {
            SECURITY_ENABLE_TL.set(enable);
        }
    
        public static void clearSecurityEnable() {
            SECURITY_ENABLE_TL.remove();
        }
    
        public static Boolean getSecurityEnable() {
            return SECURITY_ENABLE_TL.get();
        }
    
        public static LoginDomain getLoginDomain(){
            return LOGIN_DOMAIN_TL.get();
        }
    
        public static void setLoginDomain(LoginDomain domain){
             LOGIN_DOMAIN_TL.set(domain);
        }
    
        public static void clearThreadLocal() {
            for (ThreadLocal tl : THREAD_LOCAL_LIST) {
                if (tl != null) {
                    tl.remove();
                }
            }
        }
    
        public static String getRemoteIp(HttpServletRequest request) {
    
            String remoteAddr = request.getRemoteAddr();
            String x;
            if ((x = request.getHeader("X-Forwarded-For")) != null) {
                remoteAddr = x;
                int idx = remoteAddr.indexOf(',');
                if (idx > -1) {
                    remoteAddr = remoteAddr.substring(0, idx);
                }
            }
            return remoteAddr;
        }
    
        public enum ActionType {
            WEB, RMI, DUBBO
        }
    }
复制代码

    public class SpringMvcInterceptor extends HandlerInterceptorAdapter {
    
        @Autowired
        private OrgGroupService orgGroupService;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
                                 Object handler) throws Exception {
    
            String remoteIp = getRemoteIp(request);
            WxbStatic.setIp(remoteIp);
            HttpSession session = request.getSession();
            TbUser qUser = (TbUser) session.getAttribute("loginUser");
            WxbStatic.setType(WxbStatic.ActionType.WEB);
            WxbStatic.setAction(request.getRequestURI());
            if (qUser != null) {
                WxbStatic.setUser(qUser.getPkId());
                TbOrganization currentOrganization = (TbOrganization) session.getAttribute("organization");
                if (currentOrganization == null){
                    WxbStatic.setOrg(qUser.getIdOwnOrg());
                }
                else {
                    WxbStatic.setOrg(currentOrganization.getPkId());
                    WxbStatic.setCustomerForSupplier(currentOrganization.getIdCustomerCarzone());
                }
                WxbStatic.setIdsOwnOrg(cloneSet(orgGroupService.getTotalOrgIds(qUser.getIdOwnOrg())));
                List<TbOrganization> organizationList = (List<TbOrganization>) session.getAttribute("organizationList");
                Set<String> orgStrings = Sets.newHashSetWithExpectedSize(organizationList.size());
                for (TbOrganization organization : organizationList){
                    orgStrings.add(organization.getPkId());
                }
                WxbStatic.setPermissionIdsOwnOrg(orgStrings);
            }
            if (request.getServerName().toLowerCase().contains("***")) {
                WxbStatic.setLoginDomain(LoginDomain.XXX);
            } else {
                WxbStatic.setLoginDomain(LoginDomain.F6);
            }
            return true;
        }
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            super.afterCompletion(request, response, handler, ex);
            WxbStatic.clearThreadLocal();
        }
    }
复制代码

这样可以在ThreadLocal中设置指定的登录类型,后面所有的service就无需再次parse了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值