澳门金沙vip 8

MySQL的Root用户密码

缘由:最近北京市二环内大兴土木,各种挖沟埋线。忽而一纸通令周末断电,故多年不断电的服务器,便令人有了关机后是否还能正常启动的隐忧。其中一台较年迈的服务器中搭载有MySQL数据库。数据库内容本属于外包项目不需要多操心,但时至于此,为了数据安全备份一下,顺便查看下数据库的结构什么的,也在情理之中。但无奈的是,部署时间过于久远,无人清楚root的密码,由此引发的一系列思考和操作,便记录于此。

Apache Shiro 官网地址:

澳门金沙vip 1

MySQL数据结构

MySQL在安装的时候会默认构建一个叫mysql的数据库,其中有名为user的表,其中主要定义了访问数据库的用户名密码以及CURD权限等。在访问MySQL的时候,会默认到user表里验证当前用户的信息是否匹配。

在首次安装MySQL的时候,默认会产生一个root用户,密码为空。权限固然是最大的,什么都能改,当然也是非常不安全的。所以一般安装之处都会给root设定一个密码。

Table user

Host User Password
localhost root *23AE809DDACAF96AF0FD78ED04B6A265E05AA257
% root *23AE809DDACAF96AF0FD78ED04B6A265E05AA257

从表获取的*23AE809DDACAF96AF0FD78ED04B6A265E05AA257便是root用户的密码,只不过这是加密之后的,也就是说,直接输入这么一长串字符串,是不正确的。于是乎出现了下面的疑问:

  • 在不知道密码的前提下,有方法知道root的密码么?
  • 在不知道密码的前提下,有方法访问数据库么?

Apache Shiro is a powerful and easy-to-use Java security framework that
performs authentication, authorization, cryptography, and session
management. With Shiro’s easy-to-understand API, you can quickly and
easily secure any application – from the smallest mobile applications to
the largest web and enterprise applications.

严格意义上把HASH算法当成加密算法是不严谨的,加密总是相对于解密而言的。因HASH算法不可逆,就无法解密了。

MySQL加密方式

想要了解提出的疑问,首先还是先了解下MySQL的密文是怎么来的。
MySQL内置有PASSWORD()函数,目的就是将明文转换为密文,于是简单做了个实验。

1SELECT PASSWORD('123')

结果:*23AE809DDACAF96AF0FD78ED04B6A265E05AA257

说明明文123对应的密文就是这串带*的字符串。(所以我就很Lucky的知道了root的密码澳门金沙vip 2
但这依旧不能解决疑问,还是要从原理入手,于是查到了MySQL的加密算法SHA-1(澳门金沙vip,百度百科-SHA-1算法)。

算法过程这里不涉及,但是需要了解的是SHA-1加密算法有如下特性

  • 不可以从消息摘要中复原信息
  • 两个不同的消息不会产生同样的消息摘要

简而言之,即使知道了密文,也没有办法通过算法的方式获取明文。
这简直就是直接断了后路…
可事实是Google在2017年时就已经宣布成功攻破SHA-1加密算法,只不过破解它需要的造价不是我等穷苦大众所能接受的。

现在网上也有很多的在线加密解密的工具,例如在线加密解密。但绝大部分在散列/哈希算法领域,也仅提供加密不提供解密。
所以结论依旧是,没办法!

退而求其次,终归是有办法让我们访问自己服务器上的数据库内容的吧?

shiro是一个强大而且简单易用的Java安全框架,主要功能有认证(就是登陆验证),授权(就是权限管理),加密(就是密码加密),session管理。适用于各种大型或者小型企业应用。和Spring
Security比较而言,确实更加简单而且灵活易懂。

开发中,我们要遵循网络开发的原则:

无密码MySQL访问方法

  1. cmd中的mysql\bin文件夹下执行下面语句,用于跳过用户验证访问数据库。
    当然,如果本机有正在运行的mysql的服务需要先停掉。

1mysql\bin>mysqld --skip-grant-tables
  1. 然后打开另外一个cmd,同样在mysql\bin文件夹下执行mysql访问数据库。

1mysql\bin>mysql
  1. 访问名为mysql的数据库。

1mysql>use mysql;
2database changed
  1. 查询user表中已有的用户(可以省略)。

1mysql>SELECT Host,User,Password FROM User;
  1. 如果要更改现有用户的密码,例如root@localhost

1mysql>UPDATE user SET Password=PASSWORD('123') WHERE User='root' AND Host='localhost';
  1. 现有的用户可能正在被某些应用使用,这种情况下可以只增加一个新的用户用于查看数据。

1mysql>INSERT INTO user (Host,User,Password) VALUES ('localhost','NewUser',PASSWORD('123'));

至此我们便有一个已知密码的用户,可以正常通过用户名密码访问数据库了。


1.
shiro中的重要概念

  • 在网络上不允许传输用户的明文隐私数据。
  • 在本地不允许保存用户的明文隐私数据。

扩展

SHA-1破解
常用加密方式AESDESSHA-256MD5BASE64

有兴趣的请自行扩展咯澳门金沙vip 3

要理解shiro,先要理解框架的几个概念:

由此可见,只要牵扯到用户隐私的,我们都要时刻保护起来。今天就通过哈希函数的使用来保护用户的数据。

1) Subject: 代表当前登陆或者访问的用户;

特点:

  • 算法公开。
  • 对相同的数据加密,得到的结果是一样的。
  • 对不同的数据加密,得到的结果是定长的。MD5对不同的数据进行加密,得到的结果都是32个字符。
  • 单向不可逆。

2)Principals:一般指用户名等,唯一表明Subject身份也就是当前用户身份的东西;

种类:

在我们开发当中,常用的哈希(散列)函数有:

  • MD5
  • SHA1
  • SHA256/512

3)Credentials:凭证,一般指密码,对当前登陆用户进行验证;

MD5:

MD5: Message Digest Algorithm
MD5(中文名为消息摘要算法第五版)为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护。
MD5即Message-Digest Algorithm
5(信息-摘要算法5),用于确保信息传输完整一致。是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法),主流编程语言普遍已有MD5实现。…..
(百度查的…)

4)Realms:域,一般是指存储用户信息(用户名,密码,权限,角色)的数据库,也就是保存用户权限等信息的数据源

MD5有一下特点:
  1. 压缩性:任意长度的数据,算出的MD5值长度都是固定的。
  2. 不可逆性: MD5只能加密,不能解密.(理论上是这样的)

由于MD5加密算法具有较好的安全性,而且免费,因此该加密算法被广泛使用:主要运用在数字签名、文件完整性验证以及口令加密等方面。

5)SecurityManager:shiro安全管理的顶级对象。它集合或者说调用所有其它相关组件,负责所有安全和权限相关处理过程,就像一个中央集权政府;

以下列举几种加密方式:

2. shiro的子系统

1. MD5直接加密:

下面使用的常用HASH函数我已经写成了category,有需要的可以去GitHub
download下来。

/**
 *  直接用MD5加密
 */
- (NSString *)digest:(NSString *)str
{
    NSString *anwen = [str md5String];
    NSLog(@"%@ - %@", str, anwen);
    return anwen;

    /**
     (1)直接使用MD5加密(去MD5解密网站即可破解)

     123 - 202cb962ac59075b964b07152d234b70
     abc - 900150983cd24fb0d6963f7d28e17f72
     456 - 250cf8b51c773f3f8dc8b4be867a9a02
     */
}

现在的MD5已不再是绝对安全,网上有免费的对简单的MD5进行解密的网站。对于简单的数据轻而易举就在数据库中搜索出来了。

澳门金沙vip 4

上面我们说到shiro的主要功能有:认证,授权,加密,session管理等。而每一个主要功能对应于shiro的一个子系统:

2. MD5加盐:

对于第一种直接MD5加密,可以对MD5稍作改进,以增加解密的难度,使解密工具很难在数据库中找出对应的MD5真实数据。

//定义一个盐,越乱越好
#define Salt @"fsdhjkfhjksdhjkfjhkd546783765"
/**
 *  加盐
 */
- (NSString *)digest2:(NSString *)str
{
    str = [str stringByAppendingString:Salt];

    NSString *anwen = [str md5String];
    NSLog(@"%@ - %@", str, anwen);
    return anwen;

    /**
     (2)使用加盐(通过MD5解密之后,很容易发现规律)

     123fsdhjkfhjksdhjkfjhkd546783765 - ea4d90423e35126b92565a01c6181517
     abcfsdhjkfhjksdhjkfjhkd546783765 - 09613109a9672ba60c0eb9c456c8b0b2
     456fsdhjkfhjksdhjkfjhkd546783765 - 9125e7153a18c667e8cfe10cfbcc2baa
     */
}

此方法一定程度上增加了破解难度。
还有一些方法如先加盐再调换字符串的顺序、多次加盐等操作原理都是一样的。
这些方法有一个致命的缺点:这个盐是固定不变的,以后无法修改。假如开发人员离职之后把此加密算法泄露出去,后果不堪设想。

澳门金沙vip 5

3. HMAC随机盐:

在我的category中有一个HMAC的哈希函数,

/**
 *  计算HMAC MD5散列结果
 *
 *  终端测试命令:
 *  @code
 *  echo -n "string" | openssl dgst -md5 -hmac "key"
 *  @endcode
 *
 *  @return 32个字符的HMAC MD5散列字符串
 */
- (NSString *)hmacMD5StringWithKey:(NSString *)key;

实现原理是通过key(服务器获取的秘钥)对明文进行密钥拼接,并且做”两次散列”
-> 得到32位结果!
这样就安全多了。
最好是每个账号都有一个唯一的key(服务器随机生成的,随机生成后保存在数据库User表中),即使某个账号的秘钥泄漏了,也不会殃及到其他用户(每个账号的秘钥都不一样)。

下面正对每一个子系统分别介绍。

使用过程:
    1. 用户在注册的那一刻,向服务器索取 密钥(key)!!
    1. 客户端拿到KEY的这一刻,就将KEY保存在本地!!
    1. 切换了新的设备(换手机登录,登录新的已有账号!) —
      重新找服务器获取!!

此种方式已经相对安全多了,但是还有不足之处:一种情况!
如果黑客模拟你的网络请求…
不需要拿到真实密码!用加密后的信息,也可以获得登录之后的权限!!

3. Authentication认证子系统

怎么应对黑客攻击这种情况呢?

让我们的密码具有时效性!! 也就是 加密过后的密码 有时间限制!!

认证子系统,就是处理用户登录,验证用户登录。我们前面讲到Subject代表当前用户,而Principal和credential分别就代表用户名和密码。登录认证时,其实就是调用的
Subject.login(AuthenticationToken
token)方法,AuthenticationToken是一个接口:

在此说下大致思路:

客户端:

  • 点击登录按钮,从服务器获取时间,精确到分。
  • 通过钥匙串访问获取key(服务器生成的秘钥),进行pwd = [pwd hmacMD5StringWithKey:key];。如果未找到,就从服务器从新获取(获取时候可以加入授权认证:例如短信验证码),然后保存到钥匙串中;
  • 经过hmacMD5StringWithKey之后的pwd和服务器返回的时间进行MD5加密;
  • 调用登录接口,把MD5生成的字符串作为密码发送服务器;

服务器:

  • 通过请求的账号把数据库中的密码取出,和服务器时间(精确到分进行)进行MD5;
  • 对比:MD5和接收到的密码对比,不一样就对比(服务器时间上一分钟 +
    数据库密码)MD5;满足其一登录成功!

这样即使别人抓取了登录的密文,也只有在这一分钟内有效。安全系数又提高了一个层次!

public interface AuthenticationToken extends Serializable {
    /**
     * Returns the account identity submitted during the authentication process.*/
    Object getPrincipal();
    /**
     * Returns the credentials submitted by the user during the authentication process that verifies
     * the submitted {@link #getPrincipal() account identity}.*/
    Object getCredentials();
}
对上面提到的钥匙串访问进行简单的说明:
    1. Keychain是苹果的“生态圈”,从iOS7.0.3版本开放给开发者;
    1. 功能:在Mac上能够动态生成复杂密码,帮助用户记住密码!
    ![](https://upload-images.jianshu.io/upload_images/3265534-5046ea22cfb1265d.png)
    1. 明文记录。如果用户访问网站,记住密码,我们还可以看到记住的密码明文!
    1. 本身的所有接口都是 C
      语言的。我们可以借助三方库SSKeychain
    1. 采用的加密方式是 AES 对称加密。

//其中一个的方法:
/**
 *  参数
 *  1. 密码明文
 *  2. 服务,可以随便写,但是他是APP的一个标识,建议用BundleID
 *  3. 账号,用户名
 */
[SSKeychain setPassword:pwd forService:QYLoginServiceName account:accunt];

澳门金沙vip 6

iOS在使用的时候要在 Capabilities 中打开此功能。

千里之行,始于足下~

登录时就会分别调用它的实现类的 getPrincipal() 和 getCredentials() 来获得用户名(Principal:主体)和密码(Credentials:凭证)。一般实际中我们传给Subject.login()方法的是UsernamePasswordToken
类的对象,它实现了该接口:

澳门金沙vip 7

一般我们new一个UsernamePasswordToken的对象:UsernamePasswordToken token
= new UsernamePasswordToken(“xxxusername”, “xxxpassword”);, 然后
subject.login(token); 就前去登录。相关代码一般如下:

    @RequestMapping(value="/loginController", method=RequestMethod.POST)
    public String login(String userName, String password, String rememberMe, String type, HttpServletRequest req) {
        String error = null;
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
        if(rememberMe != null && "true".equals(rememberMe))
            token.setRememberMe(true);    // 记住我        
        try {
            subject.login(token);
        } catch (UnknownAccountException | IncorrectCredentialsException e1) {
            error = "用户名或密码错误";
        }catch(ExcessiveAttemptsException e){
            userService.lockAccountByNo(no);     // 锁定账户
            error = "超过了尝试登录的次数,您的账户已经被锁定。";
        }catch (AuthenticationException e) {    // 其他错误
            if(e.getMessage() != null)
                error = "发生错误:" + e.getMessage();
            else
                error = "发生错误,无法登录。";
        }
        // .. ...

Authentication
子系统会将password加密,然后使用username和加密之后的password和从Realm(一般是数据库)中根据usename获得的密码进行比较,相同就登录成功,不相同同就登录失败,或者用户名不存在也登录失败。就怎么简单。当然从Realm中根据用户名查找用户的过程是需要我们自己编码实现的。该功能的实现,shiro提供了抽象类
AuthenticatingRealm
专门用于从Realm中获得认证信息。所以我们可以继承 抽象类
AuthenticatingRealm,然后实现其中的抽象方法:

/**
 * A top-level abstract implementation of the Realm interface that only implements authentication support
 * (log-in) operations and leaves authorization (access control) behavior to subclasses.
 */
public abstract class AuthenticatingRealm extends CachingRealm implements Initializable {
    //TODO - complete JavaDoc
    private static final Logger log = LoggerFactory.getLogger(AuthenticatingRealm.class);

    // ... ...

    /**
     * Retrieves authentication data from an implementation-specific datasource (RDBMS, LDAP, etc) for the given
     * authentication token.
     * <p/>
     * For most datasources, this means just 'pulling' authentication data for an associated subject/user and nothing
     * more and letting Shiro do the rest.  But in some systems, this method could actually perform EIS specific
     * log-in logic in addition to just retrieving data - it is up to the Realm implementation.*/
    protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException;

我们只要实现 protected abstract AuthenticationInfo
doGetAuthenticationInfo(AuthenticationToken token)
方法就可以了,其它的shiro会回调该方法,进行登录认证。而实现该方法就是直接从数据源中根据
AuthenticationToken 获得数据就行了。

除了这种方法之外,其实我们也可以使用 AuthenticatingRealm 的子类
AuthorizingRealm,它本来是用于权限认证的Realm,但是因为他继承了
AuthenticatingRealm,所以实际上我们只要继承
AuthorizingRealm,然后实现它的抽象方法就行了。同时搞定 登录认证 和
权限认证(访问控制):

public class UserRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String userName = (String)principals.getPrimaryPrincipal();
        User user = userService.getUserByName(userName);
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(userService.findRolesByUserId(user.getId()));
        authorizationInfo.setStringPermissions(userService.findPermissionsByUserId(user.getId()));
        return authorizationInfo;
    }    
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String userName= (String)token.getPrincipal();
        User user = userService.getUserByName(userName);
        if(user == null) {
            throw new UnknownAccountException();//没找到账户
        }
        if(user.getLocked() == 0) {
            throw new LockedAccountException(); //帐号锁定
        }
        if(user.getLocked() == 2){
            throw new AuthenticationException("account was inactive");
        }

        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user.getUserName(), // 用户名
                user.getPassword(), // 密码
                ByteSource.Util.bytes(user.getCredentialsSalt()),    // salt
                getName()  // realm name
        );        
        return authenticationInfo;
    }
    @Override
    public void clearCachedAuthorizationInfo(PrincipalCollection principals) {
        super.clearCachedAuthorizationInfo(principals);
    }
    @Override
    public void clearCachedAuthenticationInfo(PrincipalCollection principals) {
        super.clearCachedAuthenticationInfo(principals);
    }
    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }
    public void clearAllCachedAuthorizationInfo() {
        getAuthorizationCache().clear();
    }
    public void clearAllCachedAuthenticationInfo() {
        getAuthenticationCache().clear();
    }
    public void clearAllCache() {
        clearAllCachedAuthenticationInfo();
        clearAllCachedAuthorizationInfo();
    }
}

上面的 doGetAuthorizationInfo
方法,会在权限认证也就是访问控制时,被回调,而 doGetAuthenticationInfo
方法会在登录认证时被回调,返回的
AuthenticationInfo类型的对象,会和用户登录时输入的
用户名和密码(加密之后的)进行比较,相同则登录成功,反之则登录失败。

其实还有更加简单的方法,因为shiro提供了实现了 AuthorizingRealm
中的抽象方法的子类:

澳门金沙vip 8

比如在数据库环境中,我们就可以直接使用
JdbcRealm,一是可以配置它的相关SQL语句,二是继承它,覆盖它的方法。CasRealm用户单点登录环境。

/**
 * Realm that allows authentication and authorization via JDBC calls.  The default queries suggest a potential schema
 * for retrieving the user's password for authentication, and querying for a user's roles and permissions.  The
 * default queries can be overridden by setting the query properties of the realm.
 * <p/>
 * If the default implementation
 * of authentication and authorization cannot handle your schema, this class can be subclassed and the
 * appropriate methods overridden. (usually {@link #doGetAuthenticationInfo(org.apache.shiro.authc.AuthenticationToken)},
 * {@link #getRoleNamesForUser(java.sql.Connection,String)}, and/or {@link #getPermissions(java.sql.Connection,String,java.util.Collection)}
 * <p/>
 * This realm supports caching by extending from {@link org.apache.shiro.realm.AuthorizingRealm}.*/
public class JdbcRealm extends AuthorizingRealm {
    //TODO - complete JavaDoc
    /*--------------------------------------------
    |             C O N S T A N T S             |
    ============================================*/
    /**
     * The default query used to retrieve account data for the user.
     */
    protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?";    
    /**
     * The default query used to retrieve account data for the user when {@link #saltStyle} is COLUMN.
     */
    protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?/**
     * The default query used to retrieve the roles that apply to a user.
     */
    protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";
    /**
     * The default query used to retrieve permissions that apply to a particular role.
     */
    protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";
    private static final Logger log = LoggerFactory.getLogger(JdbcRealm.class);    
    /**
     * Password hash salt configuration. <ul>
     *   <li>NO_SALT - password hashes are not salted.</li>
     *   <li>CRYPT - password hashes are stored in unix crypt format.</li>
     *   <li>COLUMN - salt is in a separate column in the database.</li> 
     *   <li>EXTERNAL - salt is not stored in the database. {@link #getSaltForUser(String)} will be called
     *       to get the salt</li></ul>
     */
    public enum SaltStyle {NO_SALT, CRYPT, COLUMN, EXTERNAL};
    /*--------------------------------------------
    |    I N S T A N C E   V A R I A B L E S    |
    ============================================*/
    protected DataSource dataSource;
    protected String authenticationQuery = DEFAULT_AUTHENTICATION_QUERY;
    protected String userRolesQuery = DEFAULT_USER_ROLES_QUERY;
    protected String permissionsQuery = DEFAULT_PERMISSIONS_QUERY;
    protected boolean permissionsLookupEnabled = false;    
    protected SaltStyle saltStyle = SaltStyle.NO_SALT;

我们可以对上面给出的sql语句进行配置,修改成对应于我们数据库中表的sql语句,也可以继承该类,然后覆盖doGetAuthenticationInfo, getRoleNamesForUser(), getPermissions()三个方法。

4. Authorization 授权子系统(访问控制)

上一节中我们已经介绍了如何获得用户所拥有的权限,在需要判断用户是否有某权限或者角色时,会自动回调方法
doGetAuthorizationInfo
来获得用户的角色和权限,我们只需要在
该方法中从Realm也就是数据库表中获得相关信息。我们先看一下shiro是如何表示角色和权限的,这一点比较重要: