本文给出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;
}
}