0
点赞
收藏
分享

微信扫一扫

ASP.Net Core Web API项目发布到IIS(二)

微言记 2023-07-04 阅读 78

目录

解决问题一

解决问题二

新的问题

第一步:添加依赖

第二步:添加redis配置类

第三步:创建redis工具类

第四步:创建用于操作用户权限的redis仓库

第五步:保存用户权限到redis


上一篇文章springboot整合shiro实现认证和授权已经介绍了怎么使用shiro来实现身份认证和资源授权,但是还存在一些问题。

数据库中添加对应的权限,并给角色绑定权限,然后给用户分配角色

/*
 Navicat Premium Data Transfer

 Source Server         : MariaDB
 Source Server Type    : MariaDB
 Source Server Version : 100605 (10.6.5-MariaDB)
 Source Host           : localhost:3306
 Source Schema         : shiro

 Target Server Type    : MariaDB
 Target Server Version : 100605 (10.6.5-MariaDB)
 File Encoding         : 65001

 Date: 03/07/2023 17:48:13
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for permission
-- ----------------------------
DROP TABLE IF EXISTS `permission`;
CREATE TABLE `permission`  (
  `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '名称',
  `type` tinyint(3) UNSIGNED NOT NULL COMMENT '权限类型(父权限/子权限)',
  `url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '接口路径',
  `method` tinyint(3) UNSIGNED NOT NULL DEFAULT 0 COMMENT '请求方式(0-get;1-post)',
  `service` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'mhxysy' COMMENT '服务名',
  `parent_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '父级权限id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统权限表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of permission
-- ----------------------------
INSERT INTO `permission` VALUES ('UserController', '用户管理', 0, '/user', 0, 'shiro', NULL);
INSERT INTO `permission` VALUES ('UserController_delete', '删除用户', 0, '/user/delete', 1, 'shiro', 'UserController');
INSERT INTO `permission` VALUES ('UserController_login', '用户登录', 1, '/user/login', 1, 'shiro', 'UserController');
INSERT INTO `permission` VALUES ('UserController_update', '修改用户', 1, '/user/update', 1, 'shiro', 'UserController');

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '名称',
  `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述',
  `sort` int(10) UNSIGNED NULL DEFAULT 0 COMMENT '自定义排序序号',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '系统角色表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, '超级管理员', '最高权限,拥有系统所有权限', 0);
INSERT INTO `role` VALUES (2, '系统管理员', '拥有系统设置相关权限', 100);

-- ----------------------------
-- Table structure for role_permission
-- ----------------------------
DROP TABLE IF EXISTS `role_permission`;
CREATE TABLE `role_permission`  (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `role_id` int(10) UNSIGNED NOT NULL COMMENT '角色id',
  `permission_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '权限id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色-权限关联表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role_permission
-- ----------------------------
INSERT INTO `role_permission` VALUES (1, 1, 'UserController_login');
INSERT INTO `role_permission` VALUES (2, 1, 'UserController_delete');
INSERT INTO `role_permission` VALUES (3, 1, 'UserController_update');
INSERT INTO `role_permission` VALUES (4, 2, 'UserController_login');
INSERT INTO `role_permission` VALUES (5, 2, 'UserController_delete');
INSERT INTO `role_permission` VALUES (6, 2, 'UserController_update');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL,
  `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '姓名',
  `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
  `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '12345' COMMENT '密码',
  `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '手机号',
  `gender` tinyint(4) UNSIGNED NOT NULL COMMENT '性别,0-女;1-男',
  `is_enable` tinyint(4) UNSIGNED NOT NULL COMMENT '是否启用(0-未启用;1-启用中)',
  `last_login_time` datetime NULL DEFAULT NULL COMMENT '上一次登录时间',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('2023', '系统管理员', 'system', '123456', '18888888888', 2, 1, '2022-11-25 00:15:42');
INSERT INTO `user` VALUES ('mhxy1218', '沐雨橙风ιε', 'mumu', 'mhxy1218', '16666666666', 0, 1, '2023-07-03 15:00:32');

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT,
  `role_id` int(10) UNSIGNED NOT NULL COMMENT '角色id,数据来源于role表的主键',
  `user_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户id,数据来源于user表的主键',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户-角色关系表表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 'mhxy1218');
INSERT INTO `user_role` VALUES (2, 2, '2023');

SET FOREIGN_KEY_CHECKS = 1;

解决问题一

优化:在配置类里查询全部系统权限,把这些权限的URL交给我们自定义过滤器处理。

package com.example.shiro.config;

import com.example.shiro.filter.AuthorizationFilter;
import com.example.shiro.mapper.PermissionMapper;
import com.example.shiro.realm.UserRealm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
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.List;
import java.util.Map;

/**
 * shiro配置类
 */
@Configuration
public class ShiroConfig {
    private final PermissionMapper permissionMapper;

    @Autowired
    public ShiroConfig(PermissionMapper mapper) {
        this.permissionMapper = mapper;
    }

    /**
     * 配置安全管理器
     * @param userRealm UserRealm
     * @return DefaultWebSecurityManager
     */
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager securityManager(@Qualifier("userRealm") UserRealm userRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        securityManager.setRealm(userRealm);

        return securityManager;
    }

    /**
     * 配置Shiro过滤器工厂
     * @param securityManager 安全管理器
     * @return ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        // 注册安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 当用户访问认证资源的时候,如果用户没有登录,那么就会跳转到该属性指定的页面
        shiroFilterFactoryBean.setLoginUrl("/login.html");

        // 添加自定义过滤器
        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();
        filters.put("authorization", new AuthorizationFilter());
        shiroFilterFactoryBean.setFilters(filters);

        // 定义资源访问规则
        Map<String, String> map = new LinkedHashMap<>();

        map.put("/", "authc");
        map.put("/html/*", "authc");
        map.put("/index.html", "authc");
        // 登录接口不需要鉴权
        map.put("/user/login", "anon");

        // 查询所有权限
        List<String> permissions = permissionMapper.selectAll();

        for (String permission : permissions) {
            map.put(permission, "authorization");
        }

        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);

        return shiroFilterFactoryBean;
    }

}

这样的话,和接口无关的资源就不需要我们自定义过滤器来处理了,可以进一步改进过滤器的代码。简化之后的代码如下:

package com.example.shiro.filter;

import com.alibaba.fastjson.JSON;
import com.example.shiro.restful.JsonResult;
import com.example.shiro.restful.ResponseCode;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 鉴权过滤器
 * @author heyunlin
 * @version 1.0
 */
@WebFilter
public class AuthorizationFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        HttpServletRequest request = (HttpServletRequest) req;
        String requestURI = request.getRequestURI();

        if ("/user/login".equals(requestURI)) {
            chain.doFilter(req, resp);
            return;
        }

        Subject subject = SecurityUtils.getSubject();

        if (subject != null && !subject.isPermitted(requestURI)) {
            HttpServletResponse response = (HttpServletResponse) resp;
            response.setContentType("application/json;charset=utf-8");

            // 构建返回对象
            JsonResult<Void> jsonResult= JsonResult.error(ResponseCode.UNAUTHORIZED, "正在访问未授权的资源");
            String data = JSON.toJSONString(jsonResult);

            response.getWriter().write(data);
            return;
        }

        chain.doFilter(req, resp);
    }

}

解决问题二

PermissionMapper

package com.example.shiro.mapper;

import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * @author heyunlin
 * @version 1.0
 */
@Repository
public interface PermissionMapper {
    /**
     * 查询所有权限
     * @return List<String>
     */
    List<String> selectAll();

    /**
     * 通过用户名查询用户权限
     * @param username 用户名
     * @return List<String> 用户权限的URL列表
     */
    List<String> selectPermissionsByUsername(String username);
}

PermissionMapper.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.example.shiro.mapper.PermissionMapper">
    <select id="selectAll" resultType="java.lang.String">
        select url from permission
    </select>

    <select id="selectPermissionsByUsername" resultType="java.lang.String">
        select qx.url from permission qx
        left join role_permission jsqx on qx.id = jsqx.permission_id
        left join user_role yhjs on jsqx.role_id = yhjs.role_id
        left join user yh on yhjs.user_id = yh.id
        where yh.username= #{username}
    </select>
</mapper>

UserRealm中的权限从数据库获取

package com.example.shiro.realm;

import com.example.shiro.entity.User;
import com.example.shiro.exception.GlobalException;
import com.example.shiro.mapper.PermissionMapper;
import com.example.shiro.mapper.UserMapper;
import com.example.shiro.restful.ResponseCode;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.List;

/**
 * @author heyunlin
 * @version 1.0
 */
@Component
public class UserRealm extends AuthorizingRealm {
    private final UserMapper userMapper;
    private final PermissionMapper permissionMapper;

    @Autowired
    public UserRealm(UserMapper userMapper, PermissionMapper permissionMapper) {
        this.userMapper = userMapper;
        this.permissionMapper = permissionMapper;
    }

    /**
     * 认证
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;

        // 得到用户名
        String username = token.getUsername();
        // 根据用户名查询用户信息
        User user = userMapper.selectByUsername(username);

        if (user == null) {
            throw new GlobalException(ResponseCode.BAD_REQUEST, "登录失败,用户不存在~");
        }
        if (user.getIsEnable()) {
            String password = new String(token.getPassword());

            if (user.getPassword().equals(password)) {
                return new SimpleAuthenticationInfo(user, password, username);
            } else {
                throw new GlobalException(ResponseCode.BAD_REQUEST, "用户名或密码错误,登录失败!");
            }
        }

        return null;
    }

    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        User user = (User) principals.getPrimaryPrincipal();
        String username = user.getUsername();

        // 通过用户名获取用户的权限
        List<String> permissions = permissionMapper.selectPermissionsByUsername(username);

        authorizationInfo.setStringPermissions(new HashSet<>(permissions));

        return authorizationInfo;
    }

}

新的问题

第一步:添加依赖

<!--redis-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

第二步:添加redis配置类

package com.example.shiro.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;

@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, ?> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, ?> redisTemplate = new RedisTemplate<>();

        redisTemplate.setConnectionFactory(factory);
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setValueSerializer(RedisSerializer.json()); // 值(Value)使用JSON

        return redisTemplate;
    }

}

第三步:创建redis工具类

package com.example.shiro.redis;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Set;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class RedisUtils {
    private final RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public RedisUtils(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 保存数据到Redis
     * @param key 键
     * @param value 值
     */
    public void save(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
        redisTemplate.expire(key, 7, TimeUnit.DAYS);
    }

    /**
     * 通过用户名获取用户的权限信息
     * @param key 键
     */
    public Object get(String key) {
        log.debug("从redis中获取key{}...", key);

        if (hasKey(key)) {
            return redisTemplate.opsForValue().get(key);
        } else {
            return null;
        }
    }

    /**
     * 通过用户名删除用户的权限信息
     * @param key 用户名
     */
    public void remove(String key) {
        if (hasKey(key)) {
            redisTemplate.delete(key);
            log.debug("从redis中删除key{}...", key);
        }
    }

    /**
     * 删除全部用户的权限信息
     */
    public void removeAll() {
        Set<String> keys = redisTemplate.keys("*");

        if (keys != null && !keys.isEmpty()) {
            redisTemplate.delete(keys);
        }

        log.debug("从redis中删除全部key...");
    }

    /**
     * 判断key是否存在
     * @param key 键
     * @return Boolean
     */
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

}

第四步:创建用于操作用户权限的redis仓库

package com.example.shiro.redis;

import com.alibaba.fastjson2.JSON;
import com.example.shiro.mapper.PermissionMapper;
import com.example.shiro.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.TimeUnit;

@Slf4j
@Component
public class RedisRepository extends RedisUtils {
    private static final String SUFFIX = "ROLE_PERMISSIONS:";

    private final PermissionMapper permissionMapper;
    private final RedisTemplate<String, Object> redisTemplate;

    @Autowired
    public RedisRepository(PermissionMapper permissionMapper, RedisTemplate<String, Object> redisTemplate) {
        super(redisTemplate);

        this.permissionMapper = permissionMapper;
        this.redisTemplate = redisTemplate;
    }

    /**
     * 查询用户的权限信息,并保存到redis
     * @param username 用户名
     */
    public void save(String username) {
        String key = SUFFIX + username;
        // 查询用户的权限
        List<String> permissions = permissionMapper.selectPermissionsByUsername(username);

        redisTemplate.opsForValue().set(key, JSON.toJSONString(permissions));
        redisTemplate.expire(key, 7, TimeUnit.DAYS);

        log.debug("用户{}的权限保存到了redis中", username);
    }

    /**
     * 通过用户名获取用户的权限信息
     * @param username 用户名
     */
    @Override
    public List<String> get(String username) {
        String key = SUFFIX + username;
        log.info("从redis中获取用户{}的权限", username);

        if (Boolean.TRUE.equals(redisTemplate.hasKey(key))) {
            String permissions = (String) redisTemplate.opsForValue().get(key);

            if (StringUtils.isNotEmpty(permissions)) {
                Object object = JSON.parse(permissions);

                return object != null ? (List<String>) object : null;
            }

            return null;
        } else {
            save(username);

            return get(username);
        }
    }

    /**
     * 通过用户名删除用户的权限信息
     * @param username 用户名
     */
    @Override
    public void remove(String username) {
        String key = SUFFIX + username;

        super.remove(key);
        log.debug("从redis中删除用户{}的权限...", username);
    }

}

第五步:保存用户权限到redis

博主采用了折中的办法,用户登录的时候查询用户的权限并保存到redis中,用户退出的时候从redis删除用户的权限。这样做的好处是,用户没有登录的时候,是不需要用到该该用户的权限缓存的,可以节省内存,而且每次登录获取到的权限都是最新的。

接下来,在用户登录的方法中添加保存用户权限的代码

package com.example.shiro.service.impl;

import com.example.shiro.dto.UserLoginDTO;
import com.example.shiro.entity.User;
import com.example.shiro.exception.GlobalException;
import com.example.shiro.mapper.UserMapper;
import com.example.shiro.redis.RedisRepository;
import com.example.shiro.restful.ResponseCode;
import com.example.shiro.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;

/**
 * @author heyunlin
 * @version 1.0
 */
@Service
public class UserServiceImpl implements UserService {
    private final UserMapper mapper;
    private final RedisRepository redisRepository;

    @Autowired
    public UserServiceImpl(UserMapper mapper, RedisRepository redisRepository) {
        this.mapper = mapper;
        this.redisRepository = redisRepository;
    }

    @Override
    public void login(UserLoginDTO loginDTO) {
        String username = loginDTO.getUsername();

        // 查询用户信息
        User user = mapper.selectByUsername(username);

        if (user != null) {
            // 如果用户被锁定,提前退出
            if (user.getIsEnable()) {
                // shiro登录认证
                UsernamePasswordToken token = new UsernamePasswordToken(username, loginDTO.getPassword());
                Subject subject = SecurityUtils.getSubject();

                subject.login(token);
                // 设置session失效时间:永不超时
                subject.getSession().setTimeout(-1001);

                // 修改管理员上一次登录时间
                user.setLastLoginTime(LocalDateTime.now());
                mapper.updateById(user);

                redisRepository.remove(username);
                redisRepository.save(username);
            } else {
                throw new GlobalException(ResponseCode.FORBIDDEN, "账号已被锁定,禁止登录!");
            }
        } else {
            throw new GlobalException(ResponseCode.NOT_FOUND, "用户名不存在~");
        }
    }

}

最新代码访问地址为:shiro: springboot整合shiro实现认证和授权 - Gitee.com

举报

相关推荐

0 条评论