Shiro登录源码分析

 登录过程源码分析

具体配置不在这里重复了,直接粘主要的ShiroFilterFactoryBean:

[java]  view plain  copy
  1. @Bean(name="shiroFilter")  
  2. public ShiroFilterFactoryBean shiroFilterFactoryBean()  
  3. {  
  4.     ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();  
  5.     shiroFilterFactoryBean.setSecurityManager(securityManager());  
  6.     shiroFilterFactoryBean.setLoginUrl("/login");  
  7.     shiroFilterFactoryBean.setUnauthorizedUrl("/404");  
  8.     Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();  
  9.     filters.put("authc", formAuthenticationFilter());  
  10.     Map<String, String> definitionMap = shiroFilterFactoryBean.getFilterChainDefinitionMap();  
  11.     definitionMap.put("/test""anon");  
  12.     definitionMap.put("/404""anon");  
  13.     definitionMap.put("/order/**""authc");  
  14.     definitionMap.put("/login""authc");  
  15.     return shiroFilterFactoryBean;  
  16. }          

 1-1            

关于definitionMap中的value,应该经常见到authc,shiro内部中的key=authc对应的Filter就是FormAuthenticationFilter,这里只是为了讲述,因而重复定义:

    当我们第一次访问受限资源的时候:shiro会调用onAccessDenied方法:

[java]  view plain  copy
  1. protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {  
[java]  view plain  copy
  1.         //1.首先判断是否是login请求(既是否点击了登录这个按钮)  
  2.         if (isLoginRequest(request, response)) {  
  3.             if (isLoginSubmission(request, response)) {  
  4.                 if (log.isTraceEnabled()) {  
  5.                     log.trace("Login submission detected.  Attempting to execute login.");  
  6.                 }  
  7.                 return executeLogin(request, response);  
  8.             } else {  
  9.                 if (log.isTraceEnabled()) {  
  10.                     log.trace("Login page view.");  
  11.                 }  
  12.                 //allow them to see the login page ;)  
  13.                 return true;  
  14.             }  
  15.         } else {  
[java]  view plain  copy
  1.             //如果不是的话,则跳转到登录页面,并且返回false,提示用户尚未登录(accessDenied)  
  2.             if (log.isTraceEnabled()) {  
  3.                 log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +  
  4.                         "Authentication url [" + getLoginUrl() + "]");  
  5.             }  
  6.             saveRequestAndRedirectToLogin(request, response);  
  7.             return false;  
  8.         }  
  9.     }                     

1-2

这个结果会一层一层往上传,shiro有一堆filter链

到了登录页面之后,注意这里的action要为""哦,当然设置其他的也行,不过就需要在shiroFilterFactoryBean中多添加一个definitionMap了,

理由如下:

    当我们输入好了需要提交的username和password之后,点击登录之后,因为action是""所以这个表单会提交到之前请求的url上,既(/login),因为我们已经在上述配置中配置了这个url请求拦截的filter,所以又会如1-2图所示,进入filter,然后会进行是否是Login请求判断:

[java]  view plain  copy
  1. protected boolean isLoginRequest(ServletRequest request, ServletResponse response) {  
  2.         return pathsMatch(getLoginUrl(), request);  
  3.     }  
[java]  view plain  copy
  1. protected boolean pathsMatch(String path, ServletRequest request) {  
  2.       String requestURI = getPathWithinApplication(request);  
  3.       log.trace("Attempting to match pattern '{}' with current requestURI '{}'...", path, requestURI);  
  4.       return pathsMatch(path, requestURI);  
  5.   }  

pathMatch是PatternMatcher的实现类,他有两个实现类:通配符matcher:AntPathMatcher以及正则实现类:RegExPatternMatcher   具体实现不在这里论述

是登录请求之后,call executeLogin :

[java]  view plain  copy
  1. protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {  
  2.       AuthenticationToken token = createToken(request, response);  
  3.       if (token == null) {  
  4.           String msg = "createToken method implementation returned null. A valid non-null AuthenticationToken " +  
  5.                   "must be created in order to execute a login attempt.";  
  6.           throw new IllegalStateException(msg);  
  7.       }  
  8.       try {  
  9.           Subject subject = getSubject(request, response);  
  10.           subject.login(token);  
  11.           return onLoginSuccess(token, subject, request, response);  
  12.       } catch (AuthenticationException e) {  
  13.           return onLoginFailure(token, e, request, response);  
  14.       }  
  15.   }  

    首先先生成token:FormAuthenticationFilter 的createToken的最终方法是返回UsernamePasswordToken

[java]  view plain  copy
  1. protected AuthenticationToken createToken(String username, String password,  
  2.                                           boolean rememberMe, String host) {  
  3.     return new UsernamePasswordToken(username, password, rememberMe, host);  
  4. }  

然后获取subject(用户,或者第三方),call login 

subject会将任务委托给SecurityManager:

[java]  view plain  copy
  1. public void login(AuthenticationToken token) throws AuthenticationException {  
  2.        clearRunAsIdentitiesInternal();  //如字面翻译即可  
  3.        Subject subject = securityManager.login(this, token);  
  4.   
  5.        PrincipalCollection principals;  
  6.   
  7.        String host = null;  
  8.   
  9.        if (subject instanceof DelegatingSubject) {  
  10.            DelegatingSubject delegating = (DelegatingSubject) subject;  
  11.            //we have to do this in case there are assumed identities - we don't want to lose the 'real' principals:  
  12.            principals = delegating.principals;  
  13.            host = delegating.host;  
  14.        } else {  
  15.            principals = subject.getPrincipals();  
  16.        }  
  17.   
  18.        if (principals == null || principals.isEmpty()) {  
  19.            String msg = "Principals returned from securityManager.login( token ) returned a null or " +  
  20.                    "empty value.  This value must be non null and populated with one or more elements.";  
  21.            throw new IllegalStateException(msg);  
  22.        }  
  23.        this.principals = principals;  
  24.        this.authenticated = true;  
  25.        if (token instanceof HostAuthenticationToken) {  
  26.            host = ((HostAuthenticationToken) token).getHost();  
  27.        }  
  28.        if (host != null) {  
  29.            this.host = host;  
  30.        }  
  31.        Session session = subject.getSession(false);  
  32.        if (session != null) {  
  33.            this.session = decorate(session);  
  34.        } else {  
  35.            this.session = null;  
  36.        }  
  37.    }  

SecurityManager又会将任务委托给旗下的Realms:

[java]  view plain  copy
  1. public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {  
  2.        AuthenticationInfo info;  
  3.        try {  
  4.            info = authenticate(token); //将任务委托给旗下的Realms   
  5.        } catch (AuthenticationException ae) {  
  6.            try {  
  7.                onFailedLogin(token, ae, subject);  
  8.            } catch (Exception e) {  
  9.                if (log.isInfoEnabled()) {  
  10.                    log.info("onFailedLogin method threw an " +  
  11.                            "exception.  Logging and propagating original AuthenticationException.", e);  
  12.                }  
  13.            }  
  14.            throw ae; //propagate  
  15.        }  
  16.   
  17.        Subject loggedIn = createSubject(token, info, subject);  
  18.   
  19.        onSuccessfulLogin(token, info, loggedIn);  
  20.   
  21.        return loggedIn;  
  22.    }  

Realm又是如何处理的呢:

    在我的配置中SecurityManager只配置了一个Realm,have a look:

[java]  view plain  copy
  1. public class MyLoginRealm extends AuthorizingRealm  
  2. {  
[java]  view plain  copy
  1.         //这个方法最好用上缓冲,不然每次鉴权都会去call this function  
  2.     @Override  
  3.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)  
  4.     {  
  5.         System.out.println("调用了MyLoginRealm的授权方法");  
  6.         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();  
  7.         authorizationInfo.addRole("admin");  
  8.         authorizationInfo.addStringPermission("user:update");  
  9.         return authorizationInfo;  
  10.     }  
  11.     @Override  
  12.     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException  
  13.     {  
  14.         System.out.println("调用了realm的登录验证的方法");  
  15.         return new SimpleAuthenticationInfo(token.getPrincipal(), token.getCredentials(), this.getName());  
  16.     }  
  17. }  

doGetAuthenticationinfo顾名思义,身份验证,返回Autheneticationinfo的子类,这个子类会被提交到上一层AuthenticatingRealm

[java]  view plain  copy
  1. public final AuthenticationInfo getAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {  
  2.   
  3.         AuthenticationInfo info = getCachedAuthenticationInfo(token);  
  4.         if (info == null) {  
  5.             //otherwise not cached, perform the lookup:  
  6.             info = doGetAuthenticationInfo(token);//获取Realm返回的结果  
  7.             log.debug("Looked up AuthenticationInfo [{}] from doGetAuthenticationInfo", info);  
  8.             if (token != null && info != null) {  
  9.                 cacheAuthenticationInfoIfPossible(token, info);//尝试将信息缓存(配置中开启这个开关)  
  10.             }  
  11.         } else {  
  12.             log.debug("Using cached authentication info [{}] to perform credentials matching.", info);  
  13.         }  
  14.   
  15.         if (info != null) {  
  16.             assertCredentialsMatch(token, info);//进行最终校验  
  17.         } else {  
  18.             log.debug("No AuthenticationInfo found for submitted AuthenticationToken [{}].  Returning null.", token);  
  19.         }  
  20.   
  21.         return info;//返回给上层  
  22.     }  

将结果返回给上层:ModularRealmAuthenticator,,这个Autenticator会通过Realm的个数选择不同的执行方式

[java]  view plain  copy
  1. protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {  
  2.        if (!realm.supports(token)) {  
  3.            String msg = "Realm [" + realm + "] does not support authentication token [" +  
  4.                    token + "].  Please ensure that the appropriate Realm implementation is " +  
  5.                    "configured correctly or that the realm accepts AuthenticationTokens of this type.";  
  6.            throw new UnsupportedTokenException(msg);  
  7.        }  
  8.        AuthenticationInfo info = realm.getAuthenticationInfo(token);//在这里返回结果  
  9.        if (info == null) {  
  10.            String msg = "Realm [" + realm + "] was unable to find account data for the " +  
  11.                    "submitted AuthenticationToken [" + token + "].";  
  12.            throw new UnknownAccountException(msg);  
  13.        }  
  14.        return info;  
  15.    }  
[java]  view plain  copy
  1. protected AuthenticationInfo doAuthenticate(AuthenticationToken authenticationToken) throws AuthenticationException {  
  2.      assertRealmsConfigured();  
  3.      Collection<Realm> realms = getRealms();  
  4.      if (realms.size() == 1) {  
  5.          return doSingleRealmAuthentication(realms.iterator().next(), authenticationToken); //因为只有1个Realm,所以执行的是这个方法  
  6.      } else {  
  7.          return doMultiRealmAuthentication(realms, authenticationToken);  
  8.      }  
  9.  }  

之后又将结果传给上层:AbstractAuthenticator 

[java]  view plain  copy
  1. public final AuthenticationInfo authenticate(AuthenticationToken token) throws AuthenticationException {  
  2.   
  3.         if (token == null) {  
  4.             throw new IllegalArgumentException("Method argument (authentication token) cannot be null.");  
  5.         }  
  6.   
  7.         log.trace("Authentication attempt received for token [{}]", token);  
  8.   
  9.         AuthenticationInfo info;  
  10.         try {  
  11.             info = doAuthenticate(token);  
  12.             if (info == null) {  
  13.                 String msg = "No account information found for authentication token [" + token + "] by this " +  
  14.                         "Authenticator instance.  Please check that it is configured correctly.";  
  15.                 throw new AuthenticationException(msg);  
  16.             }  
  17.         } catch (Throwable t) {  
  18.             AuthenticationException ae = null;  
  19.             if (t instanceof AuthenticationException) {  
  20.                 ae = (AuthenticationException) t;  
  21.             }  
  22.             if (ae == null) {  
  23.                 //Exception thrown was not an expected AuthenticationException.  Therefore it is probably a little more  
  24.                 //severe or unexpected.  So, wrap in an AuthenticationException, log to warn, and propagate:  
  25.                 String msg = "Authentication failed for token submission [" + token + "].  Possible unexpected " +  
  26.                         "error? (Typical or expected login exceptions should extend from AuthenticationException).";  
  27.                 ae = new AuthenticationException(msg, t);  
  28.                 if (log.isWarnEnabled())  
  29.                     log.warn(msg, t);  
  30.             }  
  31.             try {  
  32.                 notifyFailure(token, ae);  
  33.             } catch (Throwable t2) {  
  34.                 if (log.isWarnEnabled()) {  
  35.                     String msg = "Unable to send notification for failed authentication attempt - listener error?.  " +  
  36.                             "Please check your AuthenticationListener implementation(s).  Logging sending exception " +  
  37.                             "and propagating original AuthenticationException instead...";  
  38.                     log.warn(msg, t2);  
  39.                 }  
  40.             }  
  41.   
  42.   
  43.             throw ae;  
  44.         }  
  45.   
  46.         log.debug("Authentication successful for token [{}].  Returned account [{}]", token, info);  
  47.   
  48.         notifySuccess(token, info); //如果配置了listener,则循环执行所有的相关listener  
  49.   
  50.         return info;  
  51.     }  

传传传  

至于如何自定义呢:记住Shiro的核心是SecurityManager,因而要自定义基本离不开他

这里引用下开涛哥的讲解:Authorizer ,自定义的时候参考ModularRealmAuthorizer 




PS:在这里延伸一点:我们可以自行配置Shiro从哪个表单属性中作为username或者password:如下所示,这样我们的表单页面中用户的账户对应的name属性更改为test_username 密码则改为test_password

[java]  view plain  copy
  1.         @Bean  
  2.     public FormAuthenticationFilter formAuthenticationFilter()  
  3.     {  
  4.         FormAuthenticationFilter filter = new FormAuthenticationFilter();  
  5.         filter.setLoginUrl("/login");  
  6.         filter.setUsernameParam("test_username");  
  7.         filter.setPasswordParam("test_password");  
  8.         return filter;  
  9.     }  
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值