zoukankan      html  css  js  c++  java
  • 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》

  • 相关阅读:
    【DFS】XIII Open Championship of Y.Kupala Grodno SU Grodno, Saturday, April 29, 2017 Problem D. Divisibility Game
    【二分】【三分】【计算几何】XIII Open Championship of Y.Kupala Grodno SU Grodno, Saturday, April 29, 2017 Problem L. Lines and Polygon
    【线段树】XIII Open Championship of Y.Kupala Grodno SU Grodno, Saturday, April 29, 2017 Problem J. Jedi Training
    【贪心】【后缀自动机】XIII Open Championship of Y.Kupala Grodno SU Grodno, Saturday, April 29, 2017 Problem E. Enter the Word
    【转载】随机生成k个范围为1-n的随机数,其中有多少个不同的随机数?
    【推导】【贪心】XVII Open Cup named after E.V. Pankratiev Stage 4: Grand Prix of SPb, Sunday, Octorber 9, 2016 Problem H. Path or Coloring
    【枚举】XVII Open Cup named after E.V. Pankratiev Stage 4: Grand Prix of SPb, Sunday, Octorber 9, 2016 Problem D. Cutting Potatoes
    【找规律】【递归】XVII Open Cup named after E.V. Pankratiev Stage 4: Grand Prix of SPb, Sunday, Octorber 9, 2016 Problem F. Doubling
    【贪心】Codeforces Round #436 (Div. 2) D. Make a Permutation!
    【计算几何】【圆反演】计蒜客17314 2017 ACM-ICPC 亚洲区(南宁赛区)网络赛 G. Finding the Radius for an Inserted Circle
  • 原文地址:https://www.cnblogs.com/xiangkejin/p/8974204.html
Copyright © 2011-2022 走看看