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

server/2024/10/18 16:49:03/

本案例是使用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

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


http://www.ppmy.cn/server/110414.html

相关文章

WEB功能测试面经

一、XHTML和HTML的区别&#xff1f; XHTML&#xff08;Extensible HyperText Markup Language&#xff09;和HTML&#xff08;HyperText Markup Language&#xff09;都是用于创建网页的标记语言&#xff0c;但它们有一些关键区别&#xff1a; 语法要求&#xff1a; XHTML&…

fastapi知识点及应用

fastapi知识点及应用 1、框架对比2、asgi VS wsgi 区别3、知识点4、启动方式5、Pydantic 模型python数据类型&#xff1a;python类型指定&#xff08;类型提示&#xff09;1.指定参数类型2、指定返回值类型3、类型检查工具4、非空设置&#xff08;字段&#xff09;5、Literal只…

面试题小总结

一、为什么要使用Redis&#xff1f; 因为它是内存数据库&#xff0c;运行速度快因为它的工作线程是单线程&#xff0c;具有串行化&#xff0c;原子性具有IO模型&#xff0c;天生支撑高并发是kv模型&#xff0c;v具有多个数据结构具有本地方法&#xff0c;可以计算数据移动是二…

axios发送post请求实例

在body中的数据格式又有两种&#xff0c;一种是 json 数据格式&#xff0c;另一种是 字符串。具体要用哪种格式取决于后端入参的格式。 如果后端接收json数据类型&#xff0c;post 的 headers 需要设置 { ‘content-type’: ’application/json’ }&#xff0c;传给后端的数…

Linux系统(Centos7)没有安装GUI图形界面后期加装方法

问题背景 前期图方便安装了centos7的命令行版本&#xff0c;后期发现需要有图形界面才行&#xff0c;所以需要在后期加装图形界面。 问题解决 必要要求&#xff1a;服务器能够连接互联网&#xff0c;如果是VMware,网络选择NAT&#xff0c;开机后就可以联网&#xff1b;所有操…

常见框架报错信息

一、报错信息&#xff08;不同类型转换&#xff09; 2024-08-28 14:57:15.450 ERROR 8272 --- [io-8080-exec-12] c.w.common.exception.RRExceptionHandler : class java.lang.String cannot be cast to class java.lang.Integer (java.lang.String and java.lang.Integer ar…

概率论与编程的联系及数据科学应用

目录 引言 第一章 概率模拟与编程实现 1.1 随机数生成与蒙特卡罗模拟 1.1.2 蒙特卡罗模拟 第二章 统计建模与数据分析 2.1 统计模型实现 2.2 概率图模型 第三章 概率论在机器学习中的应用 3.1 随机森林与决策树 3.2 贝叶斯分类器 总结与展望 引言 在大数据和人工智…

NMPC非线性模型预测控制经验分享与代码实例

NMPC非线性模型预测控制经验分享与代码实例 原本做完本科毕设之后就应该动笔写这一部分&#xff0c;但是做的过程中慢慢意识到自己懂的只是一点点。最近重新接触一些优化相关的问题&#xff0c;希望能够做出我认知之下比较好的解答。本人知识有限&#xff0c;难免写的有问题&am…