使用shiro实现密码加密注册与登录
参考链接 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; } }