1. 程式人生 > 程式設計 >Live-Server-4-Shiro的簡單使用

Live-Server-4-Shiro的簡單使用

Apache Shiro是Java的一個安全框架,主要完成:認證、授權、加密、會話管理、快取等功能,而且API簡單,越來越多人使用該框架。

Shiro介紹

基本功能如下:

Shiro基本功能.png

  1. Authentications:身份認證/登入,驗證使用者是否有對應的身份。
  2. Authorization:授權,判斷使用者是否擁有某個許可權,比如當前使用者是否擁有後臺管理員管理的許可權等。
  3. SessionManager:會話管理,使用者登入後的一次會話,在還沒退出之前,都是通過會話來識別使用者。
  4. Cryptography:加密,保證資料的安全性。
  5. Web Support:便於整合到Web環境中。
  6. Cacheing:快取,儲存使用者登入、許可權等資訊。

Shiro-1.png
Shrio api核心是Subject。每個Subject表示一個主體,可以是一個使用者,也可以是一個請求。所有的Subject都會繫結到SecurityManager中,通過SecurityManager.getSubject()方法,可以獲取到當前使用者的subject。 SecurityManager:安全管理器;即所有與安全有關的操作都會與SecurityManager 互動; 且它管理著所有Subject。而SecurityManager是如何判斷使用者的身份和許可權呢?這時就要引入Realm,域的概念,Shiro從Realm中獲取安全資料(如許可權等),提供給SecurityManager來判斷使用者的許可權。

Shiro使用

通過上述的簡介,可以得知,如果我們要對使用者分不同的許可權,如管理員、普通使用者;要對會話進行管理,就需要用到Shiro這個安全框架。Shiro在SpringBoot中的使用就需要建立一個配置類,同時要配置Realm。

一、Shiro配置類

Shiro配置類中,需要配置生命週期處理器、SecurityManager,在該安全管理器中配置Realm、ShiroFilter過濾器、aop註解支援等。

/**
 * spring-boot整合shiro配置類
 */
@Configuration
public class ShiroConfiguration {
    /**
     * 例項化lifecycleBeanPostProcessor
     * shiro生命週期處理器
     *
     * @return
*/
@Bean public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * 例項化DefaultAdvisorAutoProxyCreator * * @return */ @Bean public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator daap = new DefaultAdvisorAutoProxyCreator(); daap.setProxyTargetClass(true); return daap; } /** * 例項化自定義realm * * @return */ @Bean public Realm getRealm() { return new UserRealm(); } /** * 例項化securityManager * * @return */ @Bean(name = "securityManager") public DefaultWebSecurityManager getDefaultWebSecurityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(getRealm()); return securityManager; } /** * 例項化shiroFilter * * @return */ @Bean(name = "shiroFilter") public ShiroFilterFactoryBean getShiroFilterFactoryBean() { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必須設定 SecurityManager shiroFilterFactoryBean.setSecurityManager(getDefaultWebSecurityManager()); // 如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面; shiroFilterFactoryBean.setUnauthorizedUrl("/admin/403"); return shiroFilterFactoryBean; } /** * 開啟shiro aop註解支援. * 使用代理方式;所以需要開啟程式碼支援; * * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor( org.apache.shiro.mgt.SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } } 複製程式碼

二、Realm

一個應用中可以有一個或者多個Realm,可以通過JDBC、記憶體來實現。在該系列文章中Server-2 MyBatis逆向生成中,觀察仔細的朋友會發現,我建立了permission、role、role-permission三張資料表,分別表示了許可權型別、角色型別、角色所擁有的許可權這三種含義。這三張表與Realm之間有什麼關係呢?在專案中建立了UserRealm使用者認證、授權介面。具體程式碼如下:

public class UserRealm extends AuthorizingRealm {
    private final String realmName = "UserRealm.class";
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private PermissionService permissionService;
    @Autowired
    private RoleMapper roleMapper;

    //授權方法
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        Object principal = principals.getPrimaryPrincipal();
        try {
            if (principal instanceof String) {
                UserExample example = new UserExample();
                UserExample.Criteria criteria = example.createCriteria();
                criteria.andAccountEqualTo((String) principal);
                Integer roleId = userMapper.selectByExample(example).get(0).getRoleId();
                SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
                Role role = roleMapper.selectByPrimaryKey(roleId);
                info.addRole(role.getName());
                Set<PermissionVO> permissions = permissionService.getPermissionsBy(roleId);
                for (PermissionVO permission : permissions) {
                    info.addStringPermission(permission.getName());
                }
                return info;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 認證
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        Object principal = token.getPrincipal();
        if (principal instanceof String) {
            String phone = (String) principal;
            UserExample example = new UserExample();
            UserExample.Criteria criteria = example.createCriteria();
            criteria.andAccountEqualTo(phone);
            List<User> users = userMapper.selectByExample(example);
            if (null != users && users.size() > 0) {
                User user = users.get(0);
                return new SimpleAccount(user.getAccount(),user.getPassword(),realmName);
            }
        }
        return null;
    }
}
複製程式碼

在UserRealm中,可以看出,其實認證和授權都是通過資料庫的資料來進行判斷,比如認證,其實就是通過MyBatis來查詢資料庫中是否存在該使用者;授權方法,則是傳入使用者的賬號,檢視該使用者的角色,再查詢該角色所擁有的許可權,將賬號、角色、許可權資訊儲存到AuthorizationInfo中,即完成使用者授權。

三、使用者認證

下面幾行簡單的程式碼就完成了使用者的認證。

Subject subject = SecurityUtils.getSubject();   //獲取Subject
AuthenticationToken token = new UsernamePasswordToken(account,MD5Util.encrypt(password)); //根據使用者賬號、密碼獲取令牌。
try{
    subject.login(token);
}catch( ..){
  ...
}
複製程式碼

首先,第一行程式碼,getSubject(),這裡有個巨坑,我懷疑getSubject()這個方式是根據請求中是否帶有cookie,來判斷是新的subject還是已經存在的subject,因為如果沒有帶cookie的請求,每次獲取的subject都不一致。如果Subject不唯一,那麼會導致Session會話也不唯一,那麼session儲存的鍵值對(通常是一些狀態、引數)就無法使用,導致功能的異常。 如果登入(使用者認證)失敗,那麼就會丟擲不同的異常,比如UnknownAccountException(賬號不存在)、IncorrectCredentialsException(賬號密碼不匹配)、LockedAccountException(不允許登入)等。