【SpringSecurity】认证与鉴权框架SpringSecurity——授权

news2024/10/10 12:21:25

目录

  • 权限系统的必要性
  • 常见的权限管理框架
  • SpringSecurity授权
    • 基本流程
    • 准备脚本
    • 限制访问资源所需权限
    • 菜单实体类和Mapper
    • 封装权限信息
    • 封装认证/鉴权失败处理
      • 认证失败封装
      • 鉴权失败封装
      • 配置SpringSecurity
    • 过滤器
    • 跨域处理
    • 接口添加鉴权
      • hasAuthority/hasAnyAuthority
      • hasRole/​ hasAnyRole
    • 自定义权限校验方法

权限系统的必要性

权限系统在现代软件开发、信息管理系统、网络服务和各种数字平台中扮演着至关重要的角色,其必要性主要体现在以下几个方面:

  • 安全控制:权限系统是保护数据和资源安全的第一道防线。通过限制对敏感信息和关键功能的访问,可以有效防止未经授权的访问、修改或泄露,从而降低安全风险。

  • 职责分离:在组织内部,不同的用户或角色拥有不同的职责。权限系统确保每个用户只能访问和操作与他们的工作职责相关的系统部分,这有助于实现职责分离和内部控制,减少错误和欺诈的可能性。

  • 合规性要求:许多行业都有严格的数据保护法规和标准(如GDPR、HIPAA等),要求对个人信息和敏感数据进行严格的访问控制。权限系统帮助组织符合这些法律法规的要求,避免法律风险和罚款。

  • 提升用户体验:通过为不同用户提供定制化的界面和功能,权限系统可以减少信息过载,使用户更容易找到他们需要的信息和服务,从而提升整体的用户体验。

  • 审计追踪:权限系统能够记录用户的访问和操作日志,这对于事后审计、故障排查和安全事件调查至关重要。这不仅有助于及时发现并解决问题,也为追究责任提供了依据。

  • 灵活性和可扩展性:随着组织的发展和需求的变化,权限系统允许管理员灵活地调整权限设置,新增或删除用户角色,以及对系统功能进行细粒度的控制,保证了系统的长期稳定性和可扩展性。

总之,权限系统是维护信息安全、支持组织管理和满足法律法规要求的基础架构,对于保障数字环境的稳定、安全和高效运行具有不可替代的作用。

常见的权限管理框架

Java Web开发中,为了实现权限管理,开发者常采用一些成熟的权限框架来简化开发流程和提高系统安全性。以下是一些常见的Java权限管理框架:

  • Spring Security:这是Java领域中最受欢迎和广泛使用的安全框架之一,它为Web应用程序提供了一整套安全解决方案,包括认证(Authentication)和授权(Authorization)。Spring Security支持多种认证机制(如JWT、OAuth2)、自定义权限控制,并且能够无缝集成到Spring Boot应用中。

  • Apache Shiro:Shiro是一个强大且易用的安全框架,它提供身份验证、授权、会话管理以及加密等功能。相比Spring Security,Shiro的学习曲线更平缓,适用于需要快速实现安全功能的项目。Shiro支持多种环境,不仅限于Web应用,也适用于命令行应用、Swing应用等。

  • JBoss Keycloak:Keycloak是一个开源的Identity and Access Management (IAM)系统,提供了单一登录(SSO)、身份管理、社交登录等特性。它可以通过OpenID Connect、OAuth 2.0等协议与Java Web应用集成,非常适合构建大型分布式系统的权限管理。

  • Spring Authorization Server:这是Spring生态系统中用于构建授权服务器的新项目,特别适合需要实现OAuth2协议的场景。虽然它本身不直接处理应用程序的权限控制逻辑,但与Spring Security结合使用,可以构建出强大的认证和授权体系。

  • Apache Ranger:虽然更多被用于大数据平台(如Hadoop、Hive、Kafka)的权限管理,但Apache Ranger也可以应用于其他Java Web项目中,特别是那些需要复杂数据权限控制的场景。

选择合适的权限框架时,需要根据项目的具体需求、团队熟悉度、系统规模以及是否需要支持特定的安全协议等因素综合考虑。

SpringSecurity授权

基本流程

  • SpringSecurity是使用默认的FilterSecurityInterceptor来进行权限校验。
  • 在FilterSecurityInterceptor中会从SecurityContextHolder获取其中的Authentication,然后获取其中的权限信息。
  • 并以此来判断当前用户是否拥有访问当前资源所需的权限。
  • 因此需要把用户成功登录后的的权限信息也存入Authentication, 然后设置资源所需要的权限即可。

准备脚本

/*
 Navicat Premium Data Transfer

 Source Server         : 本机连接
 Source Server Type    : MySQL
 Source Server Version : 50744
 Source Host           : localhost:3306
 Source Schema         : kgc_power

 Target Server Type    : MySQL
 Target Server Version : 50744
 File Encoding         : 65001

 Date: 24/06/2024 15:07:37
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for account
-- ----------------------------
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account`  (
  `id` int(11) NOT NULL,
  `accountCode` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `accountName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `accountPassword` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of account
-- ----------------------------
INSERT INTO `account` VALUES (1, 'admin', '系统管理员', '$2a$10$dJ1Ct/QuOOJTkhNkYeFh8uCeEqpVUO.ZjiPOfPsmbsWlX3ZIp.kDa');
INSERT INTO `account` VALUES (2, 'zhangsan', '张三', '$2a$10$dJ1Ct/QuOOJTkhNkYeFh8uCeEqpVUO.ZjiPOfPsmbsWlX3ZIp.kDa');
INSERT INTO `account` VALUES (3, 'lisi', '李四', '$2a$10$dJ1Ct/QuOOJTkhNkYeFh8uCeEqpVUO.ZjiPOfPsmbsWlX3ZIp.kDa');
INSERT INTO `account` VALUES (4, 'wangwu', '王五', '$2a$10$dJ1Ct/QuOOJTkhNkYeFh8uCeEqpVUO.ZjiPOfPsmbsWlX3ZIp.kDa');
INSERT INTO `account` VALUES (5, 'zhaoliu', '赵六', '$2a$10$dJ1Ct/QuOOJTkhNkYeFh8uCeEqpVUO.ZjiPOfPsmbsWlX3ZIp.kDa');

-- ----------------------------
-- Table structure for account_role
-- ----------------------------
DROP TABLE IF EXISTS `account_role`;
CREATE TABLE `account_role`  (
  `accountId` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '用户id',
  `roleId` bigint(200) NOT NULL DEFAULT 0 COMMENT '角色id',
  PRIMARY KEY (`accountId`, `roleId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of account_role
-- ----------------------------
INSERT INTO `account_role` VALUES (1, 1);
INSERT INTO `account_role` VALUES (2, 2);
INSERT INTO `account_role` VALUES (3, 2);
INSERT INTO `account_role` VALUES (4, 3);
INSERT INTO `account_role` VALUES (5, 3);

-- ----------------------------
-- Table structure for menu
-- ----------------------------
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `menuName` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'NULL' COMMENT '菜单名',
  `path` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '路由地址',
  `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组件路径',
  `visible` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
  `perms` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '权限标识',
  `icon` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '#' COMMENT '菜单图标',
  `createBy` bigint(20) NULL DEFAULT NULL,
  `createTime` datetime NULL DEFAULT NULL,
  `updateBy` bigint(20) NULL DEFAULT NULL,
  `updateTime` datetime NULL DEFAULT NULL,
  `delFlag` int(11) NULL DEFAULT 0 COMMENT '是否删除(0未删除 1已删除)',
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '菜单表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of menu
-- ----------------------------
INSERT INTO `menu` VALUES (1, '订单管理', '/order', '/order', '0', '0', 'system:order', '#', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `menu` VALUES (2, '系统管理', '/sys', '/sys', '0', '0', 'system:sys', '#', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `menu` VALUES (3, '个人中心', '/info', '/info', '0', '0', 'system:info', '#', NULL, NULL, NULL, NULL, 0, NULL);
INSERT INTO `menu` VALUES (4, '商品管理', '/product', '/product', '0', '0', 'system:product', '#', NULL, NULL, NULL, NULL, 0, NULL);

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `roleKey` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色权限字符串',
  `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '角色状态(0正常 1停用)',
  `delFlag` int(1) NULL DEFAULT 0 COMMENT 'del_flag',
  `createBy` bigint(200) NULL DEFAULT NULL,
  `createTime` datetime NULL DEFAULT NULL,
  `updateBy` bigint(200) NULL DEFAULT NULL,
  `updateTime` datetime NULL DEFAULT NULL,
  `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, '系统管理员', 'ADMIN', '0', 0, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `role` VALUES (2, '供应商', 'PROVIDER', '0', 0, NULL, NULL, NULL, NULL, NULL);
INSERT INTO `role` VALUES (3, '需求方', 'CONSUMER', '0', 0, NULL, NULL, NULL, NULL, NULL);

-- ----------------------------
-- Table structure for role_menu
-- ----------------------------
DROP TABLE IF EXISTS `role_menu`;
CREATE TABLE `role_menu`  (
  `roleId` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
  `menuId` bigint(200) NOT NULL DEFAULT 0 COMMENT '菜单id',
  PRIMARY KEY (`roleId`, `menuId`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of role_menu
-- ----------------------------
INSERT INTO `role_menu` VALUES (1, 2);
INSERT INTO `role_menu` VALUES (1, 3);
INSERT INTO `role_menu` VALUES (2, 1);
INSERT INTO `role_menu` VALUES (2, 3);
INSERT INTO `role_menu` VALUES (3, 1);
INSERT INTO `role_menu` VALUES (3, 3);
INSERT INTO `role_menu` VALUES (3, 4);

SET FOREIGN_KEY_CHECKS = 1;

限制访问资源所需权限

  • SpringSecurity提供了基于注解的权限控制方案,可以使用注解去指定访问对应的资源所需的权限, 但是要使用它需要先开启相关配置。
  • 启动类上加@EnableGlobalMethodSecurity(prePostEnabled = true)

菜单实体类和Mapper

package com.micro.pojo;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;

/**
 * @author: zjl
 * @datetime: 2024/6/24
 * @desc: 复兴Java,我辈义不容辞
 */
@Data
public class Menu implements Serializable {

    private Long id;
    /**
     * 菜单名
     */
    private String menuName;
    /**
     * 路由地址
     */
    private String path;
    /**
     * 组件路径
     */
    private String component;
    /**
     * 菜单状态(0显示 1隐藏)
     */
    private String visible;
    /**
     * 菜单状态(0正常 1停用)
     */
    private String status;
    /**
     * 权限标识
     */
    private String perms;
    /**
     * 菜单图标
     */
    private String icon;

    private Long createBy;

    private Date createTime;

    private Long updateBy;

    private Date updateTime;
    /**
     * 是否删除(0未删除 1已删除)
     */
    private Integer delFlag;
    /**
     * 备注
     */
    private String remark;
}
List<String> selectPermsByAccountId(Integer accountId);
    <select id="selectPermsByAccountId" resultType="string" parameterType="int">
        SELECT
            DISTINCT M.`PERMS`
        FROM
            ACCOUNT_ROLE AR
                LEFT JOIN `ROLE` R ON AR.`ROLEID` = R.`ID`
                LEFT JOIN `ROLE_MENU` RM ON AR.`ROLEID` = RM.`ROLEID`
                LEFT JOIN `MENU` M ON M.`ID` = RM.`MENUID`
        WHERE
            ACCOUNTID = #{accountId}
          AND R.`STATUS` = 0
          AND M.`STATUS` = 0
    </select>
mybatis:
  mapper-locations: classpath:mapper/*.xml
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

封装权限信息

在UserDetailsServiceImpl中去调用该mapper的方法查询权限信息封装到LoginUser对象中

@Service
public class AccountDetailsServiceImpl implements UserDetailsService {
    @Resource
    private AccountMapper accountMapper;
    @Resource
    private MenuMapper menuMapper;
    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        Account account = accountMapper.selectAccountByAccountCode(userName);
        if(Objects.isNull(account)){
            throw new RuntimeException("用户名或密码错误");
        }

        //根据用户查询权限信息 LoginAccount
        List<String> permissionKeyList =  menuMapper.selectPermsByAccountId(account.getId());
        //封装成UserDetails对象返回
        return new LoginAccount(account, permissionKeyList);
    }
}
package com.micro.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author: zjl
 * @datetime: 2024/6/22
 * @desc: 复兴Java,我辈义不容辞
 */
@Data
@NoArgsConstructor
public class LoginAccount implements UserDetails {
    private Account account;
    private List<String> permissions;
    @JSONField(serialize = false)
    private List<GrantedAuthority> authorities;
    public LoginAccount(Account account,List<String> permissions) {
        this.account = account;
        this.permissions = permissions;
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if(authorities!=null){
            return authorities;
        }
        //把permissions中字符串类型的权限信息转换成GrantedAuthority对象存入authorities中
        authorities = permissions.stream().
                map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
        return authorities;
    }

    @Override
    public String getPassword() {
        return account.getAccountPassword();
    }

    @Override
    public String getUsername() {
        return account.getAccountName();
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

封装认证/鉴权失败处理

  • 在SpringSecurity中,如果在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。
    • 认证过程中出现的异常可以被封装成AuthenticationException,然后调用AuthenticationEntryPoint 对象的方法去进行异常处理。
    • 如果是授权过程中出现的异常可以被封装成AccessDeniedException,然后调用AccessDeniedHandler对象的方法去进行异常处理。

认证失败封装

package com.micro.service;

import com.alibaba.fastjson.JSON;
import com.micro.utils.ResponseResult;
import com.micro.utils.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author: zjl
 * @datetime: 2024/6/24
 * @desc: 复兴Java,我辈义不容辞
 */

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "认证失败请重新登录");
        String json = JSON.toJSONString(result);
        WebUtils.renderString(response,json);
    }
}

鉴权失败封装

package com.micro.service;

import com.alibaba.fastjson.JSON;
import com.micro.utils.ResponseResult;
import com.micro.utils.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author: zjl
 * @datetime: 2024/6/24
 * @desc: 复兴Java,我辈义不容辞
 */
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(), "权限不足");
        String json = JSON.toJSONString(result);
        WebUtils.renderString(response,json);
    }
}

配置SpringSecurity

package com.micro.config;

import com.micro.filter.JwtAuthenticationTokenFilter;
import com.micro.service.AccessDeniedHandlerImpl;
import com.micro.service.AuthenticationEntryPointImpl;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.annotation.Resource;

/**
 * @author: zjl
 * @datetime: 2024/6/22
 * @desc: 复兴Java,我辈义不容辞
 */


@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
    @Resource
    private AccessDeniedHandlerImpl accessDeniedHandler;
    @Resource
    private AuthenticationEntryPointImpl authenticationEntryPoint;

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .antMatchers("/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();

        //把token校验过滤器添加到过滤器链中
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).
                accessDeniedHandler(accessDeniedHandler);
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

过滤器

package com.micro.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.micro.pojo.Account;
import com.micro.pojo.LoginAccount;
import com.micro.utils.JwtUtil;
import com.micro.utils.RedisStringUtil;
import io.jsonwebtoken.Claims;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.annotation.Resource;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.Objects;

/**
 * @author: zjl
 * @datetime: 2024/6/22
 * @desc: 复兴Java,我辈义不容辞
 */
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Resource
    private RedisStringUtil redisStringUtil;
    @Resource
    private ObjectMapper objectMapper;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //获取token
        String token = request.getHeader("token");
        if (!StringUtils.hasText(token)) {
            //放行
            filterChain.doFilter(request, response);
            return;
        }
        //解析token
        String userid;
        try {
            Claims claims = JwtUtil.parseJWT(token);
            userid = claims.getSubject();
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token非法");
        }
        //从redis中获取用户信息
        String redisKey = "login:" + userid;
        LoginAccount loginAccount = JSON.parseObject(redisStringUtil.get(redisKey), LoginAccount.class);
        if(Objects.isNull(loginAccount)){
            throw new RuntimeException("用户未登录");
        }
        //存入SecurityContextHolder
        //获取权限信息封装到Authentication中
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginAccount,null,loginAccount.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        //放行
        filterChain.doFilter(request, response);
    }
}

跨域处理

package com.micro.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author: zjl
 * @datetime: 2024/6/24
 * @desc: 复兴Java,我辈义不容辞
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 设置允许跨域的路径
        registry.addMapping("/**")
                // 设置允许跨域请求的域名
                .allowedOrigins("*")
                // 是否允许cookie
                .allowCredentials(true)
                // 设置允许的请求方式
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                // 设置允许的header属性
                .allowedHeaders("*")
                // 跨域允许时间
                .maxAge(60 * 60);
    }
}

SecurityConfig类中configure()方法添加以下代码

//允许跨域
        http.cors();

接口添加鉴权

SpringSecurity提供了以下方法:hasAuthority,hasAnyAuthority,hasRole,hasAnyRole等。

hasAuthority/hasAnyAuthority

  • hasAuthority方法内部调用authentication的getAuthorities方法获取用户的权限列表。然后判断存入的方法参数数据在权限列表中。
  • ​ hasAnyAuthority方法可以传入多个权限,只有用户有其中任意一个权限都可以访问对应资源。
@RestController
@RequestMapping("/order")
public class OrderController {
    @GetMapping("/list")
    //@PreAuthorize("hasAnyAuthority('system:aaa','system:bbb','system:order')")
    @PreAuthorize("hasAuthority('system:order')")
    public ResponseResult list(){
        return new ResponseResult(200, "订单列表");
    }
}

在这里插入图片描述在这里插入图片描述

hasRole/​ hasAnyRole

  • hasRole要求有对应的角色才可以访问,但是它内部会把传入的参数拼接上 ROLE_ 后再去比较。所以这种情况下要用用户对应的权限也要有 ROLE_ 这个前缀才可以。
  • hasAnyRole 有任意的角色就可以访问。它内部也会把传入的参数拼接上 ROLE_ 后再去比较。所以这种情况下要用用户对应的权限也要有 ROLE_ 这个前缀才可以。

修改数据库
在这里插入图片描述

    @GetMapping("/list")
    //@PreAuthorize("hasAnyAuthority('system:aaa','system:bbb','system:order')")
    //@PreAuthorize("hasAuthority('system:order')")
    @PreAuthorize("hasRole('system:order')")
    public ResponseResult list(){
        return new ResponseResult(200, "订单列表");
    }

自定义权限校验方法

@Component("ex")
public class KgcExpressionRoot {

    public boolean hasAuthority(String authority){
        //获取当前用户的权限
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        LoginAccount loginAccount = (LoginAccount) authentication.getPrincipal();
        List<String> permissions = loginAccount.getPermissions();
        //判断用户权限集合中是否存在authority
        return permissions.contains(authority);
    }
}
  • ​ 在SPEL表达式中使用 @ex相当于获取容器中bean的名字未ex的对象。然后再调用这个对象的hasAuthority方法
    @GetMapping("/list")
    //@PreAuthorize("hasAnyAuthority('system:aaa','system:bbb','system:order')")
    //@PreAuthorize("hasAuthority('system:order')")
    //@PreAuthorize("hasRole('system:order')")
    @PreAuthorize("@ex.hasAuthority('system:order')")
    public ResponseResult list(){
        return new ResponseResult(200, "订单列表");
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1862908.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

L58---100.路径总和(广搜)---Java版

1.题目描述 2.思路 (1)首先检查p,q节点是不是为空&#xff1b;如果同时为空&#xff0c;则他们是相同的树 (2)p&#xff0c;q节点如果一个为空&#xff0c;一个不为空&#xff0c;则他们不是相同的树 (3)p,q的值不一样&#xff0c;则他们不是相同的树 (4)递归遍历左子树和右子…

用英文介绍纽约:NEW YORK, USA‘s MEGACITY

NEW YORK, USA’s MEGACITY | America’s Largest City Link: https://www.youtube.com/watch?vdzjQ-akB3BI&listPLmSQiOQJmbZ7TU39cyx7gizM9i8nOuZXy&index24 The story of New York City, America’s megalopolis. Summary Paragraph 1: The Historical Developm…

路由表操作

路由表&#xff08;Routing Table&#xff09;是网络设备&#xff08;如计算机、路由器、交换机等&#xff09;用来确定数据包传输路径的数据库。每当网络设备收到一个数据包时&#xff0c;它会查找路由表&#xff0c;决定将数据包转发到哪个网络接口或网关。下面介绍路由表的基…

vue3 antv/g6 动态设置mode,让节点不可以拖动

1、查看一下官网的设置说明 G6 设置mode 默认模式&#xff1a; const graph new G6.Graph({container: div,width: 500,height: 500,modes: {default: [drag-node,drag-canvas],custom: [drag-canvas]} })默认情况下&#xff0c;我们定义的是default&#xff0c;然后创建节…

Emacs之显示blame插件:blamer、git-messenger(一百四十四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

【C++STL】Vector扩容机制

创作不易&#xff0c;本篇文章如果帮助到了你&#xff0c;还请点赞 关注支持一下♡>&#x16966;<)!! 主页专栏有更多知识&#xff0c;如有疑问欢迎大家指正讨论&#xff0c;共同进步&#xff01; &#x1f525;c系列专栏&#xff1a;C/C零基础到精通 &#x1f525; 给大…

AI文档助手:提升文档处理效率

随着人工智能技术的飞速发展&#xff0c;AI文档助手已经成为我们提升工作效率的重要工具。小编就来和大家分享几款AI文档助手&#xff0c;它们能够通过智能化的功能帮助我们快速、准确地完成各种文档任务。 1.百度文库AI助手 百度文库AI助手是百度基于文心一言重构的一站式智能…

【jupyter notebook】解决打不开以及安装扩展插件的问题

文章目录 问题描述问题 1解决问题 2解决 问题描述 问题 1 在自定义的虚拟环境下&#xff0c;安装 jupyter notebook 6.4.12 版本时&#xff0c;报以下错误&#xff1a; 解决 查了一些 解决方法&#xff0c;执行以下命令即可解决&#xff1a; conda install traitlets5.9.0 …

WAV怎么转mp3?将wav转成MP3的几种方法介绍

WAV怎么转mp3&#xff1f;很多情况下&#xff0c;我们可能需要将高质量的 WAV 文件转换为更小、更兼容的 MP3 文件。例如&#xff0c;你可能想要为你的音乐收藏腾出更多存储空间&#xff0c;或者需要将音频文件上传到联网平台&#xff0c;而这些平台通常对文件大小有严格限制。…

React@16.x(40)路由v5.x(5)常见应用场景(2)- 实现类似 vue 的路由模式

目录 1&#xff0c;vue-router2&#xff0c;React 模拟实现 1&#xff0c;vue-router vue 的路由配置文件&#xff0c; // src/router/index.ts const routes [{path: "/news",children: [{ path: "", component: NewsView },{ path: "detail"…

【UE5.3】笔记1

内容浏览器&#xff1a;存放项目中所有的资源&#xff1a;关卡、蓝图类...... 关卡--Map 至少有一个关卡&#xff0c;可以有多个关卡 -漫游 视野漫游&#xff1a;鼠标右键WASD QE 鼠标滑轮控制摄像机速度 运行&#xff0c;ESC退出运行,快捷键F8不停止运行单独弹出功能 -创…

Android 11 ,默认授予预置应用/APK 需要的权限,解决permission denied for window type 2003 问题。

写这篇文章的原因是解决了一个APP闪退的问题&#xff0c;闪退的原因是插拔U盘时&#xff0c;注册的广播接收者接收到广播需要弹出一个Dialog询问是否需要打开U盘&#xff0c;这个Dialog设置的是系统级别悬浮窗&#xff0c;没有这个权限&#xff0c;报错导致闪退&#xff0c;下面…

【C语言】解决C语言报错:Dangling Pointer

文章目录 简介什么是Dangling PointerDangling Pointer的常见原因如何检测和调试Dangling Pointer解决Dangling Pointer的最佳实践详细实例解析示例1&#xff1a;释放内存后未将指针置为NULL示例2&#xff1a;返回指向局部变量的指针示例3&#xff1a;指针悬空后继续使用示例4&…

Python-PDF文件密码破解小工具

背景 经常从网络上下载的PDF笔记被加了密&#xff0c;在自己学习的过程中想要添加书签却因为没有密码无法添加&#xff0c;所以通过Python实现一个解密小工具&#xff0c;亲测大多数密码都可以破解。 代码 import os import tkinter as tk from tkinter import filedialog #…

三元前驱体废水回收镍钴工艺:环保与经济效益的双重胜利

在全球新能源产业迅猛发展的背景下&#xff0c;锂离子电池作为绿色能源的核心组件&#xff0c;其需求量激增&#xff0c;带动了上游材料市场&#xff0c;尤其是三元前驱体材料的蓬勃发展。然而&#xff0c;伴随着行业的快速扩张&#xff0c;三元前驱体生产过程中产生的含镍钴废…

Qt的学习之路

目录 一、信号槽机制 1.1 基本概念 1.2 特点 1.3 使用方法 1.4 信号槽连接类型 1.5 注意 二、元对象系统 2.1 基本概念 2.2 实现方式 2.3 主要特性 2.4 使用场景 三、国际化 3.1 标记可翻译的文本&#xff08;tr函数&#xff09; 3.2 生成翻译源文件&#xff08;…

Linux 磁盘空间清理

1.检查磁盘使用情况 #显示每个挂载点的磁盘使用量&#xff0c;以及可用空间和使用率 df -h #显示当前目录的全部文件和目录&#xff08;包括隐藏的&#xff09;,以MB显示 ll -h 2. du查看最大的目录或文件 #逐级检查某个目录下各个子目录的大小。从根目录开始&#xff0c;逐级…

【HashMap和HashSetyi以及散列表的拉链法,线性探测法详解】

&#x1f308;个人主页&#xff1a;SKY-30 ⛅个人推荐&#xff1a;基于java提供的ArrayList实现的扑克牌游戏 |C贪吃蛇详解 ⚡学好数据结构&#xff0c;刷题刻不容缓&#xff1a;点击一起刷题 &#x1f319;心灵鸡汤&#xff1a;总有人要赢&#xff0c;为什么不能是我呢 &…

TSLANet:时间序列模型的新构思

实时了解业内动态&#xff0c;论文是最好的桥梁&#xff0c;专栏精选论文重点解读热点论文&#xff0c;围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型重新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;…

百度智能云千帆推出大模型普惠计划,0成本切换

百度智能云千帆推出大模型普惠计划&#xff0c;0成本切换至国内调用量第一的大模型平台。场景更丰富、模型更全面、工具链更完整易用、更安全可靠&#xff01; 即日起为新注册企业用户提供&#xff1a; 0元调用&#xff1a; 文心旗舰模型首次免费&#xff0c;赠送ERNIE3.5旗舰模…