欢迎来到DIVCSS5查找CSS资料与学习DIV CSS布局技术!
  在前后端分离项目中使用SpringBoot集成Shiro
 
  前言
 
  这次在处理一个小项目时用到了前后端分离,服务端使用springboot2.x。权限验证使用了Shiro。前后端分离首先需要解决的是跨域问题,POST接口跨域时会预发送一个OPTIONS请求,浏览器收到响应后会继续执行POST请求。前后端分离后为了保持会话状态使用session持久化插件shiro-redis,持久化session可以持久化到关系型数据库,也可以持久化到非关系型数据库(主要是重写SessionDao)。Shiro已提供了SessionDao接口和抽象类。如果项目中用到Swagger的话,还需要把swagger相关url放行。
 
  搭建依赖
 
  <dependency>
 
  <!--session持久化插件-->
 
   <groupId>org.crazycake</groupId>
 
   <artifactId>shiro-redis</artifactId>
 
   <version>3.2.3</version>
 
  </dependency>
 
  <dependency>
 
  <!--springshiro依赖-->
 
  <groupId>org.apache.shiro</groupId>
 
  <artifactId>shiro-spring</artifactId>
 
  <version>1.4.1</version>
 
  </dependency>
 
  Shiro权限配置
 
  1、ShiroConfig。这里主要是shiro核心配置。比如SecurityManager、SessionManager、CacheManager。
 
  publicclassShiroConfig{
 
  @Value("${spring.redis.shiro.host}")
 
  privateStringhost;
 
  @Value("${spring.redis.shiro.port}")
 
  privateintport;
 
  @Value("${spring.redis.shiro.timeout}")
 
  privateinttimeout;
 
  @Value("${spring.redis.shiro.password}")
 
  privateStringpassword;
 
  /**
 
  *权限规则配置
 
  **/
 
  @Bean
 
  publicShiroFilterFactoryBeanshiroFilterFactoryBean(SecurityManagersecurityManager){
 
  ShiroFilterFactoryBeanshiroFilterFactoryBean=newShiroFilterFactoryBean();
 
  shiroFilterFactoryBean.setSecurityManager(securityManager);
 
  Map<String,Filter>filters=shiroFilterFactoryBean.getFilters();
 
  filters.put("authc",newMyFormAuthorizationFilter());
 
  Map<String,String>filterChainDefinitionMap=newLinkedHashMap<>();
 
  //swagger资源不拦截
 
  filterChainDefinitionMap.put("/swagger-ui.html","anon");
 
  filterChainDefinitionMap.put("/swagger-resources/**/**","anon");
 
  filterChainDefinitionMap.put("/v2/api-docs","anon");
 
  filterChainDefinitionMap.put("/webjars/springfox-swagger-ui/**","anon");
 
  filterChainDefinitionMap.put("/configuration/security","anon");
 
  filterChainDefinitionMap.put("/configuration/ui","anon");
 
  filterChainDefinitionMap.put("/login/ajaxLogin","anon");
 
  filterChainDefinitionMap.put("/login/unauth","anon");
 
  filterChainDefinitionMap.put("/login/logout","anon");
 
  filterChainDefinitionMap.put("/login/register","anon");
 
  filterChainDefinitionMap.put("/**","authc");
 
  shiroFilterFactoryBean.setLoginUrl("/login/unauth");
 
  shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
 
  returnshiroFilterFactoryBean;
 
  }
 
  /**
 
  *shiro安全管理器(权限验证核心配置)
 
  **/
 
  @Bean
 
  publicSecurityManagersecurityManager(){
 
  DefaultWebSecurityManagersecurityManager=newDefaultWebSecurityManager();
 
  securityManager.setRealm(myShiroRealm());
 
  securityManager.setSessionManager(sessionManager());
 
  securityManager.setCacheManager(cacheManager());
 
  returnsecurityManager;
 
  }
 
  /**
 
  *会话管理
 
  **/
 
  @Bean
 
  publicSessionManagersessionManager(){
 
  MySessionManagersessionManager=newMySessionManager();
 
  sessionManager.setSessionIdUrlRewritingEnabled(false);//取消登陆跳转URL后面的jsessionid参数
 
  sessionManager.setSessionDAO(sessionDAO());
 
  sessionManager.setGlobalSessionTimeout(-1);//不过期
 
  returnsessionManager;
 
  }
 
  /**
 
  *使用的是shiro-redis开源插件缓存依赖
 
  **/
 
  @Bean
 
  publicRedisManagerredisManager(){
 
  RedisManagerredisManager=newRedisManager();
 
  redisManager.setHost(host+":"+port);
 
  redisManager.setTimeout(timeout);
 
  redisManager.setPassword(password);
 
  returnredisManager;
 
  }
 
  /**
 
  *使用的是shiro-redis开源插件session持久化
 
  **/
 
  publicRedisSessionDAOsessionDAO(){
 
  RedisSessionDAOredisSessionDAO=newRedisSessionDAO();
 
  redisSessionDAO.setRedisManager(redisManager());
 
  returnredisSessionDAO;
 
  }
 
  /**
 
  *缓存管理
 
  **/
 
  @Bean
 
  publicCacheManagercacheManager(){
 
  RedisCacheManagerredisCacheManager=newRedisCacheManager();
 
  redisCacheManager.setRedisManager(redisManager());
 
  returnredisCacheManager;
 
  }
 
  /**
 
  *权限管理
 
  **/
 
  @Bean
 
  publicMyShiroRealmmyShiroRealm(){
 
  returnnewMyShiroRealm();
 
  }
 
  }
 
  2、MyShiroRealm用户身份验证、自定义权限。
 
  publicclassMyShiroRealmextendsAuthorizingRealm{
 
  privateLoggerlogger=LoggerFactory.getLogger(MyShiroRealm.class);
 
  @Resource
 
  UserDaouserDao;
 
  @Override
 
  protectedAuthorizationInfodoGetAuthorizationInfo(PrincipalCollectionprincipalCollection){
 
  logger.info("===================权限验证==================");
 
  returnnull;
 
  }
 
  @Override
 
  protectedAuthenticationInfodoGetAuthenticationInfo(AuthenticationTokenauthenticationToken)throwsAuthenticationException{
 
  UsernamePasswordTokentoken=(UsernamePasswordToken)authenticationToken;
 
  UsercurrentUser=userDao.findUser(token.getUsername());
 
  if(null==currentUser){
 
  thrownewAuthenticationException("账户不存在");
 
  }
 
  if(!currentUser.getPassword().equals(newString(token.getPassword()))){
 
  thrownewIncorrectCredentialsException("账户密码不正确");
 
  }
 
  if(currentUser.getIsdel()==1){
 
  thrownewLockedAccountException("账户已冻结");
 
  }
 
  Subjectsubject=SecurityUtils.getSubject();
 
  BIUserbiUser=newBIUser();
 
  biUser.setUserId(currentUser.getUserId());
 
  biUser.setOrgId(currentUser.getOrgid());
 
  biUser.setUserName(currentUser.getUsername());
 
  biUser.setPassword(currentUser.getPassword());
 
  biUser.setSessionId(subject.getSession().getId().toString());
 
  biUser.setIsdel(currentUser.getIsdel());
 
  biUser.setCreateTime(currentUser.getCreatetime());
 
  logger.info("======已授权"+biUser.toString()+"====");
 
  returnnewSimpleAuthenticationInfo(biUser,biUser.getPassword(),biUser.getUserName());
 
  }
 
  }
 
  3、MySessionManager。shiro权限验证是根据客户端Cookie中的JSESSIONID值来确定身份是否合格。前后端分离后这个地方需要处理。客户端调用服务端登陆接口,验证通过后返回给客户端一个token值(这里我放的是sessionid)。客户端保存token值,然后调用其他接口时把token值放在header中。对前端来说也就是放在ajax的headers参数中。
 
  publicclassMySessionManagerextendsDefaultWebSessionManager{
 
  privatestaticfinalStringAUTHORIZATION="Authorization";
 
  privatestaticfinalStringREFERENCED_SESSION_ID_SOURCE="Statelessrequest";
 
  publicMySessionManager(){
 
  }
 
  @Override
 
  protectedSerializablegetSessionId(ServletRequestrequest,ServletResponseresponse){
 
  //从前端ajaxheaders中获取这个参数用来判断授权
 
  Stringid=WebUtils.toHttp(request).getHeader(AUTHORIZATION);
 
  if(StringUtils.hasLength(id)){
 
  request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE,REFERENCED_SESSION_ID_SOURCE);
 
  request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID,id);
 
  request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID,Boolean.TRUE);
 
  returnid;
 
  }else{
 
  //从前端的cookie中取值
 
  returnsuper.getSessionId(request,response);
 
  }
 
  }
 
  }
 
  4、MyFormAuthorizationFilter。对于跨域的POST请求,浏览器发起POST请求前都会发送一个OPTIONS请求已确定服务器是否可用,OPTIONS请求通过后继续执行POST请求,而shiro自带的权限验证是无法处理OPTIONS请求的,所以这里需要重写isAccessAllowed方法。
 
  publicclassMyFormAuthorizationFilterextendsFormAuthenticationFilter{
 
  protectedbooleanisAccessAllowed(ServletRequestservletRequest,ServletResponseservletResponse,Objecto){
 
  HttpServletRequesthttpServletRequest=WebUtils.toHttp(servletRequest);
 
  if("OPTIONS".equals(httpServletRequest.getMethod())){
 
  returntrue;
 
  }
 
  returnsuper.isAccessAllowed(servletRequest,servletResponse,o);
 
  }
 
  }
 
  5、处理跨域
 
  @Override
 
  publicvoidaddCorsMappings(CorsRegistryregistry){
 
  registry.addMapping("/**")
 
  .allowedOrigins("*")
 
  .allowedMethods("PUT","DELETE","GET","POST")
 
  .allowedHeaders("*")
 
  .exposedHeaders("access-control-allow-headers","access-control-allow-methods","access-control-allow"+
 
  "-origin","access-control-max-age","X-Frame-Options","Authorization")
 
  .allowCredentials(false).maxAge(3600);
 
  }

如需转载,请注明文章出处和来源网址:http://www.divcss5.com/html/h56839.shtml