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

前后端分离的security角色权限实现

本案例是使用SpringBoot2.7.6+security+MyBatis-plus+Vue2+axios实现

一、security简介

Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架,专为Java应用程序设计。

(1)基本功能

  • 身份验证(Authentication):确定用户身份的过程。在Spring Security中,这包括确认用户是谁,并验证用户提供的凭证(如用户名和密码)是否正确。
  • 授权(Authorization):确定用户是否有权进行某个操作的过程。根据用户的身份和角色,Spring Security授予用户访问应用程序资源的权限。

(2)主要特点

  1. 全面性:Spring Security提供了全面的安全性解决方案,包括认证、授权、攻击防范(如XSS、CSRF等)和会话管理等功能。
  2. 可扩展性:提供了一系列可扩展的模块,可以根据具体需求进行选择和配置,如不同的身份验证方式、授权方式、密码编码器等。
  3. 易用性:提供了快捷配置选项和基于注解的安全控制方式,使开发人员能够更轻松地实现认证和授权等功能。
  4. 社区支持:作为Spring生态系统的一部分,Spring Security得到了广泛的社区支持和更新维护。

(3)工作原理

        Spring Security通过一系列过滤器(Filter)来保护应用程序。这些过滤器按照特定的顺序组成过滤器链,每个过滤器都有特定的责任,如身份验证、授权、防止CSRF攻击等。当用户发起请求时,请求会经过过滤器链的处理,并在处理过程中进行安全验证和授权。

(4)核心组件

  1. SecurityContextHolder:用于存储安全上下文(SecurityContext)的信息,包括当前用户的身份信息、所拥有的权限等。
  2. AuthenticationManager:负责处理用户的身份验证,接收用户提交的凭据,并使用已配置的身份验证提供程序(AuthenticationProvider)进行验证。
  3. AuthenticationProvider:实际执行身份验证的组件,从用户存储源(如数据库、LDAP等)中获取用户信息,并进行密码比对或其他验证方式。
  4. UserDetailsService:用于加载UserDetails对象的接口,通常从数据库或LDAP服务器中获取用户信息。
  5. AccessDecisionManager:用于在授权过程中进行访问决策,根据用户的认证信息、请求的URL和配置的权限规则,判断用户是否有权访问资源。

二、依赖项

主要使用到Security、mysql和gson等依赖:

<!-- security -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!--MySQL驱动-->
<dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mybatis-plus -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.2</version>
</dependency>
<!-- Gson: Java to Json conversion -->
<dependency><groupId>com.google.code.gson</groupId><artifactId>gson</artifactId><version>2.8.9</version>
</dependency>

 其中gson是用于吧java对象转成json格式,便于响应数据。

三、配置和自定义处理器

(1)关于security的配置

        在config层中的security的配置主要有SecurityConfig核心配置类CorsConfig访问配置类

        SecurityConfig核心配置类

                在yaml配置文件中,可以自定义前端登录页面:

spring:security:# 前后端分离时自定义的security的登录页面loginPage: http://127.0.0.1:8081/#/login
import com.security.demo.handel.CustomAccessDeniedHandler;
import com.security.demo.handel.CustomAuthenticationFailureHandler;
import com.security.demo.handel.CustomAuthenticationSuccessHandler;
import com.security.demo.service.Impl.UserServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;/*** 配置security*/
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {@Value("${spring.security.loginPage}")private String loginPage;@Autowiredprivate CustomAuthenticationSuccessHandler customAuthenticationSuccessHandler;@Autowiredprivate CustomAuthenticationFailureHandler customAuthenticationFailureHandler;@Autowiredprivate CustomAccessDeniedHandler customAccessDeniedHandler;@Autowiredprivate UserServiceImpl userServiceImpl;@Overrideprotected void configure(HttpSecurity http) throws Exception {// 添加请求授权规则http.authorizeRequests()// 首页所有人都可以访问.antMatchers("/public/**").permitAll()// user下的所有请求,user角色权限才能访问.antMatchers("/user/**").hasRole("USER")// admin下的所有请求,ADMIN角色权限才能访问.antMatchers("/admin/**").hasRole("ADMIN")// 其他任何请求都要验证身份.anyRequest().authenticated();// 开启登录页面,即没有权限的话跳转到登录页面http.formLogin()// 登录页面.loginPage(loginPage)// 用户名的name.usernameParameter("user")// 密码的name.passwordParameter("pwd")// 处理登录的Controller.loginProcessingUrl("/login")// 验证成功处理器.successHandler(customAuthenticationSuccessHandler)// 验证失败处理器.failureHandler(customAuthenticationFailureHandler);http.csrf().disable();// 开启记住我功能,默认保存两周http.rememberMe()// name属性.rememberMeParameter("remember");// 开启退出登录功能http.logout().logoutUrl("/logout").logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler(HttpStatus.OK)); // 自定义登出成功后的处理// 跨域http.cors();// 权限不足处理器http.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler);}// 认证规则@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {// 在新版本的SpringSecurity中新增了许多加密方法,这里使用的是BCryptauth.userDetailsService(userServiceImpl).passwordEncoder(new BCryptPasswordEncoder());}}

        CorsConfig访问配置类

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;/***  配置security的跨域访问*/
@Configuration
public class CorsConfig implements WebMvcConfigurer {@Overridepublic void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**").allowedOriginPatterns("*").allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS").allowCredentials(true).exposedHeaders("Access-Control-Allow-Headers","Access-Control-Allow-Methods","Access-Control-Allow-Origin","Access-Control-Max-Age","X-Frame-Options").maxAge(3600).allowedHeaders("*");}}

(2)处理器Handler

        业务开发时主要是自定义登录认证成功、登录认证失败和权限不足的处理器

        登录认证成功后的处理器CustomAuthenticationSuccessHandler,主要判断验证码是否正确和响应返回R统一返回类。CustomAuthenticationSuccessHandler类

import com.google.gson.Gson;
import com.security.demo.vo.R;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/***  自定义的security的认证成功处理器*/@Component("customAuthenticationSuccessHandler")
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)throws IOException, ServletException {R result = R.ok().setMessage("登录成功。");response.setContentType("application/json;charset=UTF-8");// 校验验证码String code = (String) request.getSession().getAttribute("code");String codeInput = request.getParameter("codeInput");if(code==null || codeInput==null) {result.setCode(501).setMessage("验证码为空");}else {if (!code.equalsIgnoreCase(codeInput)) {result.setCode(501).setMessage("验证码错误");}}Gson gson = new Gson();response.getWriter().write(gson.toJson(result));}
}

        登录验证失败处理器CustomAuthenticationFailureHandler,主要用于响应返回R统一返回类。CustomAuthenticationFailureHandler类

import com.google.gson.Gson;
import com.security.demo.vo.R;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/***  自定义的security的认证失败处理器*/
@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)throws IOException, ServletException {//以返回JSON数据为例R result = R.error().setMessage("用户名或密码错误。");response.setContentType("application/json;charset=UTF-8");Gson gson = new Gson();response.getWriter().write(gson.toJson(result));}
}

        用户访问权限不足处理器CustomAccessDeniedHandler,主要返回权限不足信息。CustomAccessDeniedHandler类

import com.google.gson.Gson;
import com.security.demo.vo.R;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;/**
* 用户权限不足被拒绝处理器
*/
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)throws IOException {R result = R.error().setMessage("权限不足");response.setContentType("application/json;charset=UTF-8");Gson gson = new Gson();response.getWriter().write(gson.toJson(result));}
}

(3)重写登录验证逻辑

        在配置类中可以自定义指定的登录验证逻辑类,一般是写在service的实现类中,要求是该登录验证逻辑类必须实现UserDetailsService接口的loadUserByUsername方法,才可以在configure的auth.userDetailsService(自定义登录验证逻辑类)指定。这里我直接在UserServiceImpl类中实现:

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.security.demo.entity.User;
import com.security.demo.mapper.UserMapper;
import com.security.demo.service.UserService;
import com.security.demo.vo.R;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;import java.util.ArrayList;
import java.util.List;@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService, UserDetailsService {@Autowiredprivate UserMapper userMapper;/*** 重写UserDetailsService的loadUserByUsername方法* 用于登录验证和角色授权*/@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {User user = this.getUserByName(username);if (user == null) {throw new UsernameNotFoundException("用户名为null");}// 这里需要将User转换为UserDetails,并设置角色的GrantedAuthority集合List<GrantedAuthority> authorities = new ArrayList<>();authorities.add(new SimpleGrantedAuthority("ROLE_"+user.getRole()));// 如果是admin角色,就多添加USER权限if(user.getRole().equals("ADMIN")){authorities.add(new SimpleGrantedAuthority("ROLE_USER"));}BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();String password = encoder.encode(user.getPassword());return new org.springframework.security.core.userdetails.User(user.getName(), password, authorities);}/***  分页条件查询用户名*/@Overridepublic R getUserList(String name, int current, int size) {QueryWrapper<User> queryWrapper = new QueryWrapper<>();Page<User> page = new Page<>(current, size);if(name!=null && !name.isEmpty()){queryWrapper.like("name",name);}queryWrapper.orderByDesc("id");  // 指定根据id倒序Page<User> userPage = userMapper.selectPage(page, queryWrapper);return R.ok().data("userPage", userPage);}/*** 新增用户*/@Overridepublic R addUser(User user) {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("name", user.getName());User user1 = userMapper.selectOne(queryWrapper);if (user1 != null) {return R.error().setMessage("该用户名已存在");}userMapper.insert(user);return R.ok().setMessage("新增成功!");}/*** 更新用户*/@Overridepublic R updateUser(User user) {// 根据用户名查询是否重复QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("name", user.getName());User user1 = userMapper.selectOne(queryWrapper);if (user1 != null && user1.getId() != user.getId()) {return R.error().setMessage("该用户名已存在");}// 不重复就根据id来修改用户信息QueryWrapper<User> queryWrapper1 = new QueryWrapper<>();queryWrapper1.eq("id", user.getId());userMapper.update(user, queryWrapper1);return R.ok().setMessage("更新成功!");}/*** 删除用户*/@Overridepublic R deleteUser(String name) {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("name", name);User user = userMapper.selectOne(queryWrapper);if (user == null) {return R.error().setMessage("没有该用户");}userMapper.deleteById(user.getId());return R.ok().setMessage("删除成功");}/*** 根据用户名查询用户信息*/@Overridepublic User getUserByName(String name) {QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("name", name);return userMapper.selectOne(queryWrapper);}}

四、postman测试登录接口

        使用postman测试登录login接口时要注意,由于security的限制,需要模仿表单提交,而不是常规的json数据,获取验证码后,使用x-www-form-urlencoded格式。如下:

五、前端axios注意事项

        获取验证码后,使用x-www-form-urlencoded格式的post请求如下,参数是json数据。

// 登录login(data){return axios({method: 'post',url: '/login',data: data,// 模仿表单提交transformRequest: [function (data) {let ret = ''for (let it in data) {ret +=encodeURIComponent(it) +'=' +encodeURIComponent(data[it]) +'&'}return ret}],headers:{'Content-Type': 'application/x-www-form-urlencoded'}})},

六、案例完整源码

        以上是security的核心代码和调用踩坑点,完整代码请转到码云仓库查看:

=========================================================================

Gitee仓库源码:https://gitee.com/BuLiangShuai01033/security-demo

作者:不凉帅 https://blog.csdn.net/yueyue763184

=========================================================================

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 智能合约漏洞(四)
  • 接口测试 —— 如何设计高效的测试用例!
  • 使用 nuxi build-module 命令构建 Nuxt 模块
  • C语言中的“#”和“##”
  • 三维前缀和 C++
  • 【Centos】制作一键安装包.bin 文件
  • 【论文阅读】:Mamba YOLO SSMs-Based YOLO For Object Detection
  • 学懂C++(四十四):C++ 自定义内存管理的深入解析:内存池与自定义分配器
  • milvus使用milvus migration工具迁移数据
  • Kubernetes 上安装 Jenkins
  • 滑动窗口系列(定长滑动窗口长度)8/31
  • CRMEB商城系统功能解读——渠道码
  • Ant Design vue 多层for循环form表单自定义校验
  • css中的伪类
  • 打卡第58天------图论
  • Angular 4.x 动态创建组件
  • create-react-app做的留言板
  • css选择器
  • Effective Java 笔记(一)
  • E-HPC支持多队列管理和自动伸缩
  • ES6系列(二)变量的解构赋值
  • iOS | NSProxy
  • java 多线程基础, 我觉得还是有必要看看的
  • Joomla 2.x, 3.x useful code cheatsheet
  • Promise面试题2实现异步串行执行
  • Spark学习笔记之相关记录
  • Sublime text 3 3103 注册码
  • sublime配置文件
  • unity如何实现一个固定宽度的orthagraphic相机
  • Zepto.js源码学习之二
  • 案例分享〡三拾众筹持续交付开发流程支撑创新业务
  • 码农张的Bug人生 - 见面之礼
  • 跳前端坑前,先看看这个!!
  • 我是如何设计 Upload 上传组件的
  • 一个SAP顾问在美国的这些年
  • 用jquery写贪吃蛇
  • 回归生活:清理微信公众号
  • 组复制官方翻译九、Group Replication Technical Details
  • ​Benvista PhotoZoom Pro 9.0.4新功能介绍
  • ![CDATA[ ]] 是什么东东
  • #nginx配置案例
  • #pragma multi_compile #pragma shader_feature
  • #免费 苹果M系芯片Macbook电脑MacOS使用Bash脚本写入(读写)NTFS硬盘教程
  • $.type 怎么精确判断对象类型的 --(源码学习2)
  • (12)目标检测_SSD基于pytorch搭建代码
  • (32位汇编 五)mov/add/sub/and/or/xor/not
  • (4)Elastix图像配准:3D图像
  • (NO.00004)iOS实现打砖块游戏(十二):伸缩自如,我是如意金箍棒(上)!
  • (附源码)ssm学生管理系统 毕业设计 141543
  • (力扣)循环队列的实现与详解(C语言)
  • (六)Hibernate的二级缓存
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • (最全解法)输入一个整数,输出该数二进制表示中1的个数。
  • .Net Core 笔试1
  • .Net的DataSet直接与SQL2005交互