背景
目前开发接到需求如下,希望根据不同用户实现自定义域名登录(前台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)
问题
- 解决多域名登录跳转
- 解决登录后用户强制退出跳转页面
解决方案
扩展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(此处如果需要可以动态,比如所有用户分配二级域名,然后用户自动重定向到二级域名登录)