使用shiro实现密码加密注册与登录

时间:2024-03-07 12:01:09

参考链接 http://www.cnblogs.com/learnhow/p/5694876.html

    http://blog.csdn.net/wlwlwlwl015/article/details/48518003

    http://blog.csdn.net/evankaka/article/details/50196003#comments

这两个链接讲解的比较清楚

Shiro是什么?

  Shiro本质上就是一个安全框架,主要提供了有“加密”、“身份认证”、“权限管理”、“Session管理”四个功能模块。本文讲解“加密”与“身份认证”。

加密

  加密,顾名思义就是对利用加密算法对现有信息加密。为了安全性,通常我们会将用户密码加密后存储在数据中,Shiro的加密模块通常就是帮助我们干这件事情。

  实现的步骤如下:

  《一》首先Shiro这个框架是集成在Web项目中,所以肯定要有自己的配置文件,需要配置三个地方,主要如下所示:  

      (1)在web.xml文件中配置关于Shiro的filter

        

<filter>
        <filter-name>shiroFilter</filter-name>
        <!-- 通知spring,所有的Filter交由shiroFilter管理 -->
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <init-param>
          <param-name>targetFilterLifecycle</param-name>
          <param-value>true</param-value>
        </init-param>
      </filter>
      <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
        <dispatcher>REQUEST</dispatcher>
        <dispatcher>FORWARD</dispatcher>
      </filter-mapping>

      (2)shiro.xml配置(Shiro自身的配置)

      缓存管理器可以认为就是一个map,开发人员可以根据自己的业务在map中存储一些相应的信息。

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/beans    
                        http://www.springframework.org/schema/beans/spring-beans-3.1.xsd    
                        http://www.springframework.org/schema/context    
                        http://www.springframework.org/schema/context/spring-context-3.1.xsd    
                        http://www.springframework.org/schema/mvc    
                        http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd">

    <!-- 缓存管理器 使用Ehcache实现 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml" />
    </bean>

    <!-- 匹配器 -->
    <bean id="credentialsMatcher" class="utils.RetryLimitHashedCredentialsMatcher">
        <constructor-arg ref="cacheManager" />
        <property name="hashAlgorithmName" value="md5" />
        <property name="hashIterations" value="2" />
        <property name="storedCredentialsHexEncoded" value="true" />
    </bean>

    <!-- Realm实现 -->
    <bean id="userRealm" class="utils.UserRealm">
        <property name="credentialsMatcher" ref="credentialsMatcher" />
    </bean>

    <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm" />
    </bean>

    <!-- Shiro的Web过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager" />
        <property name="loginUrl" value="/" />
        <property name="unauthorizedUrl" value="/" />
        <property name="filterChainDefinitions">
            <value>
                /authc/admin = roles[admin]
                /authc/** = authc
                /** = anon
            </value>
        </property>
    </bean>

    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" />
</beans>

      (3) ehcache-shiro.xml关于缓存的一些配置信息

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE xml>
<ehcache updateCheck="false" name="shiroCache">

    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            />
            
     <!-- 登录记录缓存,锁定10分钟 -->
     <cache name="passwordRetryCache" eternal="false"
         timeToIdleSeconds="3600" timeToLiveSeconds="0" overflowToDisk="false"
         statistics="true">
     </cache>
</ehcache>

  《二》然后就对密码加密然后存储数据库,在这里主要说明加密的方法,加密的类一般写法如下所示:

public class PasswordHelper {
    private RandomNumberGenerator randomNumberGenerator = new SecureRandomNumberGenerator();
    private String algorithmName = "md5";
    private final int hashIterations = 2;
    
    public void encryptPassword(UUser user) {
        //设置初始化salt
        user.setSalt(randomNumberGenerator.nextBytes().toHex());
        //通过salt ,将密码进行加密,这里加密使用的salt是uName+初始化salt
        String newPassword = new SimpleHash(algorithmName, user.getuPassword(), user.getCredentialsSalt(), hashIterations).toHex();
        user.setuPassword(newPassword);
    }

}

  通过代码看出加密时,使用Md5加密算法,然后利用一个随机数对密码加密,随机数为SimpleHash()的第三个参数也就是user.getCredentialsSalt(),在这里user.getCredentialsSalt()得到的是“username+salt”,这salt就是利用randomNumberGenerator产生的。在将加密的密码存入数据库时,同时也要将salt存入数据库,因为用户登录时还要验证密码。

身份认证

  用户登录时需要进行身份验证,主要分为以下几步:

  1:首先创建关于登录名与登录密码的token,然后调用subjiect.login(token);

        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
        } catch (IncorrectCredentialsException ice) {
            // 捕获密码错误异常

  2:调用login方法后,会调用Realm的doGetAuthenticationInfo方法,在该方法中,首先检查用户是否存在,然后再检查账户是否异常,一般步骤如下:

@Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String username = (String) token.getPrincipal();
        User user = userService.findByUsername(username);
        if (user == null) {
            // 用户名不存在抛出异常
            throw new UnknownAccountException();
        }
        if (user.getLocked() == 0) {
            // 用户被管理员锁定抛出异常
            throw new LockedAccountException();
        }
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user.getUsername(),
                user.getPassword(), ByteSource.Util.bytes(user.getCredentialsSalt()), getName());
        return authenticationInfo;
    }

  3:然后调用匹配器来真正的匹配密码是否一致,可以看到真正起到匹配作用的是super.doCredentialsMatch(token, info),可以想到在该方法中无非也就是对用户输入的密码,使用md5算法,利用随机数,然后迭代两次加密,然后比较。这个方法如何知道使用md5,然后迭代两次呢,请看spring-shiro.xml中关于匹配器的配置文件。

public class RetryLimitHashedCredentialsMatcher extends HashedCredentialsMatcher {
    //声明一个缓存接口
    private Cache<String, AtomicInteger> passwordRetryCache;
    
    public RetryLimitHashedCredentialsMatcher(CacheManager cacheManager) {
        passwordRetryCache = cacheManager.getCache("passwordRetryCache");
    }
    
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        String username = (String) token.getPrincipal();
        AtomicInteger retryCount = passwordRetryCache.get(username);
        if(retryCount == null) {
            retryCount = new AtomicInteger(0);
            passwordRetryCache.put(username, retryCount);
        }
        if(retryCount.incrementAndGet() > 5) {
            throw new ExcessiveAttemptsException();
        }
        boolean isMatche = super.doCredentialsMatch(token, info);
        if (isMatche) {
            passwordRetryCache.remove(username);
        }
        return isMatche;
    }
}