Shiro用戶登錄認證、權限授權示例,以及源碼分析(上)

前言

  本篇文章主要講解用戶登錄認證模塊,下節再細講權限。當然,最後筆者會分享整套源碼。由於涉及到前端部分,年尾將至,沒啥時間整那些,因此,提供的代碼只涉及到後臺,並且是封裝過的,複用性高(但是,天上不會掉餡餅,還是要多敲多看多理解)。沒有界面視圖,確實理解比較吃力,望各位多多見諒!!!

Shiro優勢(Thinking)

  shiro認證方式,獲取前端用戶名和密碼,實例化對象UsernamePasswordToken(後續全部交由shiro處理),也就是令牌,再傳遞給Subject,Subject調用自定義AuthorizingRealm,最後將用戶的密碼和鹽值(下文講解)初始化到SimpleAuthenticationInfo對象即可。
  權限授權方式,獲取認證用戶在shiro存儲信息,查詢該用戶相關角色和權限地址,賦值到SimpleAuthorizationInfo對象中。最後通過PermissionsAuthorizationFilter權限過濾器,判斷是否具有相關權限。
  簡單來說,異曲同工,都是把認證或權限信息賦值到shiro指定對象,然後通過過濾器識別判斷。

View

代碼結構視圖
check包:權限認證;factory包:登錄認證;filter包:過濾器。

Fighting

引入shiro相關jar(version:1.4.0)

<!--shiro依賴 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>${shiro.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>${shiro.version}</version>
            <exclusions>
                <exclusion>
                    <artifactId>slf4j-api</artifactId>
                    <groupId>org.slf4j</groupId>
                </exclusion>
            </exclusions>
        </dependency>

登錄過濾器

/**
 * @Description:登錄權限
 * @author: fengjk
 * @time:2017年7月19日 下午5:47:51
 */
public class LoginUserFilter extends UserFilter {

    @Resource
    UserMapper userMapper;
    private static final Logger logger = LoggerFactory.getLogger(LoginUserFilter.class);

    @Resource
    OwnServerProperties ownServerProperties;


    @Override
    protected boolean isAccessAllowed(ServletRequest paramServletRequest,ServletResponse paramServletResponse, Object mappedValue) {

        /**
         * 業務代碼
         * 
         */     

    }

    @Override
    protected boolean onAccessDenied(ServletRequest request,ServletResponse response) throws Exception {
        return super.onAccessDenied(request,response);

    }





}

自定義Realm(關鍵類

public class ShiroDbRealm extends AuthorizingRealm {
    private static final Logger LOGGER = LoggerFactory.getLogger(ShiroDbRealm.class);
    /**
     * 登錄認證
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken)
            throws AuthenticationException {
        IShiro shiroFactory = ShiroFactroy.me();
        UsernamePasswordToken token = (UsernamePasswordToken) authcToken;
        User user = shiroFactory.user(token.getUsername());
        ShiroUser shiroUser = shiroFactory.shiroUser(user);
        SimpleAuthenticationInfo info = shiroFactory.info(shiroUser, user, super.getName());
        return info;
    }

    /**
     * 權限認證
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        IShiro shiroFactory = ShiroFactroy.me();
        ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
        List<Long> roleList = shiroUser.getRoleList();

        Set<String> permissionSet = new HashSet<>();
        Set<String> roleNameSet = new HashSet<>();

        for (Long roleId : roleList) {
            List<String> permissions = shiroFactory.findPermissionsByRoleId(roleId);
            if (permissions != null) {
                for (String permission : permissions) {
                    if (ToolUtil.isNotEmpty(permission)) {
                        permissionSet.add(permission);
                    }
                }
            }
            String roleName = shiroFactory.findRoleNameByRoleId(roleId);
            roleNameSet.add(roleName);
        }

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissionSet);
        info.addRoles(roleNameSet);
        return info;
    }

    /**
     * 設置認證加密方式
     */
    @Override
    public void setCredentialsMatcher(CredentialsMatcher credentialsMatcher) {
        HashedCredentialsMatcher md5CredentialsMatcher = new HashedCredentialsMatcher();
        md5CredentialsMatcher.setHashAlgorithmName(ShiroKit.hashAlgorithmName);
        md5CredentialsMatcher.setHashIterations(ShiroKit.hashIterations);
        super.setCredentialsMatcher(md5CredentialsMatcher);
    }       

}
package com.kmob.powernetwork.operation.shiro.factory;

import java.util.ArrayList;
import java.util.List;

import org.apache.shiro.authc.CredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.kmob.powernetwork.operation.constant.ManagerStatus;
import com.kmob.powernetwork.operation.dao.MenuDao;
import com.kmob.powernetwork.operation.dao.UserMgrDao;
import com.kmob.powernetwork.operation.factory.ConstantFactory;
import com.kmob.powernetwork.operation.model.User;
import com.kmob.powernetwork.operation.shiro.ShiroUser;
import com.kmob.powernetwork.operation.util.Convert;
import com.kmob.powernetwork.operation.util.SpringContextHolder;

@Service
@DependsOn("springContextHolder")
@Transactional(readOnly = true)
public class ShiroFactroy implements IShiro {

    @Autowired
    private UserMgrDao userMgrDao;

    @Autowired
    private MenuDao menuDao;

    public static IShiro me() {
        return SpringContextHolder.getBean(IShiro.class);
    }

    @Override
    public User user(String account) {

        User user = userMgrDao.getByAccount(account);

        // 賬號不存在
        if (null == user) {
            throw new CredentialsException();
        }
        // 賬號被凍結
        if (user.getStatus() != ManagerStatus.OK.getCode()) {
            throw new LockedAccountException();
        }
        return user;
    }

    @Override
    public ShiroUser shiroUser(User user) {
        ShiroUser shiroUser = new ShiroUser();

        shiroUser.setId(user.getId());            // 賬號id
        shiroUser.setAccount(user.getAccount());// 賬號
        shiroUser.setDeptId(user.getDeptid());    // 部門id
        shiroUser.setName(user.getName());        // 用戶名稱

        Long[] roleArray = Convert.toLongArray(true,user.getRoleid());// 角色集合
        List<Long> roleList = new ArrayList<Long>();
        List<String> roleNameList = new ArrayList<String>();
        for (Long roleId : roleArray) {
            roleList.add(roleId);
            roleNameList.add(ConstantFactory.me().getSingleRoleName(roleId));
        }
        shiroUser.setRoleList(roleList);
        shiroUser.setRoleNames(roleNameList);

        return shiroUser;
    }

    @Override
    public List<String> findPermissionsByRoleId(Long roleId) {
        List<String> resUrls = menuDao.getResUrlsByRoleId(roleId);
        return resUrls;
    }

    @Override
    public String findRoleNameByRoleId(Long roleId) {
        return ConstantFactory.me().getSingleRoleTip(roleId);
    }

    @Override
    public SimpleAuthenticationInfo info(ShiroUser shiroUser, User user, String realmName) {
        String credentials = user.getPassword();
        // 密碼加鹽處理
        String source = user.getSalt();
        ByteSource credentialsSalt = new Md5Hash(source);
        return new SimpleAuthenticationInfo(shiroUser, credentials, credentialsSalt, realmName);
    }

}

doGetAuthenticationInfo方法爲登錄認證處理操作,通過UsernamePasswordToken獲取用戶名,再獲取數據庫中用戶信息,最後存儲到SimpleAuthenticationInfo對象中。

Coding

那麼,shiro是如何做登錄校驗呢?原理無非也是驗證密碼。
通過調用AuthenticatingRealm類中assertCredentialsMatch方法,將AuthenticationToken令牌對象與AuthenticationInfo用戶認證存儲信息對象做比較。
這裏寫圖片描述
比較方法就是CredentialsMatcher接口中doCredentialsMatch(),而接口具體實現類就是SimpleCredentialsMatcher。
這裏寫圖片描述
通過該類中equals()判斷,跟平時寫的比較方法一樣。而方法中比較的參數,又是從AuthenticationToken和AuthenticationInfo中getCredentials()獲取的,那麼,獲取的又是什麼內容呢?
這裏寫圖片描述
可以看出,doCredentialsMatch()比較的參數,其實就是認證時候我們獲取到的用戶密碼。但是,有的讀者就會有個疑問,出現兩個用戶密碼相同的情況呢?
那就要靠“顏值”了,沒錯,就是密碼加鹽處理(唯一性),哈哈。上圖可以看到直接實例化shiro中Md5Hash獲取,傳參到SimpleAuthenticationInfo對象。
這裏寫圖片描述
HashedCredentialsMatcher是SimpleCredentialsMatcher的子類,因此,上文提到的doCredentialsMatch()此處也會執行,而比較的內容就是token和info對象中的credentialsSalt加密鹽值。

總結

關於shiro的安全登錄認證講解到此,其實說了那麼多,用起來卻很方便,幾句代碼就搞定了。當然,能用又理解再好不過啦,哈哈。文章如有筆誤,請及時留言告知,萬分感謝!最後,歡迎大家加羣一起討論學習,QQ:583138104
源碼下載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章