程序员最近都爱上了这个网站  程序员们快来瞅瞅吧!  it98k网:it98k.com

本站消息

站长简介/公众号

  出租广告位,需要合作请联系站长


+关注
已关注

分类  

暂无分类

标签  

暂无标签

日期归档  

2023-06(2)

SpringBoot整合Shiro实现密码/验证码登录(多Realm认证)

发布于2021-03-10 18:37     阅读(888)     评论(0)     点赞(2)     收藏(5)


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 认证)就可以了 (有点多,哈哈哈)

原文链接:https://www.cnblogs.com/dmflysky/p/14449727.html



所属网站分类: 技术文章 > 博客

作者:Hdhhd

链接:http://www.javaheidong.com/blog/article/112340/cec6f462a9aa74e203c0/

来源:java黑洞网

任何形式的转载都请注明出处,如有侵权 一经发现 必将追究其法律责任

2 0
收藏该文
已收藏

评论内容:(最多支持255个字符)