Shiro源码分析-----JdbcRealm

       在Shiro中,Realm扮演着和DAO类似的中间人角色,通过Realm获取存储在各种地方的用户凭证及权限,但具体怎么获取则交给用户去实现,实现了解耦。我们可以自己实现Realm,来控制如何获取这些数据。不过Shiro已经贴心的实现了一些通用的Realm,如下图所示:

       通常我们会将用户密码,用户权限存在关系型数据库里面,因此JdbcRealm应该是最常用的。JdbcRealm已经将数据库的读取封装好了,分别是doGetAuthenticationInfo方法和doGetAuthorizationInfo方法。我们以获取认证信息的doGetAuthenticationInfo方法为例:

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)[0];
                break;
            case CRYPT:
                // TODO: separate password and hash from getPasswordForUser[0]
                throw new ConfigurationException("Not implemented yet");
                //break;
            case COLUMN:
                String[] queryResults = getPasswordForUser(conn, username);
                password = queryResults[0];
                salt = queryResults[1];
                break;
            case EXTERNAL:
                password = getPasswordForUser(conn, username)[0];
                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;
    }

获取到前端登录传递过来的用户名,然后调用password = getPasswordForUser(conn, username)[0];方法从数据库获取密码:

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[1];
            break;
        default:
            result = new String[2];
            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[0] = rs.getString(1);
                if (returningSeparatedSalt) {
                    result[1] = rs.getString(2);
                }

                foundResult = true;
            }
        } finally {
            JdbcUtils.closeResultSet(rs);
            JdbcUtils.closeStatement(ps);
        }

        return result;
    }

可以看到都是基础的jdbc代码,那么获取用户名密码的SQL从哪里来的呢?当然这个需要自己提供,不过Shiro给了一个默认的SQL语句:

/**
     * The default query used to retrieve account data for the user.
     */
    protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?";
    

很明显不同的应用表结构是不一样的,Shiro提供这样一个SQL,似乎有点画蛇添足,会有多少人会用默认的SQL呢?根据我自己的表结构,我提供的SQL如下:

 <!-- 配置shrio的验证 -->
    <!--<bean id="shiroRealm" class="com.gameloft9.demo.security.ShiroRealm">-->
        <!--<property name="userServiceImpl" ref="sysUserServiceImpl"/>-->
    <!--</bean>-->

    <bean id="shiroRealm" class="org.apache.shiro.realm.jdbc.JdbcRealm">
        <property name="dataSource" ref="dataSource"/>
        <property name="authenticationQuery" value="select password from USER_TEST  WHERE login_name = ?"/>
        <property name="userRolesQuery" value="select r.role_name from SYS_ROLE_TEST r where r.id in( select t.role_id from SYS_USER_ROLE_TEST t where t.user_id = (select id from user_test where login_name = ?)) and r.is_deleted = '0'"/>
    </bean>

    <!-- 配置shrio的验证管理 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realms">
            <list>
                <ref bean="shiroRealm"/>
            </list>
        </property>
    </bean>

综上所述,我们只需要提供一个DataSource和获取用户认证数据的SQL就可以了,其他的事情就交给Shiro吧!

如果你不想被条条框框束缚,那么可以参考我之前写的Spring mvc集成Shrio,自定义一个Realm,自己实现用户数据的获取逻辑。下面将自定义的Realm贴出来以供参考:

package com.gameloft9.demo.security;

import com.gameloft9.demo.service.api.system.SysUserService;
import com.gameloft9.demo.dataaccess.model.system.UserTest;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

import java.util.List;

/**
 * 认证授权。
 * @author gameloft9
 */
@Slf4j
@Data
public class ShiroRealm extends AuthorizingRealm {

	/**
	 * 通过setter注入,这里没有通过@Autowired注入
	 * */
	private SysUserService userServiceImpl;

	/**
	 * 获取授权信息方法,返回用户角色信息
	 * */
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		if (principals == null) {
			throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
		}

		UserTest user = (UserTest) principals.getPrimaryPrincipal();
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
		if (user != null) {//获取用户角色信息
			List<String> roles = userServiceImpl.getRoleNames(user.getId());
			info.addRoles(roles);
		} else {
			SecurityUtils.getSubject().logout();
		}
		return info;
	}

	/**
	 * 重写回调认证方法,subject.login()调用后回调此方法,获取认证信息。
	 * 如果是与第三方用户系统集成,可在此处进行身份认证,成功后可构造一个同登录token一致的认证信息。
	 * 或者干脆跳过shiro的认证,自己实现认证逻辑,成功后将用户信息放入session、cookie.
	 * */
	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken authcToken) throws AuthenticationException {
		UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
		UserTest user = userServiceImpl.getByLoginName(token.getUsername());

		if (user == null) {//用户不存在
			throw new UnknownAccountException();
		}

		//构造一个用户认证信息并返回,后面会通过这个和token的pwd进行对比。
		return new SimpleAuthenticationInfo(user,user.getPassword(),user.getRealName());
	}
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值