SpringSecurity前后端分离(一篇就够了)

news2025/3/13 2:52:17

SpringSecurity前后端分离

从上至下操作,直接上手SpringSecurity

文章目录

  • SpringSecurity前后端分离
    • 1、项目环境
      • maven依赖
      • 数据库表
    • 2、自定义UserService接口
    • 3、屏蔽Spring Security默认重定向登录页面以实现前后端分离功能
      • 1、实现登录成功/失败、登出处理逻辑
        • 1、表单形式登录
          • 一、自定义登录接口
          • 二、自定义登录成功,失败的错误逻辑处理
          • 三、自定义用户未登录逻辑
          • 四、退出登录
      • 2、使用JSON格式进行登录
    • 4、实现基于数据库的动态权限控制

1、项目环境

maven依赖

使用的是Mybatis-Plus做数据库操作

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.1.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.1</version>
    </dependency>


    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>


    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.2.0</version>
    </dependency>



    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.15</version>
    </dependency>


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

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>


</dependencies>

数据库表

1、用户表(sys_user)

密码是加密后的(123456)

/*
 Navicat Premium Data Transfer

 Source Server         : 本机
 Source Server Type    : MySQL
 Source Server Version : 80029
 Source Host           : localhost:3306
 Source Schema         : game

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

 Date: 10/02/2023 17:04:23
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_user
-- ----------------------------
DROP TABLE IF EXISTS `sys_user`;
CREATE TABLE `sys_user`  (
  `id` int NOT NULL AUTO_INCREMENT,
  `account` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '账号',
  `user_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '用户名',
  `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '用户密码',
  `last_login_time` datetime NULL DEFAULT NULL COMMENT '上一次登录时间',
  `enabled` tinyint(1) NULL DEFAULT 1 COMMENT '账号是否可用。默认为1(可用)',
  `not_expired` tinyint(1) NULL DEFAULT 1 COMMENT '是否过期。默认为1(没有过期)',
  `account_not_locked` tinyint(1) NULL DEFAULT 1 COMMENT '账号是否锁定。默认为1(没有锁定)',
  `credentials_not_expired` tinyint(1) NULL DEFAULT 1 COMMENT '证书(密码)是否过期。默认为1(没有过期)',
  `create_time` datetime NULL DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime NULL DEFAULT NULL COMMENT '修改时间',
  `create_user` int NULL DEFAULT NULL COMMENT '创建人',
  `update_user` int NULL DEFAULT NULL COMMENT '修改人',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user
-- ----------------------------
INSERT INTO `sys_user` VALUES (1, 'user1', '用户1', '$2a$10$47lsFAUlWixWG17Ca3M/r.EPJVIb7Tv26ZaxhzqN65nXVcAhHQM4i', '2019-09-04 20:25:36', 1, 1, 1, 1, '2019-08-29 06:28:36', '2019-09-04 20:25:36', 1, 1);
INSERT INTO `sys_user` VALUES (2, 'user2', '用户2', '$2a$10$uSLAeON6HWrPbPCtyqPRj.hvZfeM.tiVDZm24/gRqm4opVze1cVvC', '2019-09-05 00:07:12', 1, 1, 1, 1, '2019-08-29 06:29:24', '2019-09-05 00:07:12', 1, 2);

SET FOREIGN_KEY_CHECKS = 1;

2、权限表(sys_permission)

/*
 Navicat Premium Data Transfer

 Source Server         : 本机
 Source Server Type    : MySQL
 Source Server Version : 80029
 Source Host           : localhost:3306
 Source Schema         : game

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

 Date: 10/02/2023 17:19:56
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `permission_code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '权限code',
  `permission_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '权限名',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '权限表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES (1, 'create_user', '创建用户');
INSERT INTO `sys_permission` VALUES (2, 'query_user', '查看用户');
INSERT INTO `sys_permission` VALUES (3, 'delete_user', '删除用户');
INSERT INTO `sys_permission` VALUES (4, 'modify_user', '修改用户');

SET FOREIGN_KEY_CHECKS = 1;

3、角色表(sys_role)

/*
 Navicat Premium Data Transfer

 Source Server         : 本机
 Source Server Type    : MySQL
 Source Server Version : 80029
 Source Host           : localhost:3306
 Source Schema         : game

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

 Date: 10/02/2023 17:20:23
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `role_code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色code',
  `role_name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色名',
  `role_description` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '角色说明',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户角色表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES (1, 'admin', '管理员', '管理员,拥有所有权限');
INSERT INTO `sys_role` VALUES (2, 'user', '普通用户', '普通用户,拥有部分权限');

SET FOREIGN_KEY_CHECKS = 1;

4、角色权限关系表(sys_role_permission_relation)

/*
 Navicat Premium Data Transfer

 Source Server         : 本机
 Source Server Type    : MySQL
 Source Server Version : 80029
 Source Host           : localhost:3306
 Source Schema         : game

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

 Date: 10/02/2023 17:21:29
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_role_permission_relation
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission_relation`;
CREATE TABLE `sys_role_permission_relation`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `role_id` int NULL DEFAULT NULL COMMENT '角色id',
  `permission_id` int NULL DEFAULT NULL COMMENT '权限id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '角色-权限关联关系表' ROW_FORMAT = Dynamic;

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

SET FOREIGN_KEY_CHECKS = 1;

5、用户角色关系表(sys_user_role_relation)

/*
 Navicat Premium Data Transfer

 Source Server         : 本机
 Source Server Type    : MySQL
 Source Server Version : 80029
 Source Host           : localhost:3306
 Source Schema         : game

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

 Date: 10/02/2023 17:22:13
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_user_role_relation
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role_relation`;
CREATE TABLE `sys_user_role_relation`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `user_id` int NULL DEFAULT NULL COMMENT '用户id',
  `role_id` int NULL DEFAULT NULL COMMENT '角色id',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '用户角色关联关系表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_user_role_relation
-- ----------------------------
INSERT INTO `sys_user_role_relation` VALUES (1, 1, 1);
INSERT INTO `sys_user_role_relation` VALUES (2, 2, 2);

SET FOREIGN_KEY_CHECKS = 1;

6、请求路径表(sys_request_path)

/*
 Navicat Premium Data Transfer

 Source Server         : 本机
 Source Server Type    : MySQL
 Source Server Version : 80029
 Source Host           : localhost:3306
 Source Schema         : game

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

 Date: 10/02/2023 17:23:23
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_request_path
-- ----------------------------
DROP TABLE IF EXISTS `sys_request_path`;
CREATE TABLE `sys_request_path`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `url` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '请求路径',
  `description` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '路径描述',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '请求路径' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_request_path
-- ----------------------------
INSERT INTO `sys_request_path` VALUES (1, '/demo/getUser', '查询用户');

SET FOREIGN_KEY_CHECKS = 1;

7、请求路径权限关系表(sys_request_path_permission_relation)

/*
 Navicat Premium Data Transfer

 Source Server         : 本机
 Source Server Type    : MySQL
 Source Server Version : 80029
 Source Host           : localhost:3306
 Source Schema         : game

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

 Date: 10/02/2023 17:23:29
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for sys_request_path_permission_relation
-- ----------------------------
DROP TABLE IF EXISTS `sys_request_path_permission_relation`;
CREATE TABLE `sys_request_path_permission_relation`  (
  `id` int NULL DEFAULT NULL COMMENT '主键id',
  `url_id` int NULL DEFAULT NULL COMMENT '请求路径id',
  `permission_id` int NULL DEFAULT NULL COMMENT '权限id'
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '路径权限关联表' ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of sys_request_path_permission_relation
-- ----------------------------
INSERT INTO `sys_request_path_permission_relation` VALUES (1, 1, 2);

SET FOREIGN_KEY_CHECKS = 1;

说明:

角色1对应的是管理员角色,有全部权限(这里主要演示的是query_user权限)

角色2对应的是用户,没有任何权限

2、自定义UserService接口

实现从数据库中获取用户的信息,自定义登录逻辑

@Service
public class UserService implements UserDetailsService{

    @Autowired
    private UserMapper userMapper;
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private RolePermissionMapper rolePermissionMapper;
    @Autowired
    private PermissionMapper permissionMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //需要构造出 org.springframework.security.core.userdetails.User 对象并返回
/**
 *         String username:用户名
 *         String password: 密码
 *         boolean enabled: 账号是否可用
 *         boolean accountNonExpired:账号是否没有过期
 *         boolean credentialsNonExpired:密码是否没有过期
 *         boolean accountNonLocked:账号是否没有被锁定
 *         Collection<? extends GrantedAuthority> authorities):用户权限列表
 */
     //1、根据用户名查询用户信息
      QueryWrapper<UserEntity> queryWrapper=new QueryWrapper<>();
        queryWrapper.eq("account",username);
        UserEntity userEntity = userMapper.selectOne(queryWrapper);
     //2、根据用户名查询用户的权限信息
        //查询userId
        Integer id = userEntity.getId();
        //查询权限id
        QueryWrapper<UserRoleRelation> queryWrapper1=new QueryWrapper<>();
         queryWrapper1.eq("user_id", id);
        UserRoleRelation userRoleRelation = userRoleMapper.selectOne(queryWrapper1);
        Integer roleId = userRoleRelation.getRoleId();
        //根据角色信息查询对应的权限信息
           //查询权限id
        QueryWrapper<RolePermissionRelation> rolePermissionRelationQueryWrapper = new QueryWrapper<>();
       rolePermissionRelationQueryWrapper.eq("role_id", roleId);
        List<RolePermissionRelation> rolePermissionRelations = rolePermissionMapper.selectList(rolePermissionRelationQueryWrapper);

        //权限列表
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();

        for (RolePermissionRelation rolePermissionRelation : rolePermissionRelations) {
            PermissionEntity permissionEntity = permissionMapper.selectById(rolePermissionRelation.getPermissionId());
            GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permissionEntity.getPermissionCode());
            grantedAuthorities.add(grantedAuthority);
        }

        //3、构造出我们需要的org.springframework.security.core.userdetails.User对象
        User user = new User(userEntity.getUserName(), userEntity.getPassword(), userEntity.getEnabled() == 1 ? true : false, userEntity.getNotExpired() == 1 ? true : false, userEntity.getCredentialsNotExpired() == 1 ? true : false, userEntity.getAccountNotLocked() == 1 ? true : false, grantedAuthorities);
        return user;
    }

进行配置使用我们自定义的UserService,并设置密码加密

/**
 * @ClassName WebSecurityConfig
 * @Author ylh
 * @Date 2023/2/7 21:28
 * @Description
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public UserDetailsService userDetailsService() {
        //获取用户账号密码及权限信息
        return new UserService();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //获取用户账号密码及权限信息
        auth.userDetailsService(userDetailsService());

    }


    // 设置默认的加密方式(强hash方式加密),注入就行
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }


}

3、屏蔽Spring Security默认重定向登录页面以实现前后端分离功能

1、实现登录成功/失败、登出处理逻辑

1、表单形式登录

一、自定义登录接口

这里是使用表单进行登录,使用JSON进行登录在下面有介绍

默认的登录接口的/login,下面我们使用自定义的路径

在WebSecurityConfig类中重写configure方法(注意参数是HttpSecurity)

@Override
protected void configure(HttpSecurity http)

在这里插入图片描述

测试:

使用postman测试http://localhost:8080/user/login?username=user1&password=123456

注意:要使用post请求

二、自定义登录成功,失败的错误逻辑处理

创建两个Component

/**
 * @Author: ylh
 * @Description: 登录成功处理逻辑
 */
@Component
public class CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {



    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, org.springframework.security.core.Authentication authentication) throws IOException, ServletException {

        //此处还可以进行一些处理,比如登录成功之后可能需要返回给前台当前用户有哪些菜单权限,
        //进而前台动态的控制菜单的显示等,具体根据自己的业务需求进行扩展
        //返回json数据
        JsonResult result = ResultTool.success();
        //处理编码方式,防止中文乱码的情况
        httpServletResponse.setContentType("text/json;charset=utf-8");
        //塞到HttpServletResponse中返回给前台
        httpServletResponse.getWriter().write(JSON.toJSONString(result));
    }
}
/**
 * @Author:
 * @Description: 登录失败处理逻辑
 */
@Component
public class CustomizeAuthenticationFailureHandler implements AuthenticationFailureHandler {


    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException, IOException {
        //返回json数据
        JsonResult result = null;
        if (e instanceof AccountExpiredException) {
            //账号过期
            result = ResultTool.fail(ResultCode.USER_ACCOUNT_EXPIRED);
        } else if (e instanceof BadCredentialsException) {
            //密码错误
            result = ResultTool.fail(ResultCode.USER_CREDENTIALS_ERROR);
        } else if (e instanceof CredentialsExpiredException) {
            //密码过期
            result = ResultTool.fail(ResultCode.USER_CREDENTIALS_EXPIRED);
        } else if (e instanceof DisabledException) {
            //账号不可用
            result = ResultTool.fail(ResultCode.USER_ACCOUNT_DISABLE);
        } else if (e instanceof LockedException) {
            //账号锁定
            result = ResultTool.fail(ResultCode.USER_ACCOUNT_LOCKED);
        } else if (e instanceof InternalAuthenticationServiceException) {
            //用户不存在
            result = ResultTool.fail(ResultCode.USER_ACCOUNT_NOT_EXIST);
        }else{
            //其他错误
            result = ResultTool.fail(ResultCode.COMMON_FAIL);
        }
        //处理编码方式,防止中文乱码的情况
        httpServletResponse.setContentType("text/json;charset=utf-8");
        //塞到HttpServletResponse中返回给前台
        httpServletResponse.getWriter().write(JSON.toJSONString(result));
    }
}

在配置类中进行配置

先注入进来

@Autowired
private CustomizeAuthenticationSuccessHandler authenticationSuccessHandler;
@Autowired
private CustomizeAuthenticationFailureHandler authenticationFailureHandler;

设置
替换原来的拦截器,使用我们自定义的

在这里插入图片描述

三、自定义用户未登录逻辑

创建一个接口

@GetMapping("/getUser")
public String getUser(){
    return "查询用户";
}

自定义component

/**
 * @Author: ylh
 * @Description: 未登录的异常处理
 */
@Component
public class CustomizeAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        //自定义返回信息返回
        JsonResult result = ResultTool.fail(ResultCode.USER_NOT_LOGIN);
        httpServletResponse.setContentType("text/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(result));
    }
}

注入到配置类中

@Autowired
private CustomizeAuthenticationEntryPoint authenticationEntryPoint;

进行相关 配置

设置权限,如果不设置,不用登录也可以访问(因为被我们重写了void configure(HttpSecurity http)这个方法)

http.authorizeRequests().antMatchers("/demo/getUser").hasAuthority("query_user")//为getUser接口设置权限
  .and()
 //异常处理(权限拒绝、登录失效等)
  //匿名用户访问无权限资源时的异常处理
        .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).

返回结果:
在这里插入图片描述

四、退出登录

springsecurity默认退出登录的url地址为http://localhost:8080/logout

创建Component

/**
 * @Author: ylh
 * @Description: 登出成功处理逻辑
 */
@Component
public class CustomizeLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException, IOException {
        JsonResult result = ResultTool.success();
        httpServletResponse.setContentType("text/json;charset=utf-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(result));
    }
}

在配置类中

将CustomizeLogoutSuccessHandler注入进来

设置

//登出
                   and().logout().
                       permitAll().//允许所有用户
                       logoutSuccessHandler(logoutSuccessHandler).//登出成功处理逻辑
                       deleteCookies("JSESSIONID").//登出之后删除cookie

2、使用JSON格式进行登录

继承UsernamePasswordAuthenticationFilter,重写attemptAuthentication方法

/**
 * @ClassName CustomAuthenticationFilter
 * @Author ylh
 * @Date 2023/2/9 21:39
 * @Description
 */
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE)
                || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE)) {
            ObjectMapper mapper = new ObjectMapper();
            UsernamePasswordAuthenticationToken authRequest = null;
            try (InputStream is = request.getInputStream()) {
                Map<String,String> authenticationBean = mapper.readValue(is, Map.class);
                authRequest = new UsernamePasswordAuthenticationToken(
                        authenticationBean.get("username"), authenticationBean.get("password"));
            } catch (IOException e) {
                e.printStackTrace();
                authRequest = new UsernamePasswordAuthenticationToken(
                        "", "");
            } finally {
                setDetails(request, authRequest);
                return this.getAuthenticationManager().authenticate(authRequest);
            }
        }
        else {
            return super.attemptAuthentication(request, response);
        }
    }
}

配置类中,创建一个Bean

@Bean
CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
    CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
    filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler() {
        @Override
        public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication authentication) throws IOException, ServletException {
            resp.setContentType("application/json;charset=utf-8");
            PrintWriter out = resp.getWriter();
            //登录成功的返回
            JsonResult result = ResultTool.success();
            out.write(new ObjectMapper().writeValueAsString(result));
            out.flush();
            out.close();
        }
    });
    filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler() {
        @Override
        public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
            resp.setContentType("application/json;charset=utf-8");
            PrintWriter out = resp.getWriter();
            //设置登录失败的返回结果
            JsonResult result = ResultTool.fail();
            out.write(new ObjectMapper().writeValueAsString(result));
            out.flush();
            out.close();
        }
    });
    filter.setAuthenticationManager(authenticationManagerBean());
    return filter;
}

在configure(HttpSecurity http)方法中,替换原有的过滤器,使用我们自定义的过滤器

http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

使用JSON进行登录配置类中就不需要formLogin(),退出登录的方法是一样的

目前configure的内容

   @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                .antMatchers("/demo/getUser").hasAuthority("query_user").//为getUser接口设置权限

                and()
//                //异常处理(权限拒绝、登录失效等)页面不能进行重定向到登录页面了,
//                //匿名用户访问无权限资源时的异常处理
                .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
//
        //登出
                .and().logout().
                permitAll().//允许所有用户
                logoutSuccessHandler(logoutSuccessHandler).//登出成功处理逻辑
                deleteCookies("JSESSIONID")//登出之后删除cookie

                .and()
                .csrf().disable();

        http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        }

4、实现基于数据库的动态权限控制

前面是直接在配置类中添加的权限控制

    http.authorizeRequests()
            .antMatchers("/demo/getUser").hasAuthority("query_user").//为

现在是基于数据库实现,将配置类中的权限配置删除

1、编写权限拦截器


/**
 * @Author: ylh
 * @Description: 权限拦截器
 */
@Service
public class CustomizeAbstractSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {

    @Autowired
    private FilterInvocationSecurityMetadataSource securityMetadataSource;

    @Autowired
    public void setMyAccessDecisionManager(CustomizeAccessDecisionManager accessDecisionManager) {
        super.setAccessDecisionManager(accessDecisionManager);
    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
        invoke(fi);
    }

    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        //fi里面有一个被拦截的url
        //里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
        //再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            //执行下一个拦截器
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }
}

2、安全元数据源FilterInvocationSecurityMetadataSource

package com.example.demo.service;

import com.example.demo.entity.PermissionEntity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.util.Collection;
import java.util.List;

/**
 * @Author: Hutengfei
 * @Description:
 * @Date Create in 2019/9/3 21:06
 */
@Component
public class CustomizeFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
//    AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Autowired
    SysPermissionService sysPermissionService;
    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        //获取请求地址
        String requestUrl = ((FilterInvocation) o).getRequestUrl();
        //查询具体某个接口的权限
        List<PermissionEntity> permissionList =  sysPermissionService.selectListByPath(requestUrl);
        if(permissionList == null || permissionList.size() == 0){
            //请求路径没有配置权限,表明该请求接口可以任意访问
            return null;
        }
        //请求路径配置了权限
        String[] attributes = new String[permissionList.size()];
        for(int i = 0;i<permissionList.size();i++){
            attributes[i] = permissionList.get(i).getPermissionCode();
        }
        return SecurityConfig.createList(attributes);
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

这里是sysPermissionService是查询路径所需要的权限,这里贴出来供大家参考

/**
 * @ClassName SysPermissionService
 * @Author ylh
 * @Date 2023/2/10 15:05
 * @Description
 */
@Service
public class SysPermissionService {
    @Autowired
    private RequestUrlMapper requestUrlMapper;
    @Autowired
    private RequestPermissionMapper requestPermissionMapper;
    @Autowired
    private PermissionMapper permissionMapper;

    public List<PermissionEntity> selectListByPath(String requestUrl){
        //根据URL查询对应权限
        List<PermissionEntity> list=new ArrayList<>();
        QueryWrapper<RequestUrlEntity> queryWrapper=new QueryWrapper();
        queryWrapper.eq("url",requestUrl);
        RequestUrlEntity requestUrlEntity = requestUrlMapper.selectOne(queryWrapper);
       if (requestUrlEntity!=null){
           //查询权限id
           QueryWrapper<RequestPermissionEntity> requestPermissionEntityQueryWrapper=new QueryWrapper();
           requestPermissionEntityQueryWrapper.eq("url_id",requestUrlEntity.getId());
           List<RequestPermissionEntity> requestPermissionEntities = requestPermissionMapper.selectList(requestPermissionEntityQueryWrapper);
           //查询权限具体信息
           for (RequestPermissionEntity requestPermissionEntity : requestPermissionEntities) {
               PermissionEntity permissionEntity = permissionMapper.selectById(requestPermissionEntity.getPermissionId());
               list.add(permissionEntity);
           }

       }


        return list;


    }
}

3、访问决策管理器AccessDecisionManager

package com.example.demo.service;

import com.example.demo.common.PermissionException;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Iterator;

/**
 * @Author: ylh
 * @Description: 访问决策管理器
 * @Date Create in 2019/9/3 20:38
 */
@Component
public class CustomizeAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
        Iterator<ConfigAttribute> iterator = collection.iterator();
        while (iterator.hasNext()) {
            ConfigAttribute ca = iterator.next();
            //当前请求需要的权限
            String needRole = ca.getAttribute();
            //当前用户所具有的权限
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(needRole)) {
                    return;
                }
            }
        }
        //权限不足抛出异常,会被AccessDeniedHandler拦截
        throw new AccessDeniedException("权限不足!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

4、在WebSecurityConfig中声明

http.authorizeRequests().
        withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
            @Override
            public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                o.setAccessDecisionManager(accessDecisionManager);//决策管理器
                o.setSecurityMetadataSource(securityMetadataSource);//安全元数据源
                return o;
            }
        }).

5、重写AccessDeniedHandler拦截器,变成我们自定义返回的内容

package com.example.demo.config;

import com.alibaba.fastjson.JSON;
import com.example.demo.common.JsonResult;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Service;

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

/**
 * @Author: ylh
 */
@Service
public class CustomAccessDeniedHandler implements AccessDeniedHandler {



    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException, IOException {
        response.setContentType("application/json;charset=UTF-8");
        JsonResult jsonResult=new JsonResult<>();
        jsonResult.setErrorCode(403);
        jsonResult.setErrorMsg("权限不足");
        response.setContentType("application/json");
        response.setStatus(HttpServletResponse.SC_OK);
        String s = JSON.toJSONString(jsonResult);
        response.getWriter().write(s);
    }
}

使用user2进行访问,提示权限不足
在这里插入图片描述

附上项目源码地址:
https://gitee.com/bai-xiaoyun/spring-security-entry-use-case.git

制作不易,觉得有用的话,留个赞再走吧
在这里插入图片描述

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

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

相关文章

鉴源论坛 ·观擎丨民机机载软件的开发与验证

作者 | 蔡喁 上海控安可信软件创新研究院副院长 版块 | 鉴源论坛 观擎 01 机载软件过程保证的目的和背景 民机机载软件研制过程一直是行业内公认的要求最为严苛、开发验证难度最大的软件开发实例之一。由于其高安全以及严格的政府监管特性&#xff0c;使得传统其他领域的软件…

1. 用Qt开发的十大理由

用Qt的十大理由 原因最主要的是很多大公司都在用&#xff0c;有钱景。 先来看看各大公司的评价&#xff1a; 奔驰&#xff1a;们用 Qt 开发了绝大部分的UI体验和软件&#xff0c;包括屏幕动画&#xff0c;屏幕间的过渡和小组件。 FORMLABS&#xff1a;凭借Qt的快速迭代&…

记录一下slf4j2打印一直不成功

整理一个之前的老项目问题&#xff0c;发现日志一直打印不出来&#xff0c;本地启动发现了第一个问题日志如下&#xff1a;此处可发现&#xff0c;jar包冲突问题&#xff0c;去掉冲突的jar包即可&#xff0c;此处不做过多赘述。然后发现了重新启动项目&#xff0c;发现jar包冲突…

工业废水的种类及其处理工艺简析

随着工业的迅速发展&#xff0c;工业废水的种类和数量随之增加&#xff0c;对水体的污染也日趋严重&#xff0c;威胁人类的健康和正常生活。所以工业废水的处理对于环保至关重要。 保护生态环境、更好地做好工业废水的处理&#xff0c;了解工业废水及其种类与处理非常必要。 工…

国内chatgpt 在VRAR上的应用场景

人工智能不鸣则已&#xff0c;一鸣惊人! 近日OpenAI发布了聊天AI ChatGPT&#xff0c;短短几天&#xff0c;其用户量直冲百万级&#xff0c;甚至服务器一度被注册用户挤爆了。 这种被网友惊叹“超越谷歌搜索”的神器究竟是怎么做到的? ChatGPT 简而言之&#xff1a;ChatGPT是一…

三行按键扫描详细解析

三行按键扫描基础 核心算法&#xff1a; unsigned char trg; unsigned char cont; void KeyRead( void ) {unsigned char readDate P3^0xff; // 第一行trg readDate & (ReadData ^ cont); // 第二行cont readDate; // 第三行 }用定时器每隔10ms执行一次按键扫描读取函…

C++:类和对象(上)

文章目录1 面向过程与面向对象的初步认识2 类的引入3 类的定义4 类的访问限定符及封装4.1 访问限定符4.2 封装5 类的实例化6 类对象模型6.1 如何计算类的大小6.2 类对象的存储方式猜测7 this指针7.1 this指针的引出7.2 this指针的特性8 C语言和C栈&#xff08;Stack&#xff09…

J-Tech Talk|如何使用Grafana Cloud Alert进行实时监控

J-Tech Talk由 Jina AI 社区为大家带来的技术分享围绕 Python 的相关话题工程师们将深入细节地讲解具体的问题分享 Jina AI 在开发过程中所积累的经验在新一代基于云原生的微服务架构中&#xff0c;不管是业务还是基础设施&#xff0c;服务的可观测性 至关重要&#xff01;它涵…

git、gitee、github关系梳理及ssh不对称加密大白话解释

温馨提示&#xff1a;本文不会讲解如何下载、安装git&#xff0c;也不会讲解如何注册、使用gitee或GitHub&#xff0c;这些内容网上一大把&#xff0c;B站上的入门课程也很多&#xff0c;自己看看就好了。 本文仅对 git、gitee、github的关系梳理及ssh公钥私钥授权原理用白话讲…

whistle 一个神奇的前端调试工具(抓包\代理工具)

在进行前端开发过程中&#xff0c;我们常常需要对一些接口进行处理&#xff0c;以及当后端接口没有弄好需要我们mock一些假数据&#xff0c;针对这些场景&#xff0c;我们就可以使用whistle 来解决。首先&#xff0c;我们要知道能满足我们需求的工具有很多&#xff0c;例如&…

第十五章 栅格数据重分类、栅格计算器、插值分析

文章目录第十五章 栅格数据分析第一章 栅格数据重分类第一节 栅格数据重分类第二节 栅格重分类的使用第三节 重分类的使用中的空值使用第四节 重分类的案例&#xff1a;分类统计面积第五节 坡度矢量分级图生成第二章 栅格计算器第一节 栅格计算器介绍第二节 栅格计算器使用第三…

操作系统题目收录(六)

1、某系统采用基于优先权的非抢占式进程调度策略&#xff0c;完成一次进程调度和进程切换的系统时间开销为1us。在T时刻就绪队列中有3个进程P1P_1P1​、P2P_2P2​和P3P_3P3​&#xff0c;其在就绪队列中的等待时间、需要的CPU时间和优先权如下表所示。若优先权值大的进程优先获…

Revit怎么生成电线和电器配件,管道附件?

一、Revit中如何自动生成电线 首先框选所有的照明设备&#xff0c;使用过滤器命令&#xff0c;只选中照明设备&#xff0c;如图1所示。 框选之后会出现创建电力系统的界面&#xff0c;选择如图2所示电力的命令。 出现图3中所示选择弧形导线或者带倒角导线&#xff0c;即可以自动…

【数字孪生百科】每周认识一个数字孪生要素 —— 玫瑰图(Rose Diagram)

简介玫瑰图&#xff08;Rose Diagram&#xff09;&#xff0c;也称之为南丁格尔玫瑰图&#xff0c;极坐标区域图&#xff0c;鸡冠花图&#xff0c;是一种圆形的直方图&#xff0c;是由弗罗伦斯・南丁格尔所发明&#xff0c;用以表达军医院季节性的死亡率的一种图表。由于半径和…

零入门kubernetes网络实战-12->基于DNAT技术使得外网可以访问本宿主机上veth-pair链接的内部网络

视频地址(稍后上传) 本篇文章测试如何让veth pair链接的内网网络可以被本局域网的其他宿主机访问到&#xff1f; 1、测试环境介绍 一台centos虚拟机 # 查看操作系统版本 cat /etc/centos-release # 内核版本 uname -a uname -r # 查看网卡信息 ip a s eth02、网络拓扑 3、操…

银行零售如何更贴近客户?是时候升级你的客户旅程平台了

随着数字化战略推进&#xff0c;各大银行持续加大对线上多渠道的建设投入&#xff0c;客户触达也愈发移动化、智能化。与此同时&#xff0c;手机银行飞速发展产生并累积了大量客户行为数据&#xff0c;呈多样化、海量化等特点&#xff0c;将在用户体验、客户经营、手机银行运营…

SaaS的阴暗面:网络攻击武器化、平民化

你不一定懂编程&#xff0c;甚至都看不懂几行代码&#xff0c;但依然能成为杀伤力十足的黑客&#xff0c;这就是现阶段不少网络攻击的特点&#xff1a;不需要掌握娴熟的技术或代码&#xff0c;仅仅利用成熟的武器化工具&#xff0c;就能通过简单的“一键操作”&#xff0c;对目…

【Java基础】018 -- 面向对象阶段项目上(拼图小游戏)

目录 拼图小游戏&#xff08;GUI&#xff09; 一、主界面分析 1、练习一&#xff1a;创建主界面1 2、练习二&#xff1a;创建主界面2&#xff08;JFrame&#xff09; 3、练习三&#xff1a;在游戏界面中添加菜单&#xff08;JMenuBar&#xff09; ①、菜单的制作 4、添加图片&a…

传奇开服架设要具备什么条件

传奇开服架设要具备什么条件 新手如果想开服的话因具备哪些条件&#xff1f;我来为您解答 1.传奇SF开服的话你要清楚 具体的有哪些步骤 需要的哪些东西 自己一定要先了解清楚&#xff01;因为这行的话 PZ比我们IDC服务器商都还要多。 {开服的话一定要了解清楚开传奇的一个具…

python(15)--函数设计

前言 函数是可重用的程序代码块。 函数的作用&#xff0c;不仅可以实现代码的复用&#xff0c;还可以保证修改函数的代码时&#xff0c;所有调用该函数的地方都能得到体现。目前我已知函数的作用是&#xff1a;对代码实现了封装、函数调用、传递参数、返回计算结果等。 正文 …