shiro登录机制及获取登录前地址,跳转到指定页面

本文详细探讨了Shiro的登录机制,包括Subject的登录流程,如何通过SecurityManager和Authenticator接口进行用户认证。此外,还阐述了Shiro如何记住登录前的地址,并在用户成功登录后跳转回原始页面,涉及session管理和WebUtils的使用。通过对源码的分析,加深了对Shiro操作的理解。

一、shiro的登录机制

登录操作一般是由用户去触发的,有用户名,密码,记住密码等(密码加密方式可根据自己需要)

 String username = request.getParameter("username");
 String password = request.getParameter("password");
 Boolean flag =false;
 if(null != rememberMe && !"".equals(String.valueOf(rememberMe))){
            flag=true;
        }
//添加用户认证信息
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(
      username,
      MD5Util.md5(password).toUpperCase());  
token.setRememberMe(flag);            

Subject的登录将委托给SecurityManager,SecurityManager的login方法实际上是产生了一个新的Subject,然后将相关属性赋予当前调用者Subject:这个过程shiro已经帮我们封装好了,我们只需要调用安全管理器就可以了

try{
            subject.login(token);//调用安全管理器,安全管理器调用Realm
            User user = (User) subject.getPrincipal();
            HttpSession session = request.getSession();
            session.setAttribute("currentUser",user);
            //明文密码的用户
            User user1 = userService.selectByUsername(username);
            user1.setPassword(password);
            session.setAttribute("originalUser",user1);
            SecurityUtils.getSubject().getSession().setTimeout(18000000);
            model.addAttribute("currentUser",user);
        }catch (UnknownAccountException e) {
            //用户名不存在,跳转到登录页面
            model.addAttribute("usernameerror","用户名不存在");
            return "login/login";
        }catch (IncorrectCredentialsException e) {
            // 密码错误,跳转到登录页面
            model.addAttribute("passwordeerror","密码错误");
            return "login/login";
        }

下面我们还是来看下shiro的源码,加深了解
当使用subject.login(token)方法时,会找到接口interface Subject的login方法,实际是进入了subject接口的实现类DelegatingSubject中

    public void login(AuthenticationToken token) throws AuthenticationException {
        this.clearRunAsIdentitiesInternal();
        Subject subject = this.securityManager.login(this, token);
        String host = null;
        PrincipalCollection principals;
        if (subject instanceof DelegatingSubject) {
            DelegatingSubject delegating = (DelegatingSubject)subject;
            principals = delegating.principals;
            host = delegating.host;
        } else {
            principals = subject.getPrincipals();
        }

        if (principals != null && !principals.isEmpty()) {
            this.principals = principals;
            this.authenticated = true;
            if (token instanceof HostAuthenticationToken) {
                host = ((HostAuthenticationToken)token).getHost();
            }

            if (host != null) {
                this.host = host;
            }

            Session session = subject.getSession(false);
            if (session != null) {
                this.session = this.decorate(session);
            } else {
                this.session = null;
            }

        } else {
            String msg = "Principals returned from securityManager.login( token ) returned a null or empty value.  This value must be non null and populated with one or more elements.";
            throw new IllegalStateException(msg);
        }
    }

通过断点追踪我们可以看到,当执行到Subject subject = this.securityManager.login(this, token)时,进入securityManager接口的实现类DefaultSecurityManager中

   public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo info;
        try {
            info = this.authenticate(token);
        } catch (AuthenticationException var7) {
            AuthenticationException ae = var7;

            try {
                this.onFailedLogin(token, ae, subject);
            } catch (Exception var6) {
                if (log.isInfoEnabled()) {
                    log.info("onFailedLogin method threw an exception.  Logging and propagating original AuthenticationException.", var6);
                }
            }

            throw var7;
        }

        Subject loggedIn = this.createSubject(token, info, subject);
        this.onSuccessfulLogin(token, info, loggedIn);
        return loggedIn;
    }

执行方法info = this.authenticate(token), 顶层实现了Authenticator 接口

   public AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        return this.authenticator.authenticate(token);
    }

在这里插入图片描述
Authenticator 接口以及实现类AbstractAuthenticator.class获取认证信息

public interface Authenticator {
    AuthenticationInfo authenticate(AuthenticationToken var1) throws AuthenticationException;
}


//Authenticator接口的实现类 AbstractAuthenticator
public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {
        if (token == null) {
            throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");
        } else {
            log.trace("Authentication attempt received for token [{}]", token);

            AuthenticationInfo info;
            try {
                info = this.doAuthenticate(token);
                if (info == null) {
                    String msg = "No account information found for authentication token [" + token + "] by this " + "Authenticator instance.  Please check that it is configured correctly.";
                    throw new AuthenticationException(msg);
                }
            } catch (Throwable var8) {
                AuthenticationException ae = null;
                if (var8 instanceof AuthenticationException) {
                    ae = (AuthenticationException)var8;
                }

                if (ae == null) {
                    String msg = "Authentication failed for token submission [" + token + "].  Possible unexpected " + "error? (Typical or expected login exceptions should extend from AuthenticationException).";
                    ae = new AuthenticationException(msg, var8);
                    if (log.isWarnEnabled()) {
                        log.warn(msg, var8);
                    }
                }

                try {
                    this.notifyFailure(token, ae);
                } catch (Throwable var7) {
                    if (log.isWarnEnabled()) {
                        String msg = "Unable to send notification for failed authentication attempt - listener error?.  Please check your AuthenticationListener implementation(s).  Logging sending exception and propagating original AuthenticationException instead...";
                        log.warn(msg, var7);
                    }
                }

                throw ae;
            }

            log.debug("Authentication successful for token [{}].  Returned account [{}]", token, info);
            this.notifySuccess(token, info);
            return info;
        }
    }

protected abstract AuthenticationInfo doAuthenticate(AuthenticationToken var1) throws AuthenticationException;

ModularRealmAuthenticator 继承AbstractAuthenticator,重写了方法doAuthenticate

 protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {
        this.assertRealmsConfigured();
        Collection<Realm> realms = this.getRealms();
        return realms.size() == 1 ? this.doSingleRealmAuthentication((Realm)realms.iterator().next(), authenticationToken) : this.doMultiRealmAuthentication(realms, authenticationToken);
    }


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);
        } else {
            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);
            } else {
                return info;
            }
        }
    }

AuthenticationInfo info = realm.getAuthenticationInfo(token)方法,追寻到Realm接口的实现类AuthenticatingRealm
在这里插入图片描述

public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        AuthenticationInfo info = this.getCachedAuthenticationInfo(token);
        if (info == null) {
            info = this.doGetAuthenticationInfo(token);
            log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);
            if (token != null && info != null) {
                this.cacheAuthenticationInfoIfPossible(token, info);
            }
        } else {
            log.debug("Using cached authentication info [{}] to perform credentials matching.", info);
        }

        if (info != null) {
            this.assertCredentialsMatch(token, info);
        } else {
            log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);
        }

        return info;
    }

使用shiro认证真正主要我们写的就是获取认证数据,MyShiroRealm extends AuthorizingRealm,重写doGetAuthenticationInfo方法

/**
     * 用户认证
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken myToken = (UsernamePasswordToken) authenticationToken;
        String username = myToken.getUsername();
        char[] password = myToken.getPassword();
        User user= userDao.selectByUsername(username);
        if(user == null){
            return null;
        }
        // 参数一:签名对象,认证通过后,可以在程序的任意位置获取当前放入的对象
        // 参数二:数据库中查询出的密码
        // 参数三:当前realm的类名
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,
                user.getPassword(), this.getClass().getName());
        return info;

shiro对用户信息的校验完成,即用户合法,不会进行拦截,就会按照我们编辑的逻辑跳转至页面,接上文章开始触发登录的代码

  // 失效时长2小时
 subject.getSession().setTimeout(7200000L);
  return "redirect:/index";

二、shiro记住登陆之前的地址

如果直接想到系统的某个页面,但是又没有登录,想要在输入用户名和密码之后到呢个页面而不是到配置的首页,那么就需要记住之前的地址。我们来看看shiro是怎么做的

shiro在跳转前会把跳转过来的页面链接保存到session的attribute中,key的值叫shiroSavedRequest,我们可以能过WebUtils类拿到。当用户登录成功后,可能通过String url = WebUtils.getSavedRequest(request).getRequestUrl();,拿到跳转到登录页面前的url,然后redirect到这个url。
来看看它的源码

   public static SavedRequest getSavedRequest(ServletRequest request) {
        SavedRequest savedRequest = null;
        Subject subject = SecurityUtils.getSubject();
        Session session = subject.getSession(false);
        if (session != null) {
            savedRequest = (SavedRequest)session.getAttribute("shiroSavedRequest");
        }

        return savedRequest;
    }

对于我们来说,只需要稍微修改返回首页的部分代码就可以达到这个目的

  // 失效时长2小时
        subject.getSession().setTimeout(7200000L);
        SavedRequest saveRequest = WebUtils.getSavedRequest(request);
        if (saveRequest == null || saveRequest.getRequestUrl() == null){
            return "redirect:/index";
        }else{
            String s = saveRequest.getRequestUrl().replaceAll("/GisqManagementCloudPlatformV1.1", "");
            if (s.contains("/login")||s.contains("logout")){
                return "redirect:/index";
            }
            return "redirect:" + s;
        }

三、写在最后

学海无涯,源码是枯燥无味的,要想弄透弄明白还需要大家自己去打断点跟踪,知其一也要知其二,大家一起进步。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值