问题背景
在实现多Realm时,扩展了ModularRealmAuthenticator 和 UsernamePasswordToken,于是在MyAuthenticationToken token = (MyAuthenticationToken) authenticationToken时出现了转型异常。
扩展ModularRealmAuthenticator 的代码如下:
public class MyModularRealmAuthenticator extends ModularRealmAuthenticator { @Override public AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { MyAuthenticationToken token = (MyAuthenticationToken) authenticationToken; String loginType = token.getLoginType(); Collection<Realm> realms = getRealms(); Collection<Realm> authRealms = new ArrayList<>(); for(Realm realm : realms){ if(realm.getName().equals(loginType)){ authRealms.add(realm); } } if(authRealms.size() == 1){ return doSingleRealmAuthentication(authRealms.iterator().next(), token); } else { return doMultiRealmAuthentication(authRealms, token); } } }
扩展UsernamePasswordToken的代码如下:
public class MyAuthenticationToken extends UsernamePasswordToken { private String loginType; public MyAuthenticationToken(final String username, final String password, String loginType){ super(username, password); this.loginType = loginType; } public String getLoginType() { return loginType; } public void setLoginType(String loginType) { this.loginType = loginType; } }
从上面的扩展中我们看到,没有地方使用我们的自己扩展的MyAuthenticationToken类,只是转型而已,并没有去实例化它,这才是引起问题的根源,问题发现了要如何解决它呢?我们在配置的使用并并未制定表单过滤器,此时默认使用的是FormAuthenticationFilter,而Token默认使用的也是UsernamePasswordToken,由此看来,我们扩展的MyAuthenticationToken就成了摆设,此时就要想办法如何将我们的扩展引用到程序里去呢?
既然默认的是UsernamePasswordToken,那么是如何将UsernamePasswordToken注入的呢?我们进入默认的Filter(FormAuthenticationFilter)可以看到createToken,这个是创建Token的,那么似乎可以和我们的Token搭上边了,FormAuthenticationFilter中的createToken如下:
protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) { String username = this.getUsername(request); String password = this.getPassword(request); return this.createToken(username, password, request, response); }
我们发现期间又调用了createToken,此createToken为父类的方法,我们可以看到FormAuthenticationFilter的声明为 public class FormAuthenticationFilter extends AuthenticatingFilter ,也即内部调用的token为AuthenticatingFilter的实现,继续看AuthenticatingFilter中的createToken实现,而createToken中又调用了自身createToken的重载,源码如下:
protected AuthenticationToken createToken(String username, String password, ServletRequest request, ServletResponse response) { boolean rememberMe = this.isRememberMe(request); String host = this.getHost(request); return this.createToken(username, password, rememberMe, host); } protected AuthenticationToken createToken(String username, String password, boolean rememberMe, String host) { return new UsernamePasswordToken(username, password, rememberMe, host); }
可以看到在重载的createToken中,UsernamePasswordToken终于现身了,那么此时,我们可以通过扩展FormAuthenticationFilter并重新createToken将我们扩展的Token引入即可,以下为扩展的FormAuthenticationFilter:
public class MyFormAuthenticationFilter extends FormAuthenticationFilter { @Override protected MyAuthenticationToken createToken(ServletRequest request, ServletResponse response) { String username = getUsername(request); String password = getPassword(request); String loginType = request.getParameter("loginType"); if("sys".equals(loginType)){ return new MyAuthenticationToken(username, password, "sys"); } else { return new MyAuthenticationToken(username, password, "wx"); } } }
扩展完成后,需要配置默认的过滤器为我们的扩展,shiro的配置如下:
<bean >
<property name="securityManager" ref="securityManager" />
<property name="loginUrl" value="/login" />
<property name="successUrl" value="/index"/>
<property name="unauthorizedUrl" value="/403"/>
<property name="filterChainDefinitions">
<value>
/favicon.ico = anon
/logout = logout
/** = authc
</value>
</property>
<property name="filters">
<map>
<entry key="authc" value-ref="myFormAuthenticationFilter" />
</map>
</property>
</bean>
<bean />