1、自定义的ShiroRealm,继承
AuthenticatingRealm
//使用认证功能 AuthenticatingRealm
public class ShiroRealm extends AuthenticatingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.把AuthenticationToken转换为UsernamePasswordToken
//authenticationToken保存了页面表单输入的用户名和密码信息
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//2.从UsernamePasswordToken中获取username
String username = token.getUsername();
//3.从数据库查询对应的用户记录
System.out.println("从数据库获取:" + username + "的信息");
//4.若用户不存在
if("unkonwn".equals(username)) {
throw new UnknownAccountException("用户不存在");
}
//5.根据用户信息情况,构建AuthenticationInfo,通常使用的实现类为:SimpleAuthenticationInfo
//以下信息是从数据库获取的
//1). principal:认证的实体信息,可以是username,也可以是数据表对应的实体类对象
Object principal = username;
//2). credentials:密码
Object credentials = "1234";
//3). realmName:当前realm对象的name,调用父类的getName()
String realmName = getName();
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName);
return info;
}
2、配置applicationContext.xml
<!--
7.配置哪些页面需要受保护 以及 访问这些页面需要的权限
1) anon 代表可以被匿名访问
2) authc 代表认证(登录)后才可以被访问的页面
3) logout 代表登出
-->
<property name="filterChainDefinitions">
<value>
/shiro/login = anon
/login.jsp = anon
/shiro/logout = logout
/list.jsp = authc
/** = authc
</value>
</property>
3、根据自定义的Realm,我们知道
UsernamePasswordToken保存了页面表单输入的用户名和密码信息,
SimpleAuthenticationInfo保存着数据库中用户的密码,那么密码的比对shiro如何完成的呢?什么时候进行比对呢?
见源码Debug:
(1)UsernamePasswordToken
(2)密码比对
我们可以发现,密码的比对是明文进行,显然是不可取、不安全。数据表应该保存加密之后的密码
那么,我们应该如何对用户输入的密码进行加密呢?
还是查看源码,我们通过
CredentialsMatcher来进行加密,从数据库获取的密码应该是加密之后的字符串。
(3)做法:替换当前Realm的credentialsMatcher属性,直接使用HashedCredentialsMatcher对象,并设置加密算法
4、配置凭证匹配器
CredentialsMatcher以及密码的MD5加密
(1)applicationContext.xml中进行配置
<!--
3.配置Realm
3.1直接配置一个实现了org.apache.shiro.realm.Realm接口的bean
-->
<bean id="jdbcRealm" class="com.dhu.shiro.realms.ShiroRealm">
<property name="credentialsMatcher">
<bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
<property name="hashAlgorithmName" value="MD5"></property>
</bean>
</property>
</bean>
(2)CredentialsMatcher源码查看
(3)加密的源码查看
(4)源码简单分析:SimpleHash中包含了salt称为盐值、hashIterations加密次数,hashIterations可以在配置文件进行设置
protected Hash hashProvidedCredentials(Object credentials, Object salt, int hashIterations) {
String hashAlgorithmName = assertHashAlgorithmName();
return new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
}
(5)根据源码HashedCredentialsMatcher.java 中 hashProvidedCredentials 方法,进行配置
<!--
3.配置Realm
3.1直接配置一个实现了org.apache.shiro.realm.Realm接口的bean
-->
<bean id="jdbcRealm" class="com.dhu.shiro.realms.ShiroRealm">
<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>
(6)修改自定义的Realm
//5.根据用户信息情况,构建AuthenticationInfo,通常使用的实现类为:SimpleAuthenticationInfo
//以下信息是从数据库获取的
//1). principal:认证的实体信息,可以是username,也可以是数据表对应的实体类对象
Object principal = username;
//2). credentials:密码
Object credentials = "a20b8e682a72eeac0049847855cecb86";
//3). realmName:当前realm对象的name,调用父类的getName()
String realmName = getName();
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(principal, credentials, realmName);
return info;
}
// 测试加密后的密码
public static void main(String[] args) {
String hashAlgorithmName = "MD5";
Object credentials = "1234";
Object salt = null;
int hashIterations = 1024;
SimpleHash res = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
System.out.println(res); //a20b8e682a72eeac0049847855cecb86
}
}
(7)再次Debug,发现加密后密码一样,同时登录成功~
5、 MD5盐值加密
这样设置之后,我们还是发现有一个问题。若明文输入的一样的,经过MD5加密后的密码也是一样的,依然不安全。我们希望即使两个原始密码一样,经过加密后的结果也不一样。
需要加点佐料,加盐salt
(1)加密后的结果需要把salt加进去
SimpleHash res = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
(2)返回值
SimpleAuthenticationInfo需要把盐salt带上
SimpleAuthenticationInfo需要把salt带上
6、修改自定义的Realm以及测试加密后的密码
盐值需要保证唯一性:
//4). 盐值 ByteSource salt = ByteSource.Util.bytes(username); //唯一性
//使用认证功能 AuthenticatingRealm
public class ShiroRealm extends AuthenticatingRealm {
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.把AuthenticationToken转换为UsernamePasswordToken
//authenticationToken保存了页面表单输入的用户名和密码信息
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//2.从UsernamePasswordToken中获取username
String username = token.getUsername();
//3.从数据库查询对应的用户记录
System.out.println("从数据库获取:" + username + "的信息");
//4.若用户不存在
if("unkonwn".equals(username)) {
throw new UnknownAccountException("用户不存在");
}
//5.根据用户信息情况,构建AuthenticationInfo,通常使用的实现类为:SimpleAuthenticationInfo
//以下信息是从数据库获取的
//1). principal:认证的实体信息,可以是username,也可以是数据表对应的实体类对象
Object principal = username;
//2). credentials:密码
Object credentials = "null";
if("admin".equals(username)) {
credentials = "c34af346c89b8b03438e27a32863c9b5";
}
if("user".equals(username)) {
credentials = "3e042e1e3801c502c05e13c3ebb495c9";
}
//3). realmName:当前realm对象的name,调用父类的getName()
String realmName = getName();
//4). 盐值
ByteSource salt = ByteSource.Util.bytes(username); //唯一性
SimpleAuthenticationInfo info = null; //new SimpleAuthenticationInfo(principal, credentials, realmName);
info = new SimpleAuthenticationInfo(principal, credentials, salt, realmName);
return info;
}
// 测试加密后的密码
public static void main(String[] args) {
String hashAlgorithmName = "MD5";
Object credentials = "1234";
Object salt = ByteSource.Util.bytes("user");
int hashIterations = 1024;
SimpleHash res = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
System.out.println(res);
}
}
7、笔记总结