【业务功能篇59】Springboot + Spring Security 权限管理 【下篇】

SpringSecurity+JWT认证流程解析 | 优快云新人第一弹 纸上得来终觉浅,觉知此事要躬行。 楔子 本文适合: 对Spring Security有一点了解或者跑过简单demo但是对整体运行流程不明白的同学,对SpringSecurity有兴趣的也可以当作你们的入门教程,示例代码中也有很多注释。本文代码: 码云地址 GitHub地址 大家在做系统的时候,一般做的第一个模块就是认证与授权模块,因为这是一个系统的入口,也是一个系统最重要最基础的一环,在认证与授权服务设计搭建好了之后,剩下的模块才得以安全访问。 市面上一般做认证授权的框架就是shiro和Spring. 阅读详情

UserDetails接口定义了以下方法:

  1. getAuthorities(): 返回用户被授予的权限集合。这个方法返回的是一个集合类型,其中每个元素都是一个GrantedAuthority对象,表示用户被授予的权限。
  2. getPassword(): 返回用户的密码。这个方法返回的是一个字符串类型,表示用户的密码。
  3. getUsername(): 返回用户的用户名。这个方法返回的是一个字符串类型,表示用户的用户名。
  4. isAccountNonExpired(): 返回一个布尔值,表示用户的账户是否未过期。
  5. isAccountNonLocked(): 返回一个布尔值,表示用户的账户是否未锁定。
  6. isCredentialsNonExpired(): 返回一个布尔值,表示用户的凭证(如密码)是否未过期。
  7. isEnabled(): 返回一个布尔值,表示用户是否已激活。

第三步 测试

访问登录地址 http://localhost:8080/login ,输入用户名密码

image.png

登录失败,后台报错

 java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"

报错原因

  • Spring Security中密码的存储格式是“{id}…………”.前面的id是加密方式,id可以是bcrypt、sha256等,后面跟着的是加密后的密码.也就是说,程序拿到传过来的密码的时候,会首先查找被“{”和“}”包括起来的id,来确定后面的密码是被怎么样加密的,如果找不到就认为id是null.

如果要测试,需要往用户表中写入用户数据,并且如果你想让用户的密码是明文存储,需要在密码前加{noop}。就可以正常登录了, 例如

image.png

(3) BCryptPasswordEncoder 密码加密存储

1. BCryptPasswordEncoder 介绍

在实际的项目中,为了保护密码的安全,我们通常不会将密码以明文的形式存储在数据库中。通常,我们使用SpringSecurity提供的BCryptPasswordEncoder来进行加密。

BCryptPasswordEncoder是Spring Security提供的一个PasswordEncoder实现类,它使用了bcrypt算法对密码进行加密和解密。

2. 常用方法测试

BCryptPasswordEncoder主要有以下方法:

  • encode(CharSequence rawPassword):对原始密码进行加密处理,并返回加密后的密码字符串。
  • matches(CharSequence rawPassword, String encodedPassword):对比原始密码和加密后的密码是否匹配。rawPassword为原始密码,encodedPassword为从数据库或其他地方获取的已经加密的密码字符串,如果匹配则返回true,否则返回false。
 @Autowired
 private PasswordEncoder passwordEncoder;
 
 @Test
 public void testBcryp(){
 
     String e1 = passwordEncoder.encode("123456");
     String e2 = passwordEncoder.encode("123456");
     System.out.println(e1);
     System.out.println(e2);
     System.out.println(e1.equals(e2));
 
     //$2a$10$0CS95XYw7GyDQNXq6FO7FuWDHR4yLTVyFXgQICjgTddWIG9OJ6isy
     boolean b = passwordEncoder.matches("123456",
                                         "$2a$10$0CS95XYw7GyDQNXq6FO7FuWDHR4yLTVyFXgQICjgTddWIG9OJ6isy");
 
     System.out.println("=============== " + b);
 }

BCryptPasswordEncoder使用随机盐值对密码进行加密,每次加密的结果都不同,即使相同的原始密码,加密后得到的字符串也是不同的。这种随机性增加了密码的安全性,防止了攻击者通过破解一个用户密码的方式,来破解其他用户的密码。

3.引入 BCryptPasswordEncoder

我们只需要将BCryptPasswordEncoder对象注入到Spring容器中,SpringSecurity就会使用该PasswordEncoder来验证密码。

为了配置SpringSecurity,我们可以定义一个继承自WebSecurityConfigurerAdapter的配置类。

 @Configuration
 public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
     @Bean
     public PasswordEncoder passwordEncoder(){
         return new BCryptPasswordEncoder();
     }
 }

修改数据库的明文密码为加密后的密码, 测试一下

image.png

(4) 自定义登录接口

我们需要自定义一个登陆接口,并让SpringSecurity不要对该接口进行登录验证,以允许未登录用户访问。

在该接口中,我们使用AuthenticationManager的authenticate方法进行用户认证,需要在SecurityConfig中配置将AuthenticationManager注入到容器中。

如果认证成功,则需要生成一个jwt并将其放入响应中返回。为了让用户在下次请求时能够通过jwt识别出具体的用户,我们需要将用户信息存储在redis中,可以将用户id作为key。

当需要自定义登录接口时,可以按照以下步骤进行:

  1. 创建一个新的登录接口,例如LoginController , 用于接收用户的登录信息。
 @RestController
 public class LoginController {
 
     @Autowired
     private LoginService loginService;
 
     @PostMapping("/user/login")
     public ResponseResult login(@RequestBody SysUser user){
 
         //登录
         return loginService.login(user);
     }
 }
  1. 创建LoginService和其实现类 LoginServiceImpl, 登录操作主要的实现逻辑都在实现类中
 public interface LoginService {
     ResponseResult login(SysUser sysUser);
 }
 
 @Service
 public class LoginServiceImpl implements LoginService {
 
     @Override
     public ResponseResult login(SysUser sysUser) {
 
         //1.调用AuthenticationManager的 authenticate方法,进行用户认证。
 
         //2.如果认证没有通过,给出错误提示
 
         //3.如果认证通过,使用userId生成一个JWT,并将其保存到 ResponseResult对象中返回
 
         //4.将用户信息存储在Redis中,在下一次请求时能够识别出用户,userid作为key
         return null;
     }
 }
  1. 配置SecurityConfig 在SecurityConfig中添加一个配置,将自定义登录接口添加到Spring Security中,并设置为放行。
 @Configuration
 public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
     @Bean
     public PasswordEncoder passwordEncoder(){
         return new BCryptPasswordEncoder();
     }
 
     /**
      * 注入 AuthenticationManager,供外部类使用
      */
     @Bean
     @Override
     public AuthenticationManager authenticationManagerBean() throws Exception {
         return super.authenticationManagerBean();
     }
 
     //该方法用于配置 HTTP 请求的安全处理
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         http
                 //关闭csrf
                 .csrf().disable()
                 //不会创建会话,每个请求都将被视为独立的请求。
                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                 .and()
                 //定义请求授权规则
                 .authorizeRequests()
                 // 对于登录接口 允许匿名访问
                 .antMatchers("/user/login").anonymous()
                 // 除上面外的所有请求全部需要鉴权认证
                 .anyRequest().authenticated();
     }
 }

后面我们再去详细说明一下configure方法中的细节.

  1. 回到loginService的login方法,补全剩余步骤
 @Service
 public class LoginServiceImpl implements LoginService {
 
     @Autowired
     private AuthenticationManager authenticationManager;
 
     @Autowired
     private RedisCache redisCache;
 
     @Override
     public ResponseResult login(SysUser sysUser) {
 
         //1.调用AuthenticationManager的 authenticate方法,进行用户认证。
         //1.1 需要传入一个Authentication对象的实现,该对象包含用户信息
         Authentication usernamePasswordAuthenticationToken =
                 new UsernamePasswordAuthenticationToken(sysUser.getUserName(),sysUser.getPassword());
         Authentication authentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken);
 
         //2.如果认证没有通过,给出错误提示
         if(Objects.isNull(authentication)){
             throw new RuntimeException("登录失败");
         }
 
         //3.如果认证通过,使用userId生成一个JWT,并将其保存到 ResponseResult对象中返回
         //3.1 获取经过身份验证的用户的主体信息
         LoginUser loginUser = (LoginUser) authentication.getPrincipal();
 
         //3.2 获取到userID 生成JWT
         String userId = loginUser.getSysUser().getUserId().toString();
         String jwt = JwtUtil.createJWT(userId);
 
         //4.将用户信息存储在Redis中,在下一次请求时能够识别出用户,userid作为key
         redisCache.setCacheObject("login:"+userId,loginUser);
 
         //5.封装ResponseResult,并返回
         Map<String,String> map = new HashMap<>();
         map.put("token",jwt);
         return new ResponseResult(200,"登录成功",map);
     }
 }
(5) 使用postman测试

image.png

(6) 实现认证过滤器

当用户再次发送请求的时候,要进行校验,用户会携带登录时生成的JWT,所以我们需要自定义一个Jwt认证过滤器

image.png

  • 获取token
  • 解析token获取其中的userid login:+userId
  • 从redis中获取用户信息
  • 存入SecurityContextHolder

    SecurityContextHolder 记录如下信息:当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色或权限等等。

    经过自定义认证过滤器过滤后的用户信息会被保存到SecurityContextHolder中,后面的过滤器会从SecurityContextHolder中获取用户信息.

操作步骤如下

  1. 自定义一个过滤器,这个过滤器会去获取请求头中的token,对token进行解析取出其中的userid

    自定义过滤器要去继承OncePerRequestFilter,OncePerRequestFilter 旨在简化过滤器的编写,并确保每个请求只被过滤一次,避免多次过滤的问题。

 /**
  * 自定义认证过滤器,用来校验用户请求中携带的Token
  * @date 2023/4/25
  **/
 @Component
 public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
 
     @Autowired
     private RedisCache redisCache;
 
     /**
      * 封装过滤器的执行逻辑
      * @param request
      * @param response
      * @param filterChain
      */
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
 
         //1.从请求头中获取token
         String token = request.getHeader("token");
 
         //2.判断token是否为空,为空直接放行
         if(!StringUtils.hasText(token)){
             //放行
             filterChain.doFilter(request,response);
 
             //return的作用是返回响应的时候,避免走下面的逻辑
             return;
         }
 
         //3.解析Token
         String userId;
         try {
             Claims claims = JwtUtil.parseJWT(token);
             userId = claims.getSubject();
         } catch (Exception e) {
             e.printStackTrace();
             throw new RuntimeException("非法token");
         }
 
         //4.从redis中获取用户信息
         String redisKey = "login:" + userId;
         LoginUser loginUser = redisCache.getCacheObject(redisKey);
         if(Objects.isNull(loginUser)){
             throw new RuntimeException("用户未登录");
         }
 
         //5.将用户新保存到SecurityContextHolder,以便后续的访问控制和授权操作使用。
         UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, null);
         SecurityContextHolder.getContext().setAuthentication(authenticationToken);
 
         //6.放行
         filterChain.doFilter(request,response);
     }
 }
 

UsernamePasswordAuthenticationToken 三个参数的构造方法:

  • principal:表示认证请求的主体,通常是一个用户名或者其他识别主体的信息。
  • credentials:表示认证请求的凭据,通常是密码或者其他证明主体身份的信息。
  • authorities: 权限信息

将Token检验过滤器 添加到过滤器链中

 @Configuration
 public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
     @Bean
     public PasswordEncoder passwordEncoder(){
         return new BCryptPasswordEncoder();
     }
 
     @Autowired
     private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
 
     /**
      * 注入 AuthenticationManager,供外部类使用
      */
     @Bean
     @Override
     public AuthenticationManager authenticationManagerBean() throws Exception {
         return super.authenticationManagerBean();
     }
 
     //该方法用于配置 HTTP 请求的安全处理
     @Override
     protected void configure(HttpSecurity http) throws Exception {
         http
                 //关闭csrf
                 .csrf().disable()
                 //不会创建会话,每个请求都将被视为独立的请求。
                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                 .and()
                 //定义请求授权规则
                 .authorizeRequests()
                 // 对于登录接口 允许匿名访问
                 .antMatchers("/user/login").anonymous()
                 // 除上面外的所有请求全部需要鉴权认证
                 .anyRequest().authenticated();
 
         //将自定义认证过滤器,添加到过滤器链中
         http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
     }
 }

使用postman进行测试

image.png

(7) 实现退出功能

定义一个登出接口,删除redis中对应的用户数据即可。

为什么不需要清除SecurityContextHolder中的数据

在退出登录时,如果使用 JWT 进行认证,并将 JWT 保存在 Redis 中,需要清除 Redis 中的 JWT 数据。由于 JWT 是无状态的,它本身不会与 Spring Security 的认证信息产生关联,因此在退出登录时,不需要清除 SecurityContextHolder 中的认证信息。

 @RestController
 public class LoginController {
 
     @GetMapping("/user/logout")
     public ResponseResult logout(){
 
         //登录
         return loginService.logout();
     }
 }
 
 public interface LoginService {
     ResponseResult login(SysUser sysUser);
 
     ResponseResult logout();
 }
 
 @Service
 public class LoginServiceImpl implements LoginService {
 
     @Autowired
     private RedisCache redisCache;
 
     @Override
     public ResponseResult logout() {
 
         //获取当前用户的认证信息
         UsernamePasswordAuthenticationToken authenticationToken =
                 (UsernamePasswordAuthenticationToken) SecurityContextHolder.getContext().getAuthentication();
 
         if(Objects.isNull(authenticationToken)){
             throw new RuntimeException("获取用户认证信息失败,请重新登录!");
         }
 
         LoginUser loginUser = (LoginUser) authenticationToken.getPrincipal();
         Long userId = loginUser.getSysUser().getUserId();
 
         //删除redis中的用户信息
         redisCache.deleteObject("login:" + userId);
         return new ResponseResult(200,"注销成功");
     }
 }

测试

image.png

4.2.4 授权

4.2.4.1 什么是授权

授权是指在认证通过之后,根据用户的身份和角色,确定用户是否有权执行某项操作或访问某个资源的过程。

在应用程序中,授权通常是通过访问控制机制来实现的,例如基于角色的访问控制(Role-Based Access Control,RBAC)

4.2.4.2 Spring Security 授权基本流程

Spring Security 的授权基本流程如下:

  1. 进行认证操作,会生成一个 Authentication 对象
  2. 确定了用户的身份和角色之后,可以通过 Spring Security 提供的注解进行授权操作。
  3. 如果授权通过,则可以执行相关操作。

其中第一步操作 将权限信息保存到Authentication,有两个地方与保存权限有关

 @Override
 public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
     //TODO 查询用户权限信息
 
     //方法的返回值是UserDetails类型,需要返回自定义的实现类,并且将user信息通过构造方法传入
     return new LoginUser(sysUser);
 }
 
 @Override
 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
 
     //TODO 获取权限信息封装到 Authentication
     UsernamePasswordAuthenticationToken authenticationToken =
         new UsernamePasswordAuthenticationToken(loginUser,null,null);
     SecurityContextHolder.getContext().setAuthentication(authenticationToken);
 
     //6.放行
     filterChain.doFilter(request,response);
 }
4.2.4.2 SpringSecurity授权实现
(1) 设置资源访问所需要的权限

在security中添加注解 @EnableGlobalMethodSecurity

 @EnableGlobalMethodSecurity(prePostEnabled = true)
 @Configuration
 public class SecurityConfig extends WebSecurityConfigurerAdapter {

@EnableGlobalMethodSecurity(prePostEnabled = true) 是 Spring Security 提供的一个注解,用于启用全局方法级别的安全控制,在使用 Spring Security 进行方法级别的授权控制时,需要使用该注解来启用相关功能。

其中,prePostEnabled = true 表示开启 Spring Security 的方法级别安全控制。pre 表示在方法执行前进行授权校验,post 表示在方法执行后进行授权校验。

在HelloController中添加 @PreAuthorize(“hasAuthority(‘test’)”) 注解

 @RestController
 public class HelloController {
 
     @RequestMapping("/hello")
     @PreAuthorize("hasAuthority('test')")
     public String hello(){
         return "hello";
     }
 }

@PreAuthorize("hasAuthority('test')") 是 Spring Security 提供的一个注解,用于在方法执行前进行权限校验。它的作用是检查当前登录用户是否具有指定的权限,如果有,则允许执行该方法,否则抛出 AccessDeniedException 异常,阻止方法执行。

hasAuthority() 方法用于检查用户是否具有指定的权限

hasAuthority('test') 表示检查当前用户是否具有名为 test 的权限

@PreAuthorize 注解是在方法执行前进行权限校验的,因此如果当前用户不具有指定的权限,该方法将不会被执行。如果需要在方法执行后进行权限校验,可以使用 @PostAuthorize 注解。

(2) 封装权限信息

第一步 在UserDetailsServiceImpl中 ,根据用户查询权限信息,添加到LoginUser中

 @Service
 public class UserDetailsServiceImpl implements UserDetailsService {
 
     @Autowired
     private UserMapper userMapper;
 
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
 
         //根据用户名查询用户信息
         LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(SysUser::getUserName,username);
         SysUser user = userMapper.selectOne(wrapper);
 
         //如果查询不到数据,抛出异常 给出提示
         if(Objects.isNull(user)){
             throw new RuntimeException("用户名或密码错误");
         }
 
         //TODO 根据用户查询权限信息,添加到LoginUser中,这里的权限信息我们写死,封装到list集合
         ArrayList<String> list = new ArrayList<>(Arrays.asList("test"));
 
         //方法的返回值是 UserDetails接口类型,需要返回自定义的实现类
         return new LoginUser(user,list);
     }
 }

第二步 由于LoginUser中还有这个构造函数,所以我们要修改一下LoginUser

 /* LoginUser */
 
 //存储权限信息集合
 private List<String> permissions;
 
 public LoginUser(SysUser user, ArrayList<String> permissions) {
     this.sysUser = user;
     this.permissions = permissions;
 }

第三步 如果SpringSecurity想要获取用户权限信息,其实最终要调用 getAuthorities()方法,所以要在这个方法中将查询到的权限信息进行转换,转换另一个List集合,其中保存的数据类型是 GrantedAuthority 类型.这是一个接口,我们用它下面的这个实现

image.png

 package com.mashibing.springsecurity_example.entity;
 
 import lombok.AllArgsConstructor;
 import lombok.Data;
 import lombok.NoArgsConstructor;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.userdetails.UserDetails;
 
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 import java.util.stream.Collectors;
 
 /**
  * @date 2023/4/24
  **/
 @Data
 public class LoginUser implements UserDetails {
 
     private SysUser sysUser;
 
     //存储权限信息集合
     private List<String> permissions;
 
     public LoginUser(SysUser user, ArrayList<String> permissions) {
         this.sysUser = user;
         this.permissions = permissions;
     }
 
     /**
      *  用于获取用户被授予的权限,可以用于实现访问控制。
      */
     @Override
     public Collection<? extends GrantedAuthority> getAuthorities() {
 
         //将permissions集合中的String类型权限信息,转换为SimpleGrantedAuthority类型
 //        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
 //        for (String permission : permissions) {
 //            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permission);
 //            authorities.add(simpleGrantedAuthority);
 //        }
         
         //1.8 语法
         List<SimpleGrantedAuthority> authorities = permissions.stream()
                 .map(SimpleGrantedAuthority::new)
                 .collect(Collectors.toList());
         
         return authorities;
     }
 }

第四步 对上面的代码进行优化, 将权限的集合提取到方法外,除第一次调用需要正在查询以外,后面判断只要authorities集合不为空,就直接返回

 @Data
 public class LoginUser implements UserDetails {
 
     private SysUser sysUser;
 
     public LoginUser() {
     }
 
     public LoginUser(SysUser sysUser) {
         this.sysUser = sysUser;
     }
 
     //存储权限信息集合
     private List<String> permissions;
 
     public LoginUser(SysUser user, ArrayList<String> permissions) {
         this.sysUser = user;
         this.permissions = permissions;
     }
 
     //authorities集合不需要序列化,只需要序列化permissions集合即可
     @JSONField(serialize = false)
     private List<SimpleGrantedAuthority> authorities;
 
     /**
      *  用于获取用户被授予的权限,可以用于实现访问控制。
      */
     @Override
     public Collection<? extends GrantedAuthority> getAuthorities() {
 
         //将permissions集合中的String类型权限信息,转换为SimpleGrantedAuthority类型
 //        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
 //        for (String permission : permissions) {
 //            SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(permission);
 //            authorities.add(simpleGrantedAuthority);
 //        }
         if(authorities != null){
             return authorities;
         }
 
         //1.8 语法
         authorities = permissions.stream()
                 .map(SimpleGrantedAuthority::new)
                 .collect(Collectors.toList());
 
         return authorities;
     }
 }

第五部分 在 JwtAuthenticationTokenFilter认证过滤器中, 将权限信息保存到 SecurityContextHolder

 //TODO 5.将用户保存到SecurityContextHolder,以便后续的访问控制和授权操作使用。
 UsernamePasswordAuthenticationToken authenticationToken =
     new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
 SecurityContextHolder.getContext().setAuthentication(authenticationToken);

第六步 debug 测试一下

(3) 根据RBAC权限模型创建表

1. RBAC权限模型

  • RBAC权限模型(Role-Based Access Control)即:基于角色的权限控制。这是目前最常被开发者使用也是相对易用、通用权限模型。

image.png

2. 创建RBAC模型所需的表

 CREATE TABLE `sys_menu` (
   `menu_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
   `menu_name` VARCHAR(50) NOT NULL COMMENT '菜单名称',
   `path` VARCHAR(200) DEFAULT '' COMMENT '路由地址',
   `component` VARCHAR(255) DEFAULT NULL COMMENT '组件路径',
   `visible` CHAR(1) DEFAULT '0' COMMENT '菜单状态(0显示 1隐藏)',
   `status` CHAR(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
   `perms` VARCHAR(100) DEFAULT NULL COMMENT '权限标识',
   `icon` VARCHAR(100) DEFAULT '#' COMMENT '菜单图标',
   `create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者',
   `create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
   `update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者',
   `update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
   `remark` VARCHAR(500) DEFAULT '' COMMENT '备注',
   PRIMARY KEY (`menu_id`) USING BTREE
 ) ENGINE=INNODB AUTO_INCREMENT=2068 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='菜单权限表'
 
 
 CREATE TABLE `sys_role` (
   `role_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
   `role_name` VARCHAR(30) NOT NULL COMMENT '角色名称',
   `role_key` VARCHAR(100) NOT NULL COMMENT '角色权限字符串',
   `status` CHAR(1) NOT NULL COMMENT '角色状态(0正常 1停用)',
   `del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
   `create_by` VARCHAR(64) DEFAULT '' COMMENT '创建者',
   `create_time` DATETIME DEFAULT NULL COMMENT '创建时间',
   `update_by` VARCHAR(64) DEFAULT '' COMMENT '更新者',
   `update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
   `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
   PRIMARY KEY (`role_id`) USING BTREE
 ) ENGINE=INNODB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='角色信息表'
 
 
 CREATE TABLE `sys_role_menu` (
   `role_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
   `menu_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '菜单id',
   PRIMARY KEY (`role_id`,`menu_id`)
 ) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;
 
 CREATE TABLE `sys_user` (
   `user_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
   `user_name` VARCHAR(64) NOT NULL DEFAULT 'NULL' COMMENT '用户名',
   `nick_name` VARCHAR(30) NOT NULL COMMENT '用户昵称',
   `password` VARCHAR(100) DEFAULT '' COMMENT '密码',
   `phonenumber` VARCHAR(11) DEFAULT '' COMMENT '手机号码',
   `sex` CHAR(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
   `status` CHAR(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
   PRIMARY KEY (`user_id`) USING BTREE
 ) ENGINE=INNODB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='用户信息表'
 
 CREATE TABLE `sys_user_role` (
   `user_id` bigint(200) NOT NULL AUTO_INCREMENT COMMENT '用户id',
   `role_id` bigint(200) NOT NULL DEFAULT '0' COMMENT '角色id',
   PRIMARY KEY (`user_id`,`role_id`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

3. 查询当前有用户所拥有的菜单权限

 SELECT 
     sm.perms
 FROM sys_user su 
 LEFT JOIN sys_user_role sur ON su.user_id = sur.user_id
 LEFT JOIN sys_role sr ON sur.role_id = sr.role_id
 LEFT JOIN sys_role_menu srm ON sr.role_id = srm.role_id
 LEFT JOIN sys_menu sm ON srm.menu_id = sm.menu_id
 
 WHERE su.user_id = 2

4. 创建菜单实体

@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@TableName(value = "sys_menu")
public class Menu implements Serializable {

    @TableId
    private Long id;

    //菜单名
    private String menuName;

    //路由地址
    private String path;

    //组件路径
    private String component;

    //菜单状态 (0 显示, 1隐藏)
    private String visible;

    //菜单状态 (0 正常, 1 停用)
    private String status;

    //权限标识
    private String perms;

    //菜单图标
    private String icon;

    private String createBy;

    private String updateBy;

    private Date updateTime;

    private Date createTime;

    private String remark;
}

(4) 从数据库获取权限信息

我们要做的就是根据用户id去查询到其所对应的菜单权限信息即可

1.mapper编写

 /**
  * @date 2023/4/26
  **/
 public interface MenuMapper extends BaseMapper<Menu> {
     
     List<String> selectPermsByUserId(Long id);
 }
 SELECT 
     DISTINCT sm.perms
 FROM sys_user_role sur 
     LEFT JOIN sys_role sr ON sur.role_id = sr.role_id
     LEFT JOIN sys_role_menu srm ON sr.role_id = srm.role_id
     LEFT JOIN sys_menu sm ON srm.menu_id = sm.menu_id
 WHERE 
     user_id = #{userid}
     AND sr.status = 0
     AND sm.status = 0
 <?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
 <mapper namespace="com.mashibing.springsecurity_example.mapper.MenuMapper">
 
     <select id="selectPermsByUserId" resultType="java.lang.String">
         SELECT 
             DISTINCT sm.perms
         FROM sys_user_role sur 
             LEFT JOIN sys_role sr ON sur.role_id = sr.role_id
             LEFT JOIN sys_role_menu srm ON sr.role_id = srm.role_id
             LEFT JOIN sys_menu sm ON srm.menu_id = sm.menu_id
         WHERE 
             user_id = #{userid}
             AND sr.status = 0
             AND sm.status = 0
     </select>
 
 </mapper>

在application.yml中配置mapperXML文件的位置

 spring:
   datasource:
     url: jdbc:mysql://localhost:3306/test_security?characterEncoding=utf-8&serverTimezone=UTC
     username: root
     password: 123456
     driver-class-name: com.mysql.cj.jdbc.Driver
   redis:
     host: localhost
     port: 6379
 mybatis-plus:
   mapper-locations: classpath*:/mapper/**/*.xml 

2.service编写

UserDetailsServiceImpl中去调用mapper的方法查询权限信息, 然后封装到LoginUser对象中.

 @Service
 public class UserDetailsServiceImpl implements UserDetailsService {
 
     @Autowired
     private UserMapper userMapper;
     
     @Autowired
     private MenuMapper menuMapper;
 
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
 
         //根据用户名查询用户信息
         LambdaQueryWrapper<SysUser> wrapper = new LambdaQueryWrapper<>();
         wrapper.eq(SysUser::getUserName,username);
         SysUser user = userMapper.selectOne(wrapper);
 
         //如果查询不到数据,抛出异常 给出提示
         if(Objects.isNull(user)){
             throw new RuntimeException("用户名或密码错误");
         }
 
         //TODO 根据用户查询权限信息,添加到LoginUser中,这里的权限信息我们写死,封装到list集合
 //        ArrayList<String> list = new ArrayList<>(Arrays.asList("test"));
 
         List<String> list = menuMapper.selectPermsByUserId(user.getUserId());
 
         //方法的返回值是 UserDetails接口类型,需要返回自定义的实现类
         return new LoginUser(user,list);
     }
 }

测试,用普通用户去测试一下

 @RestController
 public class HelloController {
 
     //拥有system:user:list权限才能访问
     @RequestMapping("/hello")
     @PreAuthorize("hasAuthority('system:user:list')")
     public String hello(){
 
         return "hello";
     }
 
     
     //拥有system:role:list 才能访问
     @RequestMapping("/ok")
     @PreAuthorize("hasAuthority('system:role:list')")
     public String ok(){
 
         return "ok";
     }
 }
 

4.4.5 SpringSecurity异常处理

除了保护应用程序中受保护资源的访问,我们还希望在认证失败或授权失败时,能够返回与应用程序其他接口相同的 JSON 格式响应,以便前端能够统一处理。

4.4.5.1 ExceptionTranslationFilter介绍

image.png

ExceptionTranslationFilter 是 Spring Security 框架中的一个关键过滤器,用于处理请求过程中抛出的异常,并将其转化为合适的响应。它的主要作用是保护应用程序中受保护资源的访问,并根据用户的身份进行适当的响应。

当 Spring Security 抛出异常时,ExceptionTranslationFilter 将会捕获该异常并根据异常类型去判断是认证失败还是授权失败出现的异常。然后根据 Spring Security 的配置进行处理。

  • 如果是认证过程中出现的异常会被封装成 AuthenticationException , 然后调用AuthenticationEntryPoint对象的方法去进行异常处理。
  • 如果是授权过程中出现的异常会被封装成 AccessDeniedException , 然后调用AccessDeniedHandler对象的方法去进行异常处理。
4.4.5.2 认证过程中的异常处理

AuthenticationEntryPoint 是 Spring Security 中用于处理未经身份验证的用户访问受保护资源时的异常的接口。

**通过实现 **AuthenticationEntryPoint 接口,我们可以自定义未经身份验证的用户访问需要认证的资源时应该返回的响应。

 /**
  * 自定义认证过程异常处理
  * @date 2023/4/26
  **/
 @Component
 public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
     
     @Override
     public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
 
         ResponseResult result = new ResponseResult(HttpStatus.UNAUTHORIZED.value(), "认证失败请重新登录");
         String json = JSON.toJSONString(result);
         WebUtils.renderString(response,json);
     }
 }
 
4.4.5.3 授权过程中的异常处理

**在 Spring Security 中,当用户请求某个受保护的资源,但是由于权限不足或其他原因被拒绝访问时,Spring Security 会调用 **AccessDeniedHandler 来处理这种情况。

**通过自定义实现 **AccessDeniedHandler 接口,并覆盖 handle 方法,我们可以自定义处理用户被拒绝访问时应该返回的响应。

 /**
  * 自定义处理授权过程中的异常
  * @date 2023/4/26
  **/
 @Component
 public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
 
     @Override
     public void handle(HttpServletRequest request, HttpServletResponse response,
                        AccessDeniedException accessDeniedException) throws IOException, ServletException {
         ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(),"权限不足,禁止访问");
         String json = JSON.toJSONString(result);
         WebUtils.renderString(response,json);
     }
 }
4.4.5.4 配置SpringSecurity
  1. 先注入对应的处理器
 @Autowired
 private AuthenticationEntryPoint authenticationEntryPoint;
 
 @Autowired
 private AccessDeniedHandler accessDeniedHandler;
  1. 然后使用HttpSecurity对象的方法去进行配置
 //配置异常处理器
 http.exceptionHandling()
     //配置认证失败处理器
     .authenticationEntryPoint(authenticationEntryPoint)
     //配置授权失败处理器
     .accessDeniedHandler(accessDeniedHandler);

测试一下

4.4.6 跨域解决方案CORS

4.4.6.1 什么是跨域 ?

首先一个url是由:协议、域名、端口 三部分组成。(一般端口默认80)
如:https://mashibing.com:80

跨域是指通过JS在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,只要****协议、域名、端口有任何一个不同,都被当作是不同的域,浏览器就不允许跨域请求。

  • 跨域的几种常见情

image.png

如果跨域调用,会出现如下错误:

image.png

 has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

翻译过来就是:已被CORS策略阻止:对请求的响应未通过访问控制检查 , 这就是没有配置相关的跨域参数,是不能访问这个接口的

由于我们采用的是前后端分离的编程方式,前端和后端必定存在跨域问题。解决跨域问 题可以采用CORS

4.4.6.2 跨域产生原因?
(1) 出于浏览器的同源策略限制

所谓同源(即在同一个域)就是两个页面具有相同的协议(protocol)、主机(host)和端口号(port)。才可以互相访问

否则只要有一个不同,是不能访问的。

image.png

同源策略(Same Orgin Policy)是一种约定,它是浏览器核心也最基本的安全功能,它会阻止一个域的js脚本和另外一个域的内容进行交互,如果缺少了同源策略,浏览器很容易受到XSS、CSFR等攻击。

(2) 跨站脚本攻击(XSS)

image.png

(3) 跨站请求伪造 (CSRF)

CSRF(Cross-site request forgery)跨站请求伪造:攻击者诱导受害者进入第三方网站,在第三方网站中,向被攻击网站发送跨站请求。利用受害者在被攻击网站已经获取的注册凭证,绕过后台的用户验证,达到冒充用户对被攻击的网站执行某项操作的目的。

image.png

总结: XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。

非同源会出现的限制

  • 无法读取非同源网页的cookie、localstorage等
  • 无法接触非同源网页的DOM和js对象
  • 无法向非同源地址发送Ajax请求
4.4.6.3 如何解决跨域问题

为了安全起见,浏览器在使用XMLHttpRequest对象发起HTTP请求时必须遵守同源策略。否则,这将被视为跨域请求,并且默认情况下将被禁止。同源策略要求协议、域名和端口号必须完全相同,以便进行正常通信。

在前后端分离的项目中,前端项目和后端项目通常不属于同一源,因此必然存在跨域请求的问题。因此,我们需要对其进行处理,以便前端能够进行跨域请求。

(1) CORS介绍

CORS(Cross-Origin Resource Sharing)即跨域资源共享,是一种用于处理跨域请求的机制。它允许浏览器向跨域服务器发送XMLHttpRequest请求,以便在不违反同源策略的情况下获取服务器上的资源。

image.png

CORS的实现方式主要是通过HTTP头部来实现的,浏览器会在请求中添加一些自定义的HTTP头部,告诉服务器请求的来源、目标地址等信息。服务器在接收到请求后,会根据请求头中的信息来判断是否允许跨域请求,并在响应头中添加一些自定义的HTTP头部,告诉浏览器是否允许请求、允许哪些HTTP方法、允许哪些HTTP头部等信息。

在响应头中添加以下字段,可以解决跨域问题:

  • access-control-allow-origin : 该字段是必须的。它的值要么是请求时 Origin字段的值,要么是一个 *,表示接受任意域名的请求。
  • access-control-allow-credentials : 该字段可选。它的值是一个布尔值,表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为 true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器。这个值也只能设为 true,如果服务器不要浏览器发送Cookie,删除该字段即可
  • Access-Control-Allow-Methods : 该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。

其实最重要的就是 access-control-allow-origin 字段,添加一个 * ,允许所有的域都能访问

(2) 配置SpringBoot的允许跨域

在SpringBoot项目中只需要编写一个配置类使其实现WebMvcConfigurer接口并重写其addCorsMappings方法即可。

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

**你也可以通过使用 **@CrossOrigin 注解来解决跨域问题。例如:

 @RestController
 public class MyController {
     
     @CrossOrigin(origins = "http://localhost:8080")
     @GetMapping("/my-endpoint")
     public String myEndpoint() {
         // ...
     }
 }

**这里 **@CrossOrigin 注解的 origins 参数指定了允许访问该接口的域名。在上面的例子中,只有来自 http://localhost:8080 域名的请求才能访问 myEndpoint 接口。

(3) 配置SpringSecurity允许跨域

由于我们的资源都会受到SpringSecurity的保护,所以想要跨域访问还要让SpringSecurity运行跨域访问。

 //该方法用于配置 HTTP 请求的安全处理
 @Override
 protected void configure(HttpSecurity http) throws Exception {
     http
         //关闭csrf
         .csrf().disable()
         //不会创建会话,每个请求都将被视为独立的请求。
         .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
         .and()
         //定义请求授权规则
         .authorizeRequests()
         // 对于登录接口 允许匿名访问
         .antMatchers("/user/login").anonymous()
         // 除上面外的所有请求全部需要鉴权认证
         .anyRequest().authenticated();
 
     //将自定义认证过滤器,添加到过滤器链中
     http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
 
     //配置异常处理器
     http.exceptionHandling()
         //配置认证失败处理器
         .authenticationEntryPoint(authenticationEntryPoint)
         //配置授权失败处理器
         .accessDeniedHandler(accessDeniedHandler);
 
     //允许跨域
     http.cors();
 }
(4) 前后端联调测试

**首先运行我在资料中给大家提供的前端项目, **注意前端环境要提前配置完成

然后运行后端的项目,进行访问测试即可. 在SpringSecurity中这两行代码注释掉,才能复现跨域请求问题

  http.csrf().disable();
  http.cors();
加密算法---BCryptPasswordEncoder的使用及原理 spring security中的BCryptPasswordEncoder方法采用SHA-256 +随机盐+密钥对密码进行加密。SHA系列是Hash算法,不是加密算法,使用加密算法意味着可以解密(这个与编码/解码一样),但是采用Hash处理,其过程是不可逆的。1)加密(encode):注册用户时,使用SHA-256+随机盐+密钥把用户输入的密码进行hash处理,得到密码的hash值,然后将其存入数据库中。 阅读详情

相关推荐

SpringBoot后台权限管理系统(三)—权限模块

SpringBoot后台权限管理系统:基于shiro框架的权限模块设计,可以后台配置菜单、角色、权限

jin07430119的专栏 9498

Spring Security(10)权限处理

1.getAuthorities的方法中实现权限配置 2.权限评估器 permissionEvaluator 3. @PreAuthorize权限注解的两种写法(user中先开启注解)

weixin_43189971的博客 2550

深入解析AutoConfigURL劫持:从注册表权限到一键修复

本文深入解析了AutoConfigURL劫持的原理,揭示其通过篡改注册表权限锁定浏览器设置的机制。文章提供了从手动修复注册表权限到使用一键修复工具的详细解决方案,并指导用户进行DNS检查与病毒查杀,以彻底根除此类顽固劫持问题。

weixin_29229989的博客 768

SpringSecurity学习笔记

使用SpringSecurity+jwt实现前后端认证和授权

雨落 2357

OPNET卫星节点仿真避坑指南:为什么你的STK轨道文件导入后位置不对?

本文深入解析OPNET卫星节点仿真中STK轨道文件导入后位置异常的常见问题,揭示坐标系差异(ECI与ECF)、时间基准不匹配等关键因素。通过修改.SA文件头标识、统一时间基准等解决方案,帮助工程师避免仿真失真,提升卫星通信系统仿真的准确性。特别适用于STK与OPNET协同工作的卫星网络仿真场景。

weixin_30335575的博客 600

如何创建属于自己的大语言模型:从零开始的指南

随着人工智能的快速发展,大语言模型(LLM)在各种场景中表现出了卓越的能力,例如文本生成、对话交互和内容总结等。本文将带你了解创建 LLM 的核心步骤,从理论到实践,让你拥有一个自己的智能助手。

赵大仁的博客 3280

springboot security 详解

一 环境 Spring Security需要Java 8或更高版本的运行时环境。Spring Boot提供了一个spring-boot-starter-security启动程序。 在pom中添加以下配置即可 <dependency> <groupId>org.springframework.boot</groupId> ...

wszhlzjl的博客 2050

海康SDK实战:如何从PS流中提取H264裸流并实现前端播放(附完整代码)

本文详细介绍了如何利用海康SDK从PS流中提取H264裸流,并构建无插件前端播放的完整链路。通过解析PS流结构、提取H264 ES流,结合WebSocket实时传输与WFS.js前端播放器,实现了低延迟、高可控性的视频流处理方案,为开发者提供了从数据源到前端渲染的实战代码与清晰指南。

weixin_29051811的博客 476

spring boot security ajax_【熵增学院】死灰复燃的Security

一直以来,Spring系列给人的感觉都是快速,简洁,好理解,易操作.但Security是一个特例,这个框架相比而言,首先就是复杂,其次是灵活性也不够.好在于是Spring出的,因此与Spring配合比较好.并且在Spring的大力推广和支持下,它仍然屹立在这里.当然它也有自己的优点,比如他与LDAP还有Oauth这些结构的集成,处理的也不错.我们今天主要从以下几个方面来分享关于Security的知...

weixin_39966130的博客 123

kivy--入门(一)(Windows)python

从零开始学习kivy 由于课程需要写一个app,还要写报告,这就很愁人,头秃,脑壳大。 虽然学过Android,但是小编对于Java就像对牛弹琴似的,不能说完全不懂,只能说Java这个语言不太适合我,不学还不行,还是先利用python的kivy编写一个吧。 在各大网站搜索开发app,发现适合写手机app的除了Android还有kivy,所以还是先学习如何使用吧! 文章目录从零开始学习kivy前言一、安装二、添加按钮+标签三、文本输入(TextInput)总结 前言 首先推荐这篇博客kivy的安装和使用

刘念卿的博客 9969

告别Keil!用VSCode+PlatformIO玩转STC单片机(附自动下载配置)

本文详细介绍了如何从Keil迁移到VSCode+PlatformIO进行STC单片机开发,包括环境搭建、自动下载配置等实用技巧。通过对比传统IDE与现代工具链的差异,展示了VSCode+PlatformIO在代码编辑体验、跨平台支持和开发效率上的优势,帮助开发者实现更流畅的STC单片机开发流程。

weixin_27252001的博客 458

Spring Boot Security入门

一、Spring Boot Security 整合 OAuth2 设计安全API接口服务 OAuth是一个关于授权(authorization)的开放网络标准,在全世界得到广泛应用,目前的版本是2.0版。本文重点讲解Spring Boot项目对OAuth2进行的实现,如果你对OAuth2不是很了解,你可以先理解OAuth 2.0 - 阮一峰, https://www.ruanyifeng.c...

曾经 720

iOS Certificates, Identifiers & Profiles简介

1. Certificates Certificates是生成开发和生产所需的证书。 2.1 certSigningRequest文件 生成证书前,需要先生成certSigningRequest文件,certSigningRequest文件由【钥匙串访问】生成。 选择菜单【钥匙串访问】,【证书助理】下的【从证书颁发机构请求证书…】 填写【用户电子邮件地址: 】,将【请求是:】的选项改为【储存到磁盘】 输入certSigningRequest文件的文件名,并指定保存位置。 保存certSignin

假装你是大灰狼的专栏 7058

Spring boot security权限管理集成cas单点登录

挣扎了两周,Spring security的cas终于搞出来了,废话不多说,开篇! Spring boot集成Spring security本篇是使用spring security集成cas,因此,先得集成spring security新建一个Spring boot项目,加入maven依赖,我这里是用的架构是Spring boot2.0.4+Spring mvc+Spring data jp...

weixin_33781606的博客 1132

45个经典Linux面试题(非常详细)零基础入门到精通,收藏这一篇就够了

(文件系统分配其中的一些磁盘块用来记录它自身的一些数据,如 i 节点,磁盘分布图,间接块,超级块等。$ chmod 751 file 给 file 的属主分配读、写、执行(7)的权限,给 file 的所在组分配读、执行(5)的权限,给其他用户分配执行(1)的权限。linux命令’hash’管理着一个内置的哈希表,记录了已执行过的命令的完整路径, 用该命令可以打印出你所使用过的命令以及执行的次数。创建文件:典型的如 touch,vi 也可以创建文件,其实只要向一个不存在的文件输出,都会创建文件。

小陈的博客 2071

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

关于junit测试与spring security时

问题描述 使用单元测试时,需要调用项目的其他接口,而其中有的接口涉及到了权限管理,因为不是从前端带token发过来的 所以会出现一些问题(比如 直接从容器中获取当前用户会报空 解决方法: 1. 1.在测试类上加 前两个注解 @RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc //非必须 其他测试需要 public class DataCollectionTest { @Autowired DataColle.

licux的博客 1552

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

SpringBoot整合Security安全框架、控制权限

我想每个写项目的人,都肯定会遇到控制权限这个问题. 例如这个这个链接只能管理员访问,那个链接丫只能超级管理员访问等等,实现方式也有多种多样,控制的粒度也不一样。 以前刚学的时候,不会框架,大都是手写注解+过滤器来进行权限的控制,但这样增加了过滤器的负担。用起来也会稍微有些麻烦,粒度不太好控制。 用框架的话,就是封装了更多的操作,让一切更简单吧。当然不局限于Security,还有像Shiro安全框架,这两种非常常见。 一起加油吧!!!???? 先看个图舒缓一下,准备开始吧????‍???? 下面就开始吧!

北 2875

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

spring security 学习二(通过数据库用户实现身份认证,权限识别)

一、Spring Security认证具体实现 Spring Security通过filter来控制web应用的安全,但filter自己不干事,分别委托给了认证管理器和决策管理器,认证管理器和决策管理器再分别委托给Provider和Voter,就这样一级级委托下来,委托的最底层就是需要我们根据实际情况实现的逻辑。可以看下图: 根据我们这次的目标,想用数据库来储存用户的认证数据,那么我们就需要一个操作数据库的Provider,这个Spring Security内部已经有了实现,就是DaoAuthent

bird_tp的博客 1093

深入Spring Security魔幻山谷-获取认证机制核心原理讲解

文/朱季谦 本文基于Springboot+Vue+Spring Security框架而写的原创学习笔记,demo代码参考《Spring Boot+Spring Cloud+Vue+Element项目实战:手把手教你开发权限管理系统》一书。 这是一个古老的传说。 在神秘的Web系统世界里,有一座名为Spring Security的山谷,它高耸入云,蔓延千里,鸟飞不过,兽攀不了。这座山谷只有一条逼仄的道路可通。然而,若要通过这条道路前往另一头的世界,就必须先拿到一块名为token的令牌,只有这..

朱季谦 519

springsecurity前后端分离的RBAC动态权限管理

springsecurity前后端分离的RBAC(静态——>动态)权限管理 注:纯属个人创作,其中涉嫌相关博主图片及时私信,立即下架 初心:自我加强巩固 入门篇(springsecurity、springboot基本整合) 一、为什么选择springsecurity,以及Shiro对比? 1.相同点: 1:认证功能 2:授权功能 3:加密功能 4:会话管理 5:缓存支持 6:rememberMe功能 2.不同点: 优点: 1:Spring Security基于Spring开发,项目

qq_46690881的博客 1576

springboot+spring security +oauth2.0 demo搭建(password模式)(认证授权端与资源服务端分离的形式)

springboot+spring security +oauth2.0 demo搭建(password模式)(认证授权端与资源服务端分离的形式) 项目security_simple(认证授权项目) 1.新建springboot项目     这儿选择springboot版本我选择的是2.0.6 点击finish后完成项目的创建 2.引入maven依赖  下面是我引入的依赖...

taihexuelang的专栏 7561

spring boot jwt_Spring Boot Security 整合 JWT 实现 无状态的分布式API接口

简介JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。JSON Web Token 入门教程 - 阮一峰,这篇文章可以帮你了解JWT的概念。本文重点讲解Spring Boot 结合 jwt ,来实现前后端分离中,接口的安全调用。快速上手之前的文章已经对 Spring Security 进行了讲解,这一节对涉及到 Spring Security 的配置不详细讲解。若不了解 ...

weixin_39634576的博客 121

【课程设计】springboot公司人事管理系统-计算机毕业设计源码36111

系统设计阶段,我们需要明确需求,包括用户角色(如管理员、员工),功能模块(如员工信息管理、考勤管理、薪资管理、福利管理等)。接着进行数据库设计,通常会包括员工表、部门表、职位表等,以便存储各种人事信息。 在技术选型上,Java作为后端编程语言,以其强大的类库支持和跨平台特性,成为了SpringBoot的首选。

Biye_Design的博客 1870

熬夜一周!终于搞懂SpringBoot电商系统全流程

最近整理了一个基于SpringBoot的商城系统源码,功能挺全的,包括商品管理、购物车、订单支付、用户收藏评论等模块,前后台都有。 技术栈: 后端:SpringBoot + MyBatis + Spring Security 前端:Thymeleaf + Bootstrap 数据库:MySQL 还集成了支付宝支付、邮件服务、验证码等 适合学习的点: ✅ 完整的电商业务流程 ✅ 权限管理和安全认证 ✅ 支付接口对接 ✅ 前后端分

myzshare的博客 877

Spring Boot Security 整合 JWT 实现 无状态的分布式API接口

简介 JSON Web Token(缩写 JWT)是目前最流行的跨域认证解决方案。JSON Web Token 入门教程 - 阮一峰,这篇文章可以帮你了解JWT的概念。本文重点讲解Spring Boot 结合 jwt ,来实现前后端分离中,接口的安全调用。 快速上手 之前的文章已经对 Spring Security 进行了讲解,这一节对涉及到 Spring Security 的配置不详细讲解。若不...

weixin_33725515的博客 107

【课程设计】springboot易购校园二手交易平台-计算机毕业设计源码82776

用户模块:1.用户注册和登录功能2.用户信息管理,包括个人资料、联系方式、收货地址等。商品模块:1.商品发布功能,让卖家可以方便地发布二手商品信息。2.商品编辑和删除功能,卖家可以随时修改商品信息或下架商品。3.商品分类和标签管理,方便用户查找和筛选商品。交易模块:1.购物车功能,用户可以将心仪的商品加入购物车,方便统一结算。2.订单生成和管理功能,记录用户的交易订单信息,包括订单状态、支付方式、物流信息等。3.支付功能,支持多种支付方式(如支付宝、微信支付、银行卡支付等),确保交易的安全和便捷。消息模块:

Biye_Design的博客 1922
上一篇: 【业务功能篇58】Springboot + Spring Security 权限管理 【中篇】
下一篇: 【业务功能篇60】Springboot + Spring Security 权限管理 【终篇】
studyday1
博客等级 码龄9年 6454粉丝 255原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值