SpringBoot集成shiro

SpringBoot集成shiro

数据库设计

  • sh_user:用户表,一个用户可以有多个角色
  • sh_role: 角色表,一个角色可以有多个资源
  • sh_resource:资源表
  • sh_user_role:用户角色中间表
  • sh_role_resource:角色资源中间表

首先自定义realm抽象类并实现

java 复制代码
package com.itheima.shiro.core;

import org.apache.shiro.authc.AuthenticationException;
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:自定义realm的抽象类
 */
public abstract class ShiroDbRealm extends AuthorizingRealm {

    /**
     * @Description 认证方法
     * @param token token对象
     * @return 认证信息
     */
    @Override
    protected abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException ;

    /**
     * @Description 授权方法
     * @param principals 令牌对象
     * @return 授权信息
     */
    @Override
    protected abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals);


   /**
    * @Description 自定义密码比较器
    * @param
    * @return
    */
   @PostConstruct
   public abstract void initCredentialsMatcher();
}

实现这个抽象类

java 复制代码
package com.itheima.shiro.core.impl;

import com.itheima.shiro.constant.SuperConstant;
import com.itheima.shiro.core.ShiroDbRealm;
import com.itheima.shiro.core.base.ShiroUser;
import com.itheima.shiro.core.base.SimpleToken;
import com.itheima.shiro.core.bridge.UserBridgeService;
import com.itheima.shiro.pojo.User;
import com.itheima.shiro.utils.BeanConv;
import com.itheima.shiro.utils.EmptyUtil;
import org.apache.shiro.authc.*;
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:自定义realm的抽象类实现
 */
public class ShiroDbRealmImpl extends ShiroDbRealm {

    @Autowired
    UserBridgeService userBridgeService;

     /**
     * @Description 认证方法
     * @param token token对象
     * @return 认证信息
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //token令牌信息,这里的SimpleToken继承了UsernamePasswordToken
        SimpleToken simpleToken = (SimpleToken) token;
        //根据登录名查询user对象
        User user = userBridgeService.findUserByLoginName(simpleToken.getUsername());
        if (EmptyUtil.isNullOrEmpty(user)){
            throw new UnknownAccountException("账号不存在!");
        }
        //构建认证令牌对象(将User对象转换成ShiroUser对象,ShiroUser类比Users实体类多了资源列表等属性)
        ShiroUser shiroUser = BeanConv.toBean(user, ShiroUser.class);
        // 根据用户id获取资源列表,并设置到shiroUser对象
        shiroUser.setResourceIds(userBridgeService.findResourcesIds(shiroUser.getId()));
        String slat  = shiroUser.getSalt();
        String password = shiroUser.getPassWord();
        //构建认证信息对象:1、令牌对象 2、密文密码  3、加密因子 4、当前realm的名称
        return new SimpleAuthenticationInfo(shiroUser, password, ByteSource.Util.bytes(slat), getName());
    }

     /**
     * @Description 授权方法
     * @param principals 令牌对象
     * @return 授权信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 获得令牌对象
        ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal();
        return userBridgeService.getAuthorizationInfo(shiroUser);
    }

     /**
    * @Description 自定义密码比较器
    * @param
    * @return
    */
    @Override
    public void initCredentialsMatcher() {
        //指定密码算法
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(SuperConstant.HASH_ALGORITHM);
        //指定迭代次数
        hashedCredentialsMatcher.setHashIterations(SuperConstant.HASH_INTERATIONS);
        //生效密码比较器
        setCredentialsMatcher(hashedCredentialsMatcher);
    }
}

SimpleToken类(说明)

java 复制代码
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;
    }
    
    
}

userBridgeService接口(说明)

java 复制代码
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 查询资源ids
     * @param userId 用户id
     * @return 资源id集合
     */
    List<String> findResourcesIds(String userId);

    /**
     * @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);
}

ShiroUser类(说明)

java 复制代码
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;
    
    /**
     * @Description 资源列表
     */
    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;
    }
    
    
}

SuperConstant类(说明)

java 复制代码
package com.itheima.shiro.constant;


/**
 *
 * @Description 静态变量
 */
public class SuperConstant {
    
    /**
     * 常量是
     */
    public static final String YES = "YES";

    /**
     * 常量否
     */
    public static final String NO = "NO";
    
    /**
     * 匿名用户ID
     */
    public static final String ANON_ID = "-1";
    
    /**
     * 树形根节点父Id
     */
    public static final String ROOT_PARENT_ID = "-1";
    
    /**
     * 树形根节点Id
     */
    public static final String ROOT_ID = "1";

    /**
     * 匿名用户登录名
     */
    public static final String ANON_LOGIN_NAME = "ANONYMITY";

    /**
     * 匿名用户真实名
     */
    public static final String ANON_REAL_NAME = "匿名";
    
    
    /**
     * hash算法
     */
    public static final String HASH_ALGORITHM = "SHA-1";
    
    /**
     * 计算次数
     */
    public static final int HASH_INTERATIONS = 1024;


}

ShiroConfig配置

java 复制代码
package com.itheima.shiro.config;

import com.itheima.shiro.core.ShiroDbRealm;
import com.itheima.shiro.core.filter.RolesOrAuthorizationFilter;
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 javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @Description:权限管理配置类
 */
@Configuration
// 不配置扫描也可以
@ComponentScan(basePackages = "com.itheima.shiro.core")
@Log4j2
public class ShiroConfig {

    //创建cookie对象
    @Bean(name = "simpleCookie")
    public SimpleCookie simpleCookie(){
        SimpleCookie simpleCookie = new SimpleCookie();
        simpleCookie.setName("ShiroSession");
        return  simpleCookie;
    }

    //创建权限管理器
    @Bean("securityManager")
    public DefaultWebSecurityManager defaultWebSecurityManager(){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //管理realm
        securityManager.setRealm(shiroDbRealm());
        //管理会话
        securityManager.setSessionManager(sessionManager());
        return securityManager;
    }

    //自定义realm
    @Bean("shiroDbRealm")
    public ShiroDbRealm shiroDbRealm(){
        return new  ShiroDbRealmImpl();
    }

    //会话管理器
    @Bean("sessionManager")
    public DefaultWebSessionManager sessionManager(){
        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        //关闭会话更新
        sessionManager.setSessionValidationSchedulerEnabled(false);
        //生效cookie
        sessionManager.setSessionIdCookieEnabled(true);
        //指定cookie的生成策略
        sessionManager.setSessionIdCookie(simpleCookie());
        //指定全局会话超时时间
        sessionManager.setGlobalSessionTimeout(3600000);
        return sessionManager;
    }

    //创建生命周期的管理
    @Bean("lifecycleBeanPostProcessor")
    public static LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        return  new LifecycleBeanPostProcessor();
    }

    //aop增强(使用注解鉴权方式)
    /**
     * @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 aasa;
    }

    /**
     * @Description 过滤器链定义
     */
    private Map<String,String> filterChainDefinitionMap(){
        // 读取authentication.properties配置文件中的过滤器链定义
        // PropertiesUtil是读取配置文件的工具类
       List<Object> list =  PropertiesUtil.propertiesShiro.getKeyList();
       // 必须创建一个有序的map
        Map<String,String> map = new LinkedHashMap<>();
        for (Object o : list) {
            String key = o.toString();
            String val = PropertiesUtil.getShiroValue(key);
            map.put(key, val);
        }
        return map;
    }

    /**
     * @Description 加载自定义过滤器
     */
    private Map<String, Filter> filters(){
        Map<String,Filter> map = new HashMap<>();
        // 这里的key表示过滤器的名称 
        map.put("roles-or", new RolesOrAuthorizationFilter());
        return map;
    }

    //shiro过滤器管理
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager());
        //过滤器,指定自己的过滤器
        shiroFilterFactoryBean.setFilters(filters());
        //过滤器链
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap());
        //登录页面
        shiroFilterFactoryBean.setLoginUrl("/login");
        //未授权页面
        shiroFilterFactoryBean.setUnauthorizedUrl("/login");
        return shiroFilterFactoryBean;
    }
}

PropertiesUtil类(说明)

java 复制代码
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);
    }

}

authentication.properties配置文件

properties 复制代码
/static/**=anon
#登录链接不拦截
/login/**=anon
#访问/resource/**需要有admin的角色
/resource/**=roles[dev,SuperAdmin]
#其他链接是需要登录的
/**=authc ```

说明:过滤器链由路径+过滤器组成,它是自上而下有序的,一个一个去匹配的。如果上面匹配失败,则不会匹配下面的内容。

自定义过滤器

java 复制代码
package com.itheima.shiro.core.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:角色or判断过滤器,只要当前用户包含传入的角色集合中的一个角色,就判断通过
 */
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);
        for (String role : roles) {
            boolean flag = subject.hasRole(role);
            if (flag){
                return flag;
            }
        }
        return false;
    }

}

加载自定义过滤器

在 ShiroConfig类中配置如下:

java 复制代码
/**
 * @Description 加载自定义过滤器
 */
private Map<String, Filter> filters(){
    Map<String,Filter> map = new HashMap<>();
    // 这里的key表示过滤器的名称
    map.put("roles-or", new RolesOrAuthorizationFilter());
    return map;
}

//shiro过滤器管理
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean(){
    ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager());
    //过滤器,指定自定义的过滤器
    shiroFilterFactoryBean.setFilters(filters());
    //过滤器链
    shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap());
    //登录页面
    shiroFilterFactoryBean.setLoginUrl("/login");
    //未授权页面
    shiroFilterFactoryBean.setUnauthorizedUrl("/login");
    return shiroFilterFactoryBean;
}

测试自定义过滤器

在 authentication.properties配置文件中修改内容如下:

properties 复制代码
#访问/resource/**需要有SuperAdmin或者dev的角色
/resource/**=roles-or[dev,SuperAdmin]

注解方式鉴权

相关推荐
RemainderTime2 小时前
从零搭建Spring Boot3.x生产级单体脚手架项目(JDK17 + Nacos + JWT + Docker)
java·spring boot·架构
黯叶2 小时前
基于 Docker+Docker-Compose 的 SpringBoot 项目标准化部署(外置 application-prod.yml 配置方案)
java·spring boot·redis·docker
say_fall2 小时前
泛型编程基石:C++ 模板从入门到熟练
java·开发语言·c++·编辑器·visual studio
代码笔耕2 小时前
写了几年 Java,我发现很多人其实一直在用“高级 C 语言”写代码
java·后端·架构
txinyu的博客2 小时前
结合游戏场景解析UDP可靠性问题
java·开发语言·c++·网络协议·游戏·udp
一路向北North2 小时前
springboot基础(85): validator验证器
java·spring boot·后端
1.14(java)2 小时前
掌握数据库约束:确保数据精准可靠
java·数据库·mysql·数据库约束
Codeking__2 小时前
Redis——value的数据类型与单线程工作模型
java·数据库·redis
人道领域2 小时前
【零基础学java】(等待唤醒机制,线程池补充)
java·开发语言·jvm