zoukankan      html  css  js  c++  java
  • Apache Shiro(三)-登录认证和权限管理MD5加密

    md5 加密

    在前面的例子里,用户密码是明文的,这样是有巨大风险的,一旦泄露,就不好了。
    所以,通常都会采用非对称加密,什么是非对称呢?就是不可逆的,而 md5 就是这样一个算法.
    如代码所示 123 用 md5 加密后,得到字符串: 202CB962AC59075B964B07152D234B70
    这个字符串,却无法通过计算,反过来得到源密码是 123.
    这个加密后的字符串就存在数据库里了,下次用户再登陆,输入密码 123, 同样用md5 加密后,再和这个字符串一比较,就知道密码是否正确了。
    如此这样,既能保证用户密码校验的功能,又能保证不暴露密码。

     1 package com.how2java;
     2  
     3 import org.apache.shiro.crypto.hash.Md5Hash;
     4  
     5 public class TestEncryption {
     6  
     7     public static void main(String[] args) {
     8         String password = "123";
     9         String encodedPassword = new Md5Hash(password).toString();
    10          
    11         System.out.println(encodedPassword);
    12     }
    13 }

    这个大家都用过,没啥好说的。


    面讲了md5加密,但是md5加密又有一些缺陷:
    1. 如果我的密码是 123,你的也是 123, 那么md5的值是一样的,那么通过比较加密后的字符串,我就可以反推过来,原来你的密码也是123.
    2. 与上述相同,虽然 md5 不可逆,但是我可以穷举法呀,我把特别常用的100万或者更多个密码的 md5 值记录下来,比如12345的,abcde的。 相当一部分人用的密码也是这些,那么只要到数据库里一找,也很快就可以知道原密码是多少了。这样看上去也就破解了,至少一部分没有想象中那么安全吧。
    为了解决这个问题,引入了盐的概念。 盐是什么意思呢? 比如炒菜,直接使用md5,就是对食材(源密码)进行炒菜,因为食材是一样的,所以炒出来的味道都一样,可是如果加了不同分量的盐,那么即便食材一样,炒出来的味道也就不一样了。

    所以,虽然每次 123 md5 之后都是202CB962AC59075B964B07152D234B70,但是 我加上盐,即 123+随机数,那么md5值不就不一样了吗? 这个随机数,就是盐,而这个随机数也会在数据库里保存下来,每个不同的用户,随机数也是不一样的。
    再就是加密次数,加密一次是202CB962AC59075B964B07152D234B70,我可以加密两次呀,就是另一个数了。 而黑客即便是拿到了加密后的密码,如果不知道到底加密了多少次,也是很难办的。

    在代码里就演示了,如何用Shiro自带的工具类,做生成盐,两次md5的做法,如图所示得到一串机密都很高的密文。

     

    盐
     
     1 package com.how2java;
     2  
     3 import org.apache.shiro.crypto.SecureRandomNumberGenerator;
     4 import org.apache.shiro.crypto.hash.SimpleHash;
     5  
     6 public class TestEncryption {
     7  
     8     public static void main(String[] args) {
     9         String password = "123";
    10         String salt = new SecureRandomNumberGenerator().nextBytes().toString();
    11         int times = 2;
    12         String algorithmName = "md5";
    13          
    14         String encodedPassword = new SimpleHash(algorithmName,password,salt,times).toString();
    15          
    16         System.out.printf("原始密码是 %s , 盐是: %s, 运算次数是: %d, 运算出来的密文是:%s ",password,salt,times,encodedPassword);
    17          
    18     }
    19 }

    数据库调整

    有了以上基础,那么就可以开始在原来的教程里加入对加密的支持了。 在开始之前,要修改一下user表,加上盐 字段: salt。
    因盐是随机数,得保留下来,如果不知道盐巴是多少,我们也就没法判断密码是否正确了。
    1 alter table user add (salt varchar(100) )

     User

     给user实体类添加一个salt属性,并提供getter和setter方法。
     1 package com.how2java;
     2 
     3 public class User {
     4 
     5     private int id;
     6     private String name;
     7     private String salt;
     8     private String password;
     9     public String getName() {
    10         return name;
    11     }
    12     public void setName(String name) {
    13         this.name = name;
    14     }
    15     public String getPassword() {
    16         return password;
    17     }
    18     public void setPassword(String password) {
    19         this.password = password;
    20     }
    21     public int getId() {
    22         return id;
    23     }
    24     public void setId(int id) {
    25         this.id = id;
    26     }
    27     public String getSalt() {
    28         return salt;
    29     }
    30     public void setSalt(String salt) {
    31         this.salt = salt;
    32     }
    33     
    34     
    35 }
    点击展开

    DAO

    增加两个方法 createUser,getUser
    createUser 用于注册,并且在注册的时候,将用户提交的密码加密
    getUser 用于取出用户信息,其中不仅仅包括加密后的密码,还包括盐

      1 package com.how2java;
      2  
      3 import java.sql.Connection;
      4 import java.sql.DriverManager;
      5 import java.sql.PreparedStatement;
      6 import java.sql.ResultSet;
      7 import java.sql.SQLException;
      8 import java.util.HashSet;
      9 import java.util.Set;
     10  
     11 import org.apache.shiro.crypto.SecureRandomNumberGenerator;
     12 import org.apache.shiro.crypto.hash.SimpleHash;
     13  
     14 public class DAO {
     15     public DAO() {
     16         try {
     17             Class.forName("com.mysql.jdbc.Driver");
     18         } catch (ClassNotFoundException e) {
     19             e.printStackTrace();
     20         }
     21     }
     22  
     23     public Connection getConnection() throws SQLException {
     24         return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=UTF-8", "root",
     25                 "admin");
     26     }
     27      
     28     public String createUser(String name, String password) {
     29          
     30         String sql = "insert into user values(null,?,?,?)";
     31          
     32         String salt = new SecureRandomNumberGenerator().nextBytes().toString(); //盐量随机
     33         String encodedPassword= new SimpleHash("md5",password,salt,2).toString();
     34          
     35         try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {
     36              
     37             ps.setString(1, name);
     38             ps.setString(2, encodedPassword);
     39             ps.setString(3, salt);
     40             ps.execute();
     41         } catch (SQLException e) {
     42  
     43             e.printStackTrace();
     44         }
     45         return null;       
     46          
     47     }
     48  
     49     public String getPassword(String userName) {
     50         String sql = "select password from user where name = ?";
     51         try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {
     52              
     53             ps.setString(1, userName);
     54              
     55             ResultSet rs = ps.executeQuery();
     56  
     57             if (rs.next())
     58                 return rs.getString("password");
     59  
     60         } catch (SQLException e) {
     61  
     62             e.printStackTrace();
     63         }
     64         return null;
     65     }
     66     public User getUser(String userName) {
     67         User user = null;
     68         String sql = "select * from user where name = ?";
     69         try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {
     70              
     71             ps.setString(1, userName);
     72              
     73             ResultSet rs = ps.executeQuery();
     74              
     75             if (rs.next()) {
     76                 user = new User();
     77                 user.setId(rs.getInt("id"));
     78                 user.setName(rs.getString("name"));
     79                 user.setPassword(rs.getString("password"));
     80                 user.setSalt(rs.getString("salt"));
     81             }
     82              
     83         } catch (SQLException e) {
     84              
     85             e.printStackTrace();
     86         }
     87         return user;
     88     }
     89      
     90     public Set<String> listRoles(String userName) {
     91          
     92         Set<String> roles = new HashSet<>();
     93         String sql = "select r.name from user u "
     94                 + "left join user_role ur on u.id = ur.uid "
     95                 + "left join Role r on r.id = ur.rid "
     96                 + "where u.name = ?";
     97         try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {
     98             ps.setString(1, userName);
     99             ResultSet rs = ps.executeQuery();
    100              
    101             while (rs.next()) {
    102                 roles.add(rs.getString(1));
    103             }
    104              
    105         } catch (SQLException e) {
    106              
    107             e.printStackTrace();
    108         }
    109         return roles;
    110     }
    111     public Set<String> listPermissions(String userName) {
    112         Set<String> permissions = new HashSet<>();
    113         String sql =
    114             "select p.name from user u "+
    115             "left join user_role ru on u.id = ru.uid "+
    116             "left join role r on r.id = ru.rid "+
    117             "left join role_permission rp on r.id = rp.rid "+
    118             "left join permission p on p.id = rp.pid "+
    119             "where u.name =?";
    120          
    121         try (Connection c = getConnection(); PreparedStatement ps = c.prepareStatement(sql);) {
    122              
    123             ps.setString(1, userName);
    124              
    125             ResultSet rs = ps.executeQuery();
    126              
    127             while (rs.next()) {
    128                 permissions.add(rs.getString(1));
    129             }
    130              
    131         } catch (SQLException e) {
    132              
    133             e.printStackTrace();
    134         }
    135         return permissions;
    136     }
    137 }
    点击展开

    DatabaseRealm

    修改 DatabaseRealm,把用户通过 UsernamePasswordToken 传进来的密码,以及数据库里取出来的 salt 进行加密,加密之后再与数据库里的密文进行比较,判断用户是否能够通过验证。

     1 package com.how2java;
     2  
     3 import java.util.Set;
     4  
     5 import org.apache.shiro.authc.AuthenticationException;
     6 import org.apache.shiro.authc.AuthenticationInfo;
     7 import org.apache.shiro.authc.AuthenticationToken;
     8 import org.apache.shiro.authc.SimpleAuthenticationInfo;
     9 import org.apache.shiro.authc.UsernamePasswordToken;
    10 import org.apache.shiro.authz.AuthorizationInfo;
    11 import org.apache.shiro.authz.SimpleAuthorizationInfo;
    12 import org.apache.shiro.crypto.hash.SimpleHash;
    13 import org.apache.shiro.realm.AuthorizingRealm;
    14 import org.apache.shiro.subject.PrincipalCollection;
    15 import org.apache.shiro.util.ByteSource;
    16  
    17 public class DatabaseRealm extends AuthorizingRealm {
    18  
    19     @Override
    20     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    21          
    22         //能进入到这里,表示账号已经通过验证了
    23         String userName =(String) principalCollection.getPrimaryPrincipal();
    24         //通过DAO获取角色和权限
    25         Set<String> permissions = new DAO().listPermissions(userName);
    26         Set<String> roles = new DAO().listRoles(userName);
    27          
    28         //授权对象
    29         SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
    30         //把通过DAO获取到的角色和权限放进去
    31         s.setStringPermissions(permissions);
    32         s.setRoles(roles);
    33         return s;
    34     }
    35  
    36     @Override
    37     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
    38         //获取账号密码
    39         UsernamePasswordToken t = (UsernamePasswordToken) token;
    40         String userName= token.getPrincipal().toString();
    41         String password =new String(t.getPassword());
    42         //获取数据库中的密码
    43          
    44         User user = new DAO().getUser(userName);
    45         String passwordInDB = user.getPassword();
    46         String salt = user.getSalt();
    47         String passwordEncoded = new SimpleHash("md5",password,salt,2).toString();
    48          
    49         if(null==user || !passwordEncoded.equals(passwordInDB))
    50             throw new AuthenticationException();
    51          
    52         //认证信息里存放账号密码, getName() 是当前Realm的继承方法,通常返回当前类名 :databaseRealm
    53         SimpleAuthenticationInfo a = new SimpleAuthenticationInfo(userName,password,getName());
    54         return a;
    55     }
    56  
    57 }
    点击展开

    shiro.ini

    1 [main]
    2 credentialsMatcher=org.apache.shiro.authc.credential.HashedCredentialsMatcher
    3 credentialsMatcher.hashAlgorithmName=md5
    4 credentialsMatcher.hashIterations=2
    5 credentialsMatcher.storedCredentialsHexEncoded=true
    6  
    7 databaseRealm=com.how2java.DatabaseRealm
    8 databaseRealm.credentialsMatcher=$credentialsMatcher
    9 securityManager.realms=$databaseRealm

    TestShiro

    进行账号密码验证测试。 注意,测试之前要先释放注释,以执行一次注册用户行为,如果不注册,后面肯定是不能验证通过的,因为数据库里没有嘛。

    new DAO().createUser("tom", "123");

    TestShiro

     1 package com.how2java;
     2  
     3 import org.apache.shiro.SecurityUtils;
     4 import org.apache.shiro.authc.AuthenticationException;
     5 import org.apache.shiro.authc.UsernamePasswordToken;
     6 import org.apache.shiro.config.IniSecurityManagerFactory;
     7 import org.apache.shiro.mgt.SecurityManager;
     8 import org.apache.shiro.subject.Subject;
     9 import org.apache.shiro.util.Factory;
    10  
    11 public class TestShiro {
    12     public static void main(String[] args) {
    13         //这里要释放注释,先注册一个用户
    14 //      new DAO().createUser("tom", "123");
    15          
    16         User user = new User();
    17         user.setName("tom");
    18         user.setPassword("123");
    19          
    20         if(login(user))
    21             System.out.println("登录成功");
    22         else
    23             System.out.println("登录失败");
    24          
    25     }
    26      
    27     private static boolean hasRole(User user, String role) {
    28         Subject subject = getSubject(user);
    29         return subject.hasRole(role);
    30     }
    31      
    32     private static boolean isPermitted(User user, String permit) {
    33         Subject subject = getSubject(user);
    34         return subject.isPermitted(permit);
    35     }
    36  
    37     private static Subject getSubject(User user) {
    38         //加载配置文件,并获取工厂
    39         Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
    40         //获取安全管理者实例
    41         SecurityManager sm = factory.getInstance();
    42         //将安全管理者放入全局对象
    43         SecurityUtils.setSecurityManager(sm);
    44         //全局对象通过安全管理者生成Subject对象
    45         Subject subject = SecurityUtils.getSubject();
    46          
    47         return subject;
    48     }
    49      
    50     private static boolean login(User user) {
    51         Subject subject= getSubject(user);
    52         //如果已经登录过了,退出
    53         if(subject.isAuthenticated())
    54             subject.logout();
    55          
    56         //封装用户的数据
    57         UsernamePasswordToken token = new UsernamePasswordToken(user.getName(), user.getPassword());
    58         try {
    59             //将用户的数据token 最终传递到Realm中进行对比
    60             subject.login(token);
    61         } catch (AuthenticationException e) {
    62             //验证错误
    63             return false;
    64         }              
    65          
    66         return subject.isAuthenticated();
    67     }
    68      
    69 }
    点击展开

    最后

    代码下载地址:https://gitee.com/fengyuduke/my_open_resources/blob/master/shiro-md5.rar

  • 相关阅读:
    (中等) HDU 1495 非常可乐,BFS。
    (简单) POJ 1562 Oil Deposits,BFS。
    (简单) POJ 3984 迷宫问题,BFS。
    动态规划(斐波那契系列)---爬楼梯
    回溯---N皇后
    回溯---数独
    回溯---分割字符串使得每个部分都是回文数
    回溯---含有相同元素求子集
    回溯---子集
    回溯---组合求和
  • 原文地址:https://www.cnblogs.com/fengyuduke/p/10404863.html
Copyright © 2011-2022 走看看