快速上手Shiro拦截器、认证、授权功能

本文介绍了如何在Spring Boot项目中集成Shiro与JWT,用于实现用户登录认证、权限拦截和授权功能。详细配置了Shiro的相关组件,包括自定义Realm、过滤器链,并使用Redis作为缓存管理器。此外,还提供了免拦截的URL配置示例。
摘要由CSDN通过智能技术生成

本文主要是为让大家快速上手整合Shiro进入项目,完成拦截,认证,授权等功能,具体的原理可能并不会过多的描述请大家谅解 。

什么是Shiro

Shiro是一个功能强大且易于使用的Java安全框架,它执行身份验证、授权、加密和会话管理。使用Shiro易于理解的API,您可以快速轻松地保护任何应用程序—从最小的移动应用程序到最大的web和企业应用程序。

下文配置的安全管理器和会话管理器中有用到Redis实现的自定义缓存,大家可以要是有自定义缓存的需要可以参考下这篇文章:

Redis实现自定义Shiro的缓存管理器_六木老师的博客-CSDN博客

依赖

        <!-- Shiro JWT begin -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>${shiro.version}</version>
        </dependency>

        <dependency>
            <groupId>nz.net.ultraq.thymeleaf</groupId>
            <artifactId>thymeleaf-layout-dialect</artifactId>
            <version>${thymeleaf-layout-dialect.version}</version>
        </dependency>

        <dependency>
            <groupId>com.github.theborakompanioni</groupId>
            <artifactId>thymeleaf-extras-shiro</artifactId>
            <version>${thymeleaf-extras-shiro.version}</version>
        </dependency>
        <!-- Shiro JWT end -->

Shiro使用自定义Relam实现认证\授权

@Slf4j
public class JwtRealm extends AuthorizingRealm {

    @Resource
    private JwtTokenUtil jwtTokenUtil;

    @DubboReference
    private ISysUserService userService;

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }


    /**
     * 权限信息认证(包括角色以及权限)是用户访问controller的时候才进行验证(redis存储的此处权限信息)
     * 触发检测用户权限时才会调用此方法,例如checkRole,checkPermission
     *
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        log.info("===============Shiro权限认证开始============ [ roles、permissions]==========");
        Long userId = null;
        String loginName = null;
        if (null != principalCollection) {
            LoginUserVo loginUserVoVo = (LoginUserVo) principalCollection.getPrimaryPrincipal();
            userId = loginUserVoVo.getId();
            loginName = loginUserVoVo.getLoginName();
        }
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        //TODO: 设置用户拥有的角色集合
        Set<String> roleSet = userService.selectUserRoles(userId);
        info.setRoles(roleSet);

        //TODO: 设置用户拥有的权限集合
        Set<String> permissionSet = userService.selectUserPermissions(userId);
        info.addStringPermissions(permissionSet);
        log.info("===============Shiro权限认证成功==============");
        return info;
    }

    /**
     * 用户信息认证是在用户进行登录的时候进行验证
     * 验证用户输入的账号和密码是否正确,错误抛出异常
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        log.info("===============Shiro登录认证开始============");
        //TODO: 校验token
        String token = (String) authenticationToken.getCredentials();
        if (StringUtils.isEmpty(token)) {
            throw new AuthenticationException("token为空!");
        }
        //TODO:从token中取出用户名
        String username = jwtTokenUtil.getUserNameFromToken(token);
        if (org.apache.commons.lang3.StringUtils.isEmpty(username)) {
            throw new AuthenticationException("token非法无效!");
        }
        //TODO: 判断用户是否存在
        LoginUserVo loginUserVoVo = userService.selectLoginUserVoByLoginName(username);
        if (ObjectUtils.isEmpty(loginUserVoVo)) {
            throw new AuthenticationException("用户不存在!");
        }
        //TODO: 判断用户状态
        if (loginUserVoVo.getStatus() == 1) {
            throw new AuthenticationException("账号已被锁定,请联系管理员!");
        }

        //TODO:校验token是否超时失效
        if (!jwtTokenUtil.validateToken(token, username)) {
            throw new AuthenticationException("Token失效,请重新登录!");
        }
        log.info("===============Shiro登录认证成功============");
        return new SimpleAuthenticationInfo(loginUserVoVo, token, getName());
    }

    /**
     * 清除当前用户的权限认证缓存
     *
     * @param principals 权限信息
     */
    @Override
    public void clearCache(PrincipalCollection principals) {
        super.clearCache(principals);
    }
}

通过代码实现拦截

@Configuration
public class ShiroJwtConfig {

    @Resource(name = "shiroRedisTemplate")
    private RedisTemplate redisTemplate;

    /**
     * 全局缓存时间,单位为秒
     */
    @Value("${hdw.jwt.expiration}")
    private int cacheLive;

    /**
     * 全局缓存名称前缀,默认为应用名
     */
    @Value("${spring.application.name}")
    private String cacheKeyPrefix;

    @Bean
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * Filter Chain定义说明
     * <p>
     * 1、一个URL可以配置多个Filter,使用逗号分隔
     * 2、当设置多个过滤器时,全部验证通过,才视为通过
     * 3、部分过滤器可指定参数,如perms,roles
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 拦截器
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        //TODO:配置不会被拦截的链接 顺序判断
        filterChainDefinitionMap.put("/sys/captcha", "anon"); //登录验证码接口排除
        filterChainDefinitionMap.put("/sys/login", "anon"); //登录接口排除
        filterChainDefinitionMap.put("/sys/logout", "anon"); //登出接口排除
        filterChainDefinitionMap.put("/sys/encrypt", "anon");//加密
        filterChainDefinitionMap.put("/api/**", "anon");// API接口

        //TODO:开放的静态资源
        filterChainDefinitionMap.put("/favicon.ico", "anon");// 网站图标
        filterChainDefinitionMap.put("/bootstrap/**", "anon");
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/font/**", "anon");
        filterChainDefinitionMap.put("/images/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/plugins/**", "anon");
        filterChainDefinitionMap.put("/upload/**", "anon");
        filterChainDefinitionMap.put("/qr/**", "anon");

        filterChainDefinitionMap.put("/**/*.js", "anon");
        filterChainDefinitionMap.put("/**/*.css", "anon");
        filterChainDefinitionMap.put("/**/*.html", "anon");
        filterChainDefinitionMap.put("/**/*.svg", "anon");
        filterChainDefinitionMap.put("/**/*.pdf", "anon");
        filterChainDefinitionMap.put("/**/*.jpg", "anon");
        filterChainDefinitionMap.put("/**/*.png", "anon");
        filterChainDefinitionMap.put("/**/*.ico", "anon");

        //TODO:排除字体格式的后缀
        filterChainDefinitionMap.put("/**/*.ttf", "anon");
        filterChainDefinitionMap.put("/**/*.woff", "anon");
        filterChainDefinitionMap.put("/**/*.woff2", "anon");

        filterChainDefinitionMap.put("/druid/**", "anon");
        filterChainDefinitionMap.put("/swagger-ui.html", "anon");
        filterChainDefinitionMap.put("/swagger**/**", "anon");
        filterChainDefinitionMap.put("/webjars/**", "anon");
        filterChainDefinitionMap.put("/v2/**", "anon");
        filterChainDefinitionMap.put("/doc.html", "anon");

        //TODO:性能监控
        filterChainDefinitionMap.put("/actuator/**", "anon");

        //TODO:测试示例
        filterChainDefinitionMap.put("/test/**", "anon"); //模板页面

        //TODO:websocket排除
        filterChainDefinitionMap.put("/ws/**", "anon");

        //TODO:添加自己的过滤器并且取名为jwt
        Map<String, Filter> filterMap = new HashMap<String, Filter>(1);
        filterMap.put("jwt", new JwtFilter());
        shiroFilterFactoryBean.setFilters(filterMap);
        //TODO:过滤链定义,从上向下顺序执行,一般将/**放在最为下边
        filterChainDefinitionMap.put("/**", "jwt");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 安全管理器配置
     *
     * @return
     */
    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 设置自定义缓存
        securityManager.setCacheManager(shiroRedisCacheManager());
        // 设置自定义realm
        securityManager.setRealm(jwtRealm());
        securityManager.setSubjectDAO(subjectDAO());
        // 设置自定义会话
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }
    
    //创建自定义realm
    @Bean
    public Realm jwtRealm() {
        JwtRealm jwtRealm = new JwtRealm();
        jwtRealm.setCacheManager(shiroRedisCacheManager());
        jwtRealm.setCachingEnabled(true);
        return jwtRealm;
    }

    /**
     * 缓存管理器
     *
     * @return
     */
    @Bean
    public CacheManager shiroRedisCacheManager() {
        ShiroRedisCacheManager redisCacheManager = new ShiroRedisCacheManager(cacheLive * 1000, cacheKeyPrefix + ":shiro-cache:", redisTemplate);
        return redisCacheManager;

    }

    /**
     * 会话管理器
     *
     * @return
     */
    @Bean
    public SessionManager sessionManager() {
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();

        sessionManager.setCacheManager(shiroRedisCacheManager());
        /**
         * 会话验证
         * Shiro提供了会话验证调度器,用于定期的验证会话是否已过期,如果过期将停止会话;
         * 出于性能考虑,一般情况下都是获取会话时来验证会话是否过期并停止会话的;
         * 但是如在web环境中,如果用户不主动退出是不知道会话是否过期的,因此需要定期的检测会话是否过期,
         * Shiro提供了会话验证调度器SessionValidationScheduler来做这件事情。
         */
        //TODO:单位为毫秒(1秒=1000毫秒)设置为7天
        sessionManager.setSessionValidationInterval(1000 * cacheLive);
        //TODO: 设置全局session超时时间
        sessionManager.setGlobalSessionTimeout(1000 * cacheLive);
        //TODO:删除过期session
        sessionManager.setDeleteInvalidSessions(true);
        //TODO: 开启/禁用绘画验证
        sessionManager.setSessionValidationSchedulerEnabled(false);

        SimpleCookie cookie = new SimpleCookie();
        //TODO:设置Cookie名字,默认为JSESSIONID;
        cookie.setName(cacheKeyPrefix + "-");
        //TODO:设置Cookie的域名,默认空,即当前访问的域名;
        cookie.setDomain("");
        //TODO:设置Cookie的路径,默认空,即存储在域名根下;
        cookie.setPath("");
        //TODO:设置Cookie的过期时间,单位为秒,默认-1表示关闭浏览器时过期Cookie;
        cookie.setMaxAge(cacheLive);
        //TODO:如果设置为true,则客户端不会暴露给客户端脚本代码,使用HttpOnly cookie有助于减少某些类型的跨站点脚本攻击
        cookie.setHttpOnly(true);
        //TODO:sessionManager创建会话Cookie的模板
        sessionManager.setSessionIdCookie(cookie);
        //TODO:是否启用/禁用Session Id Cookie,默认是启用的;如果禁用后将不会设置Session Id Cookie,即默认使用了Servlet容器的JSESSIONID,且通过URL重写(URL中的“;JSESSIONID=id”部分)保存Session Id。
        sessionManager.setSessionIdCookieEnabled(false);
        return sessionManager;
    }

    /**
     * 关闭Shiro自带的Session,详见文档
     * http://shiro.apache.org/session-management.html#SessionManagement
     *
     * @return
     */
    @Bean
    public DefaultSubjectDAO subjectDAO() {
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator sessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        sessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(sessionStorageEvaluator);
        return subjectDAO;
    }

    /**
     * 下面的代码是添加注解支持
     *
     * @return
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }


}

以上拦截认证授权的具体业务场景可以从各自的实际情况进行修改

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

六木老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值