第一次遇到这个问题,Shiro框架若配置了多个Realm,并不是将remlm添加到SecurityManager就完事,这样情况下多个realm在登录subject.login(token)时调用的还是同一个UsernamePasswordToken,这种情况下会出现两种权限的账号密码通用,所以必须自定义一个带登录权限的Token(UsernamePasswordUserTypeToken)
import org.apache.shiro.authc.UsernamePasswordToken;public class MyToken extends UsernamePasswordToken { private String loginType; public MyToken(String username, String password,String loginType){ super(username,password); this.loginType=loginType; } public String getLoginType() { return loginType; } public void setLoginType(String loginType) { this.loginType = loginType; }}
MyToken继承了UsernamePasswordToken,比父类多了loginType变量,接受登录账号的类型
另外,接收后的逻辑判断如下,默认会调用realm里面的doGetAuthenticationInfo方法进行身份验证,所以要继承并重写此方法,在用我们重写的方法替换掉原来代码的认证器!!!
package com.happ.config;import com.google.inject.internal.cglib.core.$Customizer;import org.apache.shiro.authc.AuthenticationException;import org.apache.shiro.authc.AuthenticationInfo;import org.apache.shiro.authc.AuthenticationToken;import org.apache.shiro.authc.Authenticator;import org.apache.shiro.authc.pam.ModularRealmAuthenticator;import org.apache.shiro.realm.Realm;import java.util.ArrayList;import java.util.Collection;public class MyAuthenticator extends ModularRealmAuthenticator { @Override protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException { // 判断realm是否为空 assertRealmsConfigured(); // 将authenticationToken强转为MyToken MyToken myToken=(MyToken) authenticationToken; // 获取myToken的登录类型 String loginType=myToken.getLoginType(); // Realms集合 Collection<Realm> realms=getRealms(); // 登录类型集 typeRealms Collection<Realm> typeRealms=new ArrayList<>(); for(Realm realm:realms){ if (realm.getName().contains(loginType)){ typeRealms.add(realm); } } if (typeRealms.size()==1){ // 长度为1 执行单一SingleRealmAuthentication return doSingleRealmAuthentication(typeRealms.iterator().next(), myToken); }else { // 长度不为1 执行多个MultiRealmAuthentication return doMultiRealmAuthentication(typeRealms,myToken); } }}
@Bean(name="authenticator")public Authenticator getAuthenticator(){ return new MyAuthenticator();}
@Bean(name = "securityManager")public DefaultWebSecurityManager getSecurityManager(@Qualifier("myRealm") MyRealm myRealm,@Qualifier("teacherRealm") TeacherRealm teacherRealm,@Qualifier("authenticator") Authenticator authenticator){ DefaultWebSecurityManager SecurityManager = new DefaultWebSecurityManager(); SecurityManager.setAuthenticator(authenticator); List realms=new ArrayList<>(); realms.add(myRealm); realms.add(teacherRealm); SecurityManager.setRealms(realms); return SecurityManager;}
一定要先配置认证器,再配置Realms集合,这样验证方法成功后会进入自己配置的认证器,登录接口之一:
@RequestMapping("/loginData")public String loginData(String num, String password, Model model){ // 获取当前 subject Subject subject = SecurityUtils.getSubject(); ByteSource credentialsSalt=ByteSource.Util.bytes(num); SimpleHash md5 = new SimpleHash("MD5", password, credentialsSalt, 1); password=md5.toString(); // 创建 UsernamePasswordToken 令牌 MyToken token = new MyToken(num,password, MyRealm.class.getName());// token.setRememberMe(true); Session session = subject.getSession(); session.setAttribute("username",num); try{ // 令牌登录 subject.login(token); model.addAttribute("login",num); // 登录成功返回欢迎页 return "Students/happy"; }catch (Exception ice) { model.addAttribute("msg","账号或密码不正确"); return "Students/login"; }}
通过反射动态获取到此接口绑定的Realm名,传入认证器后将传入的权限字段放入权限集合再进行判断,如果只有一个权限,执行单一认证,反之多重认证,账号混淆问题终于解决!!!