Shiro与Spring集合时的密码加密问题

本文给出Shiro用户认证时,对密码的三种处理方式:不加密、MD5加密、MD5加盐加密。

首先给出代码:

shiro.xml配置如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd  
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-4.0.xsd">

	<!-- 1. 配置 SecurityManager,Shiro核心安全管理器接口-->
	<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
		<property name="realm" ref="myRealm" />
	    <property name="cacheManager" ref="shiroCacheManager" />
    </bean>

    <!-- 2. 配置 CacheManager.缓存授权信息、不然每次访问都要登陆 -->
    <!--  2.1 配置简单的非shiro自己实现的缓存-->
    <!--
    <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager" />
    -->
    <!--  2.2 配置ehcache缓存,需要加入ehcache的jar包及配置文件-->
	<bean id="ehCacheManager"
		  class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
		<property name="configLocation" value="classpath:ehcache.xml" />
		<property name="shared" value="true"></property>
	</bean>
	<!-- 缓存管理器 使用Ehcache实现 -->
	<bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
		<property name="cacheManager" ref="ehCacheManager" />
	</bean>

    <!-- 3. 配置Realm,自定义认证与授权-->
    <!-- 3.1不对密码加密-->
	<bean id="myRealm" class="com.qiqi.account.shiro.core.Realm" />
	<!-- 3.1配置对密码加密-->
    <!--
	<bean id="myRealm" class="com.qiqi.account.shiro.core.Realm">
		<property name="credentialsMatcher">
			<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
				<property name="hashAlgorithmName" value="MD5"></property>
				<property name="hashIterations" value="1024"></property>
			</bean>
		</property>
	</bean>
    -->
    <!-- 4. 配置 LifecycleBeanPostProcessor. 可以自定的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法-->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- 5. 启用IOC容器中使用shiro的注解,但必须在配置了LifecycleBeanPostProcessor之后才可以使用-->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

    <!-- 6. 配置 ShiroFilter.
      6.1 id必须和web.xml文件中配置的DelegatingFilterProxy的<filter-name>一致.若不一致,则会抛出:
      NoSuchBeanDefinitionException.因为Shiro会来IOC容器中查找和<filter-name>名字对应的filter bean.
    -->
	<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
		<property name="securityManager" ref="securityManager" />
		<!-- 要求登录时的链接 -->
		<property name="loginUrl" value="/ui/login" />
		<!-- 用户访问未对其授权的资源时,所显示的连接 -->
		<property name="unauthorizedUrl" value="/unauthorized.jsp" />
		<!-- Shiro过滤链的定义 -->
		<!-- anon: 该认证拦截器表示匿名访问(即不需要登录即可访问)就是不需要走loginurl,
		           也就是不拦截该路径,一般用于静态资源过滤 -->
		<!-- authc:该认证拦截器表示需要身份认证通过后才能访问(走loginurl,在loginurl路径的方法中通过调用
		            subject.login(token)方法调用Realm类的doGetAuthenticationInfo()方法进行登录验证)-->
		<!-- roles[admin]:该授权拦截器表示需要有admin角色授权才能访问 -->
		<!-- perms["user:create"]:该授权拦截器表示需要有“user:create”权限才能访问-->
		<!-- user:该认证拦截器,用户已经身份验证/记住我登录的即可-->
		<!--以下只配置了认证拦截器,没有配置授权拦截器,不对用户进行权限访问的控制-->
		<property name="filterChainDefinitions">
			<value>
				/test=anon
				/logout = anon
				/ui/checkLogin = anon
				<!-- 过滤静态资源 如js、css、图片-->
				/public/**=anon
				/js/**=anon
				/*.ico=anon
				/webjars/**=anon
				/** = authc,user
			</value>
		</property>
	</bean>
</beans>

Controller中的登录逻辑:

	/**
	 * 登录认证:验证用户名和密码
	 * @return
	 */
	@RequestMapping(value = "checkLogin", method = RequestMethod.POST)
	@ResponseBody
	public String login(String username, String password){
		//获取当前的Subject
		Subject currentUser = SecurityUtils.getSubject();
		//测试当前用户是否已经被认证(即是否已经登录)
		if (!currentUser.isAuthenticated()){
			//将用户名与密码封装为UsernamePasswordToken对象
			UsernamePasswordToken token = new UsernamePasswordToken(username, password);
			//设置rememberMe记录用户
			token.setRememberMe(true);
			try {
				//调用Subject的login方法执行登录
				currentUser.login(token);
			} catch (UnknownAccountException uae) {
				logger.warn("不存在用户 " + token.getPrincipal());
				return "{\"error\":\"7\"}";
			} catch (IncorrectCredentialsException ice) {
				logger.warn("用户 " + token.getPrincipal() + " 密码错误!");
				return "{\"error\":\"8\"}";
			} catch (LockedAccountException lae) {
				logger.warn("用户 " + token.getPrincipal() + " 被锁定!");
				return "{\"error\":\"15\"}";
			} catch (AuthenticationException ae) {
				logger.warn("用户:" + token.getPrincipal() + " 登录失败!");
			}
		}
		//登录成功
		return "{\"error\":\"0\"}";
	}

自定义的Real:

package com.qiqi.account.shiro.core;

import com.qiqi.account.exception.AccountNotExistException;
import com.qiqi.account.shiro.model.User;
import com.qiqi.account.utils.MD5Util;
import com.qiqi.account.utils.SaltAndMD5Util;
import org.apache.log4j.Logger;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

import java.util.*;

/**
 * 权限检查类
 */
public class Realm extends AuthorizingRealm {

	private static final Logger logger = Logger.getLogger(Realm.class);


	private static Map<String,User> userMap = new HashMap<String,User>();
	static{
		//使用Map模拟数据库获取User表信息
		userMap.put("qiqi", new User("qiqi","123456",false));
		userMap.put("77", new User("77","123456",false));
		userMap.put("qq", new User("qq","123456",true));
	}

	/*
	 * 授权
	 */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

		/**
		 * 从数据库加载当前用户的角色,例如:[admin]
		 * authorizationInfo.setRoles(new HashSet<String>(sysusersservice.getSysRoles(username)));
		 * 从数据库加载当前用户可以访问的资源,例如:[index.jsp, abc.jsp]
		 *  authorizationInfo.setStringPermissions(new HashSet<String>(sysusersservice.getSysResource(username)));
		 */
		Set<String> roleNames = new HashSet<>();
		Set<String> permissions = new HashSet<String>();
		roleNames.add("administrator");//添加角色
		permissions.add("newPage.jhtml");  //添加权限
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
		info.setStringPermissions(permissions);
		return info;
	}

	/*
	 * 登录验证  只有调用自己调用Subject subject.login(token)才会调用该方法
	 */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken authcToken) throws AuthenticationException {

		//1.把AuthenticationToken转换为UsernamePasswordToken
		UsernamePasswordToken userToken = (UsernamePasswordToken) authcToken;

		//2.从UsernamePasswordToken中获取username
		String username = userToken.getUsername();

		//3.调用数据库的方法,从数据库中查询Username对应的用户记录
		System.out.println("从数据库中获取用户:"+username+"所对应的信息。");
		//Map模拟数据库取数据
		User user = userMap.get(username);

		//4.若用户不行存在,可以抛出UnknownAccountException
		if(user==null){
			System.out.println("用户:"+username+"不存在");
			throw new UnknownAccountException("用户:"+username+"不存在");
		}

		//5.若用户被锁定,可以抛出LockedAccountException
		if(user.isLocked()){
			System.out.println("用户:"+username+"不存在");
			System.out.println("用户:"+username+"被锁定");
			throw new LockedAccountException("用户:"+username+"被锁定");
		}

		//6.根据用户的情况,来构建AuthenticationInfo对象,通常使用的实现类为SimpleAuthenticationInfo
		//以下信息是从数据库中获取的
		//1)principal :认证的实体信息,这里使用username,也可以是数据库表对应的用户的实体对象
		Object principal  = user.getUsername();

		//2)credentials:密码
		// 2.1 不加密
		Object credentials = user.getPassword();
		// 2.2 MD5加密
		//Object credentials = MD5Util.ShiroMD5(user.getPassword());
		// 2.3 MD5加盐加密
		//Object credentials = SaltAndMD5Util.SaltAndMD5(user.getUsername(),user.getPassword());

		//3)realmName:当前realm对象的name,调用父类的getName()方法即可
		String realmName = getName();

		//4)credentialsSalt盐值,这里使用账号作为盐值
		ByteSource credentialsSalt = ByteSource.Util.bytes(principal);

        //5)构建SimpleAuthenticationInfo对象
		// 5.1 credentials可以为不加密或者MD5加密后的密码
		SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal ,credentials,realmName);
		// 5.2 使用MD5加盐加密的方式
		//SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);

		return info;
	}


	/**
	 * 获取加盐后的密码
	 * @param args
	 */
	public static void main(String[] args) {
		User user = null;
		Iterator<String> it = userMap.keySet().iterator();
		while(it.hasNext()){
			user = userMap.get(it.next());
			String hashAlgorithmName = "MD5";//加密方式
			Object crdentials = user.getPassword();//密码原值
			ByteSource salt = ByteSource.Util.bytes(user.getUsername());//以账号作为盐值
			int hashIterations = 1024;//加密1024次
			Object result = new SimpleHash(hashAlgorithmName,crdentials,salt,hashIterations);
			System.out.println(user.getUsername()+":"+result);
		}
	}

}

一:不加密

不对密码加密时,在shiro.xml配置自定义Real时,使用

<!-- 3.1 不对密码加密 -->

<bean id="myRealm" class="com.qiqi.account.shiro.core.Realm" />

在Real中封装SimpleAuthenticationInfo对象时,直接获取原密码进行封装就可以了:

// 2.1 不加密 

Object credentials = user.getPassword();

SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal ,credentials,realmName);

二:MD5加密

这时需要在shiro.xml中配置加密的类:这里的参数说明我们使用MD5加密,并加密1024次

	<!-- 3.2 对密码加密 -->
	<!--
	<bean id="myRealm" class="com.qiqi.account.shiro.core.Realm">
		<property name="credentialsMatcher">
			<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
				<property name="hashAlgorithmName" value="MD5"></property>
				<property name="hashIterations" value="1024"></property>
			</bean>
		</property>
	</bean>
    -->

在Real中封装SimpleAuthenticationInfo对象时,我们需要自己对原密码加密后再进行封装

// 2.2 MD5加密
Object credentials = MD5Util.ShiroMD5(user.getPassword());
// 5.1 credentials可以为不加密或者MD5加密后的密码
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal ,credentials,realmName);

加密方法:

    public final static String ShiroMD5(String password) {
        try {
            String hashAlgorithmName = "MD5";//加密方式
            Object crdentials = password;//密码原值
            Object salt = null;//盐值
            int hashIterations = 1024;//加密1024次
            Object result = new SimpleHash(hashAlgorithmName,crdentials,salt,hashIterations);
            System.out.println(result);
            return  result.toString();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }


三:MD5加盐加密

shiro.xml配置和MD5加密一样,和MD5的不同是,我们增加了盐值,使用用户名作为盐值,还有构建SimpleAuthenticationInfo对象时,是使用了带有盐值的含有四个参数的构造函数。

// 2.3 MD5加盐加密
Object credentials = SaltAndMD5Util.SaltAndMD5(user.getUsername(),user.getPassword());
// 5.2 使用MD5加盐加密的方式
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);

加密方法:

    public final static Object SaltAndMD5(String userName,String password) {
        try {
            String hashAlgorithmName = "MD5";//加密方式
            ByteSource salt = ByteSource.Util.bytes(userName);//以账号作为盐值
            int hashIterations = 1024;//加密1024次
            Object result = new SimpleHash(hashAlgorithmName,password,salt,hashIterations);
            System.out.println(userName+":"+result);
            return result;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值