apache旗下的一个开源框架,实现用户身份认证,权限授权,加密,会话管理等功能
内容均转载自Shiro和JWT
MD5加密
概述
MD5消息摘要算法,属Hash算法一类。MD5算法对输入任意长度的消息进行运行,产生一个128位的消息摘要(32位的数字字母混合码)。 也就是0123456789ABCDEF构成的混合数字码
特点
不可逆:相同数据的MD5值肯定一样,不同数据的MD5值不一样。
压缩性:任意长度的数据,算出的MD5值长度都是固定的
弱抗碰撞:已知原数据和MD5值,想找到一个具有相同MD5值是非常困难的。
强抗碰撞:想找到两个不同的数据,使它们具有相同的MD5值,是非常困难的。
为了让MD5码更加安全,涌现了很多其他方法,如加盐。 盐要足够长足够乱 得到的MD5码就很难查到。
用途
- 防止被篡改
A发送数据,通过MD5得到输出A,在B接受数据时,也计算一次MD5得到输出B,如果A=B,就相当于没有被篡改 - 防止直接看到明文
网站数据库存储用户密码都是存储的密码的MD5值,这样就算黑客得到MD5值,也无法知道密码(不可逆性) - 数字签名
就比如可以在用户登陆的时候,将用户输入的密码与系统数据库中的salt进行MD5,此时生成的字符串与系统中注册生成的MD5值是否一致,判断是否为合法登陆。这样既保证了系统不会知道用户密码明文的情况就可以确定用户登陆的合法性,又可以恶意者增加密码破解的难度
身份认证
介绍
realm:相当于datasource数据源,securityManager进行安全认证需要通过Realm获取数据,但realm并不是只从数据源中获取数据,也包含了认证授权校验相关代码。
流程:
开发
2.1 依赖
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.5.3</version>
</dependency>
2.2 创建resource/shrio,ini配置文件
[users]
xiaochen=123
zhangsan=456
2.3 开发认证
public class TestAuthenticator {
public static void main(String[] args) {
//1.创建安全管理器对象
DefaultSecurityManager securityManager = new DefaultSecurityManager();
//2.给安全管理器设置realm
securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
//3.SecurityUtils 给全局安全工具类设置安全管理器
SecurityUtils.setSecurityManager(securityManager);
//4.关键对象 subject 主体
Subject subject = SecurityUtils.getSubject();
//5.创建令牌
UsernamePasswordToken token = new UsernamePasswordToken("xiaochen","123");
try{
System.out.println("认证状态: "+ subject.isAuthenticated());
subject.login(token);//用户认证
System.out.println("认证状态: "+ subject.isAuthenticated());
}catch (UnknownAccountException e){
e.printStackTrace();
System.out.println("认证失败: 用户名不存在~");
}catch (IncorrectCredentialsException e){
e.printStackTrace();
System.out.println("认证失败: 密码错误~");
}
}
}
但上述的是从shiro.ini配置文件获取用户信息,实际开发中一般都将用户信息存储在数据库之中,因此需要自定义realm
认证使用的是SimpleAccountRealm,源码如下
public class SimpleAccountRealm extends AuthorizingRealm {
//.......省略
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken upToken = (UsernamePasswordToken) token;
SimpleAccount account = getUser(upToken.getUsername());
if (account != null) {
if (account.isLocked()) {
throw new LockedAccountException("Account [" + account + "] is locked.");
}
if (account.isCredentialsExpired()) {
String msg = "The credentials for account [" + account + "] are expired";
throw new ExpiredCredentialsException(msg);
}
}
return account;
}
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = getUsername(principals);
USERS_LOCK.readLock().lock();
try {
return this.users.get(username);
} finally {
USERS_LOCK.readLock().unlock();
}
}
}
因此在自定义realm中实现认证和授权两个方法(现在只实现了认证)
package com.baizhi.realm;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
/**
* 自定义realm实现 将认证|授权数据的来源转为数据库的实现
*/
public class CustomerRealm extends AuthorizingRealm {
//授权
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("==================");
return null;
}
//认证
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//在token中获取用户名
String principal = (String) token.getPrincipal();
System.out.println(principal);
//根据身份信息使用jdbc mybatis查询相关数据库
if("xiaochen".equals(principal)){
//参数1:返回数据库中正确的用户名 //参数2:返回数据库中正确密码 //参数3:提供当前realm的名字 this.getName();
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"123",this.getName());
return simpleAuthenticationInfo;
}
return null;
}
}
2.4 使用MD5和Salt
Salt:由系统随机生成,并且只有系统知道,这样即便两个用户使用了同一个密码,由于系统给他们生成的salt的值不同,散列值也是不同的,这样即便黑客能够获取到密码,也需要获取到系统中salt才能成功登陆
加Salt可以一定程度上解决这一问题。所谓加Salt,就是加点“佐料”。其基本想法是这样的——当用户首次提供密码时(通常是注册时),由系统自动往这个密码里撒一些“佐料”,然后再散列。而当用户登录时,系统为用户提供的代码撒上同样的“佐料”,然后散列,再比较散列值,已确定密码是否正确。
实际应用:是将盐和散列后的值存在数据库中,自动realm从数据库取出盐和加密后的值由shiro完成密码校验。
校验流程:
2.5 实现工具类
package com.lx.shopping.utils;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Arrays;
/*这是一个非常好用的使用MD5+salt加密的工具类。使用这个工具类,非常简单,
从前台拿到密码passwd,直接HexUtil.getEncryptedPwd(passwd)就可以返回一个长度为56的字符串,
可以用来保存到数据库中,相反,登录的时候,因为MD5加密是不可逆的运算,只能拿用户输入的密码走一遍MD5+salt加密之后,
跟数据库中的passwd比较,看是否一致,一致时密码相同,登录成功,通过调用HexUtil.validPasswd(String passwd,String dbPasswd)方法,
就可以了,不用再做其他事。*/
public class MD5Util {
private final static String HEX_NUMS_STR = "0123456789ABCDEF";
private final static Integer SALT_LENGTH = 12;
/**
* 将16进制字符串转换成数组
*
* @return byte[]
* @author jacob
* */
public static byte[] hexStringToByte(String hex) {
/* len为什么是hex.length() / 2 ?
* 首先,hex是一个字符串,里面的内容是像16进制那样的char数组
* 用2个16进制数字可以表示1个byte,所以要求得这些char[]可以转化成什么样的byte[],首先可以确定的就是长度为这个char[]的一半
*/
int len = (hex.length() / 2);
byte[] result = new byte[len];
char[] hexChars = hex.toCharArray();
for (int i = 0; i < len; i++) {
int pos = i * 2;
result[i] = (byte) (HEX_NUMS_STR.indexOf(hexChars[pos]) << 4 | HEX_NUMS_STR
.indexOf(hexChars[pos + 1]));
}
return result;
}
/**
* 将数组转换成16进制字符串
*
* @return String
* @author jacob
*
* */
public static String byteToHexString(byte[] salt){
StringBuffer hexString = new StringBuffer();
for (int i = 0; i < salt.length; i++) {
String hex = Integ