原创 2021-04-16 16:07:03

前言
最近在做一个前后端分离的项目。前端使用vue,后端使用的是spring boot,因为需要做权限管理。就选择集成shiro框架。以前都是在传统项目中使用shiro。第一次在前后端分离的项目中使用shiro。给我带来了很大的困扰。遇到了很多麻烦。所以在此记录。方便以后查阅。也希望能让同样面临同样问题的人能节约点时间。

坑点总结
1.前后端分离项目没有部署在同一台服务器上,要面临跨域问题。
2.使用token 作为shiro认证标识
3.前后端分离项目中,未登录时用返回json代替重定向。

详解
1. 解决跨域问题
spring boot 跨域问题很好解决。使用下面代码。或者在网上搜索springboot解决跨域问题。很快便可以完成此步骤。

 

package com.common.config.cors;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;


@Configuration
public class CorsConfig {

   private CorsConfiguration buildConfig() {
       CorsConfiguration corsConfiguration = new CorsConfiguration();
       // 允许任何域名使用
       corsConfiguration.addAllowedOrigin("*");
       // 允许任何头
       corsConfiguration.addAllowedHeader("*");
       // 允许任何方法(post、get等)
       corsConfiguration.addAllowedMethod("*");
       return corsConfiguration;
   }


   @Bean
   public CorsFilter corsFilter() {
       UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
       // 对接口配置跨域设置
       source.registerCorsConfiguration("/**", buildConfig());
       return new CorsFilter(source);
   }
}


2.使用token 作为shiro认证标识
一开始面临这个问题,感觉无比的复杂。shiro根据sessionId来判断是不是同一个用户发起的request请求。但是前后端分离的项目中。用户的每次请求都相当于新的请求。sessionId可能会发生变化。
我们需要的就是解决这种问题。最先想到的是使用token来代替session,让前端发送的请求都携带登录成功后返回的token令牌。(其实和session原理一样。就是以前的sessionId存储在cookie中,现在用token,将sessionId存储在了请求头中。)下面看代码。

 

package com.**.*.config;

import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

/** shiro 的 session 管理
*      自定义session规则,实现前后分离,在跨域等情况下使用token 方式进行登录验证才需要,否则没必须使用本类。
*      shiro默认使用 ServletContainerSessionManager 来做 session 管理,它是依赖于浏览器的 cookie 来维护 session 的,调用 storeSessionId  方法保存sesionId 到 cookie中
*      为了支持无状态会话,我们就需要继承 DefaultWebSessionManager
*      自定义生成sessionId 则要实现 SessionIdGenerator
* @author zzy
* @date 2020/11/18 11:23
*/
public class ShiroSession extends DefaultWebSessionManager {
   private static final String AUTH_TOKEN = "authorization";

   private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";


   public ShiroSession() {
       super();
       //设置 shiro session 失效时间,默认为30分钟,这里现在设置为15分钟
       //setGlobalSessionTimeout(MILLIS_PER_MINUTE * 15);
   }



   /**
    * 获取sessionId,原本是根据sessionKey来获取一个sessionId
    * 重写的部分多了一个把获取到的token设置到request的部分。这是因为app调用登陆接口的时候,是没有token的,登陆成功后,产生了token,我们把它放到request中,返回结
    * 果给客户端的时候,把它从request中取出来,并且传递给客户端,客户端每次带着这个token过来,就相当于是浏览器的cookie的作用,也就能维护会话了
    * @param request
    * @param response
    * @return
    */
   @Override
   protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
       //获取请求头中的 AUTH_TOKEN 的值,如果请求头中有 AUTH_TOKEN 则其值为sessionId。shiro就是通过sessionId 来控制的
       String sessionId = WebUtils.toHttp(request).getHeader(AUTH_TOKEN);
       if (StringUtils.isEmpty(sessionId)){
           //如果没有携带id参数则按照父类的方式在cookie进行获取sessionId
           return super.getSessionId(request, response);

       } else {
           //请求头中如果有 authToken, 则其值为sessionId
           request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
           //sessionId
           request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
           request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
           return sessionId;
       }
   }
}


在shiro配置类中创建安全管理器的时候使用自定义ShiroSession来做会话管理。

 



3.登录失败时,用返回json来代替重定向
一步一个坑。在解决完第二步骤的时候,发现shiro基本算是配置成功了。但是发现如果没有登录的时候访问具有登录权限的接口总是会报404错误。发现这些请求都是被重定向到了很目录下面的index.jsp页面。因为本地没有这个页面。所以引发404错误。在经过查阅资料后发现,进本都是通过配置过滤器来解决问题的。
代码如下。

package com.**.*.filter;

import com.alibaba.fastjson.JSONObject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import springfox.documentation.service.ResponseMessage;

import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
* @author zzy
* @date 2020/11/19 11:09
*/
public class MyFormAuthenticationFilter extends FormAuthenticationFilter {
   @Override
   protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
       HttpServletResponse resp = (HttpServletResponse) response;
       resp.setContentType("application/json; charset=utf-8");
       PrintWriter out = resp.getWriter();
       JSONObject jsonObject = new JSONObject();
       jsonObject.put("code", "100");
       jsonObject.put("desc", "请前往登录页面");
       try {
           flushMsgStrToClient(response, jsonObject);
       } catch (Exception e) {
           e.printStackTrace();
       }
       out.flush();
       out.close();
       return false;
   }

   public static void flushMsgStrToClient(ServletResponse response, Object object)
           throws IOException, ServletException {
       response.setContentType("application/json;charset=UTF-8");
       response.getWriter().write(JSONObject.toJSONString(object));
       response.getWriter().flush();
   }
}


在配置类的过滤工厂中添加配置

public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
       //1.创建过滤器工厂
       ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
       Map<String, Filter> filters = new HashMap<>();
       MyFormAuthenticationFilter myFormAuthenticationFilter = new MyFormAuthenticationFilter();
       filters.put("authc",myFormAuthenticationFilter);
       filterFactory.setFilters(filters);
       //2.设置安全管理器
       filterFactory.setSecurityManager(securityManager);
       //4.设置过滤器集合

       /**
        * 设置所有的过滤器:有顺序map
        *     key = 拦截的url地址
        *     value = 过滤器类型
        *
        */
       Map<String, String> filterMap = new LinkedHashMap<>();
       filterMap.put("/System/SystemLogin","anon");
       filterMap.put("/**","authc");
       filterFactory.setFilterChainDefinitionMap(filterMap);

       return filterFactory;
   }
取消

感谢您的支持,我会继续努力的!

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

分享从这里开始,精彩与您同在

延伸阅读
  1. springboot + maven (profile)实现项目多环境配置
  2. springboot 项目之多配置文件
  3. quartz定时任务cron表达式详解
  4. java基础模块面试题
  5. 一款比较有意思的404页面
发表评论