SpringBoot整合Shiro

一、什么是Shiro

Shiro是Apache提供的安全框架,是一个权限管理框架,执行身份验证、授权、密码和会话管理,功能十分强大并且容易使用;
Spring中有Spring Security,是一个权限框架,它和Spring依赖过于紧密,没有Shiro使用简单;

二、核心组成

在这里插入图片描述

Subject

主体,即为”当前操作用户“,系统需要对“当前操作用户”进行认证、授权;它仅仅意味着当前跟软件交互的东西;

SecurityManager

安全管理器,主体进行认证和授权都是通过SecurityManager进行,SecurityManager是一个集合,真正做事的不是SecurityManager而是它里面的东西;相当于Spring MVC中的前端控制器;

Realm

数据源,通过realm存取认证、授权相关数据(原来是通过数据库取的);
authenticator认证器和authorizer授权器是调用realm中存储授权和认证的数据和逻辑;

三、SpringBoot整合

来源:https://blog.csdn.net/chy_18883701161/article/details/107454059

前期准备:

依赖:

<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>2.3.1</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/com.alibaba.fastjson2/fastjson2 -->
		<dependency>
			<groupId>com.alibaba.fastjson2</groupId>
			<artifactId>fastjson2</artifactId>
			<version>2.0.41</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.2.20</version>
		</dependency>
		
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.5.3</version>
		</dependency>

		<dependency>
			<groupId>org.crazycake</groupId>
			<artifactId>shiro-redis</artifactId>
			<version>3.2.3</version>
		</dependency>

		<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
		<dependency>
			<groupId>com.baomidou</groupId>
			<artifactId>mybatis-plus-boot-starter</artifactId>
			<version>3.5.4.1</version>
		</dependency>

mysql建表

-- 用户信息表-- 
create table user_info(
id bigint primary key,
user_name varchar(50),
pwd varchar(300)
) engine = InnoDB default charset = utf8;

-- 角色信息表--
create table role_info(
id bigint primary key,
role_desc varchar(50)
) engine = InnoDB default charset = utf8;

-- 权限信息表--
create table module_info(
id bigint primary key,
module_code varchar(50),
module_name varchar(50),
parent_module varchar(50)
) engine = InnoDB default charset = utf8;

-- 用户与角色的中间表
create table user_role(
id bigint primary key,
user_id bigint,
role_id bigint
) engine = InnoDB default charset = utf8;

-- 角色与权限的中间表
create table role_module(
id bigint primary key,
role_id bigint,
module_id bigint
) engine = InnoDB default charset = utf8;

自定义Realm

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lq.mycreate.bean.ModuleInfo;
import com.lq.mycreate.bean.RoleInfo;
import com.lq.mycreate.bean.RoleModule;
import com.lq.mycreate.bean.UserInfo;
import com.lq.mycreate.bean.UserRole;
import com.lq.mycreate.mapper.ModuleInfoMapper;
import com.lq.mycreate.mapper.RoleInfoMapper;
import com.lq.mycreate.mapper.RoleModuleMapper;
import com.lq.mycreate.mapper.UserInfoMapper;
import com.lq.mycreate.mapper.UserRoleMapper;
import org.apache.commons.collections.CollectionUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 自定义realm
 */
public class UserRealm extends AuthorizingRealm {
    @Autowired
    private UserInfoMapper userInfoMapper;

    @Autowired
    private RoleInfoMapper roleInfoMapper;

    @Autowired
    private ModuleInfoMapper moduleInfoMapper;

    @Autowired
    private UserRoleMapper userRoleMapper;

    @Autowired
    private RoleModuleMapper roleModuleMapper;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("执行授权操作");
        //获取主体标识
        String username = (String) principalCollection.getPrimaryPrincipal();

        //根据主体标识查询用户对应的角色、权限
        //角色
        QueryWrapper<UserInfo> userInfoWrapper = new QueryWrapper<>();
        userInfoWrapper.eq("user_name", username);
        UserInfo userInfo = userInfoMapper.selectOne(userInfoWrapper);
        if (userInfo == null) {
            return null;
        }
        QueryWrapper<UserRole> userRoleWrapper = new QueryWrapper<>();
        userRoleWrapper.eq("user_id", userInfo.getId());
        List<UserRole> userRoles = userRoleMapper.selectList(userRoleWrapper);
        if (CollectionUtils.isEmpty(userRoles)) {
            return null;
        }
        List<RoleInfo> roleInfos = new ArrayList<>();
        Set<String> roles = userRoles.stream().map(userRole -> {
            RoleInfo roleInfo = roleInfoMapper.selectById(userRole.getRoleId());
            if (roleInfo != null) {
                roleInfos.add(roleInfo);
                return roleInfo.getRoleDesc();
            }
            return null;
        }).filter(node -> node != null).collect(Collectors.toSet());


        Set<String> permissions = new HashSet<>();
        roleInfos.stream().forEach(role -> {
            QueryWrapper<RoleModule> roleModuleWrapper = new QueryWrapper<>();
            roleModuleWrapper.eq("role_id", role.getId());
            List<RoleModule> roleModules = roleModuleMapper.selectList(roleModuleWrapper);
            List<String> collect = roleModules.stream().map(roleModule -> {
                ModuleInfo moduleInfo = moduleInfoMapper.selectById(roleModule.getModuleId());
                return moduleInfo.getModuleCode();
            }).collect(Collectors.toList());
            permissions.addAll(collect);
        });

        //设置并返回主体的授权信息
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        simpleAuthorizationInfo.setRoles(roles);
        simpleAuthorizationInfo.setStringPermissions(permissions);
        return simpleAuthorizationInfo;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("执行认证操作");
        //获取主体标识
        String username = (String) token.getPrincipal();

        QueryWrapper<UserInfo> wrapper = new QueryWrapper<>();
        wrapper.eq("user_name", username);
        UserInfo userInfo = userInfoMapper.selectOne(wrapper);
        if (userInfo == null) {
            return null;
        }

        String pwd = userInfo.getPwd();

        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, pwd,
                ByteSource.Util.bytes(userInfo.getUserName()), this.getName());
        return authenticationInfo;
    }

    public static void main(String[] args) {
        SimpleHash simpleHash = new SimpleHash("md5", "123456", "wangwu", 2);
        String s = simpleHash.toHex();
        System.out.println(s);
    }
}

配置ShiroConfig

import com.lq.mycreate.filter.CustomRolesOrAuthorizationFilter;
import com.lq.mycreate.realm.UserRealm;
import com.lq.mycreate.session.CustomSessionManager;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.Filter;
import java.util.LinkedHashMap;
import java.util.Map;


@Configuration
public class ShiroConfig {
    //@Autowired
    //private UserRealm userRealm;

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("securityManager") SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

        //设置securityManager
        factoryBean.setSecurityManager(securityManager);

        //某些页面需要登录后才能访问,如果未登录,会交给指定的url处理;
        //如果是前后端分离,设置未controller映射的url:如果前后端未分离,设置为也视图页面的url
        factoryBean.setLoginUrl("/login");

        //登录成功后跳转到的url
        //如果前后端分离,则不用设置此项
        //factoryBean.setSuccessUrl("/");

        //没有权限,未授权会交给指定的url处理,处理方式一般是:先验证登录,再验证是否有权限;
        //如果前后端没有分离,直接设置为视图页面的url
        factoryBean.setUnauthorizedUrl("/pub/notPermit.html");

        //设置自定义的Filter
        Map<String, Filter> filterMap = new LinkedHashMap<>();
        filterMap.put("roleOrFilter",new CustomRolesOrAuthorizationFilter());
        factoryBean.setFilters(filterMap);

        //Filter的执行有一定顺序,应该使用LinkedHashMap,使用HashMap会出问题
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        //退出过滤器
        filterChainDefinitionMap.put("/logout", "logout");

        //不需要登录(校验)就可以访问
        filterChainDefinitionMap.put("/pub/**", "anon");

        //用户登录后才可以访问
        filterChainDefinitionMap.put("/authc/**", "authc");

        //角色是管理员的用户才可以访问
        //filterChainDefinitionMap.put("/admin/**","roleOrFilter[admin]");

        //具有update权限的用户才可以访问
        filterChainDefinitionMap.put("/update", "perms[update]");

        //[]标识参数是数组,有多个元素时逗号隔开,[video_watch,video_download],必须同时满足[]中的要求才会通过

        //LinkedHashMap,Filter的执行顺序和添加顺序一致,一般把/**放到最后

        //前面的url未匹配的请求路径,就使用/**来过滤
        filterChainDefinitionMap.put("/**", "authc");

        factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return factoryBean;
    }

    /**
     * 获取redisManager
     * @return RedisManager
     */
    public RedisManager redisManager(){
        RedisManager redisManager = new RedisManager();
        //设置redis服务器的地址,默认就是127.0.0.1:6379
        redisManager.setHost("127.0.0.1:6379");
        return redisManager;
    }

    /**
     * 获取RedisSessionDAO
     * @return RedisSessionDAO
     */
    public RedisSessionDAO redisSessionDAO(){
        RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
        redisSessionDAO.setRedisManager(redisManager());
        return redisSessionDAO;
    }

    /**
     * 获取自定义的sessionManager
     * @return
     */
    @Bean
    public SessionManager sessionManager(){
        CustomSessionManager customSessionManager = new CustomSessionManager();

        //设置session超时时间,单位ms,默认30min
        customSessionManager.setGlobalSessionTimeout(1800_000);

        //配置session持久化
        customSessionManager.setSessionDAO(redisSessionDAO());
        return customSessionManager;
    }

    public RedisCacheManager redisCacheManager(){
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());

        //设置授权信息对应的key的过期时间,单位s,用户权限有变化时,除了要更新数据库,还需要更新redis中对应的key
        //一周
        redisCacheManager.setExpire(24*60*60*7);
        return redisCacheManager;
    }

    /**
     * 配置SecurityManager
     *
     * @return
     */
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        //如果前后端没有分离,则不必使用sessionManager,不必设置SessionManager相关配置
        //securityManager.setSessionManager(sessionManager());

        //使用自定义的CacheManager
        //securityManager.setCacheManager(redisCacheManager());

        //设置Realm,建议放到最后,否则可能会出问题
        securityManager.setRealm(userRealm());
        return securityManager;
    }

    /**
     * 返回密码匹配器
     *
     * @return
     */
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
        //设置加密算法
        matcher.setHashAlgorithmName("md5");
        //设置加密次数
        matcher.setHashIterations(2);

        //设置密文是16进制编码,false是base64编码
        matcher.setStoredCredentialsHexEncoded(true);

        return matcher;
    }

    /**
     * 返回自定义的realm
     *
     * @return
     */
    @Bean
    public UserRealm userRealm() {
        UserRealm userRealm = new UserRealm();
        //设置密码匹配器
        userRealm.setCredentialsMatcher(hashedCredentialsMatcher());

        //取消缓存
        userRealm.setCachingEnabled(false);
        return userRealm;
    }
}

登录控制

import com.alibaba.fastjson2.JSON;
import com.lq.mycreate.bean.UserInfo;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class LoginController {

    @RequestMapping("/login")
    public String login(UserInfo userInfo) {
        System.out.println("前端页面的参数是:" + JSON.toJSONString(userInfo));
        UsernamePasswordToken token = new UsernamePasswordToken(userInfo.getUserName(), userInfo.getPwd());
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
            return "index.html";
        } catch (Exception ex) {
            ex.printStackTrace();
            return "login.html";
        }
    }

    @RequestMapping("/update")
    public String update() {
        return "update";
    }
}

自定义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.util.Set;

/**
 * 自定义过滤器
 */
public class CustomRolesOrAuthorizationFilter extends AuthorizationFilter {
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mapperValue) throws Exception {
        Subject subject = getSubject(request, response);

        //获取当前访问路径所需要的角色集合
        String[] roles = (String[]) mapperValue;

        //没有角色限制,可以直接访问
        if (roles == null || roles.length == 0) {
            return true;
        }

        Set<String> roleSet = CollectionUtils.asSet(roles);

        for (String role : roleSet) {
            if (subject.hasRole(role)) {
                return true;
            }
        }
        return false;
    }
}

自定义SessionManager(未验证)

public class CustomSessionManager extends DefaultWebSessionManager {
    private static final String AUTHORIZATION = "token";

    public CustomSessionManager() {
        super();
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String sessionId = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        if (sessionId != null) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, ShiroHttpServletRequest.COOKIE_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, sessionId);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, true);
            return sessionId;
        } else {
            return super.getSessionId(request, response);
        }
    }
}

UserInfoMapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lq.mycreate.mapper.UserInfoMapper">
    <resultMap id="UserRole" type="com.lq.mycreate.bean.UserInfo">
        <id property="id" column="uid"/>
        <result property="userName" column="userName"/>
        <result property="pwd" column="pwd"/>
        <!--一对多-->
        <collection property="userRoles" ofType="com.lq.mycreate.bean.UserRole">
            <!--多中的一对一-->
            <association property="roleInfo" javaType="com.lq.mycreate.bean.RoleInfo">
                <result property="id" column="rid"/>
                <result property="roleDesc" column="roleDesc"/>
            </association>
        </collection>
    </resultMap>

    <select id="selectRoleByUserName" resultMap="UserRole">
        select u.id uid,u.user_name userName,u.pwd pwd,r.id rid,r.role_desc roleDesc from user_info u,user_role
        ur,role_info r
        where u.id =ur.user_id and ur.role_id=r.id and user_name = #{userName};
    </select>

</mapper>



注意

启动的时候会报错:xxx is not eligible for getting processed by all BeanPostProcessors
处理方法:https://blog.csdn.net/u014163312/article/details/124954945

工程代码:https://gitee.com/qi_tian_sheng_sheng/SpringBoot.git

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值