NutzCN Logo
问答 shiro 不管登录成功还是失败都出现这个bug
发布于 2829天前 作者 javanan 29146 次浏览 复制 上一个帖子 下一个帖子
标签:

org.apache.shiro.authc.UnknownAccountException: Realm [com.coracle.fast.service.impl.shiro.ShiroRealm@3ef2de86] was unable to find account data for the submitted AuthenticationToken [org.apache.shiro.authc.UsernamePasswordToken - null, rememberMe=false (127.0.0.1)]. at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doSingleRealmAuthentication(ModularRealmAuthenticator.java:184) at org.apache.shiro.authc.pam.ModularRealmAuthenticator.doAuthenticate(ModularRealmAuthenticator.java:267) at org.apache.shiro.authc.AbstractAuthenticator.authenticate(AbstractAuthenticator.java:198) at org.apache.shiro.mgt.AuthenticatingSecurityManager.authenticate(AuthenticatingSecurityManager.java:106) at org.apache.shiro.mgt.DefaultSecurityManager.login(DefaultSecurityManager.java:270) at org.apache.shiro.subject.support.DelegatingSubject.login(DelegatingSubject.java:256) at org.apache.shiro.web.filter.authc.AuthenticatingFilter.executeLogin(AuthenticatingFilter.java:53) at org.apache.shiro.web.filter.authc.FormAuthenticationFilter.onAccessDenied(FormAuthenticationFilter.java:154) at org.apache.shiro.web.filter.AccessControlFilter.onAccessDenied(AccessControlFilter.java:133) at org.apache.shiro.web.filter.AccessControlFilter.onPreHandle(AccessControlFilter.java:162) at org.apache.shiro.web.filter.PathMatchingFilter.isFilterChainContinued(PathMatchingFilter.java:203) at org.apache.shiro.web.filter.PathMatchingFilter.preHandle(PathMatchingFilter.java:178) at org.apache.shiro.web.servlet.AdviceFilter.doFilterInternal(AdviceFilter.java:131) at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125) at org.apache.shiro.web.servlet.ProxiedFilterChain.doFilter(ProxiedFilterChain.java:66) at org.apache.shiro.web.servlet.AbstractShiroFilter.executeChain(AbstractShiroFilter.java:449) at org.apache.shiro.web.servlet.AbstractShiroFilter$1.call(AbstractShiroFilter.java:365) at org.apache.shiro.subject.support.SubjectCallable.doCall(SubjectCallable.java:90) at org.apache.shiro.subject.support.SubjectCallable.call(SubjectCallable.java:83) at org.apache.shiro.subject.support.DelegatingSubject.execute(DelegatingSubject.java:383) at org.apache.shiro.web.servlet.AbstractShiroFilter.doFilterInternal(AbstractShiroFilter.java:362) at org.apache.shiro.web.servlet.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:125) at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344) at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261) at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241) at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208) at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220) at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122) at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:505) at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169) at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103) at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:956) at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116) at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:436) at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1078) at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:625) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.doRun(AprEndpoint.java:2517) at org.apache.tomcat.util.net.AprEndpoint$SocketProcessor.run(AprEndpoint.java:2506) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) at java.lang.Thread.run(Thread.java:744)
28 回复

贴realm代码,全部

来自炫酷的 NutzCN

@wendal

package com.coracle.fast.service.impl.shiro;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.coracle.fast.entity.shiro.Permission;
import com.coracle.fast.entity.shiro.Role;
import com.coracle.fast.entity.shiro.User;
import com.coracle.fast.service.UserService;

@Service("shiroRealm")
@Transactional(readOnly = true)
public class ShiroRealm extends AuthorizingRealm {

	@Autowired
	private UserService userService;

	/**
	 * 权限认证
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

		if (principalCollection == null) {
			throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
		}

		// 获取登录时输入的用户名
		String loginName = (String) principalCollection.fromRealm(getName()).iterator().next();
		// 到数据库查是否有此对象
		User user = userService.findByUserName(loginName);
		if (null == user) {
			return null;
		}

		if (user.isLocked()) {
			throw new LockedAccountException("Account [" + user.getUserName() + "] is locked.");
		}

		// 权限信息对象info,用来存放查出的用户的所有的角色(role)及权限(permission)
		SimpleAuthorizationInfo auth = new SimpleAuthorizationInfo();

		if (user.getRoles() != null) {

			for (Role role : user.getRoles()) {
				auth.addRole(role.getName());
				if (role.getPermissions() != null) {
					for (Permission p : role.getPermissions()) {
						auth.addStringPermission(p.getName());
					}
				}
			}
		}
		if (user.getPermissions() != null) {
			for (Permission p : user.getPermissions()) {
				auth.addStringPermission(p.getName());
			}
		}
		return auth;

	}

	/**
	 * 登录认证;
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
			throws AuthenticationException {
		// UsernamePasswordToken对象用来存放提交的登录信息
		UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
		// 查出是否有此用户
		User user = userService.findByUserName(token.getUsername());
		if (user != null) {
			// 若存在,将此用户存放到登录认证info中
			// 用户名 密码 slat realm name
			return new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(),
					ByteSource.Util.bytes(user.getSalt()), getName());
		}
		return null;
	}

}

不能返回null

来自炫酷的 NutzCN

@wendal 不反null 返回什么 ?
并且登录成功 也不是返回null

throw new UnknownAccountException();

debug 了一个 发现执行顺序是这样的
/**
* 认证回调函数,登录时调用.
* 登录认证;
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
// UsernamePasswordToken对象用来存放提交的登录信息
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
// 查出是否有此用户
User user = userService.findByUserName(token.getUsername());
if (user != null) {
// 若存在,将此用户存放到登录认证info中
// 用户名 密码 slat realm name
return new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(),
ByteSource.Util.bytes(user.getSalt()), getName());
}else{
throw new UnknownAccountException();
}

}


1、执行 User user = userService.findByUserName(token.getUsername());
发现 user 是 null 
2、 throw new UnknownAccountException();
然后没跳出方法,反而 有执行了 一边 User user = userService.findByUserName(token.getUsername());
这个时候 发现  user 不是null  
但是有由于密码错误
抛出  IncorrectCredentialsException

这样就导致了 不管登录成功还是失败  都出现了 上面的 bug

没跳出方法? 抛异常还能没跳出方法???

是的 ,
第一次 User user = userService.findByUserName(token.getUsername()); user 是null (数据库有数据)
就执行了
throw new UnknownAccountException();

这时候 程序又执行了 一遍 User user = userService.findByUserName(token.getUsername()); user 这时候不是null 了

登录成功后 doGetAuthorizationInfo 方法也没执行

第一次为null, 这必须先查清楚

User user = userService.findByUserName(token.getUsername()); // user 是null (数据库有数据)

打印 token.getUsername()的值出来

log.debug("name=["+token.getUsername()+"]");

发现是 点击 submit后
没有到controller,现在执行了
User user = userService.findByUserName(token.getUsername()); // user 是null (数据库有数据)
这时候 getUsername 是 null

抛出异常,
执行了 controller
执行 User user = userService.findByUserName(token.getUsername());
这个时候 getUsername 是 页面的 admin
密码错误,跳出。

这是同一个req? 那就检查第一个token是哪里冒出的

通一个 req
仅仅点击了 一次 提交

怎么检查 这个token呢,?

发现 第一个 进来的时候 password 是 页面的值 但是 username是null

然后又进来了一个 这时候 username和password 都是页面的值

去debug这个token类的构造方法,看它是哪里创建的,创建了多少次

@wendal 在没有进入 controller之前 在 FormAuthenticationFilter 里的 excutelogin 产生的 token

@javanan 那另外一个呢?

来自炫酷的 NutzCN

@wendal 另一个是我 页面请求 form表单 产生的

@javanan 我晕,我是构造出token的代码

来自炫酷的 NutzCN

不是总共构建了2个token对象吗?我要知道这两个对象是哪里的java代码new出来的

不过,基本上确定是shiro.ini 重复拦截了,需要把第一个token干掉

来自炫酷的 NutzCN

@wendal
第一个 token

public abstract class AuthenticatingFilter extends AuthenticationFilter {
    public static final String PERMISSIVE = "permissive";

    //TODO - complete JavaDoc

    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        AuthenticationToken token = createToken(request, response);
        if (token == null) {
            String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +
                    "must be created in order to execute a login attempt.";
            throw new IllegalStateException(msg);
        }
        try {
            Subject subject = getSubject(request, response);
            subject.login(token);
            return onLoginSuccess(token, subject, request, response);
        } catch (AuthenticationException e) {
            return onLoginFailure(token, e, request, response);
        }
    }

这里 AuthenticationToken token = createToken(request, response);
产出来的
这个 过滤 AuthenticationFilter
不知哪里出来的

@javanan shiro.ini里面配置的咯,贴

那另外一个token呢?

来自炫酷的 NutzCN

@wendal

这个类的
public class ModularRealmAuthenticator extends AbstractAuthenticator
这里

    protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
        if (!realm.supports(token)) {
            String msg = "Realm [" + realm + "] does not support authentication token [" +
                    token + "].  Please ensure that the appropriate Realm implementation is " +
                    "configured correctly or that the realm accepts AuthenticationTokens of this type.";
            throw new UnsupportedTokenException(msg);
        }
        AuthenticationInfo info = realm.getAuthenticationInfo(token);
        if (info == null) {
            String msg = "Realm [" + realm + "] was unable to find account data for the " +
                    "submitted AuthenticationToken [" + token + "].";
            throw new UnknownAccountException(msg);
        }
        return info;
    }

@javanan 哪里?我没看到任何new字样

来自炫酷的 NutzCN

@wendal
在自己的
controller
里 传过去的
SecurityUtils.getSubject().login(new UsernamePasswordToken(user.getUserName(), user.getPassword()));

登录操作是控制器里面主动login,但shiro filter又做了匹配

所以,现在要解决的就是把第一个token干掉,也就是shiro.ini里面的匹配去掉

来自炫酷的 NutzCN

@wendal
哦 原来是这样,, 那么直接用 shiro的 登录就好了。。

添加回复
请先登陆
回到顶部