51工具盒子

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

Shiro关闭session,无状态接入Springboot

# Shiro关闭session配置 {#shiro关闭session配置}

Apache_Shiro_logo.svg

# 前言 {#前言}

本文基于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}

实现 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 {#三、实现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的过滤器 {#四、实现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}

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

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(&quot;用户不存在!&quot;);
     }
     //5. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
     return new SimpleAuthenticationInfo(user, accessToken, this.getName());
    

    }

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

# 六、编写Shiro配置类 {#六、编写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(&quot;securityManager&quot;) 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(&quot;shiroFilter&quot;) public ShiroFilterFactoryBean shiroFilterFactoryBean() { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager()); Map&lt;String, Filter&gt; filters = new HashMap&lt;&gt;(1); // 设置自定义过滤器 filters.put(&quot;auth&quot;, new AuthFilter()); shiroFilterFactoryBean.setFilters(filters); LinkedHashMap&lt;String, String&gt; map = new LinkedHashMap&lt;&gt;(); // 用户登陆 map.put(&quot;/ftUser/login&quot;, &quot;anon&quot;); // Vue静态资源 map.put(&quot;/img/&quot;, &quot;anon&quot;); map.put(&quot;/static/&quot;, &quot;anon&quot;); map.put(&quot;/tinymce/**&quot;, &quot;anon&quot;); map.put(&quot;/favicon.ico&quot;, &quot;anon&quot;); map.put(&quot;/manifest.json&quot;, &quot;anon&quot;); map.put(&quot;/robots.txt&quot;, &quot;anon&quot;); map.put(&quot;/precache&quot;, &quot;anon&quot;); map.put(&quot;/service-worker.js&quot;, &quot;anon&quot;); // swagger UI 静态资源 map.put(&quot;/swagger-ui.html&quot;,&quot;anon&quot;); map.put(&quot;/doc.html&quot;,&quot;anon&quot;); map.put(&quot;/swagger-resources/&quot;,&quot;anon&quot;); map.put(&quot;/webjars/&quot;,&quot;anon&quot;); map.put(&quot;/v2/api-docs&quot;,&quot;anon&quot;); map.put(&quot;/v2/api-docs-ext&quot;,&quot;anon&quot;); map.put(&quot;/swagger/&quot;,&quot;anon&quot;); // druid 资源路径 map.put(&quot;/druid/&quot;,&quot;anon&quot;); // cas 接口 map.put(&quot;/cas/valid&quot;,&quot;anon&quot;); map.put(&quot;/cas/logout&quot;,&quot;anon&quot;); // 其余路径均拦截 map.put(&quot;/**&quot;, &quot;auth&quot;); 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
赞(4)
未经允许不得转载:工具盒子 » Shiro关闭session,无状态接入Springboot