yangcctv 发表于 2018-11-19 12:51:57

Apache Shiro学习笔记(二)身份验证JdbcRealm

鲁春利的工作笔记,好记性不如烂笔头
  

  Shiro默认提供的Realm

  

  实际系统应用中一般继承AuthorizingRealm(授权)即可;其继承了AuthenticatingRealm(即身份验证),而且也间接继承了CachingRealm(带有缓存实现)。
  

  Shiro Realm主要默认实现
  org.apache.shiro.realm.text.IniRealm:
    通过ini文件配置进行验证,
  部分指定用户名/密码及其角色;
  部分指定角色即权限信息;
org.apache.shiro.realm.text.PropertiesRealm:
    user.username=password,role1,role2    指定用户名/密码及其角色;
    role.role1=permission1,permission2      指定角色及权限信息;
org.apache.shiro.realm.jdbc.JdbcRealm:
    通过sql查询相应的信息,    如:
    获取用户密码:“select“select password from users where username = ?”,
    获取用户密码及盐:“select password, password_salt from users where username = ?”
    获取用户角色:“select role_name from user_roles where username = ?”
    获取角色对应的权限信息:“select permission from roles_permissions where role_name = ?”
    也可以调用相应的api进行自定义sql。

  

  JdbcRealm
  1、数据库表
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`username` varchar(20) DEFAULT NULL COMMENT '用户名',
`password` varchar(50) DEFAULT NULL COMMENT '密码',
`password_salt` varchar(10) DEFAULT NULL COMMENT '生成密码时用的随机种子',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
  2、配置文件(shiro-authenticator-jdbc-realm.ini)

# 配置JDBC数据库连接
dataSource=com.alibaba.druid.pool.DruidDataSource
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/spring_test
dataSource.username=root
dataSource.password=Mvtech123!@
# JdbcRealm
jdbcRealm=org.apache.shiro.realm.jdbc.JdbcRealm
jdbcRealm.dataSource=$dataSource
# 重写SQL查询
#jdbcRealm.authenticationQuery = SELECT password FROM ho_user WHERE name = ?
#jdbcRealm.userRolesQuery = SELECT role FROM ho_user WHERE name = ?
#jdbcRealm.permissionsQuery = SELECT permission FROM ho_user WHERE name = ?
# 指定securityManager的realms实现
securityManager.realms=$jdbcRealm  3、测试类
@Test
public void testAuthenticatorFromJDBCRealm () {
    // 1、获取SecurityManager工厂,此处使用Ini配置文件初始化SecurityManager
    Factory factory = new IniSecurityManagerFactory("classpath:shiro/shiro-authenticator-jdbc-realm.ini");
    // 2、得到SecurityManager实例并绑定给SecurityUtils
    org.apache.shiro.mgt.SecurityManager securityManager = factory.getInstance();
    SecurityUtils.setSecurityManager(securityManager);
    // 3、得到Subject及创建用户名/密码身份验证Token(即用户身份/凭证)
    Subject subject = SecurityUtils.getSubject();
    /*
   * 用户身份Token 可能不仅仅是用户名/密码,也可能还有其他的,如登录时允许用户名/邮箱/手机号同时登录。
   */
    UsernamePasswordToken token = new UsernamePasswordToken("lucl", "123");
    try{
      // 4、登录,即身份验证
      subject.login(token);
    } catch (AuthenticationException e) {
      // 5、身份验证失败
      logger.info("用户身份验证失败");
      e.printStackTrace();
    }
    if (subject.isAuthenticated()) {
      logger.info("用户登录成功。");
    } else {
      logger.info("用户登录失败。");
    }
    // 6、退出
    subject.logout();
}  

  org.apache.shiro.realm.jdbc.JdbcRealm源码:
package org.apache.shiro.realm.jdbc;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Realm that allows authentication and authorization via JDBC calls.
* This realm supports caching by extending from {@link org.apache.shiro.realm.AuthorizingRealm}.
*
* @since 0.2
*/
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.
   *   NO_SALT - password hashes are not salted.
   *   CRYPT - password hashes are stored in unix crypt format.
   *   COLUMN - salt is in a separate column in the database.
   *   EXTERNAL - salt is not stored in the database. {@link #getSaltForUser(String)} will be called
   *       to get the salt
   */
    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;
    // 代码略
    // 认证操作的代码
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
      UsernamePasswordToken upToken = (UsernamePasswordToken) token;
      String username = upToken.getUsername();
      // Null username is invalid
      if (username == null) {
            throw new AccountException("Null usernames are not allowed by this realm.");
      }
      Connection conn = null;
      SimpleAuthenticationInfo info = null;
      try {
            conn = dataSource.getConnection();
            String password = null;
            String salt = null;
            switch (saltStyle) {
            case NO_SALT:
                password = getPasswordForUser(conn, username);
                break;
            case CRYPT:
                // TODO: separate password and hash from getPasswordForUser
                throw new ConfigurationException("Not implemented yet");
                //break;
            case COLUMN:
                String[] queryResults = getPasswordForUser(conn, username);
                password = queryResults;
                salt = queryResults;
                break;
            case EXTERNAL:
                password = getPasswordForUser(conn, username);
                salt = getSaltForUser(username);
            }
            if (password == null) {
                throw new UnknownAccountException("No account found for user [" + username + "]");
            }
            info = new SimpleAuthenticationInfo(username, password.toCharArray(), getName());
            if (salt != null) {
                info.setCredentialsSalt(ByteSource.Util.bytes(salt));
            }
      } catch (SQLException e) {
            final String message = "There was a SQL error while authenticating user [" + username + "]";
            if (log.isErrorEnabled()) {
                log.error(message, e);
            }
            // Rethrow any SQL errors as an authentication exception
            throw new AuthenticationException(message, e);
      } finally {
            JdbcUtils.closeConnection(conn);
      }
      return info;
    }
    private String[] getPasswordForUser(Connection conn, String username) throws SQLException {
      String[] result;
      boolean returningSeparatedSalt = false;
      switch (saltStyle) {
      case NO_SALT:
      case CRYPT:
      case EXTERNAL:
            result = new String;
            break;
      default:
            result = new String;
            returningSeparatedSalt = true;
      }
      PreparedStatement ps = null;
      ResultSet rs = null;
      try {
            ps = conn.prepareStatement(authenticationQuery);
            ps.setString(1, username);
            // Execute query
            rs = ps.executeQuery();
            // Loop over results - although we are only expecting one result, since usernames should be unique
            boolean foundResult = false;
            while (rs.next()) {
                // Check to ensure only one row is processed
                if (foundResult) {
                  throw new AuthenticationException("More than one user row found for user [" + username + "]. Usernames must be unique.");
                }
                result = rs.getString(1);
                if (returningSeparatedSalt) {
                  result = rs.getString(2);
                }
                foundResult = true;
            }
      } finally {
            JdbcUtils.closeResultSet(rs);
            JdbcUtils.closeStatement(ps);
      }
      return result;
    }
    // 代码略
}  

  

  




页: [1]
查看完整版本: Apache Shiro学习笔记(二)身份验证JdbcRealm