51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

springboot集成用户认证授权框架shiro基础教程

# springboot 集成用户认证授权框架 shiro 基础教程 {#springboot-集成用户认证授权框架-shiro-基础教程}

本文介绍 springboot 项目集成 shiro 框架的步骤,使系统支持用户认证和授权。java 生态中,常用的用户认证授权框架有 2 个:spring security、shiro。而 shiro 因为使用相对简单, 且能满足大部分需求而成为首选的权限框架。shiro 对资源的认证和授权配置非常灵活, 支持全局配置(通过配置类)和局部配置(通过注解)。
提示

本文只讲述最基础的配置,不说废话,带您快速入门。

# 1. 安装依赖 {#_1-安装依赖}

//    权限框架shiro
    compile 'org.apache.shiro:shiro-spring-boot-web-starter:1.6.0'

# 2. 创建 UserRealm 类 {#_2-创建-userrealm-类}

UserRealm 类做的工作只有 2 个:

  1. 认证
    在函数 doGetAuthenticationInfo 中实现用户认证逻辑。 何时执行: 执行后文提到的 ShiroServiceImpl.login 方法会触发认证逻辑。

  2. 授权
    在函数 doGetAuthorizationInfo 中实现授权逻辑。
    何时执行: 只有被访问的资源需要授权访问时, 才执行授权逻辑。

    package com.ruiboyun.facehr.permission.shiro;

    import cn.hutool.core.util.ObjectUtil; import com.ruiboyun.facehr.permission.service.IPermissionService; import com.ruiboyun.facehr.user.entity.User; import com.ruiboyun.facehr.user.service.IUserService; import lombok.extern.log4j.Log4j2; import org.apache.shiro.authc.*; 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 java.util.List;

    @Log4j2 public class UserRealm extends AuthorizingRealm { @Autowired private IUserService iUserService;

     @Autowired
     private IPermissionService iPermissionService;
    

    /**

    • shiro授权
    • @param principals
    • @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  3. // 从shiro取出当前用户对象 User user = (User) principals.getPrimaryPrincipal();

    // 从数据库查询出当前用户的角色列表和权限列表 List permissionStringList = iPermissionService.listPermissionStringByUsername(user.getUsername()); List roleNameList = iPermissionService.listRoleNameByUsername(user.getUsername());

    // 将当前用户的角色和权限列表赋给shiro SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); authorizationInfo.addStringPermissions(permissionStringList); authorizationInfo.addRoles(roleNameList);

         log.info("shiro授权, 完成, user[{}], permissionStringList[{}]", user, permissionStringList);
         return authorizationInfo;
     }
    

    /**

    • shiro认证
    • @param authenticationToken
    • @return
    • @throws AuthenticationException */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) { UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken; User user = iUserService.getByUsername(token.getUsername()); if (ObjectUtil.isNull(user)) { log.error("shiro认证, 失败, 用户不存在, username[{}]", token.getUsername()); return null; } log.info("shiro认证, 完成, user[{}]", user); return new SimpleAuthenticationInfo( user,

    // 注意是从数据库读取的密码,并不是从客户端传入的密码: 若对密码做了加密存储的话,这2个密码的值是不同的 user.getPassword().toCharArray(), getName() ); } }

您只需要根据具体需求, 改动如下逻辑: 根据用户名查询出用户的角色列表和权限列表。
这个逻辑您就可以任意发挥了,甚至不需要数据库,把数据写死都可以。

//        从数据库查询出当前用户的角色列表和权限列表
        List<String> permissionStringList = iPermissionService.listPermissionStringByUsername(user.getUsername());
        List<String> roleNameList = iPermissionService.listRoleNameByUsername(user.getUsername());

为了从简, 本教程假定密码为明文存储, 否则, UserRealm 类还需要做对应修改。

# 3. 创建 shiro 配置类 {#_3-创建-shiro-配置类}

shiro 配置类完成的工作包括:

  • 定义密码匹配器
    若用户是加密的,则需要定义密码匹配器,否则无需定义。本教程没有定义。

  • 以"url 配置"方式初始化认证权限规则
    需要依赖 shiro 提供的认证过滤器或权限过滤器,或者是用户自定义过滤器。

如前所述, 假定用户密码在数据库中没有加密存储。否则, 还需要再定义密码匹配器。

package com.ruiboyun.facehr.config;

import com.ruiboyun.facehr.permission.shiro.UserRealm; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; 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 java.util.LinkedHashMap; import java.util.Map;

/**

  • 权限框架shiro的配置 / @Configuration public class ShiroConfig { /*

    • 自定义realm
    • @return */ @Bean public UserRealm userRealm() { UserRealm userRealm = new UserRealm(); return userRealm; }

    /**

    • 安全管理器
    • @return */ @Bean public DefaultWebSecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(userRealm()); return securityManager; }

    /**

    • 设置过滤规则

    • @param defaultWebSecurityManager

    • @return / @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //设置安全管理器 shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); /*

      • 资源需要认证, 且认证失败时, 跳转的url
      • 注意: 如果不设置,默认会自动寻找Web工程根目录下的"/login.jsp"页面或"/login"映射 */ shiroFilterFactoryBean.setLoginUrl("http://demo.com/login/"); // 资源需要授权, 且授权失败时, 跳转的url shiroFilterFactoryBean.setUnauthorizedUrl("/unauth");

      //指定路径和过滤器的对应关系 //注意此处使用的是LinkedHashMap,是有顺序的,shiro会按从上到下的顺序匹配验证,匹配了就不再继续验证 //所以上面的url要苛刻,宽松的url要放在下面,尤其是"/**"要放到最下面,如果放前面的话其后的验证规则就没作用了。 Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>(); filterChainDefinitionMap.put("/api/user/user/loginByPhoneAndPassword", "anon");

// 其它请求都需要认证 filterChainDefinitionMap.put("/**", "authc");

    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    return shiroFilterFactoryBean;
}

}

提示

将如上配置类复制到您的工程中,需要对如下 3 处按实际情况修改:

  • shiroFilterFactoryBean.setLoginUrl("http://demo.com/login/")
  • shiroFilterFactoryBean.setUnauthorizedUrl("/unauth")
  • filterChainDefinitionMap.put("/api/user/user/loginByPhoneAndPassword", "anon")

若项目中集成了 swagger,则需要对 swagger api 文档的资源访问路径做"匿名访问"配置:

/************** start swagger接口文档支持匿名访问 ***************/
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/v2/api-docs", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
/************** end swagger接口文档支持匿名访问 ***************/

# 4. 创建 ShiroService {#_4-创建-shiroservice}

为了代码清晰, 我们创建一个独立的 ShiroService 类, 用于封装 shiro 的登录和登出逻辑。

package com.ruiboyun.facehr.permission.service.impl;

import com.ruiboyun.facehr.permission.service.IShiroService; import lombok.extern.log4j.Log4j2; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Service;

@Service @Log4j2 public class ShiroServiceImpl implements IShiroService { /** * shiro登录 * * @param username * @param password * @return */ @Override public void login(String username, String password) { Subject user = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(username, password); user.login(token); log.info("shiro登录, 完成, username[{}], password[{}]", username, password); }

/**
 * shiro登出
 */
@Override
public void logout() {
    Subject user = SecurityUtils.getSubject();
    user.logout();
log.info(&amp;quot;shiro登出, user[{}]&amp;quot;, user);

}

}

# 5. 修改业务逻辑 {#_5-修改业务逻辑}

修改登录接口, 补充逻辑: 调用 ShiroServiceImpl.login 方法。
修改登出接口, 补充逻辑: 调用 ShiroServiceImpl.logout 方法。

# 6. 定义请求的认证和授权规则 {#_6-定义请求的认证和授权规则}

请求的认证和授权规则, 有 2 种配置方式:

| 配置方式 | 实现方法 | 控制精度 | 灵活性 | 认证或授权失败的处理方式 | |------------|------------------------------------------------------|---------|--------------------|-------------------------------------------------------------| | 通过"url 配置" | 修改 ShiroConfig.shiroFilterFactoryBean 方法, 追加认证和授权规则。 | 粗粒度控制 | 支持动态配置 | 跳转到配置中指定的 url | | 通过注解 | 在接口定义方法上写注解 | 细粒度地控制。 | 因为注解是写死到代码中,无法动态配置 | 抛出异常。为了更友好的给用户提供错误信息,建议在全局异常捕获处理器中捕获认证或授权的所有异常,并以友好的方式返回给用户 |

为了能够捕获"注解方式"的认证或授权异常, 需要在全局异常处理类中补充如下异常处理函数:

    /**
     * shiro认证异常
     * Shiro在登录认证过程中,认证失败需要抛出的异常
     *
     * @param ex
     * @return
     */
    @ExceptionHandler(value = AuthenticationException.class)
    public Result handleAuthenticationException(AuthenticationException ex) {
//        凭证异常
        if (ex instanceof CredentialsException) {
//            不正确的凭证
            if (ex instanceof IncorrectCredentialsException) {
                log.error("shiro认证异常, 凭证异常, 不正确的凭证");
                return Result.error("账号或密码错误");
            } else if (ex instanceof ExpiredCredentialsException) {
                log.error("shiro认证异常, 凭证异常, 凭证过期");
                return Result.error("账号或密码错误");
            }
        log.error(&quot;shiro认证异常, 凭证异常&quot;);
        return Result.error(&quot;账号或密码错误&quot;);
    }
    //账号异常
    else if (ex instanceof AccountException) {

// 并发访问异常: 多个用户同时登录时抛出 if (ex instanceof ConcurrentAccessException) { log.error("shiro认证异常, 账号异常, 并发访问异常"); return Result.error("不允许多个用户同时登录"); } else if (ex instanceof UnknownAccountException) { log.error("shiro认证异常, 账号异常, 未知的账号"); return Result.error("账号或密码错误"); } else if (ex instanceof ExcessiveAttemptsException) { log.error("shiro认证异常, 账号异常, 认证次数超过限制"); return Result.error("登录次数超过限制"); } else if (ex instanceof DisabledAccountException) { log.error("shiro认证异常, 账号异常, 禁用的账号"); return Result.error("账号已禁用"); } else if (ex instanceof LockedAccountException) { log.error("shiro认证异常, 账号异常, 账号被锁定"); return Result.error("账号被锁定"); }

        log.error(&quot;shiro认证异常, 账号异常&quot;);
        return Result.error(&quot;账号异常&quot;);
    }

// 使用了不支持的Token else if (ex instanceof UnsupportedTokenException) { log.error("shiro认证异常, 使用了不支持的Token"); return Result.error("认证失败"); }

    log.error(&quot;shiro认证异常&quot;);
    return Result.error(&quot;您没有登录,无权执行此操作&quot;);
}

/**

  • shiro授权异常

  • @param ex

  • @return */ @ExceptionHandler(value = AuthorizationException.class) public Result handleAuthorizationException(AuthorizationException ex) { if (ex instanceof UnauthorizedException) { log.error(&quot;shiro授权异常, 无权访问&quot;); return Result.error(&quot;无权访问&quot;); } //当尚未完成成功认证时, 尝试执行授权操作时引发该异常 else if (ex instanceof UnauthenticatedException) { log.error(&quot;shiro授权异常, 没有通过认证无法执行授权操作&quot;); return Result.error(&quot;请先登录&quot;); }

    log.error(&quot;shiro授权异常&quot;); return Result.error(&quot;无权访问&quot;); }

# 7. 验证 {#_7-验证}

完成了如上步骤以后, 就完成了 shiro 的基本集成工作。可以进行功能验证了。

# 参考资料 {#参考资料}

Spring Boot2 整合 Shiro - 仅支持身份认证 (opens new window)
Spring Boot2 整合 Shiro - 密码加密存储 (opens new window)
https://blog.csdn.net/gnail_oug/article/details/80662553
https://segmentfault.com/a/1190000014479154
https://www.cnblogs.com/seve/p/12241197.html
原生和 starter 整合 shiro 的区别 (opens new window)

赞(6)
未经允许不得转载:工具盒子 » springboot集成用户认证授权框架shiro基础教程