shiro的Realm


public class UserRealm extends AuthorizingRealm {  
    private UserService userService = new UserServiceImpl();  
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
        String username = (String)principals.getPrimaryPrincipal();  
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();  
        authorizationInfo.setRoles(userService.findRoles(username));  
        authorizationInfo.setStringPermissions(userService.findPermissions(username));  
        return authorizationInfo;  
    }  
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {  
        String username = (String)token.getPrincipal();  
        User user = userService.findByUsername(username);  
        if(user == null) {  
            throw new UnknownAccountException();//沒找到帳號  
        }  
        if(Boolean.TRUE.equals(user.getLocked())) {  
            throw new LockedAccountException(); //帳號鎖定  
        }  
        //交給AuthenticatingRealm使用CredentialsMatcher進行密碼匹配,如果覺得人家的不好可以在此判斷或自定義實現  
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(  
                user.getUsername(), //用戶名  
                user.getPassword(), //密碼  
                ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt  
                getName()  //realm name  
        );  
        return authenticationInfo;  
    }  
}   

1、UserRealm父類AuthorizingRealm將獲取Subject相關信息分成兩步:獲取身份驗證信息(doGetAuthenticationInfo)及授權信息(doGetAuthorizationInfo);

2、doGetAuthenticationInfo獲取身份驗證相關信息:首先根據傳入的用戶名獲取User信息;然后如果user為空,那么拋出沒找到帳號異常UnknownAccountException;

如果user找到但鎖定了拋出鎖定異常LockedAccountException;最后生成AuthenticationInfo信息,交給間接父類AuthenticatingRealm使用CredentialsMatcher進行判斷密碼是否匹配,

如果不匹配將拋出密碼錯誤異常IncorrectCredentialsException;另外如果密碼重試此處太多將拋出超出重試次數異常ExcessiveAttemptsException;

在組裝SimpleAuthenticationInfo信息時,需要傳入:身份信息(用戶名)、憑據(密文密碼)、鹽(username+salt),CredentialsMatcher使用鹽加密傳入的明文密碼和此處的密文密碼進行匹配。

3、doGetAuthorizationInfo獲取授權信息:PrincipalCollection是一個身份集合,因為我們現在就一個Realm,所以直接調用getPrimaryPrincipal得到之前傳入的用戶名即可;然后根據用戶名調用UserService接口獲取角色及權限信息。

 

我們來看下Realm類的繼承關系:

一般我們使用的是AuthorizingRealm、CasRealm、JdbcRealm,AuthorizingRealm前面已經使用過了。CasRealm、JdbcRealm的使用例子如下:  

 @Bean(name = "myCasRealm")
    public CasRealm myCasRealm(EhCacheManager cacheManager) {
        CasRealm casRealm = new CasRealm();
        casRealm.setCacheManager(cacheManager);
        casRealm.setCasServerUrlPrefix(ShiroCasConfig.casServerUrlPrefix);
        // 客戶端回調地址 https://localhost:8081/cas  用來接收cas服務端票據,並且必須要和下面過濾器攔截的地址一致,攔截之后交由casFilter處理驗證票據
        casRealm.setCasService(ShiroCasConfig.shiroServerUrlPrefix + ShiroCasConfig.casFilterUrlPattern);
        return casRealm;
    }

    @Bean(name="myJdbcRealm")
    public JdbcRealm myJdbcRealm(@Qualifier("shirocasDataSource") DataSource shirocasDataSource){
        JdbcRealm jdbcRealm = new JdbcRealm();
         jdbcRealm.setDataSource(shirocasDataSource);
         String authenticationQuery = "select password from account where name=?";
         jdbcRealm.setAuthenticationQuery(authenticationQuery);        
        String userRolesQuery="SELECT NAME FROM role WHERE id =(SELECT roleId FROM account_role WHERE userId = (SELECT id FROM account WHERE NAME = ?))";
         jdbcRealm.setUserRolesQuery(userRolesQuery);
        String permissionsQuery = "SELECT NAME FROM permission WHERE id in (SELECT permissionId FROM permission_role WHERE (SELECT id FROM role WHERE NAME = ?))";
        jdbcRealm.setPermissionsQuery(permissionsQuery);
        jdbcRealm.setPermissionsLookupEnabled(true);
        return jdbcRealm;
     }

 

casRealm的部分源碼:

/**
 * This realm implementation acts as a CAS client to a CAS server for authentication and basic authorization.
 * <p/>
 * This realm functions by inspecting a submitted {@link org.apache.shiro.cas.CasToken CasToken} (which essentially 
 * wraps a CAS service ticket) and validates it against the CAS server using a configured CAS
 * {@link org.jasig.cas.client.validation.TicketValidator TicketValidator}.
 * <p/>
View Code
/**
     * Authenticates a user and retrieves its information.
     * 
     * @param token the authentication token
     * @throws AuthenticationException if there is an error during authentication.
     */
    @Override
    @SuppressWarnings("unchecked")
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        CasToken casToken = (CasToken) token;
        if (token == null) {
            return null;
        }
        
        String ticket = (String)casToken.getCredentials();
        if (!StringUtils.hasText(ticket)) {
            return null;
        }
        
        TicketValidator ticketValidator = ensureTicketValidator();

        try {
            // contact CAS server to validate service ticket
            Assertion casAssertion = ticketValidator.validate(ticket, getCasService());
            // get principal, user id and attributes
            AttributePrincipal casPrincipal = casAssertion.getPrincipal();
            String userId = casPrincipal.getName();
            log.debug("Validate ticket : {} in CAS server : {} to retrieve user : {}", new Object[]{
                    ticket, getCasServerUrlPrefix(), userId
            });

            Map<String, Object> attributes = casPrincipal.getAttributes();
            // refresh authentication token (user id + remember me)
            casToken.setUserId(userId);
            String rememberMeAttributeName = getRememberMeAttributeName();
            String rememberMeStringValue = (String)attributes.get(rememberMeAttributeName);
            boolean isRemembered = rememberMeStringValue != null && Boolean.parseBoolean(rememberMeStringValue);
            if (isRemembered) {
                casToken.setRememberMe(true);
            }
            // create simple authentication info
            List<Object> principals = CollectionUtils.asList(userId, attributes);
            PrincipalCollection principalCollection = new SimplePrincipalCollection(principals, getName());
            return new SimpleAuthenticationInfo(principalCollection, ticket);
        } catch (TicketValidationException e) { 
            throw new CasAuthenticationException("Unable to validate ticket [" + ticket + "]", e);
        }
    }
    
    /**
     * Retrieves the AuthorizationInfo for the given principals (the CAS previously authenticated user : id + attributes).
     * 
     * @param principals the primary identifying principals of the AuthorizationInfo that should be retrieved.
     * @return the AuthorizationInfo associated with this principals.
     */
    @Override
    @SuppressWarnings("unchecked")
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // retrieve user information
        SimplePrincipalCollection principalCollection = (SimplePrincipalCollection) principals;
        List<Object> listPrincipals = principalCollection.asList();
        Map<String, String> attributes = (Map<String, String>) listPrincipals.get(1);
        // create simple authorization info
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        // add default roles
        addRoles(simpleAuthorizationInfo, split(defaultRoles));
        // add default permissions
        addPermissions(simpleAuthorizationInfo, split(defaultPermissions));
        // get roles from attributes
        List<String> attributeNames = split(roleAttributeNames);
        for (String attributeName : attributeNames) {
            String value = attributes.get(attributeName);
            addRoles(simpleAuthorizationInfo, split(value));
        }
        // get permissions from attributes
        attributeNames = split(permissionAttributeNames);
        for (String attributeName : attributeNames) {
            String value = attributes.get(attributeName);
            addPermissions(simpleAuthorizationInfo, split(value));
        }
        return simpleAuthorizationInfo;
    }
View Code

JdbcRealm的部分源碼:

  /**
     * 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 = ?";

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;
    }


  /**
     * This implementation of the interface expects the principals collection to return a String username keyed off of
     * this realm's {@link #getName() name}
     *
     * @see #getAuthorizationInfo(org.apache.shiro.subject.PrincipalCollection)
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        //null usernames are invalid
        if (principals == null) {
            throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
        }

        String username = (String) getAvailablePrincipal(principals);

        Connection conn = null;
        Set<String> roleNames = null;
        Set<String> permissions = null;
        try {
            conn = dataSource.getConnection();

            // Retrieve roles and permissions from database
            roleNames = getRoleNamesForUser(conn, username);
            if (permissionsLookupEnabled) {
                permissions = getPermissions(conn, username, roleNames);
            }

        } catch (SQLException e) {
            final String message = "There was a SQL error while authorizing user [" + username + "]";
            if (log.isErrorEnabled()) {
                log.error(message, e);
            }

            // Rethrow any SQL errors as an authorization exception
            throw new AuthorizationException(message, e);
        } finally {
            JdbcUtils.closeConnection(conn);
        }

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
        info.setStringPermissions(permissions);
        return info;

    }

  protected Set<String> getRoleNamesForUser(Connection conn, String username) throws SQLException {
        PreparedStatement ps = null;
        ResultSet rs = null;
        Set<String> roleNames = new LinkedHashSet<String>();
        try {
            ps = conn.prepareStatement(userRolesQuery);
            ps.setString(1, username);

            // Execute query
            rs = ps.executeQuery();

            // Loop over results and add each returned role to a set
            while (rs.next()) {

                String roleName = rs.getString(1);

                // Add the role to the list of names if it isn't null
                if (roleName != null) {
                    roleNames.add(roleName);
                } else {
                    if (log.isWarnEnabled()) {
                        log.warn("Null role name found while retrieving role names for user [" + username + "]");
                    }
                }
            }
        } finally {
            JdbcUtils.closeResultSet(rs);
            JdbcUtils.closeStatement(ps);
        }
        return roleNames;
    }

    protected Set<String> getPermissions(Connection conn, String username, Collection<String> roleNames) throws SQLException {
        PreparedStatement ps = null;
        Set<String> permissions = new LinkedHashSet<String>();
        try {
            ps = conn.prepareStatement(permissionsQuery);
            for (String roleName : roleNames) {

                ps.setString(1, roleName);

                ResultSet rs = null;

                try {
                    // Execute query
                    rs = ps.executeQuery();

                    // Loop over results and add each returned role to a set
                    while (rs.next()) {

                        String permissionString = rs.getString(1);

                        // Add the permission to the set of permissions
                        permissions.add(permissionString);
                    }
                } finally {
                    JdbcUtils.closeResultSet(rs);
                }

            }
        } finally {
            JdbcUtils.closeStatement(ps);
        }

        return permissions;
    }
View Code

 

AuthenticationToken

AuthenticationToken用於收集用戶提交的身份(如用戶名)及憑據(如密碼):

Java代碼  
public interface AuthenticationToken extends Serializable {  
    Object getPrincipal(); //身份  
    Object getCredentials(); //憑據  
}   

擴展接口RememberMeAuthenticationToken:提供了“boolean isRememberMe()”現“記住我”的功能;

擴展接口是HostAuthenticationToken:提供了“String getHost()”方法用於獲取用戶“主機”的功能。

Shiro提供了一個直接拿來用的UsernamePasswordToken,用於實現用戶名/密碼Token組,另外其實現了RememberMeAuthenticationToken和HostAuthenticationToken,可以實現記住我及主機驗證的支持。

 

AuthenticationInfo

public interface AuthenticationInfo extends Serializable {
    PrincipalCollection getPrincipals();
    Object getCredentials();
}

 

AuthenticationInfo有兩個作用:

1、如果Realm是AuthenticatingRealm子類,則提供給AuthenticatingRealm內部使用的CredentialsMatcher進行憑據驗證;(如果沒有繼承它需要在自己的Realm中自己實現驗證);

2、提供給SecurityManager來創建Subject(提供身份信息);

 CredentialsMatcher:

public interface CredentialsMatcher {
    boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info);
}

 它的實現:

 public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        Object tokenCredentials = getCredentials(token);
        Object accountCredentials = getCredentials(info);
        return equals(tokenCredentials, accountCredentials);
    }

 

PrincipalCollection

因為我們可以在Shiro中同時配置多個Realm,所以呢身份信息可能就有多個;因此其提供了PrincipalCollection用於聚合這些身份信息:

Java代碼  
public interface PrincipalCollection extends Iterable, Serializable {  
    Object getPrimaryPrincipal(); //得到主要的身份  
    <T> T oneByType(Class<T> type); //根據身份類型獲取第一個  
    <T> Collection<T> byType(Class<T> type); //根據身份類型獲取一組  
    List asList(); //轉換為List  
    Set asSet(); //轉換為Set  
    Collection fromRealm(String realmName); //根據Realm名字獲取  
    Set<String> getRealmNames(); //獲取所有身份驗證通過的Realm名字  
    boolean isEmpty(); //判斷是否為空  
}

因為PrincipalCollection聚合了多個,此處最需要注意的是getPrimaryPrincipal,如果只有一個Principal那么直接返回即可,如果有多個Principal,則返回第一個(因為內部使用Map存儲,所以可以認為是返回任意一個);

 

AuthorizationInfo

 

AuthorizationInfo用於聚合授權信息的:

Java代碼  
public interface AuthorizationInfo extends Serializable {  
    Collection<String> getRoles(); //獲取角色字符串信息  
    Collection<String> getStringPermissions(); //獲取權限字符串信息  
    Collection<Permission> getObjectPermissions(); //獲取Permission對象信息  
}   

當我們使用AuthorizingRealm時,如果身份驗證成功,在進行授權時就通過doGetAuthorizationInfo方法獲取角色/權限信息用於授權驗證。

Shiro提供了一個實現SimpleAuthorizationInfo,大多數時候使用這個即可。

 

 摘自:第六章 Realm及相關對象——《跟我學Shiro》


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM