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;@Overridepublic 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 {@Autowiredprivate OrgGroupService orgGroupService;@Overridepublic 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;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {super.afterCompletion(request, response, handler, ex);WxbStatic.clearThreadLocal();}}
复制代码

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

Shiro实现多域名登录界面相关推荐

  1. apache shiro jar包_只需要6个步骤,springboot集成shiro,并完成登录

    小Hub领读: 导入jar包,配置yml参数,编写ShiroConfig定义DefaultWebSecurityManager,重写Realm,编写controller,编写页面,一气呵成.搞定,是个 ...

  2. androidstudio做登录界面_Vue-cli+Element-ui实现后台管理系统(二)实现后台登录功能...

    前言 接上文,本文主要讲解vue+element-ui后台管理系统的登录功能的实现,api接口这块如果对后端技术以及node的实现不太了解的情况下,可以写出假数据进行模拟操作~ 一.创建登录文件并配置 ...

  3. linux登录界面说明,Linux登录界面以及简单使用入门

    一.登录界面介绍 安装完操作系统以后,就进入到linux登录界面,linux默认登录界面可以分为:字符界面和图形界面: 图形界面如下所示: 字符界面如下所示: 字符登录界面的含义: CentOS re ...

  4. 实训|第三天Linux登录界面的修改以及Richard Stallman、自由软件运动

    在写博客之前我想说两点: 承认一个错误,昨天写的实训第二天,我把redhat6.7写成了Linux6.7,感谢热心人士的指出! 昨天写的文章名字太长了,今天改善,内容感觉表述不全,希望各位谅解! 官方 ...

  5. 【踩坑系列】 SpringBoot ,SpringSecurity,activiti 项目无法正常跳转到登录界面

    [踩坑系列] SpringBoot ,SpringSecurity,activiti 项目无法正常跳转到登录界面 前言 一直强制跳转到springSecurity 的默认的登录界面,无法正常跳转到自己 ...

  6. shiro配置多Realm登录

    需求背景 在做一个学习系统,接到需求要前后台用户通过不同的界面登录,前台用户不能通过他的账号登录后台,后台用户也不能用后台账号登录前台页面,前后台用户还有各自的权限级别划分.大概就这么个意思,这是一个 ...

  7. spring+shiro+cas实现单点登录,登出

    cas.war下载地址:https://download.csdn.net/download/qq_37160920/10662543 1.下载cas.war,放在tomcat的 webapps下发布 ...

  8. linux系统一直循环登录界面,Ubuntu 14.04解决登录界面无限循环的方法

    在Ubuntu下配置Android的环境时,想像在Windows中那样在终端中直接启动adb,以为Linux和Windows一样,将adb的路径添加到环境变量中,于是将adb的路径也export到/e ...

  9. Pretty Login便携版:Windows 7登录界面修改器

    Pretty Login是由chnable开发的一个美化小工具,用来辅助修改Widnows 7登陆界面的背景图片,除此之外,它也能定制欢迎界面上的文本.按钮样式,如设置阴影.半透明效果. 由于Wind ...

最新文章

  1. Numpy入门教程:练习作业01
  2. java 基础(匿名内部类)
  3. 算法------买卖股票的最佳时机
  4. mybatis查询如何返回ListMap类型数据
  5. java11正式发布了,让java代码更完美
  6. activemq生产者和消费者的双向通信
  7. java虚拟机可以处理_Java虚拟机对类加载的处理机制
  8. 【java】创建一个顶层框架类的对象
  9. Android ImageSwitcher 配合Picasso解决内存溢出(OOM)问题
  10. WinForm教程(一)App.config等配置文件
  11. WPS-批量把数字转换成文本格式
  12. C#读取windows注册表键值的代码
  13. 如何写好简历与迎接面试
  14. linux基础-命令
  15. excel 日期选择器_Excel日期选择器工具
  16. 为了牙齿美白,该不该给百天的小宝宝补充氟化物?
  17. 一个较为感人的升学故事
  18. linux配置pcie无线网卡,【Linux c】读写pcie配置空间(安装lib库)
  19. 【matlab】Matlab中产生正态分布随机数的函数normrnd
  20. 使用numpy实现李代数se(3)和SE(3)之间的指对数映射

热门文章

  1. 深度挖掘新闻营销带给企业的好处和优势
  2. 【《2021机器学习-李宏毅》学习笔记】
  3. 实战技法 - 短线操盘 (8)
  4. canvas 画线条
  5. 抖音seo源码,抖音短视频SEO,SEO系统源码搭建
  6. 遇见你是我最美的意外
  7. python代码画樱花主要特色,手机python代码画樱花
  8. 基于javaweb的房屋租赁系统(前端+后端)
  9. nrf51822代码流程(从main展开)
  10. 无爬虫团队,企业如何实现1000万级数据采集?