`
suichangkele
  • 浏览: 200326 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

shiro学习04-登录校验(2)

阅读更多

用户的信息多都在Realm中,所以我们先从realm开始看源码吧。

我们在上一篇中用到了AuthenticationRealm,这个就是Realm接口的实现类,我们具体看这个类的源码以及在校验时所用到的方法。

先从这个类的无参构造方法开始

 

public AuthenticatingRealm() {
        this(null, new SimpleCredentialsMatcher());
}
其中第一个参数是CacheManager,是一个null,第二个是CredentialsMatcher。AuthenticationRealm是CachingRealm的实现类,CachingRealm允许对将用户已经获得AuthenticationIfon进行缓存,方便下一次使用。但是这个缓存默认是关闭的,AuthenticationRealm构造方法中的null表示没有传入CaqcheManager,我们在实际开发中都是关闭缓存,改用redis来实现对用户信息的缓存。

 

第二个参数CredentialsMatcher很重要,他用来将用户通过表单提交的密码和在数据库中的密码进行比对,判断是不是一直,默认提供的实现类是SimpleCredentialsMatcher,这个类的对比方式如下:

 

protected boolean equals(Object tokenCredentials, Object accountCredentials) {
        if (log.isDebugEnabled()) {
            log.debug("Performing credentials equality check for tokenCredentials of type [" +
                    tokenCredentials.getClass().getName() + " and accountCredentials of type [" +
                    accountCredentials.getClass().getName() + "]");
        }
        if (isByteSource(tokenCredentials) && isByteSource(accountCredentials)) {
            if (log.isDebugEnabled()) {
                log.debug("Both credentials arguments can be easily converted to byte arrays.  Performing " +
                        "array equals comparison");
            }
            byte[] tokenBytes = toBytes(tokenCredentials);
            byte[] accountBytes = toBytes(accountCredentials);
            return Arrays.equals(tokenBytes, accountBytes);
        } else {
            return accountCredentials.equals(tokenCredentials);
        }
    }
我们一般采用字符串作为密码,假设这样的话,会将字符串转化为数组,采用的办法是

 

 

String.getBytes("UTF-8")
然后调用Arrays.equals的方法。从这里我们看出,SimpleCredentialsMatcher没有涉及任何的加密算法,只适合于我们的普通测试字符串是否相同,如果是加密的我们需要自己定义自己的加密算法和使用加密算法进行对比的CredentialMatcher,但是在实际中我们更倾向于这样做:

 

在数据库中存储的是经过加密的密码,将用户提交的密码在进行对比之前我们自己使用相同的办法将其进行加密,然后仍然使用SimpleCredentialsMatcher,这样就避免了创建CredentialMatcher的麻烦了。在接触shiro前我的加密算法都是采用的md5 + base64,幸运的是shiro也提供了这两种加密算法,我推荐搭建使用shiro自带的加密类,然后将其封装成一个工具类,我的代码如下:

 

import org.apache.shiro.crypto.hash.Md5Hash;
public class EncryptUtil {
	public String toMd5(String pwd){		
		return  new Md5Hash(pwd,"salt2016",111).toBase64();
	}
}
shiro提供了Hash接口,表示对明文加密,其中Md5Hash只是个例(这个不是重点,也可以使用Md2Hash) 具体解释如下:org.apache.shiro.crypto.hash.Md5Hash.Md5Hash(Object source, Object salt, int encryptionTime) 第一个参数表示要加密的密码,第二个表示加密过程中加的盐,第三个表示加密的次数,也就是明文加密一次后再对新生成的暗纹继续加密,还有很多重载的Md5Hash构造方法,并且可以不用加盐,也不用指定加密的次数。最后toBase64是用来格式化显示的,因为调用md5加密算法之后会有一些不友好的字符,通过base64将其完全转化为键盘上的字符,这样更加友好。(也可以toHex,只是算法不同,意思一样)

 

 

继续我们的AuthenticatingRealm 在另一个构造方法中 AuthenticatingRealm(CacheManager, CredentialsMatcher),我们看一下源码

 

public AuthenticatingRealm(CacheManager cacheManager, CredentialsMatcher matcher) {
       authenticationTokenClass = UsernamePasswordToken.class;

       //retain backwards compatibility for Shiro 1.1 and earlier.  Setting to true by default will probably cause
       //unexpected results for existing applications:
       this.authenticationCachingEnabled = false;
        int instanceNumber = INSTANCE_COUNT.getAndIncrement();
        this.authenticationCacheName = getClass().getName() + DEFAULT_AUTHORIZATION_CACHE_SUFFIX;
        if (instanceNumber > 0) {
            this.authenticationCacheName = this.authenticationCacheName + "." + instanceNumber;
        }

        if (cacheManager != null) {
            setCacheManager(cacheManager);
        }
        if (matcher != null) {
            setCredentialsMatcher(matcher);
        }
} 

 

其中指定了默认支持的AuthenticationTokenClass是UsernamePassowrdToken,所以我们在上一节的代码中使用了UsernamePasswordToken,如果不适用这个的话必须改对AuthenticationReal进行设置,否则会不支持的。从这个代码中发现默认是不支持缓存的,所以关于缓存的我们就可以放心了。然后在这个构造方法中设置了CredentialsMather,就是上面介绍的SimpleCredentialsMatcher。

 

当某个用户登录时,通过dubug发现调用的是AuthenticatingRealm的getAuthenticationInfo方法,就是通过传入的UsernamePasswordToken,将源码拿出来

 

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

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

        return info;
    }
上面代码的 大致意思是先从以前缓存的获取,因为我们不做缓存,所以忽略这个,如果缓存中没有的话调用的是doGetAuthenticationInfo(token)方法,这个就是我们上一节中覆写的方法,用来从数据库中获得用户的信息。最后assertCredentialsMatcher调用之前设置的CredentialMatcher进行对比传递进来的token和最后从数据库中获取的info是否一致,我们看一下assertCredentialsMatcher方法的源码:

 

protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
        CredentialsMatcher cm = getCredentialsMatcher();
        if (cm != null) {
            if (!cm.doCredentialsMatch(token, info)) {
                //not successful - throw an exception to indicate this:
                String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
                throw new IncorrectCredentialsException(msg);
            }
        } else {
            throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify " +
                    "credentials during authentication.  If you do not wish for credentials to be examined, you " +
                    "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
        }
}
如果对比一致的话,没有任何返回或者异常,但是如果对比不一致就会抛IncorrectCredentialsException。这里很容易理解,问题是我们知道AuthenticationException有很多子类,如果是其他异常呢,应该在哪里抛,但是是在我们复写的doGetAuthenticationInfo方法中,如果有其他异常,可以直接在这里抛,比如上一节我们复写的方法中就抛出了UnknownAccountException,我们可以根据我们的业务逻辑规定自己的异常,但是别忘了定义的异常要继承AuthenticationException。
 
接下来是AuthenticationInfo,表示根据用户名从数据库中获得的用户的信息,在我自己的实现中是直接new了一个匿名类,其实shiro是给了我们实现类的——SimpleAuthenticationInfo,我们可以直接实例化这个类,改写的代码如下:
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
	final String username = (String) token.getPrincipal();
	final String password = db.get(username);
	if(db.get(username)==null){
		throw new UnknownAccountException();
	}	
	AuthenticationInfo ainfo = new SimpleAuthenticationInfo(username, password, "DB");
	return ainfo;
}
 我们进入SimpleAuthenticationInfo的构造方法中,代码如下:
public SimpleAuthenticationInfo(Object principal, Object credentials, String realmName) {
    this.principals = new SimplePrincipalCollection(principal, realmName);
    this.credentials = credentials;
}
里面是实例化了一个PrincipalCollection的实现类,进入到里面,代码如下:
public SimplePrincipalCollection(Object principal, String realmName) {
        if (principal instanceof Collection) {
            addAll((Collection) principal, realmName);
        } else {
            add(principal, realmName);
        }
}
 由于我们从一开始就假设我们只有一个realm所以我们传入的pricipal是一个String类型的对象,所以上面的代码执行的是add方法,add方法最终执行的是这个代码getPrincipalsLazy(realmName).add(principal);其中getPrincipalLazy的代码如下:
protected Collection getPrincipalsLazy(String realmName) {
        if (realmPrincipals == null) {
            realmPrincipals = new LinkedHashMap<String, Set>();
        }
        Set principals = realmPrincipals.get(realmName);
        if (principals == null) {
            principals = new LinkedHashSet();
            realmPrincipals.put(realmName, principals);
        }
        return principals;
} 
其中realmPrincipals是SimplePrincipalCollection的一个属性,表示按照realm按照pricipal进行分开,存在一个hashmap中,key是realm的名字,value就是一个set,这里它使用了LinkedHashMap,是为了保证顺序,方便下面的getPrimaryPrincipal使用,由于我们只有一个Realm,所以不用特别关心这些。
通过上面的分析我们可以发现,
SimpleAuthenticationInfo(username, password, "DB");
这行代码是将username作为一个pricipal存到HashMap中key为DB的set中了。
 
 
还有一个比较重要的类PrincipalCollection,我们在复写的doGetAuthenticationInfo方法中有这个类,在刚才的分析中也发现了他的实现类SimplePrincipalCollection,它里面有一个很重要的方法:getPrimaryPrincipal,表示获得所有的pricipal中最主要的一个,我们看一下SimplePrincipalCollection的源码:
 public Object getPrimaryPrincipal() {
        if (isEmpty()) {
            return null;
        }
        return iterator().next();
}
 然后进入他的iterator方法发现调用的是如下的方法asSet
 public Set asSet() {
        if (realmPrincipals == null || realmPrincipals.isEmpty()) {
            return Collections.EMPTY_SET;
        }
        Set aggregated = new LinkedHashSet();
        Collection<Set> values = realmPrincipals.values();
        for (Set set : values) {
            aggregated.addAll(set);
        }
        if (aggregated.isEmpty()) {
            return Collections.EMPTY_SET;
        }
        return Collections.unmodifiableSet(aggregated);
}
 如果是多个realm的话会将realmPrincipals(就是上面提到的那个根据realm区分pricipal的map)中的所有pricipal都放到一个set去,这里我们联想之前提到的那个LinkedHashMap,就能明白作者的用意——作者是想保存顺序,因为在getPrimaryPrincipal方法中直接就是将返回的iteration调用的next方法,即取得第一个值。所以如果是多个域的话,第一个放入的principal是primaryPrincipal。当然我们只有一个域,所以得到的primaryPrincipal就是我们那个唯一的域了。
学到这里,用户登录基本就可以做完了,下一节将介绍访问权限的控制。
分享到:
评论

相关推荐

    shiro-core-1.6.0.jar

    该资源是目前最新的shiro-core核心包,暂时魏安全性较高的版本,没有发现漏洞版本。提供登录安全性的校验,该资源来自官网,仅供学习参考,请前往官网下载

    shiro-jwt-oauth权限认证

    通过这两个模块,开发者可以学习如何在实际项目中集成和配置Shiro、JWT以及OAuth2.0,以实现安全且灵活的权限认证。这不仅有助于提高系统的安全性,还能提升用户体验,因为用户只需要登录一次即可访问多个受保护的...

    JFinal-Shiro-JDBC-Demo-master.zip_DEMO_jfinal_shiro

    4. **过滤器配置**:Shiro通过Filter链来实现安全控制,如登录拦截、权限校验等。在JFinal中,我们通常会看到对应的ShiroFilter配置,用于定义哪些URL需要经过Shiro的处理。 5. **数据库集成**:JDBC的使用意味着...

    shiro-root-1.4.0-RC2.rar

    总之,Apache Shiro 1.4.0-RC2的示例代码为你提供了一手的实践经验,帮助你深入学习和掌握这个安全框架的各个方面。从身份认证到授权,再到加密和会话管理,每个示例都是一次探索Shiro特性的机会。通过研究这些示例...

    springboot+shiro+mybatis-plus.7z

    在本案例中,Shiro主要用来实现用户权限的管理,如登录验证、角色分配和权限校验。你可以通过Shiro的过滤器链来定义哪些URL需要用户登录才能访问,哪些操作需要特定的角色权限。 **MyBatis-Plus** MyBatis-Plus是在...

    shiro视频教程-最全,通俗易懂

    通过上述知识点的学习,Java开发者及相关专业和技术爱好者将能够深入理解Shiro框架的核心概念与工作原理,并能够运用到实际项目开发中,实现用户认证、授权、加密等功能,从而提升项目的整体安全性和用户体验。

    shiro登录拦截校验demo

    "shiro登录拦截校验demo"是一个实际应用Shiro框架进行登录验证和权限拦截的示例项目。 在该demo中,我们可以学习到以下几个核心知识点: 1. **Shiro的基本概念**: - **身份验证(Authentication)**:确认用户...

    SSM整合Shiro-登录案例.zip

    通过这个案例,开发者可以学习到如何在实际项目中结合SSM和Shiro,实现一个完整的用户登录功能。这不仅涉及到了前后端交互、数据库操作,还涵盖了权限管理和会话控制等核心安全概念。理解并掌握这些知识,对于提升...

    shiro学习示例

    在"shiro学习示例"中,我们可以深入理解Shiro的核心概念和实际应用。 1. **Shiro基本概念** - **认证**:确认用户身份的过程,即用户输入用户名和密码,系统通过验证来确认用户的身份。 - **授权**:确定用户是否...

    shiro-Demo01

    5. **编写Controller**:在Controller层,Shiro会自动处理登录、登出等操作。你可以使用`@ShiroSubject`注解来访问Subject对象,进行权限检查。 6. **权限注解**:Shiro提供了一系列的注解,如`@...

    shiro-web.zip

    1. **Filter配置**:Shiro通过Web Filter实现安全控制,我们需要在`web.xml`中配置Shiro的过滤器,如`FormAuthenticationFilter`用于表单登录,`AuthorizationFilter`用于权限校验。 2. ** Realm配置**:在Spring ...

    shiro-springboot.zip

    2. Shiro 的 `FormAuthenticationFilter` 捕获请求,校验凭证。 3. 如果凭证有效,`Subject` 对象进行认证,通过 Realm 获取用户信息。 4. 如果认证成功,用户被标记为已登录状态,会话中存储相关信息。 授权则涉及...

    SpringMVC-Activiti5.16-Shiro-EasyUI项目整合

    开发者可以通过Shiro的API来定制登录、注销、权限校验等逻辑,实现灵活的安全策略。 EasyUI则是一个基于jQuery的前端框架,提供了丰富的UI组件,如表格、树形控件、下拉框等,大大简化了前端页面的开发。在本项目中...

    springboot-shiro-2.rar

    Realm 是Shiro与应用程序数据源交互的桥梁,负责处理用户的登录验证和权限校验。 3. 创建Realm 在SpringBoot项目中,我们通常会创建自定义的Realm类,继承自`AuthorizingRealm`,并重写其`...

    2.2 SpringBoot与Shiro整合-权限管理实战-课堂笔记.docx

    2. 配置Shiro Realm,实现用户和权限的获取。 3. 配置Shiro的SecurityManager,与Spring Boot整合。 4. 创建Shiro过滤器链,定义URL拦截规则。 5. 在Controller层使用Shiro的注解(如`@RequiresPermissions`)进行...

    shiro 学习资料

    Shiro 的身份认证过程包括认证发起、凭证匹配、权限校验等步骤。用户提交用户名和密码等凭证后,Shiro 会与事先存储的凭证进行比对,如果匹配成功,就认为用户已通过身份验证。Shiro 支持多种凭证类型,如简单的...

    spring boot shiro demo项目

    2. **Spring Boot集成Shiro** - 在`pom.xml`中添加Shiro的依赖。 - 配置Shiro Realm,实现认证和授权逻辑。 Realm是Shiro与应用的持久层交互的接口,你需要自定义 Realm 类并实现获取用户信息、验证用户凭证的方法...

    shiro学习笔记.rar

    2) Shiro通过Subject进行登录尝试。 3) Shiro调用Realm进行身份验证, Realm会从数据源查询用户信息并与提交的凭证进行匹配。 4) 验证成功后,Shiro创建一个基于当前Subject的安全会话。 **4. 授权机制** Shiro的...

    最详细Shiro学习资料(源码)

    Shiro(Apache Shiro)是一个强大且易于使用的Java安全框架,用于身份验证、授权、加密和会话管理等安全功能。它提供了简单的API和灵活的配置选项,可以方便地集成到现有的Java应用程序中。 Shiro的主要特点包括: ...

    Shiro权限管理 的jar包 ,一些example ,shiro资料笔记与核心单词

    - **shiro资料笔记与核心单词**:用户的学习笔记,可能涵盖了关键概念和常用API,是学习Shiro的好帮手。 在实际应用中,Shiro可以通过简单的配置实现用户的登录、权限检查、会话管理等功能。通过 Realm,我们可以...

Global site tag (gtag.js) - Google Analytics