进入shiro,登录逻辑验证逻辑跑通_executelogin-程序员宅基地

技术标签: 百度实习  shiro  realm  token  

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)来验证登录信息的。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/u011607686/article/details/80360744

智能推荐

OpenCV这么简单为啥不学——2、逐帧播放视频(VideoCapture函数、waitKey函数、0xFF == ord(‘1‘))_opencv 逐帧播放-程序员宅基地

文章浏览阅读4k次。OpenCV这么简单为啥不学——2、逐帧播放视频(VideoCapture函数、waitKey函数、0xFF == ord('1'))前言显示视频自定义修改图片的值总结_opencv 逐帧播放

调用第三方接口成功后,如果自己本身接口报错,如何实现事务回滚?_跨接口改数据怎么同时回滚-程序员宅基地

文章浏览阅读7.6k次。解决思路如下:1.创建事务2.先更新本地订单状态(失败则回滚)3.再调用第三方接口4.根据接口返回值决定事务提交还是回滚这样首先保证了本地数据能够更新,再根据接口返回值判断是否更新。这种方法只是针对单一操作,如果是大量的批量操作,建议使用队列进行异步处理。问题一:在事务中去请求第三方接口,容易导致事务时间过长,对方接口超时多久,你的db事务也卡多久问题二如果我们是接口提供方,就需要考虑程序异常,网络超时问题、服务器重启、并发等问题。为了保证提供方数据一致性,我们需要提供的特性有:回_跨接口改数据怎么同时回滚

openldap的配置手册-程序员宅基地

文章浏览阅读160次。最近一直在安装opneldap-2.0.25,现在终于搞定了,所以拿来和大家分享一下,如果有什么意见,可以共同讨论一下: 1) 一般需要安装以下四个rpm包: openldap-2.0.25-1.i386.rpm openldap-servers-2.0.25-1.i386.rpm openldap-clients-2.0.25-1.i386.rpm openlda..._ldap搭建手册

Java中间件之介绍_什么叫java中间件-程序员宅基地

文章浏览阅读1.3w次,点赞7次,收藏28次。1. Java中间件的定义 在Java web开发的演进与进化中,我们对于消息系统,数据库,服务化接口的抽象等,涉及数据分离的过程中,在分离过程中,就会涉及到分离后系统间,数据库间的交互。java中间件就是处理我们数据间交互,连接数据分离后两个系统间的通信,中间件不属于任何一个开发项目,就是让我们对应系统间或者数据库间数据流通无感知。有点像Linux下的管道。2. 中间件应用的基本领域与代..._什么叫java中间件

张宇1000题高等数学 第十六章 无穷级数_张宇1000题数三有无穷级数吗-程序员宅基地

文章浏览阅读1.2k次。张宇1000题高等数学 第十六章 无穷级数 易错题和难题记录_张宇1000题数三有无穷级数吗

Java之String系列--String不可变的含义、原因、好处_java string不可变-程序员宅基地

文章浏览阅读1.1w次,点赞43次,收藏35次。本文介绍Java的String不可变相关内容,包括:含义、原因、好处。_java string不可变

随便推点

Linux的基本指令_在/etc目录下找出所有的目录文件。 find/etc -type d -print-程序员宅基地

文章浏览阅读196次。Linux的基本指令一、进阶指令(重点)1、cat选项:-b, --number-nonblank 对非空输出行编号-E, --show-ends 在每行结束处显示 $-n, --number 对输出的所有行编号-T, --show-tabs 将跳 字符显示为 ^I注意:tac命令 从最后一行开始显示文件的信息,tac是cat倒着写的2、df指令作用:查看磁盘的空间(disk..._在/etc目录下找出所有的目录文件。 find/etc -type d -print

苹果和华为鸿蒙,华为太给力!新发布的鸿蒙OS对比苹果iOS,差距过于明显!-程序员宅基地

文章浏览阅读1.6k次。TASTER有趣|鸿蒙OS华为在前不久刚刚推出了鸿蒙HarmonyOS,在这次发布会上,余承东表示HarmonyOS升级将会覆盖百余款机型(仅仅是华为),第二天随时荣耀多款机型也确定将会适配鸿蒙,HarmonyOS覆盖机型之多在手机圈属实少见!更有趣的是,我发现本次HarmonyOS升级竟然覆盖了4年之前的机型,比如华为P10,要知道时间相隔如此久,很多品牌都会选择放弃维护这些旧产品,而华为却表示..._苹果手机app页面比华为精美

协作平台--介绍-程序员宅基地

文章浏览阅读419次。简介本文简介项目流程常用的一些软件。蓝湖其他网址官网:https://lanhuapp.com/简介高效的产品设计协作平台。产品经理轻松表达需求,设计师更快交付设计图,前端/APP 工程师快速省心开发Jira..._协作平台

The7 v.10.2.0-中文汉化主题/可视化拖拽编辑的WordPress主题企业外贸商城网站模板_随意拖拽编辑主题 页面框架模板-程序员宅基地

文章浏览阅读310次。️ 编号:ym217️ 品牌:WordPress️ 语言:php️ 大小:20.2MB️ 类型:外贸商城️ 支持:网站模板???? 欢迎免费领取(注明编号) ???? 源码介绍The7 v.10.2.0-中文汉化主题,可视化拖拽编辑的WordPress主题企业外贸商城网站模板。The7汉化版是一个非常漂亮的企业主题,功能非常强大,内置可视化编辑器,操作方便,支持layer和revsilder幻灯片,支持视网膜屏幕、支持自适应,所以流动元素都已集成。不管是企业门户或者个人网站、_随意拖拽编辑主题 页面框架模板

Linux命令--diff/patch(区别与补丁)--使用/示例_diff生成补丁字段含义-程序员宅基地

文章浏览阅读549次。原文网址:简介 本文介绍Linux操作文件内容的一些命令,包括如下:diff:比较文件或者文件夹(可以生成补丁文件,用patch去打补丁)patch:给文件打补丁awk:分析处理文本内容diff作用:比较文件或者文件夹格式:diff [参数] [文件1或目录1] [文件2或目录2]参数: --normal 以正常的 diff 方式输出 (默认) -q, --brief ..._diff生成补丁字段含义

第六章上机练习_jquery第六章上机作业-程序员宅基地

文章浏览阅读3.2k次。练习1:批量插入学生考试成绩BEGIN TRANSACTION DECLARE @errorSum INT SET @errorSum=0 /*--插入数据--*/ INSERT INTO Result(StudentNo,SubjectNo,ExamDate,StudentResult) VALUES(1,_jquery第六章上机作业

推荐文章

热门文章

相关标签