前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Shiro关闭session,无状态接入Springboot

Shiro关闭session,无状态接入Springboot

作者头像
taixingyiji
发布2022-07-25 16:46:47
9040
发布2022-07-25 16:46:47
举报
文章被收录于专栏:taixingyiji的博客笔记

# Shiro关闭session配置

Apache_Shiro_logo.svg
Apache_Shiro_logo.svg

# 前言

本文基于token进行身份认证,由于接入cas会和shiro的session管理冲突,所以关闭shiro的session,进行无状态管理。

特此记录一下shiro如何进行无状态管理。

# 一、引入依赖

此处引入的为 shiro-spring ,版本为 1.7.1

代码语言:javascript
复制
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.7.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.0</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
        </dependency>

# 二、实现DefaultWebSubjectFactory

实现 DefaultWebSubjectFactory 关闭session

代码语言:javascript
复制
package com.hcframe.base.module.shiro;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;

/**
 * @author lhc
 * @version 1.0
 * @className StatelessDefaultSubjectFactory
 * @date 2021年04月19日 1:54 下午
 * @description 描述
 */
public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {

    @Override
    public Subject createSubject(SubjectContext context) {
        //不创建session
        context.setSessionCreationEnabled(false);
        return super.createSubject(context);
    }
}

# 三、实现AuthenticationToken

此处是为了将用户信息改为token传递,通过token方式进行验证

代码语言:javascript
复制
package com.hcframe.base.module.shiro;

import org.apache.shiro.authc.AuthenticationToken;

/**
 * @author lhc
 * @version 1.0
 * @className AuthToken
 * @date 2021年04月19日 2:56 下午
 * @description 实现shiro AuthenticationToken
 */
public class AuthToken implements AuthenticationToken {

    private String token;

    public AuthToken(String token) {
        this.token = token;
    }

    @Override
    public Object getPrincipal() {
        return token;
    }

    @Override
    public Object getCredentials() {
        return token;
    }
}

# 四、实现shiro的过滤器

此处为权限过滤器,具体内容参见注释

代码语言:javascript
复制
package com.hcframe.base.module.shiro;

import com.alibaba.fastjson.JSON;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author lhc
 * @version 1.0
 * @className AuthFilter
 * @date 2021年04月19日 2:56 下午
 * @description 实现shiro 过滤器
 */
public class AuthFilter extends AuthenticatingFilter {

    /**
     * @author lhc
     * @description 创建token
     * @date 4:35 下午 2021/4/26
     * @params [request, response]
     * @return org.apache.shiro.authc.AuthenticationToken
     **/
    @Override
    protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
        //获取请求token
        String token = getRequestToken((HttpServletRequest) request);
        if (StringUtils.isBlank(token)) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            // 增加跨域支持
            String myOrigin = httpServletRequest.getHeader("origin");
            httpResponse.setContentType("application/json;charset=utf-8");
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with, X-Access-Token, datasource-Key");
            httpResponse.setHeader("Access-Control-Allow-Origin", myOrigin);
            httpResponse.setCharacterEncoding("UTF-8");
            // 返回错误状态信息
            Map<String, Object> result = new HashMap<>();
            result.put("code", 3);
            result.put("msg", "未登陆");
            String json = JSON.toJSONString(result);
            httpResponse.getWriter().print(json);
            return null;
        }
        return new AuthToken(token);
    }

    /**
     * @author lhc
     * @description 步骤1.所有请求全部拒绝访问
     * @date 4:37 下午 2021/4/26
     * @params [request, response, mappedValue]
     * @return boolean
     **/
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return ((HttpServletRequest) request).getMethod().equals(RequestMethod.OPTIONS.name());
    }

    /**
     * @author lhc
     * @description 步骤2,拒绝访问的请求,会调用onAccessDenied方法,onAccessDenied方法先获取 token,再调用executeLogin方法
     * @date 4:37 下午 2021/4/26
     * @params [request, response]
     * @return boolean
     **/
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        //获取请求token,如果token不存在,直接返回
        String token = getRequestToken((HttpServletRequest) request);
        if (StringUtils.isBlank(token)) {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            HttpServletRequest httpServletRequest = (HttpServletRequest) request;
            // 增加跨域支持
            String myOrigin = httpServletRequest.getHeader("origin");
            httpResponse.setContentType("application/json;charset=utf-8");
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with, X-Access-Token, datasource-Key");
            httpResponse.setHeader("Access-Control-Allow-Origin", myOrigin);
            httpResponse.setCharacterEncoding("UTF-8");
            // 返回错误状态信息
            Map<String, Object> result = new HashMap<>();
            result.put("code", 3);
            result.put("msg", "未登陆");
            String json = JSON.toJSONString(result);
            httpResponse.getWriter().print(json);
            return false;
        }
        return executeLogin(request, response);
    }

    /**
     * @author lhc
     * @description 登陆失败时候调用
     * @date 4:38 下午 2021/4/26
     * @params [token, e, request, response]
     * @return boolean
     **/
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {
        //处理登录失败的异常
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setContentType("application/json;charset=utf-8");
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String myOrigin = httpServletRequest.getHeader("origin");
        httpResponse.setHeader("Access-Control-Allow-Headers", "x-requested-with, X-Access-Token, datasource-Key");
        httpResponse.setHeader("Access-Control-Allow-Origin", myOrigin);
        httpResponse.setCharacterEncoding("UTF-8");
        try {
            //处理登录失败的异常
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            Map<String, Object> result = new HashMap<>();
            result.put("code", 3);
            result.put("msg", "未登陆");
            String json = JSON.toJSONString(result);
            httpResponse.getWriter().print(json);
        } catch (IOException e1) {
        }
        return false;
    }

    /**
     * @author lhc
     * @description 获取请求的token
     * @date 4:38 下午 2021/4/26
     * @params [httpRequest]
     * @return java.lang.String
     **/
    private String getRequestToken(HttpServletRequest httpRequest) {
        //从header中获取token
        String token = httpRequest.getHeader("X-Access-Token");
        //如果header中不存在token,则从参数中获取token
        if (StringUtils.isBlank(token)) {
            if (StringUtils.isBlank(token)) {
                token = httpRequest.getParameter("token");
            }
        }
        return token;
    }
}

# 五、编写自定义的Realm

编写自定义realm,此步骤是为了定义权限校验和用户信息验证。

代码语言:javascript
复制
package com.hcframe.base.module.shiro;


import com.hcframe.base.common.config.FrameConfig;
import com.hcframe.base.module.shiro.service.ShiroService;
import com.hcframe.base.module.shiro.service.SystemRealm;
import com.hcframe.redis.RedisUtil;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.AssertionImpl;
import org.springframework.beans.factory.annotation.Autowired;

import javax.annotation.Resource;
import java.util.Date;
import java.util.Map;


/**
 * @author lhc
 * @version 1.0
 * @className CustomRealm
 * @date 2021年04月19日 2:56 下午
 * @description 自定义Realm
 */
public class CustomRealm extends AuthorizingRealm {

    @Resource
    private RedisUtil redisUtil;

    @Autowired
    AuthService authService;

    @Resource
    private SystemRealm systemRealm;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        Object user = principalCollection.getPrimaryPrincipal();
        Map<String, Object> map = (Map<String, Object>) user;
        // 从数据库读取权限,注入到shiro中
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        Set<String> set = authService.getUserAuth(String.valueOf(map.get("ID")));
        for (String auth : set) {
            simpleAuthorizationInfo.addStringPermission(auth);
        }
        return simpleAuthorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
        String accessToken = (String) token.getPrincipal();
        //1. 根据accessToken,查询用户信息
        FtToken tokenEntity = shiroService.findByToken(accessToken);
        userId = tokenEntity.getUserId();
        //2. token失效
        if (tokenEntity.getExpireTime().getTime() < System.currentTimeMillis()) {
            throw new IncorrectCredentialsException("token失效,请重新登录");
        }
        
        //3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
        Object user = shiroService.findByUserId(userId);
        //4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
        if (user == null) {
            throw new UnknownAccountException("用户不存在!");
        }
        //5. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
        return new SimpleAuthenticationInfo(user, accessToken, this.getName());
    }

    @Override
    public boolean supports(AuthenticationToken authenticationToken) {
        return authenticationToken instanceof AuthToken;
    }
}

# 六、编写Shiro配置类

编写shiro配置类,将bean交给Spring管理

代码语言:javascript
复制
package com.hcframe.base.module.shiro;

import com.hcframe.base.module.shiro.service.SystemRealm;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.DefaultSessionManager;
import org.apache.shiro.session.mgt.SessionManager;
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.apache.shiro.web.mgt.DefaultWebSubjectFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {

    /**
     * 不加这个注解不生效,具体不详
     */
    @Bean
    @ConditionalOnMissingBean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAAP = new DefaultAdvisorAutoProxyCreator();
        defaultAAP.setProxyTargetClass(true);
        return defaultAAP;
    }

    /**
     * 将自己的验证方式加入容器
     */
    @Bean
    public CustomRealm myShiroRealm() {
        CustomRealm customRealm = new CustomRealm();
        customRealm.setCachingEnabled(false);
        return customRealm;
    }

    /**
     * @return org.apache.shiro.web.mgt.DefaultWebSubjectFactory
     * @author lhc
     * @description // 自定义subject工厂
     * @date 4:50 下午 2021/4/19
     * @params []
     **/
    @Bean
    public DefaultWebSubjectFactory subjectFactory() {
        return new StatelessDefaultSubjectFactory();
    }

    /**
     * @return org.apache.shiro.session.mgt.SessionManager
     * @author lhc
     * @description // 自定义session管理器
     * @date 5:50 下午 2021/4/19
     * @params []
     **/
    @Bean
    public SessionManager sessionManager() {
        DefaultSessionManager shiroSessionManager = new DefaultSessionManager();
        // 关闭session校验轮询
        shiroSessionManager.setSessionValidationSchedulerEnabled(false);
        return shiroSessionManager;
    }

    /**
     * 权限管理,配置主要是Realm的管理认证
     */
    @Bean("securityManager")
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        // 禁用session
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        // 设置自定义subject工厂
        securityManager.setSubjectFactory(subjectFactory());
        // 设置自定义session管理器
        securityManager.setSessionManager(sessionManager());
        // 设置自定义realm
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }


    /**
     * Filter工厂,设置对应的过滤条件和跳转条件
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        Map<String, Filter> filters = new HashMap<>(1);
        // 设置自定义过滤器
        filters.put("auth", new AuthFilter());
        shiroFilterFactoryBean.setFilters(filters);
        LinkedHashMap<String, String> map = new LinkedHashMap<>();
        // 用户登陆
        map.put("/ftUser/login", "anon");
        // Vue静态资源
        map.put("/img/**", "anon");
        map.put("/static/**", "anon");
        map.put("/tinymce/**", "anon");
        map.put("/favicon.ico", "anon");
        map.put("/manifest.json", "anon");
        map.put("/robots.txt", "anon");
        map.put("/precache*", "anon");
        map.put("/service-worker.js", "anon");
        // swagger UI 静态资源
        map.put("/swagger-ui.html","anon");
        map.put("/doc.html","anon");
        map.put("/swagger-resources/**","anon");
        map.put("/webjars/**","anon");
        map.put("/v2/api-docs","anon");
        map.put("/v2/api-docs-ext","anon");
        map.put("/swagger/**","anon");
        // druid 资源路径
        map.put("/druid/**","anon");
        // cas 接口
        map.put("/cas/valid","anon");
        map.put("/cas/logout","anon");
        // 其余路径均拦截
        map.put("/**", "auth");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    /**
     * 加入注解的使用,不加入这个注解不生效
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager());
        return authorizationAttributeSourceAdvisor;
    }

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

# 七、添加权限注解

此处只展示权限注解,其余注解请查询官方文档

注意

添加权限的注解必须被自定义拦截器拦截

否则会出现不调用自定义 CustomRealm中的doGetAuthorizationInf()方法的情况

代码示例:

代码语言:javascript
复制
    @GetMapping("/system/list")
    @RequiresPermissions(value = { "systemManage","system:list" },logical = Logical.OR)
    public ResultVO<Integer> resetPassword(String userId,@PathVariable Integer version) {
        return manageService.resetPassword(userId,version);
    }
  1. value :字符串数组,填写之前realm中权限注入的字符串即可
  2. logical:逻辑关系,数组中权限的逻辑关系,分为AND和OR两种,默认为AND
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-04-26,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • # Shiro关闭session配置
    • # 前言
      • # 一、引入依赖
        • # 二、实现DefaultWebSubjectFactory
          • # 三、实现AuthenticationToken
            • # 四、实现shiro的过滤器
              • # 五、编写自定义的Realm
                • # 六、编写Shiro配置类
                  • # 七、添加权限注解
                  相关产品与服务
                  容器服务
                  腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
                  领券
                  问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档