Shiro关闭session,无状态接入Springboot

本文介绍了如何在Apache Shiro中关闭session,采用token进行身份认证,以实现基于Spring Boot的应用的无状态管理,并详细讲解了自定义SubjectFactory、AuthenticationToken和过滤器的实现步骤。
摘要由CSDN通过智能技术生成

前言

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

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

#一、引入依赖

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

        <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

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方式进行验证

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的过滤器

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

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,此步骤是为了定义权限校验和用户信息验证。

#六、编写Shiro配置类

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

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()方法的情况

代码示例:

    @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
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值