进入shiro,登录逻辑验证逻辑跑通

shiro-登录验证https://blog.csdn.net/pjx827480541/article/details/53908886

shiro实现登录验证,可以用它自身的方法来实现,也可以自定义方法来实现登录验证,了解了shiro的登录逻辑,实现自定义的验证逻辑就很简单

1、用shiro方法实现

shiro配置:

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" >  
        <property name="securityManager" ref="securityManager" />  
        <property name="loginUrl" value = "/login" />  
        <property name="successUrl" value = "/"  />  
        <property name="unauthorizedUrl" value = "/unauthorize"/>  
<property name="filterChainDefinitions">  
            <value>  
                /static/**=anon  
                /login=authc  
                /logout=logout  
                /unauthorize=authc  
                /**=user,perms  
            </value>  
        </property>  
</bean>

由于shiro默认注册了FormAuthenticationFilter,所以配置中可以不需要为此方法定义bean,但有个前提,登录页面中的登录账号和密码,记住我的name必须和FormAuthenticationFilter默认的名称一致,如下图

如果登录页面的name和FormAuthenticationFilter不一致,则需要自己为FormAuthenticationFilter进行配置


<bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">  
        <property name="usernameParam" value="name"/>  
        <property name="passwordParam" value="password1"/>  
        <property name="rememberMeParam" value="rememberMe1"/>  
        <property name="loginUrl" value="/login"/>  
        <property name="successUrl" value="/"/>  
</bean>  
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" >  
        <property name="securityManager" ref="securityManager" />  
        <property name="loginUrl" value = "/login" />  
        <property name="successUrl" value = "/"  />  
        <property name="unauthorizedUrl" value = "/unauthorize"/>  
<property name="filters">  
            <map>  
                <entry key="authc" value-ref="formAuthenticationFilter"/>  
            </map>  
        </property>  
<property name="filterChainDefinitions">  
            <value>  
                /static/**=anon  
                /login=authc  
                /logout=logout  
                /unauthorize=authc  
                /**=user,perms <  
            </value>  
        </property>  
</bean>  

登录页面提交后,跳转到 /login,进入登录方法,由于此路径权限设置为authc,shiro对该路径进行过滤,authc权限由FormAuthenticationFilter进行过滤。登录请求进入onAccessDenied方法

 
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {  
        if (isLoginRequest(request, response)) {  //判断是否是登录请求  
            if (isLoginSubmission(request, response)) { // 是否是http post请求  
                if (log.isTraceEnabled()) {  
                    log.trace("Login submission detected.  Attempting to execute login.");  
                }  
                return executeLogin(request, response);  
            } else {  
                if (log.isTraceEnabled()) {  
                    log.trace("Login page view.");  
                }  
                //allow them to see the login page ;)  
                return true;  
            }  
        } else {  
            if (log.isTraceEnabled()) {  
                log.trace("Attempting to access a path which requires authentication.  Forwarding to the " +  
                        "Authentication url [" + getLoginUrl() + "]");  
            }  
            saveRequestAndRedirectToLogin(request, response);  
            return false;  
        }  
}  

其中 executeLogin(request, response)方法的具体实现在继承的AuthenticatingFilter里

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);  
        }  
}  

剖析:createToken(request, response); 具体实现在子类FormAuthenticationFilter中

protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {  
    String username = getUsername(request);  
    String password = getPassword(request);  
    return createToken(username, password, request, response);  
}  

从上可以看出,具体的登录账号和密码从request中取出来,并创建了token对象,调用subject的login方法,login方法实现大致流程是用token去realm中取AuthenticationInfo对象,AuthenticationInfo对象存放的是正确的登录账号和密码,并和token中数据进行匹配,然后根据匹配情况返回相应的结果。

**如何进入Realm?–>**https://blog.csdn.net/long270022471/article/details/62423286

shiro源码分析之Realm调用过程
1、首先看使用shiro(1.3.0)框架要使用Realm的配置。

配置Spring.xml

<!-- 自定义Realm实现 -->
<bean id="shiroRealm" class="com.lcl.shiro.filter.realmManage"/>    
<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="shiroRealm"/>
        <property name="sessionManager" ref="sessionManager"/>
    <!-- <property name="cacheManager" ref="cacheManager"/> -->
 </bean>

此时,我们把自定义的realm配置在securityManager中,然后看登录时候代码:

 //得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证) 
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, pwd);
try {
    //登录,即身份验证  
    if (!subject.isAuthenticated()) {//判断时候已经登录
        subject.login(token);
    }
} catch (UnknownAccountException e) {
} catch (IncorrectCredentialsException e) {
} catch (ShiroException e) {
}
return oMap;

注:这段代码是创建Subject和UsernamePasswordToken对象,然后执行subject.login(token)方法,系统就自动调用realm中的登录验证。

下面我们看realm中的代码实现:

  public class realmManage extends AuthorizingRealm implements Serializable{
/**
 * Logger日志
 */
private static final Logger LOGGER = LoggerFactory.getLogger(realmManage.class);    
@Autowired
private LoginService loginService;
/**
 * 权限授权函数,查詢用戶的所擁有的權限
 */
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
    String userName = (String) principal.getPrimaryPrincipal();
    // 取得用户的所有权限
    Set<String> permissions = new HashSet<String>();
    Set<String> roleNames = new HashSet<String>();      
    //查詢用戶角色集合
    List<String> roleList = loginService.selectRolesByName(userName);
    for(String role : roleList){
        roleNames.add(role);
    }
    //查詢用戶權限集合
    List<String> permissionList = loginService.selectHasPermissionsByName(userName);
    for(String permissionUnion : permissionList){
        permissions.add(permissionUnion);
    }
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
    info.setStringPermissions(permissions);
    return info;
}
/**
 * 身份认证函数
 */
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authctoken) throws AuthenticationException {
    UsernamePasswordToken token  = (UsernamePasswordToken) authctoken;
    String userName = (String) token.getPrincipal(); // 得到用户名
    String pwd = new String((char[]) token.getCredentials()); // 得到密码
    String password ="";
    try {
        password = loginService.selectPwdByName(userName);
    } catch (Exception e) {
        throw new ShiroException();//账号异常
    }       
    if (password == null || "".equals(password)) {
        throw new UnknownAccountException(); //如果用户名错误  
    } 
    if(!pwd.equals(password)) {  
        throw new IncorrectCredentialsException(); //如果密码错误  
    } 
//如果身份认证验证成功,返回一个AuthenticationInfo实现;  
return new SimpleAuthenticationInfo(userName, pwd, getName());

}
}

这段代码中的realmManage继承了AuthorizingRealm对象,重写了doGetAuthenticationInfo(身份认证函数)和doGetAuthorizationInfo(权限授权函数)两个方法,然后在执行subject.login(token)方法,系统就自动调用realm中的登录验证。现在开始看subject.login(token)(org.apache.shiro.subject.support.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 session = (DelegatingSubject) subject;
        principals = session.principals;
        host = session.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 session2 = subject.getSession(false);
        if (session2 != null) {
            this.session = this.decorate(session2);
        } else {
            this.session = null;
        }
    } else {
        String session1 = "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(session1);
    }
}

注:在方法内我们看到参数进入了this.securityManager.login(this, token)中,然后我们在看看这securityManager(org.apache.shiro.mgt.DefaultSecurityManager)对象中的login(this, token)方法:

 public Subject login(Subject subject, AuthenticationToken token) throws AuthenticationException {
    AuthenticationInfo info;
    try {
        info = this.authenticate(token);
    } catch (AuthenticationException arg6) {
        AuthenticationException loggedIn = arg6;
        try {
            this.onFailedLogin(token, loggedIn, subject);
        } catch (Exception arg5) {
            if (log.isInfoEnabled()) {
                log.info(
                        "onFailedLogin method threw an exception.  Logging and propagating original AuthenticationException.",
                        arg5);
            }
        }
        throw arg6;
    }
    Subject loggedIn1 = this.createSubject(token, info, subject);
    this.onSuccessfulLogin(token, info, loggedIn1);
    return loggedIn1;
}

注:在这个方法中我们可以看到参数进入 this.authenticate(token)方法中,而且在这个方法中已经抓取了AuthenticationException异常并且做了处理。在DefaultSecurityManager的父类中(org.apache.shiro.mgt.AuthenticatingSecurityManager),我们进入authenticate(token)方法,继续深入查看:

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

}

然后继续进入方法,在AbstractAuthenticator(org.apache.shiro.authc.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 t = "No account information found for authentication token [" + token
                        + "] by this Authenticator instance.  Please check that it is configured correctly.";
                throw new AuthenticationException(t);
            }
        } catch (Throwable arg7) {
            AuthenticationException ae = null;
            if (arg7 instanceof AuthenticationException) {
                ae = (AuthenticationException) arg7;
            }
            if (ae == null) {
                String t2 = "Authentication failed for token submission [" + token
                        + "].  Possible unexpected error? (Typical or expected login exceptions should extend from AuthenticationException).";
                ae = new AuthenticationException(t2, arg7);
                if (log.isWarnEnabled()) {
                    log.warn(t2, arg7);
                }
            }
            try {
                this.notifyFailure(token, ae);
            } catch (Throwable arg6) {
                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, arg6);
                }
            }
            throw ae;
        }
        log.debug("Authentication successful for token [{}].  Returned account [{}]", token, info);
        this.notifySuccess(token, info);
        return info;
    }
}

然后继续进入this.doAuthenticate(token)方法,在其子类ModularRealmAuthenticator(org.apache.shiro.authc.pam.ModularRealmAuthenticator)中我们找到其实现:

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

此时,我们终于看到目标realm,这时候我们可以发现,该方法中通过Collection大小的判断,是否存在多个realm,然后分别走不通的方法进行验证,下面在贴出这两个方法的分别实现:

 protected AuthenticationInfo doSingleRealmAuthentication(Realm realm, AuthenticationToken token) {
    if (!realm.supports(token)) {
        String info1 = "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(info1);
    } 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;
        }
    }
}
protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
    AuthenticationStrategy strategy = this.getAuthenticationStrategy();
    AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);
    if (log.isTraceEnabled()) {
        log.trace("Iterating through {} realms for PAM authentication", Integer.valueOf(realms.size()));
    }
    Iterator arg4 = realms.iterator();
    while (arg4.hasNext()) {
        Realm realm = (Realm) arg4.next();
        aggregate = strategy.beforeAttempt(realm, token, aggregate);
        if (realm.supports(token)) {
            log.trace("Attempting to authenticate token [{}] using realm [{}]", token, realm);
            AuthenticationInfo info = null;
            Throwable t = null;
            try {
                info = realm.getAuthenticationInfo(token);
            } catch (Throwable arg10) {
                t = arg10;
                if (log.isWarnEnabled()) {
                    String msg = "Realm [" + realm
                            + "] threw an exception during a multi-realm authentication attempt:";
                    log.warn(msg, arg10);
                }
            }
            aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);
        } else {
            log.debug("Realm [{}] does not support token {}.  Skipping realm.", realm, token);
        }
    }
    aggregate = strategy.afterAllAttempts(token, aggregate);
    return aggregate;
}

可以看到,两个方法都是调用了realm.getAuthenticationInfo(token)来验证登录信息的。

猜你喜欢

转载自blog.csdn.net/u011607686/article/details/80360744
今日推荐