1. 程式人生 > >如果你的shiro沒學明白,那麽應該看看這篇文章,將shiro整合進springboot

如果你的shiro沒學明白,那麽應該看看這篇文章,將shiro整合進springboot

最小 3.2 開始 depend art tostring 登陸 string int

最近在做項目的時候需要用到shiro做認證和授權來管理資源

在網上看了很多文章,發現大多數都是把官方文檔的簡介摘抄一段,然後就開始貼代碼,告訴你怎麽怎麽做,怎麽怎麽做

相信很多小夥伴即使是跟著那些示例代碼做完配完,並且成功搭建,估計也是一頭霧水,可能會不理解,為什麽要這麽做

本人也是在看了大量文章之後,然後又自己動手搭了一便,得出如下使用的感悟,特此分享給大家

依照程序,我要在這裏對shiro做一些簡介,以下內容翻譯與官網首頁

Apache SHIO是一個功能強大、易於使用的Java安全框架,它執行身份驗證、授權、加密和會話管理。有了Shiro易於理解的API,您可以快速輕松地保護任何應用程序——從最小的移動應用程序到最大的Web和企業應用程序。

其次要和相關類似技術進行一個對比

在spring大家族中,安全框架也是肯定有的,那就是spring-security,至於我們為什麽要用shiro而不用spring-security大家可以自行搜索下相關內容我們在此就不過多闡述了

ok,廢話少說,我們直奔主題

可能有些小夥伴在用shiro這種框架的時候會有這種疑惑,我們為什麽要用這樣的框架

首先我們開發者在做一個項目的時候,對資源的權限控制和劃分應該是必不可少的一部分,即哪些資源哪些角色可以訪問,哪些資源哪些角色不可以訪問

那麽試想下這種情況,如果沒有shiro這種框架,你要實現對資源的權限控制,可能會有如下幾點

1,首先你要設計角色

2,為資源分配角色,並且統一註冊進配置文件中

4,給用戶授權

3,每當有新的請求進來時你要驗證當前用戶的身份,然後遍歷資源權限的配置文件,查明當前用戶的角色是否有資格訪問

第1,2步體現在設計層面,第3步要周而復始每當有新的請求的時候都要做一遍的事情,這一步可以使用spring提供的aop機制,配置成前置通知

綜上所述是不是感覺很麻煩

那麽shiro呢,就為我們簡化了上述步驟,使我們能以一種較為簡潔的方式,實現對項目資源的權限控制

同時shiro還集成了很多加密機制(MD5,base64,SHA)等,可以直接以調用的方式對我們的密碼或者報文進行加密措施非常的方便

首先我門引入pom依賴

<!--  shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>

集成shiro非常的簡單,我門只需要自定義一下我們的認證和授權然後配置以下過濾器即可

認證和授權

import com.sc.starry_sky.entity.PageData;
import com.sc.starry_sky.service.UserService;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.ArrayList;

@Component("MyShiroRealm")
public class MyShiroRealm extends AuthorizingRealm{

    //用於用戶查詢
    @Autowired
    private UserService userService;

    //添加角色權限
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //獲取用戶
        PageData user = (PageData) principalCollection.getPrimaryPrincipal();
        //獲取權限列表
        ArrayList<PageData> roles = (ArrayList<PageData>) user.get("roles");
        //添加角色和權限
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        roles.forEach(item -> simpleAuthorizationInfo.addRole(item.getString("roleName")));
        return simpleAuthorizationInfo;
    }

    //登錄認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;

        String username = usernamePasswordToken.getUsername();
        
        PageData user = userService.findUserByUsername(username);
        if (user == null) {
            return null;
        } else {
            SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(user, user.getString("password"), getName());
            return simpleAuthenticationInfo;
        }
    }

}

自定義密碼的校驗規則

import com.sc.starry_sky.util.PasswordUtil;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.springframework.beans.factory.annotation.Autowired;
import com.sc.starry_sky.entity.PageData;
import org.springframework.stereotype.Component;

@Component("CredentialMatcher")
public class CredentialMatcher extends SimpleCredentialsMatcher{
    
    @Autowired
    PasswordUtil passwordUtil;
    
    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        PageData user = (PageData)info.getPrincipals().getPrimaryPrincipal(); //這裏取出的是我們在認證方法中放入的用戶信息也就是我們從數據庫查詢出的用戶信息
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;//這裏是前端發送的用戶名密碼信息
        String requestPwd = new String(usernamePasswordToken.getPassword());
        String requestPwdEncryption = passwordUtil.getEncryptionPassword(requestPwd, user.getString("userId"));//獲取加密後的密碼

        String dbPwd = user.getString("password");
        return requestPwdEncryption.equals(dbPwd);
    }
    

}

密碼加密工具類

import org.apache.shiro.crypto.hash.SimpleHash;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Random;

@Component
public class PasswordUtil {
    
    @Value("${password.algorithmName}") //這個可以在application.properites裏自行配置 如(md5,sha,base64)等等
    private String algorithmName;
    @Value("${password.hashIterations}")//這個是加密的次數
    private int hashIterations;
    
    /**
     * 返回加密後的密碼
     * @param password
     * @param userId
     * @return
     */
    public String getEncryptionPassword(String password , String userId){
      //四個參數的講解 1,加密類型,2,原始密碼,3,鹽值,可以是固定的,這裏我門采用用戶的userId作為用戶的鹽值進行加鹽,4,加密的叠代次數 return new SimpleHash(algorithmName, password, userId, hashIterations).toString(); }
}

配置過濾器

import java.util.HashMap;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ShiroConfig {

    @Autowired
    MyShiroRealm myShiroRealm;
    @Autowired
    CredentialMatcher CredentialMatcher;

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        filterFactoryBean.setSecurityManager(securityManager);

        filterFactoryBean.setLoginUrl("/loginPage");

        // 成功登陸後的界面
        //filterFactoryBean.setSuccessUrl("/indexPage");

        // 沒有權限訪問的界面
        //filterFactoryBean.setUnauthorizedUrl("/unauthorized");

        HashMap<String, String> filterMap = new HashMap<>();

        // 配置不會被攔截的鏈接 順序判斷
        filterMap.put("/static/**", "anon");
        filterMap.put("/doLogin", "anon");
        filterMap.put("/loginPage", "anon");
        filterMap.put("/SubmitRegistInformation", "anon");
        filterMap.put("/SendMessageCode", "anon");
        // 配置退出過濾器,其中的具體的退出代碼Shiro已經替我們實現了
        filterMap.put("/logout", "logout");

        // <!-- 過濾鏈定義,從上向下順序執行,一般將 /**放在最為下邊 -->:這是一個坑呢,一不小心代碼就不好使了;
        // <!-- authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問-->
        filterMap.put("/**", "authc");
        // 所有的頁面都需要user這個角色
        filterMap.put("/**", "roles[user]");

        filterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return filterFactoryBean;
    }

    @Bean("securityManager")
    public SecurityManager securityManager(@Qualifier("myShiroRealm") MyShiroRealm authRealm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(authRealm);
        return manager;
    }

    @Bean("myShiroRealm")
    public MyShiroRealm myShiroRealm() {
        //設置緩存
        myShiroRealm.setCacheManager(new MemoryConstrainedCacheManager());
        //設置密碼校驗規則
        myShiroRealm.setCredentialsMatcher(CredentialMatcher);
        return myShiroRealm;
    }

}

登錄的controller方法

@Controller
public class LoginRegistController extends BaseController {

    @Autowired
    PasswordUtil passwordUtil;

    @Autowired
    UserService userService;

    @PostMapping("/doLogin")
    @ResponseBody
    public Map<String,Object> doLogin(){
        HashMap<String, Object> resultMap = new HashMap<String, Object>();
        try{
            PageData user = this.getPageData();
            UsernamePasswordToken token = new UsernamePasswordToken(user.getString("username"), user.getString("password"));
            SecurityUtils.getSubject().login(token);
            PageData db_user = (PageData) SecurityUtils.getSubject().getPrincipal();//登錄成功之後,取出用戶信息放進session存儲域
            this.getRequest().getSession().setAttribute("user_information",db_user);
            resultMap.put("status", 200);
            resultMap.put("message", "登錄成功");
            return resultMap;
        }catch(Exception e){
            resultMap.put("status", 500);
            resultMap.put("message", e.getMessage());
            return resultMap;
        }
    }


    @GetMapping("/loginPage")
    public ModelAndView loginPage(String name){
        ModelAndView mv = new ModelAndView();
        mv.setViewName("login_regist");
        return mv;
    }
}

這樣一個基礎的shiro就算是搭建完成了,至於其余的高級特性,還需要小夥伴門私下裏自行研究了!

如有不明之處,歡迎下方評論指正,感謝您的閱讀!

如果你的shiro沒學明白,那麽應該看看這篇文章,將shiro整合進springboot