【Shiro】Springboot集成Shiro

简介: 【Shiro】Springboot集成Shiro


1、技术栈

主框架:springboot

响应层:springMVC

持久层:mybatis

事务控制:jta

前端技术:easyui

2、数据库设计

【1】数据库图解

 

sh_user:用户表,一个用户可以有多个角色

sh_role:角色表,一个角色可以有多个资源

sh_resource:资源表

sh_user_role:用户角色中间表

sh_role_resource:角色资源中间表

【2】数据库脚本

sh_user

CREATE TABLE `sh_user` (

 `ID` varchar(36) NOT NULL COMMENT '主键',

 `LOGIN_NAME` varchar(36) DEFAULT NULL COMMENT '登录名称',

 `REAL_NAME` varchar(36) DEFAULT NULL COMMENT '真实姓名',

 `NICK_NAME` varchar(36) DEFAULT NULL COMMENT '昵称',

 `PASS_WORD` varchar(150) DEFAULT NULL COMMENT '密码',

 `SALT` varchar(36) DEFAULT NULL COMMENT '加密因子',

 `SEX` int(11) DEFAULT NULL COMMENT '性别',

 `ZIPCODE` varchar(36) DEFAULT NULL COMMENT '邮箱',

 `ADDRESS` varchar(36) DEFAULT NULL COMMENT '地址',

 `TEL` varchar(36) DEFAULT NULL COMMENT '固定电话',

 `MOBIL` varchar(36) DEFAULT NULL COMMENT '电话',

 `EMAIL` varchar(36) DEFAULT NULL COMMENT '邮箱',

 `DUTIES` varchar(36) DEFAULT NULL COMMENT '职务',

 `SORT_NO` int(11) DEFAULT NULL COMMENT '排序',

 `ENABLE_FLAG` varchar(18) DEFAULT NULL COMMENT '是否有效',

 PRIMARY KEY (`ID`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户表';


sh_role

CREATE TABLE `sh_role` (

 `ID` varchar(36) NOT NULL COMMENT '主键',

 `ROLE_NAME` varchar(36) DEFAULT NULL COMMENT '角色名称',

 `LABEL` varchar(36) DEFAULT NULL COMMENT '角色标识',

 `DESCRIPTION` varchar(200) DEFAULT NULL COMMENT '角色描述',

 `SORT_NO` int(36) DEFAULT NULL COMMENT '排序',

 `ENABLE_FLAG` varchar(18) DEFAULT NULL COMMENT '是否有效',

 PRIMARY KEY (`ID`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户角色表';

sh_resource

CREATE TABLE `sh_resource` (

 `ID` varchar(36) NOT NULL COMMENT '主键',

 `PARENT_ID` varchar(36) DEFAULT NULL COMMENT '父资源',

 `RESOURCE_NAME` varchar(36) DEFAULT NULL COMMENT '资源名称',

 `REQUEST_PATH` varchar(200) DEFAULT NULL COMMENT '资源路径',

 `LABEL` varchar(200) DEFAULT NULL COMMENT '资源标签',

 `ICON` varchar(20) DEFAULT NULL COMMENT '图标',

 `IS_LEAF` varchar(18) DEFAULT NULL COMMENT '是否叶子节点',

 `RESOURCE_TYPE` varchar(36) DEFAULT NULL COMMENT '资源类型',

 `SORT_NO` int(11) DEFAULT NULL COMMENT '排序',

 `DESCRIPTION` varchar(200) DEFAULT NULL COMMENT '描述',

 `SYSTEM_CODE` varchar(36) DEFAULT NULL COMMENT '系统code',

 `IS_SYSTEM_ROOT` varchar(18) DEFAULT NULL COMMENT '是否根节点',

 `ENABLE_FLAG` varchar(18) DEFAULT NULL COMMENT '是否有效',

 PRIMARY KEY (`ID`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='资源表';


sh_role_resource

CREATE TABLE `sh_role_resource` (

 `ID` varchar(36) NOT NULL,

 `ENABLE_FLAG` varchar(18) DEFAULT NULL,

 `ROLE_ID` varchar(36) DEFAULT NULL,

 `RESOURCE_ID` varchar(36) DEFAULT NULL,

 PRIMARY KEY (`ID`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='角色资源表';

sh_user_role

CREATE TABLE `sh_user_role` (

 `ID` varchar(36) NOT NULL,

 `ENABLE_FLAG` varchar(18) DEFAULT NULL,

 `USER_ID` varchar(36) DEFAULT NULL,

 `ROLE_ID` varchar(36) DEFAULT NULL,

 PRIMARY KEY (`ID`)

) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户角色表';

3、项目骨架

 

4、ShiroDbRealm定义

【1】图解

 

【2】原理分析

(1)、ShiroDbRealmImpl继承ShiroDbRealm向上继承AuthorizingRealm,ShiroDbRealmImpl实例化时会创建密码匹配器HashedCredentialsMatcher实例,HashedCredentialsMatcher指定hash次数与方式,交于AuthenticatingRealm

(2)、调用login方法后,最终调用doGetAuthenticationInfo(AuthenticationToken authcToken)方法,拿到SimpleToken的对象,调用UserBridgeService的查找用户方法,把ShiroUser对象、密码和salt交于SimpleAuthenticationInfo去认证

(3)、访问需要鉴权时,调用doGetAuthorizationInfo(PrincipalCollection principals)方法,然后调用UserBridgeService的授权验证

【3】核心类代码
【3.1】ShiroDbRealm



package com.itheima.shiro.core;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import javax.annotation.PostConstruct;
/**
 *
 * @Description shiro自定义realm
 */
public abstract class ShiroDbRealm extends AuthorizingRealm {
  
  /**
   * @Description 认证
   * @param authcToken token对象
   * @return 
   */
  public abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) ;
  /**
   * @Description 鉴权
   * @param principals 令牌
   * @return
   */
  public abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);
  
  /**
   * @Description 密码匹配器
   */
  @PostConstruct
  public abstract void initCredentialsMatcher() ;
  
}
【3.2】ShiroDbRealmImpl
package com.itheima.shiro.core.impl;
import com.itheima.shiro.constant.SuperConstant;
import com.itheima.shiro.core.base.ShiroUser;
import com.itheima.shiro.core.base.SimpleToken;
import com.itheima.shiro.core.ShiroDbRealm;
import com.itheima.shiro.core.bridge.UserBridgeService;
import com.itheima.shiro.pojo.User;
import com.itheima.shiro.utils.BeanConv;
import com.itheima.shiro.utils.DigestsUtil;
import com.itheima.shiro.utils.EmptyUtil;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;
/**
 * @Description:自定义shiro的实现
 */
public class ShiroDbRealmImpl extends ShiroDbRealm {
    @Autowired
    private UserBridgeService userBridgeService;
    /**
     * @Description 认证方法
     * @param authcToken 校验传入令牌
     * @return AuthenticationInfo
     */
    @Override
    public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {
        SimpleToken token = (SimpleToken)authcToken;
        User user  = userBridgeService.findUserByLoginName(token.getUsername());
        if(EmptyUtil.isNullOrEmpty(user)){
            throw new UnknownAccountException("账号不存在");
        }
        ShiroUser shiroUser = BeanConv.toBean(user, ShiroUser.class);
        shiroUser.setResourceIds(userBridgeService.findResourcesIdsList(user.getId()));
        String salt = user.getSalt();
        String password = user.getPassWord();
        return new SimpleAuthenticationInfo(shiroUser, password, ByteSource.Util.bytes(salt), getName());
    }
    /**
     * @Description 授权方法
     * @param principals SimpleAuthenticationInfo对象第一个参数
     * @return
     */
    @Override
    public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
        return userBridgeService.getAuthorizationInfo(shiroUser);
    }
    /**
     * @Description 加密方式
     */
    @Override
    public void initCredentialsMatcher() {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(SuperConstant.HASH_ALGORITHM);
        matcher.setHashIterations(SuperConstant.HASH_INTERATIONS);
        setCredentialsMatcher(matcher);
    }
}
【3.3】SimpleToken



package com.itheima.shiro.core.base;
import org.apache.shiro.authc.UsernamePasswordToken;
/**
 * @Description 自定义tooken
 */
public class SimpleToken extends UsernamePasswordToken {
  
  /** serialVersionUID */
  private static final long serialVersionUID = -4849823851197352099L;
  private String tokenType;
  
  private String quickPassword;
  /**
   * Constructor for SimpleToken
   * @param tokenType
   */
  public SimpleToken(String tokenType, String username,String password) {
    super(username,password);
    this.tokenType = tokenType;
  }
  
  public SimpleToken(String tokenType, String username,String password,String quickPassword) {
    super(username,password);
    this.tokenType = tokenType;
    this.quickPassword = quickPassword;
  }
  public String getTokenType() {
    return tokenType;
  }
  public void setTokenType(String tokenType) {
    this.tokenType = tokenType;
  }
  public String getQuickPassword() {
    return quickPassword;
  }
  public void setQuickPassword(String quickPassword) {
    this.quickPassword = quickPassword;
  }
  
  
}
【3.4】ShiroUser



package com.itheima.shiro.core.base;
import com.itheima.shiro.utils.ToString;
import lombok.Data;
import java.util.List;
/**
 * @Description 自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息
 */
@Data
public class  ShiroUser extends ToString {
  /** serialVersionUID */
  private static final long serialVersionUID = -5024855628064590607L;
  /**
   * 主键
   */
  private String id;
  /**
   * 登录名称
   */
  private String loginName;
  /**
   * 真实姓名
   */
  private String realName;
  /**
   * 昵称
   */
  private String nickName;
  /**
   * 密码
   */
  private String passWord;
  /**
   * 加密因子
   */
  private String salt;
  /**
   * 性别
   */
  private Integer sex;
  /**
   * 邮箱
   */
  private String zipcode;
  /**
   * 地址
   */
  private String address;
  /**
   * 固定电话
   */
  private String tel;
  /**
   * 电话
   */
  private String mobil;
  /**
   * 邮箱
   */
  private String email;
  /**
   * 职务
   */
  private String duties;
  /**
   * 排序
   */
  private Integer sortNo;
  /**
   * 是否有效
   */
  private String enableFlag;
    
  private List<String> resourceIds;
  public ShiroUser() {
    super();
  }
  public ShiroUser(String id, String loginName) {
    super();
    this.id = id;
    this.loginName = loginName;
  }
  @Override
  public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + ((email == null) ? 0 : email.hashCode());
    result = prime * result + ((id == null) ? 0 : id.hashCode());
    result = prime * result
        + ((loginName == null) ? 0 : loginName.hashCode());
    result = prime * result + ((mobil == null) ? 0 : mobil.hashCode());
    return result;
  }
  @Override
  public boolean equals(Object obj) {
    if (this == obj)
      return true;
    if (obj == null)
      return false;
    if (getClass() != obj.getClass())
      return false;
    ShiroUser other = (ShiroUser) obj;
    if (email == null) {
      if (other.email != null)
        return false;
    } else if (!email.equals(other.email))
      return false;
    if (id == null) {
      if (other.id != null)
        return false;
    } else if (!id.equals(other.id))
      return false;
    if (loginName == null) {
      if (other.loginName != null)
        return false;
    } else if (!loginName.equals(other.loginName))
      return false;
    if (mobil == null) {
      if (other.mobil != null)
        return false;
    } else if (!mobil.equals(other.mobil))
      return false;
    return true;
  }
  
  
}
【3.5】UserBridgeService
package com.itheima.shiro.core.bridge;
import com.itheima.shiro.core.base.ShiroUser;
import com.itheima.shiro.pojo.User;
import org.apache.shiro.authz.AuthorizationInfo;
import java.util.List;
/**
 * @Description:用户信息桥接(后期会做缓存)
 */
public interface UserBridgeService {
    /**
     * @Description 查找用户信息
     * @param loginName 用户名称
     * @return user对象
     */
    User findUserByLoginName(String loginName);
    /**
     * @Description 鉴权方法
     * @param shiroUser 令牌对象
     * @return 鉴权信息
     */
    AuthorizationInfo getAuthorizationInfo(ShiroUser shiroUser);
    /**
     * @Description 查询用户对应角色标识list
     * @param userId 用户id
     * @return 角色标识集合
     */
    List<String> findRoleList(String userId);
    /**
     * @Description 查询用户对应资源标识list
     * @param userId 用户id
     * @return 资源标识集合
     */
    List<String> findResourcesList(String userId);
    /**
     * @Description 查询资源ids
     * @param userId 用户id
     * @return 资源id集合
     */
    List<String> findResourcesIds(String userId);
}
【3.6】UserBridgeServiceImpl
package com.itheima.shiro.core.bridge.impl;
import com.itheima.shiro.core.adapter.UserAdapter;
import com.itheima.shiro.core.base.ShiroUser;
import com.itheima.shiro.core.bridge.UserBridgeService;
import com.itheima.shiro.pojo.Resource;
import com.itheima.shiro.pojo.Role;
import com.itheima.shiro.pojo.User;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
/**
 * @Description:用户信息桥接(后期会做缓存)
 */
@Component("userBridgeService")
public class UserBridgeServiceImpl implements UserBridgeService {
    @Autowired
    UserAdapter userAdapter;
    @Override
    public User findUserByLoginName(String loginName) {
        return userAdapter.findUserByLoginName(loginName);
    }
    @Override
    public AuthorizationInfo getAuthorizationInfo(ShiroUser shiroUser) {
        //查询用户对应的角色标识
        List<String> roleList = this.findRoleList(shiroUser.getId());
        //查询用户对于的资源标识
        List<String> resourcesList = this.findResourcesList(shiroUser.getId());
        //构建鉴权信息对象
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.addRoles(roleList);
        simpleAuthorizationInfo.addStringPermissions(resourcesList);
        return simpleAuthorizationInfo;
    }
    @Override
    public List<String> findRoleList(String userId){
        List<Role> roles = userAdapter.findRoleByUserId(userId);
        List<String> roleLabel = new ArrayList<>();
        for (Role role : roles) {
            roleLabel.add(role.getLabel());
        }
        return roleLabel;
    }
    @Override
    public List<String> findResourcesList(String userId){
        List<Resource> resources = userAdapter.findResourceByUserId(userId);
        List<String> resourceLabel = new ArrayList<>();
        for (Resource resource : resources) {
            resourceLabel.add(resource.getLabel());
        }
        return resourceLabel;
    }
    @Override
    public List<String> findResourcesIds(String userId) {
        List<Resource> resources = userAdapter.findResourceByUserId(userId);
        List<String> ids = new ArrayList<>();
        for (Resource resource : resources) {
            ids.add(resource.getId());
        }
        return ids;
    }
}
【3.7】UserAdapter
package com.itheima.shiro.core.adapter;
import com.itheima.shiro.pojo.Resource;
import com.itheima.shiro.pojo.Role;
import com.itheima.shiro.pojo.User;
import java.util.List;
/**
 * @Description 后台登陆用户适配器接口
 */
public interface UserAdapter {
  
  /**
   * @Description 按用户名查找用户
   * @param loginName 登录名
   * @return
   */
  User findUserByLoginName(String loginName);
  /**
   * @Description 查找用户所有角色
   * @param userId 用户Id
   * @return
   */
  List<Role> findRoleByUserId(String userId);
  /**
   * @Description 查询用户有那些资源
   * @param userId 用户Id
   * @return
   */
  List<Resource> findResourceByUserId(String userId);
}
【3.8】UserAdapterImpl
package com.itheima.shiro.core.adapter.impl;
import com.itheima.shiro.constant.SuperConstant;
import com.itheima.shiro.core.adapter.UserAdapter;
import com.itheima.shiro.mapper.UserMapper;
import com.itheima.shiro.mappercustom.UserAdapterMapper;
import com.itheima.shiro.pojo.Resource;
import com.itheima.shiro.pojo.Role;
import com.itheima.shiro.pojo.User;
import com.itheima.shiro.pojo.UserExample;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
/**
 * @Description 后台登陆用户适配器接口实现
 */
@Component("userAdapter")
public class UserAdapterImpl implements UserAdapter {
  @Autowired
  private UserMapper userMapper;
  @Autowired
  private UserAdapterMapper userAdapterMapper;
  @Override
  public User findUserByLoginName(String loginName) {
    UserExample userExample = new UserExample();
    userExample.createCriteria().andEnableFlagEqualTo(SuperConstant.YES).andLoginNameEqualTo(loginName);
    List<User> userList = userMapper.selectByExample(userExample);
    if (userList.size()==1) {
      return userList.get(0);
    }else {
      return null;
    }
  }
  @Override
  public List<Role> findRoleByUserId(String userId) {
    Map<String, Object> values = new HashMap<String, Object>();
    values.put("userId", userId);
    values.put("enableFlag", SuperConstant.YES);
    List<Role> list = userAdapterMapper.findRoleByUserId(values);
    return list;
  }
  @Override
  public List<Resource> findResourceByUserId(String userId) {
    Map<String, Object> values = new HashMap<String, Object>();
    values.put("userId", userId);
    values.put("enableFlag", SuperConstant.YES);
    List<Resource> list=userAdapterMapper.findResourceByUserId(values);
    return list;
  }
}

5、ShiroConfig配置

【1】图解

 

【2】原理分析

(1)、创建SimpleCookie,访问项目时,会在客户端中cookie中存放ShiroSession的对

(2)、创建DefaultWebSessionManager会话管理器定义cookie机制、定时刷新、全局会话超时时间然后交

于DefaultWebSecurityManager权限管理器管理

(3)、创建自定义ShiroDbRealm实现,用于权限认证、授权、加密方式的管理,同时从数据库中取得相关的

角色、资源、用户的信息,然后交于DefaultWebSecurityManager权限管理器管理

(4)、创建DefaultWebSecurityManager权限管理器用于管理DefaultWebSessionManager会话管理器、ShiroDbRealm

(5)、创建lifecycleBeanPostProcessor和DefaultAdvisorAutoProxyCreator相互配合事项注解的权限鉴权

(6)、创建ShiroFilterFactoryBean的shiro过滤器指定权限管理器、同时启动连接链及登录URL、未登录的URL

的跳转

【3】ShiroConfig代码
package com.itheima.shiro.config;
import com.itheima.shiro.core.ShiroDbRealm;
import com.itheima.shiro.core.impl.ShiroDbRealmImpl;
import com.itheima.shiro.properties.PropertiesUtil;
import lombok.extern.log4j.Log4j2;
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.servlet.SimpleCookie;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
 * @Description:权限配置类
 */
@Configuration
@ComponentScan(basePackages = "com.itheima.shiro.core")
@Log4j2
public class ShiroConfig {
    /**
     * @Description 创建cookie对象
     */
    @Bean(name="sessionIdCookie")
    public SimpleCookie simpleCookie(){
        SimpleCookie simpleCookie = new SimpleCookie();
        simpleCookie.setName("ShiroSession");
        return simpleCookie;
    }
    /**
     * @Description 权限管理器
     */
    @Bean(name="securityManager")
    public DefaultWebSecurityManager defaultWebSecurityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(shiroDbRealm());
        securityManager.setSessionManager(shiroSessionManager());
        return securityManager;
    }
    /**
     * @Description 自定义RealmImpl
     */
    @Bean(name="shiroDbRealm")
    public ShiroDbRealm shiroDbRealm(){
        return new ShiroDbRealmImpl();
    }
    /**
     * @Description 会话管理器
     */
    @Bean(name="sessionManager")
    public DefaultWebSessionManager shiroSessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        sessionManager.setSessionValidationSchedulerEnabled(false);
        sessionManager.setSessionIdCookieEnabled(true);
        sessionManager.setSessionIdCookie(simpleCookie());
        sessionManager.setGlobalSessionTimeout(3600000);
        return sessionManager;
    }
    /**
     * @Description 保证实现了Shiro内部lifecycle函数的bean执行
     */
    @Bean(name = "lifecycleBeanPostProcessor")
    public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }
    /**
     * @Description AOP式方法级权限检查
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }
    /**
     * @Description 配合DefaultAdvisorAutoProxyCreator事项注解权限校验
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() {
        AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor();
        aasa.setSecurityManager(defaultWebSecurityManager());
        return new AuthorizationAttributeSourceAdvisor();
    }
    /**
     * @Description 过滤器链
     */
    private Map<String, String> filterChainDefinition(){
        List<Object> list  = PropertiesUtil.propertiesShiro.getKeyList();
        Map<String, String> map = new LinkedHashMap<>();
        for (Object object : list) {
            String key = object.toString();
            String value = PropertiesUtil.getShiroValue(key);
            log.info("读取防止盗链控制:---key{},---value:{}",key,value);
            map.put(key, value);
        }
        return map;
    }
    /**
     * @Description Shiro过滤器
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(){
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(defaultWebSecurityManager());
        shiroFilter.setFilterChainDefinitionMap(filterChainDefinition());
        shiroFilter.setLoginUrl("/login");
        shiroFilter.setUnauthorizedUrl("/login");
        return shiroFilter;
    }
}

6、Shiro过滤器、过滤器链

【1】过滤器

Shiro内置了很多默认的过滤器,比如身份验证、授权等相关的。默认过滤器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举过滤器

 

【2】过滤器链

定义:authentication.properties

#静态资源不过滤

/static/**=anon

#登录链接不过滤

/login/**=anon

#其他链接是需要登录的

/**=authc

注意:这里定义的过滤器是有执行顺序的,从上向下执行

【3】加载原理分析

定义:PropertiesUtil,从classpath中加载authentication.properties

package com.itheima.shiro.properties;
import com.itheima.shiro.utils.EmptyUtil;
import lombok.extern.log4j.Log4j2;
/**
 * @Description 读取Properties的工具类
 */
@Log4j2
public class PropertiesUtil {
    public static LinkProperties propertiesShiro = new LinkProperties();
    /**
     * 读取properties配置文件信息
     */
    static {
        String sysName = System.getProperty("sys.name");
        if (EmptyUtil.isNullOrEmpty(sysName)) {
            sysName = "application.properties";
        } else {
            sysName += ".properties";
        }
        try {
            propertiesShiro.load(PropertiesUtil.class.getClassLoader()
                    .getResourceAsStream("authentication.properties"));
        } catch (Exception e) {
            log.warn("资源路径中不存在authentication.properties权限文件,忽略读取!");
        }
    }
    /**
     * 根据key得到value的值
     */
    public static String getShiroValue(String key) {
        return propertiesShiro.getProperty(key);
    }
}

定义LinkProperties,这个类保证了Properties类的有序

package com.itheima.shiro.properties;
import java.io.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;
/**
 * @Description 有序Properties类
 */
public class LinkProperties extends Properties{
  /** serialVersionUID */
  private static final long serialVersionUID = 7573016303908223266L;
  
  private List<Object> keyList = new ArrayList<Object>();  
    
    /** 
     * 默认构造方法 
     */  
    public LinkProperties() {  
          
    }  
      
    /** 
     * 从指定路径加载信息到Properties 
     * @param path 
     */  
    public LinkProperties(String path) {  
        try {  
            InputStream is = new FileInputStream(path);  
            this.load(is);  
        } catch (FileNotFoundException e) {  
            e.printStackTrace();  
            throw new RuntimeException("指定文件不存在!");  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
      
    /** 
     * 重写put方法,按照property的存入顺序保存key到keyList,遇到重复的后者将覆盖前者。 
     */  
    @Override  
    public synchronized Object put(Object key, Object value) {  
        this.removeKeyIfExists(key);  
        keyList.add(key);  
        return super.put(key, value);  
    }  
      
  
    /** 
     * 重写remove方法,删除属性时清除keyList中对应的key。 
     */  
    @Override  
    public synchronized Object remove(Object key) {  
        this.removeKeyIfExists(key);  
        return super.remove(key);  
    }  
      
    /** 
     * keyList中存在指定的key时则将其删除 
     */  
    private void removeKeyIfExists(Object key) {  
        keyList.remove(key);  
    }  
      
    /** 
     * 获取Properties中key的有序集合 
     * @return 
     */  
    public List<Object> getKeyList() {  
        return keyList;  
    }  
      
    /** 
     * 保存Properties到指定文件,默认使用UTF-8编码 
     * @param path 指定文件路径 
     */  
    public void store(String path) {  
        this.store(path, "UTF-8");  
    }  
      
    /** 
     * 保存Properties到指定文件,并指定对应存放编码 
     * @param path 指定路径 
     * @param charset 文件编码 
     */  
    public void store(String path, String charset) {  
        if (path != null && !"".equals(path)) {  
            try {  
                OutputStream os = new FileOutputStream(path);  
                BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os, charset));  
                this.store(bw, null);  
                bw.close();  
            } catch (FileNotFoundException e) {  
                e.printStackTrace();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        } else {  
            throw new RuntimeException("存储路径不能为空!");  
        }  
    }  
  
    /** 
     * 重写keys方法,返回根据keyList适配的Enumeration,且保持HashTable keys()方法的原有语义, 
     * 每次都调用返回一个新的Enumeration对象,且和之前的不产生冲突 
     */  
    @Override  
    public synchronized Enumeration<Object> keys() {  
        return new EnumerationAdapter<Object>(keyList);  
    }  
      
    /** 
     * List到Enumeration的适配器 
     */  
    private class EnumerationAdapter<T> implements Enumeration<T> {  
        private int index = 0;  
        private final List<T> list;  
        private final boolean isEmpty;  
          
        public EnumerationAdapter(List<T> list) {  
            this.list = list;  
            this.isEmpty = list.isEmpty();  
        }  
          
        public boolean hasMoreElements() {  
            //isEmpty的引入是为了更贴近HashTable原有的语义,在HashTable中添加元素前调用其keys()方法获得一个Enumeration的引用,  
            //之后往HashTable中添加数据后,调用之前获取到的Enumeration的hasMoreElements()将返回false,但如果此时重新获取一个  
            //Enumeration的引用,则新Enumeration的hasMoreElements()将返回true,而且之后对HashTable数据的增、删、改都是可以在  
            //nextElement中获取到的。  
            return !isEmpty && index < list.size();  
        }  
  
        public T nextElement() {  
            if (this.hasMoreElements()) {  
                return list.get(index++);  
            }  
            return null;  
        }  
          
    }  
}

查看shirocConfig

 

加载完整之后交于ShiroFilterFactoryBean使用setFilterChainDefinitionMap使得过滤生效

【4】自定义过滤器

上面我们使用了shiro的默认过滤器,但是由于业务需求,咱们可能要定义自己的过滤器,那么咱们定义呢?

这里我们先查看RolesAuthorizationFilter

 

分析:改源码表示,例如:/admin/order= roles["admin, root"] ,只有当放问该接口同时具备admin和root两种角色时,才可以被访问。

【5】自定义过滤器使用
【5.1】需求

1、实现只要有其中一个角色,则可访问对应路径

【5.2】RolesOrAuthorizationFilter

新建filter层,新建类RolesOrAuthorizationFilter

package com.itheima.shiro.filter;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.CollectionUtils;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.util.Set;
/**
 * @Description:角色或关系
 */
public class RolesOrAuthorizationFilter extends AuthorizationFilter {
    //TODO - complete JavaDoc
    @SuppressWarnings({"unchecked"})
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
        Subject subject = getSubject(request, response);
        String[] rolesArray = (String[]) mappedValue;
        if (rolesArray == null || rolesArray.length == 0) {
            //no roles specified, so nothing to check - allow access.
            return true;
        }
        Set<String> roles = CollectionUtils.asSet(rolesArray);
        //循环roles判断只要有角色则返回true
        for (String role : roles) {
            if(subject.hasRole(role)){
                return true;
            }
        }
        return false;
    }
}
【5.3】编辑ShiroConfig

在ShiroConfig类中添加如下内容

/**
     * @Description 自定义过滤器定义
     */
    private Map<String, Filter> filters() {
        Map<String, Filter> map = new HashMap<String, Filter>();
        map.put("role-or", new RolesOrAuthorizationFilter());
        return map;
    }
    /**
     * @Description Shiro过滤器
     */
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(){
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(defaultWebSecurityManager());
        //使自定义过滤器生效
        shiroFilter.setFilters(filters());
        shiroFilter.setFilterChainDefinitionMap(filterChainDefinition());
        shiroFilter.setLoginUrl("/login");
        shiroFilter.setUnauthorizedUrl("/login");
        return shiroFilter;
    }

【2.2.3】编辑authentication.properties

#静态资源不过滤

/static/**=anon

#登录链接不过滤

/login/**=anon

#访问/resource/**需要有admin的角色

/resource/**=role-or[admin]

#其他链接是需要登录的

/**=authc

7、注解方式鉴权

【1】注解介绍

以下为常用注解

注解 说明
@RequiresAuthentication 表明当前用户需是经过认证的用户
@ RequiresGuest 表明该用户需为”guest”用户
@RequiresPermissions 当前用户需拥有指定权限
@RequiresRoles 当前用户需拥有指定角色
@ RequiresUser 当前用户需为已认证用户或已记住用户

例如RoleAction类中我们添加

/**
   *@Description: 跳转到角色的初始化页面
   */
  @RequiresRoles(value ={"SuperAdmin","dev"},logical = Logical.OR)
  @RequestMapping(value = "listInitialize")
  public ModelAndView listInitialize(){
    return  new ModelAndView("/role/role-listInitialize");
  }
【2】注解原理分析
【2.1】装载过程
【2.2】调用过程
相关文章
|
19天前
|
消息中间件 Java Kafka
Springboot集成高低版本kafka
Springboot集成高低版本kafka
|
25天前
|
NoSQL Java Redis
SpringBoot集成Redis解决表单重复提交接口幂等(亲测可用)
SpringBoot集成Redis解决表单重复提交接口幂等(亲测可用)
284 0
|
30天前
|
NoSQL Java Redis
SpringBoot集成Redis
SpringBoot集成Redis
426 0
|
1月前
|
NoSQL Java Redis
小白版的springboot中集成mqtt服务(超级无敌详细),实现不了掐我头!!!
小白版的springboot中集成mqtt服务(超级无敌详细),实现不了掐我头!!!
272 1
|
22小时前
|
Java Spring
Spring Boot脚手架集成校验框架
Spring Boot脚手架集成校验框架
6 0
|
2天前
|
Java Docker 容器
SpringBoot项目集成XXL-job
SpringBoot项目集成XXL-job
|
4天前
|
Java 关系型数据库 数据库
【SpringBoot系列】微服务集成Flyway
【4月更文挑战第7天】SpringBoot微服务集成Flyway
【SpringBoot系列】微服务集成Flyway
|
19天前
|
SQL Java 调度
SpringBoot集成quartz定时任务trigger_state状态ERROR解决办法
SpringBoot集成quartz定时任务trigger_state状态ERROR解决办法
|
26天前
|
NoSQL Java Redis
SpringBoot集成Redis
SpringBoot集成Redis
54 1
|
29天前
|
Java 测试技术 Maven
SpringBoot集成Elasticsearch
SpringBoot集成Elasticsearch
24 0