当前位置: 首页 > news >正文

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

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 TransferSource Server         : 本机Source Server Type    : MySQLSource Server Version : 80029Source Host           : localhost:3306Source Schema         : gameTarget Server Type    : MySQLTarget Server Version : 80029File Encoding         : 65001Date: 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 TransferSource Server         : 本机Source Server Type    : MySQLSource Server Version : 80029Source Host           : localhost:3306Source Schema         : gameTarget Server Type    : MySQLTarget Server Version : 80029File Encoding         : 65001Date: 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 TransferSource Server         : 本机Source Server Type    : MySQLSource Server Version : 80029Source Host           : localhost:3306Source Schema         : gameTarget Server Type    : MySQLTarget Server Version : 80029File Encoding         : 65001Date: 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 TransferSource Server         : 本机Source Server Type    : MySQLSource Server Version : 80029Source Host           : localhost:3306Source Schema         : gameTarget Server Type    : MySQLTarget Server Version : 80029File Encoding         : 65001Date: 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 TransferSource Server         : 本机Source Server Type    : MySQLSource Server Version : 80029Source Host           : localhost:3306Source Schema         : gameTarget Server Type    : MySQLTarget Server Version : 80029File Encoding         : 65001Date: 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 TransferSource Server         : 本机Source Server Type    : MySQLSource Server Version : 80029Source Host           : localhost:3306Source Schema         : gameTarget Server Type    : MySQLTarget Server Version : 80029File Encoding         : 65001Date: 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 TransferSource Server         : 本机Source Server Type    : MySQLSource Server Version : 80029Source Host           : localhost:3306Source Schema         : gameTarget Server Type    : MySQLTarget Server Version : 80029File Encoding         : 65001Date: 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{@Autowiredprivate UserMapper userMapper;@Autowiredprivate UserRoleMapper userRoleMapper;@Autowiredprivate RolePermissionMapper rolePermissionMapper;@Autowiredprivate PermissionMapper permissionMapper;@Overridepublic 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、根据用户名查询用户的权限信息//查询userIdInteger id = userEntity.getId();//查询权限idQueryWrapper<UserRoleRelation> queryWrapper1=new QueryWrapper<>();queryWrapper1.eq("user_id", id);UserRoleRelation userRoleRelation = userRoleMapper.selectOne(queryWrapper1);Integer roleId = userRoleRelation.getRoleId();//根据角色信息查询对应的权限信息//查询权限idQueryWrapper<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 {@Beanpublic UserDetailsService userDetailsService() {//获取用户账号密码及权限信息return new UserService();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {//获取用户账号密码及权限信息auth.userDetailsService(userDetailsService());}// 设置默认的加密方式(强hash方式加密),注入就行@Beanpublic 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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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的内容

   @Overrideprotected 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 {@Autowiredprivate FilterInvocationSecurityMetadataSource securityMetadataSource;@Autowiredpublic void setMyAccessDecisionManager(CustomizeAccessDecisionManager accessDecisionManager) {super.setAccessDecisionManager(accessDecisionManager);}@Overridepublic Class<?> getSecureObjectClass() {return FilterInvocation.class;}@Overridepublic SecurityMetadataSource obtainSecurityMetadataSource() {return this.securityMetadataSource;}@Overridepublic 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();@AutowiredSysPermissionService sysPermissionService;@Overridepublic 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);}@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}@Overridepublic boolean supports(Class<?> aClass) {return true;}
}

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

/*** @ClassName SysPermissionService* @Author ylh* @Date 2023/2/10 15:05* @Description*/
@Service
public class SysPermissionService {@Autowiredprivate RequestUrlMapper requestUrlMapper;@Autowiredprivate RequestPermissionMapper requestPermissionMapper;@Autowiredprivate 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){//查询权限idQueryWrapper<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 {@Overridepublic 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("权限不足!");}@Overridepublic boolean supports(ConfigAttribute configAttribute) {return true;}@Overridepublic boolean supports(Class<?> aClass) {return true;}
}

4、在WebSecurityConfig中声明

http.authorizeRequests().withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <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 {@Overridepublic 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

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

相关文章:

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

SpringSecurity前后端分离 从上至下操作&#xff0c;直接上手SpringSecurity 文章目录SpringSecurity前后端分离1、项目环境maven依赖数据库表2、自定义UserService接口3、屏蔽Spring Security默认重定向登录页面以实现前后端分离功能1、实现登录成功/失败、登出处理逻辑1、表…...

Allegro如何用Label Tune功能自动调整丝印到器件中心

Allegro如何用Label Tune功能自动调整丝印到器件中心 在做PCB设计的时候,调整丝印是比较费时的工作,如果需要把整板的丝印位号调整到器件的中心做装配图使用,Allegro的Label Tune功能支持快速把丝印位号居中到器件中心。 以下图为例,快速把所有丝印位号居中 调整前 调整后…...

Linux(十)线程安全 上

目录 一、概念 二、互斥锁实现互斥 三、条件变量实现同步 银行家算法 生产者与消费者模型 一、概念 概念&#xff1a;在多线程程序中&#xff0c;如果涉及到了对共享资源的操作&#xff0c;则有可能会导致数据二义性&#xff0c;而线程安全就指的是&#xff0c;就算对共享…...

CRM系统能给企业带来什么? CRM系统推荐

什么是CRM系统&#xff1f; CRM系统&#xff08;又称客户关系管理系统&#xff09;是一个以客户为核心的管理软件&#xff0c;能有效改善企业与现有客户的关系&#xff0c;且帮助企业寻找新的潜在客户&#xff0c;并赢回以前老客户。 CRM系统能给企业带来什么&#xff1f; C…...

ESP32设备驱动-LED控制器生成PWM信号

LED控制器生成PWM信号 文章目录 LED控制器生成PWM信号1、LED控制器介绍2、软件准备3、硬件准备4、代码实现PWM 是一种在数字引脚上获取类似模拟信号的方法。PWM实际上是一个在高电平和低电平之间切换的方波信号,在 0V 和 3.3V 之间。 当信号为 HIGH 和 LOW 时,这种连续的 HIG…...

秒杀项目之网关服务限流熔断降级分布式事务

目录一、网关服务限流熔断降级二、Seata--分布式事务2.1 分布式事务基础2.1.1 事务2.1.2 本地事务2.1.3 分布式事务2.1.4 分布式事务场景2.2 分布式事务解决方案2.2.1 全局事务可靠消息服务2.2.2 最大努力通知2.2.3 TCC事事务三、Seata介绍四、 Seata实现分布式事务控制4.1 案例…...

OSS(Object Storage Service)进行上传图片,下载图片(详细看文档可以完成操作)

文章目录1.单体前后端项目上传1.上传流程2. BuckName 和EndPoint3. AccessKey 和Access Secret(创建RAM&#xff08;Resource Access Manage&#xff09;的子账号&#xff0c;然后可以获得Accesskey和Acess Secret)3.根据创建的子账号分配OSS的所有权限(可以对文件进行上传&…...

4年功能测试经验,裸辞后找不到工作怎么办?

软件测试四年&#xff0c;主要是手动测试&#xff08;部分自动化测试和性能测试&#xff0c;但是用的是公司内部自动化工具&#xff0c;而且我自动化方面是弱项。&#xff09; 现在裸辞三个月了&#xff0c;面试机会少而且面试屡屡受挫。总结就是自动化&#xff0c;性能&#…...

类和对象(中)(二)

类和对象&#xff08;中&#xff09;&#xff08;二&#xff09;1.赋值运算符重载1.1运算符重载1.2赋值运算符重载1.3前置和后置重载2.const成员3.取地址及const取地址操作符重载&#x1f31f;&#x1f31f;hello&#xff0c;各位读者大大们你们好呀&#x1f31f;&#x1f31f;…...

Hadoop自动安装JDK

目录 1、使用xftp工具 在opt目录下创建install和soft文件 ​2、使用xftp工具 将压缩包上传到install文件 3、编写shell脚本 3.1、创建目录来放shell脚本 3.2、创建autoinsatll.sh文件并修改权限 3.3、编写autoinsatll.sh 文件 4、 运行 5、测试 1、使用xftp工具 在opt目…...

Springboot+Vue java毕业论文选题管理系统

在分析并得出使用者对程序的功能要求时&#xff0c;就可以进行程序设计了。如图展示的就是管理员功能结构图。 系统实现前端技术&#xff1a;nodejsvueelementui 前端&#xff1a;HTML5,CSS3、JavaScript、VUE 系统分为不同的层次&#xff1a;视图层&#xff08;vue页面&#…...

面向战场的cesium基础到进阶的案例展示(我相信VIP总是有原因的)

cesium 前置说明(友情提示,关注重点代码,其他影响复现的都可以删除或者替换数值解决) 这里面用到了cesium的模型加载、图片加载、着色器、实时改变模型状态、模型删除等知识点,这需要你自己去观摩下述会包含所有相关代码,他们的联系其实在代码中能看到(比如飞机操作类会…...

XXL-JOB 分布式任务调度平台

目录 一、简介 1.1 概述 1.2 社区交流 1.3 特性 1.4 架构设计 1.4.1 设计思想 1.4.2 系统组成 1.4.3 调度模块剖析 1&#xff09; quartz的不足 1.5、同类型框架对比 1.6 下载 1.6.1 文档地址 1.7 环境 二、XXL-JOB安装部署 2.1、配置部署“调度中心” 1&…...

通过 指针 引用 多维数组 详解

目录 一&#xff1a;回顾多维数组地址知识 二&#xff1a;二维数组的有关指针 三&#xff1a;指向数组元素的指针变量 四&#xff1a;用指向数组的指针作为函数参数 首先简单来讲&#xff0c;指针变量可以指向一维数组中的元素&#xff0c;也可以指向多维数组中的元素。下面…...

【Linux】宝塔面板 SSL 证书安装部署

宝塔面板 SSL 证书安装部署前言证书下载宝塔配置SSL注意事项前言 前期有讲过Tomcat和Nginx分别部署SSL证书&#xff0c;但也有好多小伙伴们私信我说&#xff0c;帮忙出一期宝塔面板部署SSL证书的教程&#xff0c;毕竟宝塔的用户体量也是蛮大的&#xff0c;于是宠粉的博主&…...

由 GPT 驱动的沙盒,尽情发挥想象力! #NovelAI

一个由 GPT 驱动的沙盒&#xff0c;供用户尽情发挥想象力的空间&#xff0c;会获得怎样的体验&#xff1f;NovelAI NovelAI 是一项用于 AI 辅助创作、讲故事、虚拟陪伴的工具。NovelAI 的人工智能算法会根据用户的方式创建类似人类的写作&#xff0c;使任何人&#xff0c;无论能…...

ubuntu 服务器安装配置VNC访问

ubuntu 如果服务器没有桌面相关图形包&#xff0c;需手动安装下&#xff1a; sudo apt install ubuntu-desktop sudo apt install lightdm VNC安装&#xff1a; 1.安装 在Ubuntu上安装x11vnc&#xff0c;如下&#xff1a; sudo apt-get install x11vnc 2.配置vnc密码 x1…...

【C→C++】打开C++世界的大门

文章目录前言什么是CC的发展史C的重要性1. 使用广泛度2. 工作领域的应用1. C关键字(C98)2. 命名空间2.1 命名空间的定义2.2 命名空间的使用2.3 std命名空间的使用惯例3. C输入&输出3.1 输入输出3.2 说明4. 缺省参数4.1 缺省参数概念4.2 缺省参数分类5. 函数重载5.1 函数重载…...

点云深度学习系列博客(四): 注意力机制原理概述

目录 1. 注意力机制由来 2. Nadaraya-Watson核回归 3. 多头注意力与自注意力 4. Transformer模型 Reference 随着Transformer模型在NLP&#xff0c;CV甚至CG领域的流行&#xff0c;注意力机制&#xff08;Attention Mechanism&#xff09;被越来越多的学者所注意&#xff0c;将…...

设置Visual Studio 2022背景图

前言 编写代码时界面舒服&#xff0c;自己喜欢很重要。本篇文章将会介绍VS2022壁纸的一些设置&#xff0c;主题的更改以及如何设计界面。 理想的界面应该是这样的 接下来我们来一步步学习如何将界面设计成这样 一、壁纸插件下载 1.拓展->点击拓展管理 2.右上角搜索backgro…...

1. 用Qt开发的十大理由

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

俄罗斯方块游戏代码

♥️作者&#xff1a;小刘在C站 ♥️个人主页&#xff1a;小刘主页 ♥️每天分享云计算网络运维课堂笔记&#xff0c;努力不一定有收获&#xff0c;但一定会有收获加油&#xff01;一起努力&#xff0c;共赴美好人生&#xff01; ♥️夕阳下&#xff0c;是最美的&#xff0c;绽…...

设计模式相关面试题

文章目录面向对象编程中&#xff0c;都有哪些设计原则&#xff1f;设计模式的分类 &#xff1f;单例模式的特点是什么&#xff1f;单例模式有哪些实现&#xff1f;什么是简单⼯⼚模式什么是抽象⼯⼚模式&#xff1f;什么是⼯⼚⽅法模式&#xff1f;什么是代理模式&#xff1f;S…...

构建Jenkins 2.340持续集成环境

一、前言 本文学习自&#xff1a;2022版Jenkins教程&#xff08;从配置到实战) 如有不妥&#xff0c;欢迎指正 二、构建资料 已经包括了本文档使用的所有所需的安装包 三、安装docker 1、解压docker docker-20.10.10.tgz2、复制文件 cp docker/* /usr/bin/3、编写启动文…...

Ubuntu/Centos下OpenJ9 POI输出Excel的Bug

项目更换 JDK为 OpenJ9 后, 使用 POI 导出 Excel 遇到的问题 OpenJ9 版本信息 /opt/jdk/jdk-11.0.178/bin/java -version openjdk version "11.0.17" 2022-10-18 IBM Semeru Runtime Open Edition 11.0.17.0 (build 11.0.178) Eclipse OpenJ9 VM 11.0.17.0 (build …...

链接脚本学习笔记

IAR 一般步骤 链接器用于链接过程。它通常执行以下过程&#xff08;请注意&#xff0c;某些步骤可以通过命令行选项或链接器配置文件中的指令关闭&#xff09;&#xff1a; 1.确定应用程序中要包含哪些模块。始终包含对象文件中提供的模块。仅当库文件中的模块为从包含的模块…...

NLP顶会近三年小众研究领域

ACL 2022 编码器和解码器框架、自然语言生成、知识i神经元、抽取式文本摘要、预训练语言模型、零样本神经机器翻译等。 2021 新闻标题生成任务等。跨语言命名实体识别、代码搜索、音乐生成、Hi-Transformer、预训练语言模型、语义交互等。 EMNLP 2021 代码摘要生成、隐私…...

[electron] 一 vue3.2+vite+electron 项目集成

一 开发环境系统&#xff1a;windows开发工具&#xff1a; git , vscode&#xff0c;termial环境依赖&#xff1a; node, npm 二 步骤2.1 通过vite 创建vue项目通过 终端执行命令&#xff0c;选择 模板 vuenpm init vite cd 项目目录 npm install npm run dev2.2 集成 electro…...

ESP32 Arduino(十二)lvgl移植使用

一、简介LVGL全程LittleVGL&#xff0c;是一个轻量化的&#xff0c;开源的&#xff0c;用于嵌入式GUI设计的图形库。并且配合LVGL模拟器&#xff0c;可以在电脑对界面进行编辑显示&#xff0c;测试通过后再移植进嵌入式设备中&#xff0c;实现高效的项目开发。SquareLine Studi…...

js一数组按照另一数组进行排序

有时我们需要一个数组按另一数组的顺序来进行排序&#xff0c;总结一下方法&#xff0c;同时某些场景也会用到。 首先一个数组相对简单的情况&#xff1a; var arr1 [52,23,36,11,09]; var arr2 [23,09,11,36,52]; // 要求arr1按照arr2的顺序来排序&#xff0c;可以看到两个…...