• 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》

  • 相关阅读:
    python基础学习-无参装饰器
    python基础学习-day16==课后作业练习(函数对象和闭包)
    python基础学习-函数闭包
    python基础学习-函数对象与函数嵌套
    4.15作业
    反射、元类
    Mixins、多态、绑定方法与非绑定方法
    propety装饰器、继承
    封装
    4.8作业
  • 原文地址:https://www.cnblogs.com/xiangkejin/p/8974204.html
Copyright © 2020-2023  润新知