在前后端分离项目中使用SpringBoot集成Shiro

  前言
  
  这次在处理一个小项目时用到了前后端分离,服务端使用springboot2.x。权限验证使用了Shiro。前后端分离首先需要解决的是跨域问题,POST接口跨域时会预发送一个OPTIONS请求,浏览器收到响应后会继续执行POST请求。 前后端分离后为了保持会话状态使用session持久化插件shiro-redis,持久化session可以持久化到关系型数据库,也可以持久化到非关系型数据库(主要是重写SessionDao)。Shiro已提供了SessionDao接口和抽象类。如果项目中用到Swagger的话,还需要把swagger相关url放行。
  
  搭建依赖
  
  
  
  
  
  org.crazycake
  
  shiro-redis
  
  3.2.3
  
  

  
  
  
  
  
  org.apache.shiro
  
  shiro-spring
  
  1.4.1
  
  

  
  Shiro权限配置
  
  1、ShiroConfig。这里主要是shiro核心配置。比如SecurityManager、SessionManager、CacheManager。
  
  public class ShiroConfig {
  
  @Value("${spring.redis.shiro.host}")
  
  private String host;
  
  @Value("${spring.redis.shiro.port}")
  
  private int port;
  
  @Value("${spring.redis.shiro.timeout}")
  
  private int timeout;
  
  @Value("${spring.redis.shiro.password}")
  
  private String password;
  
  /**
  
  * 权限规则配置
  
  **/
  
  @Bean
  
  public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
  
  ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
  
  shiroFilterFactoryBean.setSecurityManager(securityManager);
  
  Map filters = shiroFilterFactoryBean.getFilters();
  
  filters.put("authc", new MyFormAuthorizationFilter());
  
  Map filterChainDefinitionMap = new LinkedHashMap<>();
  
  //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);
  
  return shiroFilterFactoryBean;
  
  }
  
  /**
  
  * shiro安全管理器(权限验证核心配置)
  
  **/
  
  @Bean
  
  public SecurityManager securityManager() {
  
  DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
  
  securityManager.setRealm(myShiroRealm());
  
  securityManager.setSessionManager(sessionManager());
  
  securityManager.setCacheManager(cacheManager());
  
  return securityManager;
  
  }
  
  /**
  
  * 会话管理
  
  **/
  
  @Bean
  
  public SessionManager sessionManager() {
  
  MySessionManager sessionManager = new MySessionManager();
  
  sessionManager.setSessionIdUrlRewritingEnabled(false); //取消登陆跳转URL后面的jsessionid参数
  
  sessionManager.setSessionDAO(sessionDAO());
  
  sessionManager.setGlobalSessionTimeout(-1);//不过期
  
  return sessionManager;
  
  }
  
  /**
  
  * 使用的是shiro-redis开源插件 缓存依赖
  
  **/
  
  @Bean
  
  public RedisManager redisManager() {
  
  RedisManager redisManager = new RedisManager();
  
  redisManager.setHost(host+":"+port);
  
  redisManager.setTimeout(timeout);
  
  redisManager.setPassword(password);
  
  return redisManager;
  
  }
  
  /**
  
  * 使用的是shiro-redis开源插件 session持久化
  
  **/
  
  public RedisSessionDAO sessionDAO() {
  
  RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
  
  redisSessionDAO.setRedisManager(redisManager());
  
  return redisSessionDAO;
  
  }
  
  /**
  
  * 缓存管理
  
  **/
  
  @Bean
  
  public CacheManager cacheManager() {
  
  RedisCacheManager redisCacheManager = new RedisCacheManager();
  
  redisCacheManager.setRedisManager(redisManager());
  
  return redisCacheManager;
  
  }
  
  /**
  
  * 权限管理
  
  **/
  
  @Bean
  
  public MyShiroRealm myShiroRealm(www.chaoyuL.com ) {
  
  return new MyShiroRealm(www.chenghyLpt.com);
  
  }
  
  }
  
  2、MyShiroRealm 用户身份验证、自定义权限。
  
  public class MyShiroRealm extends AuthorizingRealm {
  
  private Logger logger= LoggerFactory.getLogger(MyShiroRealm.class);
  
  @Resource
  
  UserDao userDao;
  
  @Override
  
  protected AuthorizationInfo www.yaoshiyulegw.com doGetAuthorizationInfo(PrincipalCollection principalCollection) {
  
  logger.info("===================权限验证==================");
  
  return null;
  
  }
  
  @Override
  
  protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
  
  UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
  
  User currentUser=userDao.findUser(token.getUsername());
  
  if(null == currentUser){
  
  throw new AuthenticationException("账户不存在");
  
  }
  
  if(!currentUser.getPassword().equals(new String(token.getPassword()))){
  
  throw new IncorrectCredentialsException("账户密码不正确");
  
  }
  
  if(currentUser.getIsdel(www.yifayuled.cn)==1){
  
  throw new LockedAccountException("账户已冻结");
  
  }
  
  Subject subject = SecurityUtils.getSubject();
  
  BIUser biUser=new BIUser();
  
  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(www.chaoyuepint.com)+"====");
  
  return new SimpleAuthenticationInfo(biUser,biUser.getPassword(),biUser.getUserName());
  
  }
  
  }
  
  3、MySessionManager。shiro权限验证是根据客户端Cookie中的JSESSIONID值来确定身份是否合格。前后端分离后这个地方需要处理。客户端调用服务端登陆接口,验证通过后返回给客户端一个token值(这里我放的是sessionid)。客户端保存token值,然后调用其他接口时把token值放在header中。对前端来说也就是放在ajax的headers参数中。
  
  public class MySessionManager extends DefaultWebSessionManager {
  
  private static final String AUTHORIZATION = "Authorization";
  
  private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";
  
  public MySessionManager() {
  
  }
  
  @Override
  
  protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
  
  //从前端ajax headers中获取这个参数用来判断授权
  
  String id = 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);
  
  return id;
  
  } else {
  
  //从前端的cookie中取值
  
  return super.getSessionId(request, response);
  
  }
  
  }
  
  }
  
  4、MyFormAuthorizationFilter。对于跨域的POST请求,浏览器发起POST请求前都会发送一个OPTIONS请求已确定服务器是否可用,OPTIONS请求通过后继续执行POST请求,而shiro自带的权限验证是无法处理OPTIONS请求的,所以这里需要重写isAccessAllowed方法。
  
  public class MyFormAuthorizationFilter extends FormAuthenticationFilter {
  
  protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) {
  
  HttpServletRequest httpServletRequest = WebUtils.toHttp(servletRequest);
  
  if ("OPTIONS".equals(httpServletRequest.getMethod())) {
  
  return true;
  
  }
  
  return super.isAccessAllowed(servletRequest, servletResponse, o);
  
  }
  
  }
  
  5、处理跨域
  
  @Override
  
  public void addCorsMappings(CorsRegistry registry) {
  
  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);

你可能感兴趣的:(在前后端分离项目中使用SpringBoot集成Shiro)