认识md5
md5是一种加密算法,保护数据安全(现被证实并不怎么安全),shiro中提供了md5加密算法
salt表示加盐,通过加盐的方式能够进一步提高数据的安全性,所以在数据库users表设计的时候,除了id、username、password之外,还有一列password_salt
//md5加密 Md5Hash hash=new Md5Hash("123"); System.out.println(hash.toString()); //md5加盐 hash=new Md5Hash("123","wit"); System.out.println(hash.toString()); //md5加盐 散列两次,散列次数越多,密码越安全 hash=new Md5Hash("123","wit",2); System.out.println(hash.toString()); hash=new Md5Hash("456","wit",2); System.out.println(hash.toString());
使用md5呢,首先要保证数据库中的密码是(已经用工具)加密过的
自定义的realm
从数据库拿到数据user(id、username、password、password_salt)对象后,下边这行代码传给shiro,shiro就知道需要数据是加密过的,还有一些参数,比如散列次数,加密方式,就需要对realm进行配置
SimpleAuthenticationInfo(username,password,ByteSource.Util.bytes(salt),getName());
package cn.wit.realm; import java.beans.PropertyVetoException; import java.sql.ResultSet; import java.sql.SQLException; 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; import org.apache.shiro.util.ByteSource; import cn.wit.users.Users; import com.mchange.v2.c3p0.ComboPooledDataSource; import java.sql.Connection; import java.sql.PreparedStatement; public class UserRealm extends AuthorizingRealm{ @Override public String getName() { // TODO Auto-generated method stub return "uesrRealm"; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) { return null; } @Override protected AuthenticationInfo doGetAuthenticationInfo( AuthenticationToken taken) throws AuthenticationException { String username=(String) taken.getPrincipal(); Users users=null; Connection conn=null; PreparedStatement ps=null; ResultSet rs=null; ComboPooledDataSource cpds=null; try { //c3p0获取数据库连接conn cpds= new ComboPooledDataSource(); cpds.setDriverClass("com.mysql.jdbc.Driver"); cpds.setJdbcUrl("jdbc:mysql://localhost:3306/login"); cpds.setUser("root"); cpds.setPassword("wityy"); conn = cpds.getConnection(); String sql="select *from users where username=?"; ps= conn.prepareStatement(sql); ps.setObject(1,username); rs=ps.executeQuery(); while(rs.next()){ users=new Users(); users.setId(rs.getInt("id")); users.setUsername(rs.getString("username")); users.setPassword(rs.getString("password")); users.setPassword_salt(rs.getString("password_salt")); } } catch (SQLException e) { e.printStackTrace(); } catch (PropertyVetoException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { try { if(rs!=null){ rs.close(); } } catch (SQLException e) { e.printStackTrace(); } try { if(ps!=null){ ps.close(); } } catch (SQLException e) { e.printStackTrace(); } try { if(conn!=null){ conn.close(); } } catch (SQLException e) { e.printStackTrace(); } } username=users.getUsername(); String password=users.getPassword(); String salt=users.getPassword_salt(); System.out.println(username); System.out.println(password); System.out.println(salt); //从数据库中拿到的密码是经过加密的(并且加了盐),所以数据库中才设计盐这个属性,想要解密不仅要拿到密码,还要从数据库拿到盐 //除此之外,需要设置realm 告知shiro需要用md5的方式进行解密 SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(username,password,ByteSource.Util.bytes(salt),getName()); return info; } }
ini
hashAlgorithmName表示加密方式,hashIterations表示散列次数,credentialsMatcher是关于设置加密的类,将其赋值给realm。
[main] credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher credentialsMatcher.hashAlgorithmName=md5 credentialsMatcher.hashIterations=2 userRealm=cn.wit.realm.UserRealm userRealm.credentialsMatcher=$credentialsMatcher securityManager.realm=$userRealm
public class Authentication { public static void main(String[] args) { //拿到SecurityManager并将它放到环境当中 Factory<SecurityManager>factory=new IniSecurityManagerFactory("classpath:shiro.ini"); SecurityManager securityManager = factory.getInstance(); SecurityUtils.setSecurityManager(securityManager); //拿到subject接口 Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken taken=new UsernamePasswordToken("zhangsan","123"); try { if(taken!=null){ subject.login(taken); } if(subject.isAuthenticated()){ System.out.println("登录成功"); } } catch (UnknownAccountException e) { e.printStackTrace(); System.out.println("账号或密码错误"); }catch (IncorrectCredentialsException e) { e.printStackTrace(); System.out.println("账号或密码错误"); } } }
md5源码跟踪
从login进入源码,subject实现类DelegatingSubject,将自己和taken都给了SecurityManager,实现类为DefaultSecurityManager,该类login方法调用Authenticator的Authenticate方法,进入Authenticator,实现类为ModularRealmAuthenticator,里边有setRealms、setAuthenticationStrategy,可以进行 认证策略和realms的设置,进入Realm接口,在它的实现类AuthenticatingRealm里面有credentialsMatcher属性,表示加密
查看类层次,下边有不同的解密方式的实现类,
这些加密的方式都是继承的HashedCredentialsMatcher,下边的这个类的set方法,我们在ini中设置的正是这些属性,出于对salt信息的保护,所以设置ini只设置了加密方式和散列次数,将salt在自定义的realm中跟账号密码一起传递给shiro