SpringBoot整合Shiro實現密碼/驗證碼登錄(多Realm認證)


shiro安全框架簡介

Apache Shiro是一個強大且易用的Java安全框架,執行身份驗證、授權、密碼和會話管理。

三個核心組件:Subject, SecurityManager 和 Realms.

  • Subject:代表了當前用戶的安全操作,SecurityManager則管理所有用戶的安全操作。
  • SecurityManager:它是Shiro框架的核心,Shiro通過SecurityManager來管理內部組件實例,並通過它來提供安全管理的各種服務。
  • Realm: Realm充當了Shiro與應用安全數據間的“橋梁”或者“連接器”。當對用戶執行認證(登錄)和授權(訪問控制)驗證時,Shiro會從應用配置的Realm中查找用戶及其權限信息。Realm實質上是一個安全相關的DAO:它封裝了數據源的連接細節,並在需要時將相關數據提供給Shiro。當配置Shiro時,你必須至少指定一個Realm,用於認證和(或)授權。
  • 導入依賴(pom.xml) 

        <!--整合Shiro安全框架-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!--集成jwt實現token認證-->
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>3.2.0</version>
        </dependency>
  • 創建 ShiroConfig 配置類

@Configuration
public class ShiroConfig {

    /**
     * ShiroFilterFactoryBean
     * <p>
     * anon:無需認證就可以訪問
     * authc:必須認證才能訪問
     * user:必須擁有 記住我 功能才能用
     * perms:擁有對某個資源的權限能訪問
     * role:擁有某個角色權限能訪問
     */
    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        // 設置安全管理器
        factoryBean.setSecurityManager(defaultWebSecurityManager);
        // 添加shiro的內置過濾器
        Map<String, String> filterMap = new LinkedHashMap<>();
        // 放行不需要權限認證的接口
        // 網站首頁
        filterMap.put("/", "anon");
        filterMap.put("/index", "anon");
        filterMap.put("/index.html", "anon");
        // 不驗證跳轉接口
        filterMap.put("/into/**", "anon");

        // 需要權限認證的接口
        // 驗證跳轉接口
        filterMap.put("/verifyInto/**", "authc");
        
        factoryBean.setFilterChainDefinitionMap(filterMap);

        // 訪問沒有授權的資源
        factoryBean.setLoginUrl("redirect:/into/login");
        // 設置無權限時跳轉的url
        factoryBean.setUnauthorizedUrl("redirect:/into/login");

        return factoryBean;
    }

    /**
     * 管理shiro的生命周期
     */
    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * 注入 密碼登錄CustomRealm
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public UserPasswordRealm userPasswordRealm() {
        return new UserPasswordRealm();
    }

    /**
     * 注入 郵箱驗證登錄EmailRealm
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public UserEmailRealm userEmailRealm() {
        return new UserEmailRealm();
    }

    /**
     * 默認安全管理器
     */
    @Bean
    public DefaultWebSecurityManager securityManager(UserPasswordRealm userPasswordRealm, UserEmailRealm userEmailRealm, AbstractAuthenticator abstractAuthenticator) {
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        List<Realm> realms = new ArrayList<>();
        realms.add(userPasswordRealm);
        realms.add(userEmailRealm);
        defaultWebSecurityManager.setRealms(realms);
        // 記住我
        defaultWebSecurityManager.setRememberMeManager(cookieRememberMeManager());
        defaultWebSecurityManager.setAuthenticator(abstractAuthenticator);
        return defaultWebSecurityManager;
    }

    /**
     * 認證器 把我們的自定義驗證加入到認證器中
     */
    @Bean
    public AbstractAuthenticator abstractAuthenticator(UserPasswordRealm userPasswordRealm, UserEmailRealm userEmailRealm) {
        // 自定義模塊化認證器,用於解決多realm拋出異常問題
        //開始沒用自定義異常問題,發現不管是賬號密碼錯誤還是什么錯誤
        //shiro只會拋出一個AuthenticationException異常
        ModularRealmAuthenticator authenticator = new MyCustomModularRealmAuthenticator();
        // 認證策略:AtLeastOneSuccessfulStrategy(默認),AllSuccessfulStrategy,FirstSuccessfulStrategy
        authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        // 加入realms
        List<Realm> realms = new ArrayList<>();
        realms.add(userPasswordRealm);
        realms.add(userEmailRealm);
        authenticator.setRealms(realms);
        return authenticator;
    }

    /**
     * 加入shiro注解  代理生成器 切面
     */
    @Bean
    @DependsOn({"lifecycleBeanPostProcessor"})
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 加入shiro注解 切點
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    /**
     * 設置cookie 記住我生成cookie
     */
    @Bean
    public CookieRememberMeManager cookieRememberMeManager() {
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        cookieRememberMeManager.setCookie(rememberMeCookie());
        return cookieRememberMeManager;
    }

    /**
     * 設置cookie有效時間
     */
    @Bean
    public SimpleCookie rememberMeCookie() {
        /*這個參數是cookie的名稱,對應前端頁面的checkbox的name=remremberMe*/
        SimpleCookie simpleCookie = new SimpleCookie("rememberMe");
        /*cookie的有效時間為30天,單位秒*/
        simpleCookie.setMaxAge(259200);
        return simpleCookie;
    }

}
  • 創建自定義驗證器 MyCustomModularRealmAuthenticator 類

public class MyCustomModularRealmAuthenticator extends ModularRealmAuthenticator {

    @Override
    protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {
        AuthenticationStrategy authenticationStrategy = this.getAuthenticationStrategy();
        AuthenticationInfo authenticationInfo = authenticationStrategy.beforeAllAttempts(realms, token);

        Iterator var5 = realms.iterator();
        while (var5.hasNext()) {
            Realm realm = (Realm) var5.next();
            authenticationInfo = authenticationStrategy.beforeAttempt(realm, token, authenticationInfo);
            if (realm.supports(token)) {

                AuthenticationInfo info = null;
                Throwable t = null;

                info = realm.getAuthenticationInfo(token);

                authenticationInfo = authenticationStrategy.afterAttempt(realm, token, info, authenticationInfo, t);
            }
        }
        authenticationInfo = authenticationStrategy.afterAllAttempts(token, authenticationInfo);
        return authenticationInfo;
    }
}
  • 創建密碼登錄時驗證授權 UserPasswordRealm 類

@Component
public class UserPasswordRealm extends AuthorizingRealm {

    // 注入用戶業務
    @Autowired
    private UserMapper userMapper;

    /**
     * 授權
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("————密碼授權————doGetAuthorizationInfo————");

        return null;
    }

    /**
     * 認證
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("————密碼認證————doGetAuthenticationInfo————");

        UsernamePasswordToken userToken = (UsernamePasswordToken) token;
        // 連接數據庫  查詢用戶數據
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("user_name", userToken.getUsername());
        User user = userMapper.selectOne(wrapper);
        // 驗證用戶
        if (user == null) {
            throw new UnknownAccountException();
        }
        return new SimpleAuthenticationInfo("", user.getUserPassword(), "");
    }

    /**
     * 用來判斷是否使用當前的 realm
     *
     * @param var1 傳入的token
     * @return true就使用,false就不使用
     */
    @Override
    public boolean supports(AuthenticationToken var1) {
        return var1 instanceof UsernamePasswordToken;
    }

}
  • 創建郵件驗證碼登錄時驗證授權 UserEmailRealm 

@Component
public class UserEmailRealm extends AuthorizingRealm {

    // 注入用戶業務
    @Autowired
    UserService userService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("————郵箱登錄授權————doGetAuthorizationInfo————");
        return null;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("————郵箱登錄認證————doGetAuthenticationInfo————");
        UserEmailToken userEmailToken = (UserEmailToken) token;
        String userEmail = (String) userEmailToken.getPrincipal();
        // 連接數據庫  查詢用戶數據
        QueryWrapper<User> wrapper = new QueryWrapper<>();
        wrapper.eq("user_email", userEmail);
        User user = userService.getOne(wrapper);
        //因為沒有密碼,並且驗證碼在之前就驗證了
        if (user == null) {
            throw new UnknownAccountException();
        }
        return new SimpleAuthenticationInfo("", userEmail, "");
    }

    /**
     * 用來判斷是否使用當前的 realm
     *
     * @param var1 傳入的token
     * @return true就使用,false就不使用
     */
    @Override
    public boolean supports(AuthenticationToken var1) {
        return var1 instanceof UserEmailToken;
    }
}
  • 創建郵件驗證碼登錄驗證通過生成令牌的 UserEmailToken 類(密碼登錄時使用shiro默認的 UsernamePasswordToken 令牌)

@Data  // 使用lombok 生成get方法、set方法
public class UserEmailToken implements HostAuthenticationToken, RememberMeAuthenticationToken {

    private String userEmail;
    private boolean rememberMe;
    private String host;

    public UserEmailToken() {
        this.rememberMe = false;
    }

    public UserEmailToken(String userEmail) {
        this(userEmail, false, null);
    }

    public UserEmailToken(String userEmail, boolean rememberMe) {
        this(userEmail, rememberMe, null);
    }

    public UserEmailToken(String userEmail, boolean rememberMe, String host) {
        this.userEmail = userEmail;
        this.rememberMe = rememberMe;
        this.host = host;
    }

    @Override
    public String getHost() {
        return host;
    }

    @Override
    public boolean isRememberMe() {
        return rememberMe;
    }

    /**
     * 重寫getPrincipal方法
     */
    @Override
    public Object getPrincipal() {
        return userEmail;
    }

    /**
     * 重寫getCredentials方法
     */
    @Override
    public Object getCredentials() {
        return userEmail;
    }
}
  • 創建密碼鹽值加密 MDPasswordUtil 工具類 

public class MDPasswordUtil {

    public String getMDPasswordUtil(String userName, String userPassword) {
        String hashAlgorithmName = "MD5";  // 加密方式:md5加密
        Object credentials = userPassword;  // 密碼
        Object salt = ByteSource.Util.bytes(userName); //
        int hashIterations = 512;  // 加密次數
        Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
        return result.toString();
    }
}
  • 控制層用戶密碼登錄

// 用戶密碼登錄
    @PostMapping("/passwordLogin")
    public String userLogin(@RequestParam("userName") String userName,
                            @RequestParam("userPassword") String userPassword,
                            HttpSession session, Model model) {
        // 獲取當前的用戶
        Subject subject = SecurityUtils.getSubject();
        // 對密碼進行MD5鹽值加密
        String md5Password = new MDPasswordUtil().getMDPasswordUtil(userName, userPassword);
        // 封裝用戶的登錄數據
        UsernamePasswordToken token = new UsernamePasswordToken(userName, md5Password);
        //rememberme記住我
        token.setRememberMe(true);
        try {
            // 登錄,驗證,保存令牌
            subject.login(token);

            //查詢登錄信息
            QueryWrapper<User> wrapper = new QueryWrapper<>();
            wrapper.eq("user_name", userName);
            User user = userService.getOne(wrapper);
            //保存登錄用戶信息
            session.setAttribute(user.getUserId().toString(), user);

           return "admin";
        } catch (UnknownAccountException e) {
            model.addAttribute("userError", "用戶名錯誤!請重新輸入。");
            return "login";
        } catch (IncorrectCredentialsException ice) {
            model.addAttribute("pwError", "密碼錯誤!請重新輸入。");
            return "login";
        }
    }
  • 控制層用戶郵件驗證碼密碼登錄

 // 用戶郵箱登錄
    @PostMapping("/emailLogin")
    public String emailLogin(@RequestParam("userEmail") String userEmail,
                             @RequestParam("emailCode") String emailCode,
                             HttpSession session, Model model) {
        // 根據userEmail從session中取出發送的驗證碼
        String sendEmailCode = (String) session.getAttribute(userEmail);
        // 比對驗證碼
        if (StringUtils.isNoneBlank(sendEmailCode) && sendEmailCode.equals(emailCode)) {
            try {
                UserEmailToken token = new UserEmailToken(userEmail);
                //rememberme記住我
                token.setRememberMe(true);
                // 登錄,驗證,保存令牌
                Subject subject = SecurityUtils.getSubject();
                subject.login(token);

                //查詢登錄信息
                QueryWrapper<User> wrapper = new QueryWrapper<>();
                wrapper.eq("user_email", userEmail);
                User user = userService.getOne(wrapper);
                //保存登錄用戶信息
                session.setAttribute(user.getUserId().toString(), user);

                // 銷毀驗證碼
                session.removeAttribute(emailCode);

                return "admin";
            } catch (Exception e) {
                model.addAttribute("error", "驗證碼錯誤!請重新輸入。");
                return "login";
            }
        } else {
            return "login";
        }
    }
  • SpringBoot 整合 Shiro 密碼登錄與郵件驗證碼登錄(多 Realm 認證)就可以了 (有點多,哈哈哈)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM