天天看點

【Springboot+Vue】做一個權限管理背景(五):引入shiro

文章目錄

    • 前言
    • 代碼部分
    • Shiro架構的引入
    • 下章預告
    • 結語

前言

由于将使用者的賬号密碼明文存儲在資料庫中具有不安全性,比如容易洩露,使用者覺得不靠譜等,是以我們需要将使用者的密碼加密存儲在資料庫中。

  • Hash加密
hash 算法(雜湊演算法、摘要算法)即把任意長度的輸入映射為固定長度的輸出,比如密碼 Evanniubi 變成五位的輸出kchpl,這種算法不可逆,且存在資訊損失,雖然随着時間推移,出現了字典法、彩虹表法等優化手段,但本質上想要破解還是靠窮舉與瞎蒙,而且對于複雜密碼來說,破解成本極高。
  • 加鹽加密

    加鹽,是提高 hash 算法的安全性的一個常用手段。其意義在于利用你的賬戶生成“鹽”,通過這個鹽生成密碼并用hash加密。

    為什麼更安全?

    • 黑客就算拿到資料庫中的密碼也無法使用。
    • 即使使用者使用相同的密碼,由于各個使用者的鹽不同,是以也無法破解使用者的密碼。

代碼部分

我們首先要在user表中添加salt字段,存儲每個使用者生成的“鹽”。

【Springboot+Vue】做一個權限管理背景(五):引入shiro

在SpringBoot的Pojo檔案夾下的User同樣增加Salt字段以及get,set方法。

  • 引入Service層

    大型的項目中,一般不由控制層直接調用Mapper層,是以需要提供一個Service層,讓它擔當Controller層與Mapper層的媒介,接收Controller層的請求,處理Mapper層的資料。是以我們在項目檔案下建立Service檔案夾,建立UserService.java檔案,注解@Service,代表Service層,并在其中對UserMapper進行@autowired 。

    @Service
    public class UserService {
        
        @Autowired
        private UserMapper userMapper;
        
        public boolean checkUser(User user){
            User u = userMapper.selectUser(user);
            if(u == null){
                return false;//如果賬号密碼輸入不正确,傳回false
            }
            return true;
        }
        
        public boolean isExist(String username){
            User user = userMapper.isExist(username);
            if(user != null){
                return true;//從資料庫可以查找出使用者 
            }
            return false;
        }
    }
               
  • 使用者注冊

    前面說過,每個使用者生成唯一的鹽,是以我們得保證使用者是唯一的,這樣就需要使用者注冊的時候驗證資料庫中是否存在該賬号。

    <select id="isExist" resultType="com.demo.demo.Pojo.User">
       select * from user where username = #{username}
    </select>
               
    @PostMapping("/register")
    public String register(@RequestBody User user){
        if(userService.isExist(user.getUsername())){
            return "使用者名已存在";
        }
        // 根據使用者名生成鹽
        String salt = new SecureRandomNumberGenerator().nextBytes().toString();
        // 根據生成的鹽和使用者輸入的密碼疊代2次生成新的密碼
        String newPassword = new SimpleHash("md5", user.getPassword(), salt, 2).toString();
        // 存儲使用者資訊,包括 salt 與 hash 後的密碼
        user.setSalt(salt);
        user.setPassword(newPassword);
        userService.add(user);
        return "注冊成功";
    }
               
  • Shiro的Jar包引入
    <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-core</artifactId>
                <version>1.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-web</artifactId>
                <version>1.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.4.0</version>
            </dependency>
               
  • 前端提供一個注冊的按鈕寫注冊函數
    register(){
            var params = {
              username:this.username,
              password:this.password
            }
            axios.post('http://localhost:9527/user/register',params).then(res => {
              consoel.log(res)
            })
          }
               
  • 驗證

    當輸入已存在使用者名:

    【Springboot+Vue】做一個權限管理背景(五):引入shiro
    當輸入不同使用者名:
    【Springboot+Vue】做一個權限管理背景(五):引入shiro
    可以看到,資料庫中存儲了使用者的密碼和鹽值都是加密方式的,大大提高了使用者資訊的安全性。
    【Springboot+Vue】做一個權限管理背景(五):引入shiro
    以後使用者登入的過程:
    • 前端明文送出賬号密碼
    • 後端根據使用者名取出資料庫中對應的鹽值
    • 根據取出的鹽值進行同樣hash加密兩次
    • 比如加密後的結果與資料庫中的是否一緻

Shiro架構的引入

Shiro提供了一系列安全的加密驗證,使用者認證,使用者授權,使用者會話等方法,我們的一切驗證授權手段都是在調用Shiro的API,與之相類似的有Spring Security。

  1. Shiro的三個概念

    Subject:目前與Shiro互動的東西,比如前端發送登入請求到後端,那麼Subject就是送出的使用者類。

    SecurityManager :管理全部的Subject。

    Realms:将使用者資訊進行一系列處理傳遞給SecurityManage。

  2. Realm的配置

    我們說過,Realm是輸入的使用者資訊與SecurityManaga之間的橋梁,那麼我們需要在Realm中做什麼呢?比如對使用者輸入的使用者名變成鹽,對輸入的密碼進行加密等,操作完後才送出給SecurityManage。

    我們建立Realm檔案夾下建立UserRealm.java檔案,在其中對使用者資訊進行處理。

    public class UserRealm extends AuthorizingRealm {//所有自定義的Realm必須繼承AuthorizingRealm
    
        @Autowired
        private UserService userService;
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            return null;//對登入使用者的授權,現在我們先不寫
        }
    
        //此部分處理登入
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            String username = token.getPrincipal().toString();//token由控制層傳入,擷取token中存儲的使用者名
            User user = userService.getUserByName(username);//根據使用者名去資料庫查詢是否存在該使用者
            if(user == null){
                throw new UnknownAccountException();//使用者不存在抛出不存在異常交給控制層處理
            }
            String password = user.getPassword();
            String salt = user.getSalt();
            //再次把salt轉成byte将整個認證交給SecurityManage
            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username,password, ByteSource.Util.bytes(salt),getName());
            return authenticationInfo;
        }
    }
               
  3. Shiro配置

    在Config檔案夾下建立ShiroConfig.java檔案進行Shiro配置

    package com.demo.demo.Confg;
    
    import com.demo.demo.Realm.UserRealm;
    import org.apache.shiro.authc.AbstractAuthenticator;
    import org.apache.shiro.authc.credential.CredentialsMatcher;
    import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
    import org.apache.shiro.spring.LifecycleBeanPostProcessor;
    import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.apache.shiro.mgt.SecurityManager;
    
    @Configuration
    public class ShiroConfig {
        @Bean
        public static LifecycleBeanPostProcessor getLifecycleBeanProcessor() {
            return new LifecycleBeanPostProcessor();
        }
    
        @Bean
        public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            shiroFilterFactoryBean.setSecurityManager(securityManager);
            return shiroFilterFactoryBean;
        }
    
        @Bean
        public SecurityManager securityManager() {
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            securityManager.setRealm(getUserRealm());
            return securityManager;
        }
    
        @Bean
        public UserRealm getUserRealm() {
            UserRealm realm = new UserRealm();
            realm.setCredentialsMatcher(hashedCredentialsMatcher());
            return realm;
        }
    
        @Bean
        public CredentialsMatcher hashedCredentialsMatcher() {
            HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
            hashedCredentialsMatcher.setHashAlgorithmName("md5");
            hashedCredentialsMatcher.setHashIterations(2);
            return hashedCredentialsMatcher;
        }
    
        @Bean
        public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
            AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
            authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
            return authorizationAttributeSourceAdvisor;
        }
    }
    
               
  4. 登入控制層重寫
    @PostMapping("/login")
        public String Login(@RequestBody User user){
            Subject subject = SecurityUtils.getSubject();
            //Shiro幫我們寫好了UsernamePasswordToken,隻要送出賬号密碼,後面的交給Realm,Realm交給SecurityManage
            UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(),user.getPassword());
            //隻要一行代碼就能實作登入
            try {
                subject.login(token);
                return "登入成功";
            }catch (UnknownAccountException e){ //處理我們在Realm中抛出的異常
                return "使用者不存在";
            }catch (AuthenticationException e){ //當Shiro發現使用者的賬号密碼不比對時自動抛出這個異常
                return "賬号或密碼錯誤";
            }
        }
               
  5. 登入驗證一下
    【Springboot+Vue】做一個權限管理背景(五):引入shiro
    發現登入成功,說明我們的Shiro配置完成,已實作基本的登入。

下章預告

  • 對登入的使用者給予授權憑證
  • 登出功能

結語

有問題的可以在評論區留言哦,也可以提出意見或者建議。