首页 > 其他分享 >SpringSecurity权限控制的学习

SpringSecurity权限控制的学习

时间:2023-11-11 15:44:50浏览次数:36  
标签:return String SpringSecurity 学习 token user 权限 public

Security权限控制流程

 

环境

数据库

数据库采用RBAC结构,大概如下图所示

创建的表结构如下所示,用户表,角色表,权限表和两个关联他们的表

img

导入springsecurity依赖坐标和我们需要的各种依赖坐标

  <!--        mysql 的驱动和mybatisplus依赖-->

       <dependency>
           <groupId>mysql</groupId>
           <artifactId>mysql-connector-java</artifactId>
           <version>8.0.29</version>
       </dependency>
       <dependency>
           <groupId>com.baomidou</groupId>
           <artifactId>mybatis-plus-boot-starter</artifactId>
           <version>3.3.1</version>
       </dependency>

       <!--       lombok-->
       <dependency>
           <groupId>org.projectlombok</groupId>
           <artifactId>lombok</artifactId>
       </dependency>
       <!--redis依赖-->
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-data-redis</artifactId>
       </dependency>
       <!--fastjson依赖-->
       <dependency>
           <groupId>com.alibaba</groupId>
           <artifactId>fastjson</artifactId>
           <version>1.2.83</version>
       </dependency>
       <!--jwt依赖-->
       <dependency>
           <groupId>io.jsonwebtoken</groupId>
           <artifactId>jjwt-api</artifactId>
           <version>0.11.2</version>
       </dependency>
       <dependency>
           <groupId>io.jsonwebtoken</groupId>
           <artifactId>jjwt-impl</artifactId>
           <version>0.11.2</version>
           <scope>runtime</scope>
       </dependency>
       <dependency>
           <groupId>io.jsonwebtoken</groupId>
           <artifactId>jjwt-jackson</artifactId> <!-- or jjwt-gson if Gson is preferred -->
           <version>0.11.2</version>
           <scope>runtime</scope>
       </dependency>

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

然后在pom中配置mysql配置,redis配置,mybatisplus配置

 

 

SpringSecurity异常捕获

因为security一般不会被我们自己定义的全局异常捕获而捕获。

在SpringSecurity中如果认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。

所以我们这里代码这样子写

@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
   @Override
   public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
       ResponseResult result = new ResponseResult(401, authException.getMessage());
       String json = JSON.toJSONString(result);
       WebUtils.renderString(response, json);
  }
}
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
   @Override
   public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
       ResponseResult result = new ResponseResult(403, accessDeniedException.getMessage());
       String json = JSON.toJSONString(result);
       WebUtils.renderString(response, json);
  }
}

然后要在security的配置类中注册。

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
   //密码加密方式
   @Bean
   public PasswordEncoder passwordEncoder(){
       return new BCryptPasswordEncoder();
  }
   //过滤器
   @Resource
   JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
   //注入   身份认证和权限控制异常捕获
   @Autowired
   private AuthenticationEntryPoint authenticationEntryPoint;
   @Autowired
   private AccessDeniedHandler accessDeniedHandler;
   @Override
   protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       // 从数据库读取的用户进行身份认证
       super.configure(auth);
  }
   @Override
   protected void configure(HttpSecurity http) throws Exception {
       http
               //关闭csrf
              .csrf().disable()
               //不通过Session获取SecurityContext
              .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
              .and()
              .authorizeRequests()
               // 对于登录接口 允许匿名访问 已经登录后不可访问
              .antMatchers("/user/login").anonymous()
//               .antMatchers("").permitAll()   无论是否认证对资源放行
               // 除上面外的所有请求全部需要鉴权认证
              .anyRequest().authenticated();
       //捕获security异常统一返回
       http.exceptionHandling()
              .authenticationEntryPoint(authenticationEntryPoint)
              .accessDeniedHandler(accessDeniedHandler);

       //把token校验过滤器添加到过滤器链中
       http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

  }

   @Bean
   @Override
   public AuthenticationManager authenticationManagerBean() throws Exception {
       return super.authenticationManagerBean();
  }
}

登录代码

定义数据库中user对应的实体类和mapper还有service

在登录的service中写一个登录的方法

再写一个该方法的实现类在实现类中写入以下代码

    @Override
   public ResponseResult login(User user) {

           //是一个将账号密码封装起来的一个方法
           UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
           // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername   返回一个LoginUser里面包含账号密码和对应的权限
           Authentication authenticate = authenticationManager.authenticate(authenticationToken);
return null;

  }

因为authenticationManager.authenticate()会调用UserDetailsService所以我们写一个UserdetailsService的实现类并且重写里面的loadUserByUsername。

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
   //判断用户表中是否有该用户,并且返回该用户的所有信息
   LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
   wrapper.eq(User::getUserName,username);
   User user = userMapper.selectOne(wrapper);
   
   return createLoginUser(user);
}

一般是在这个类中以用户的用户名查询用户信息,再接着去查询用户所拥有的权限然后给到springSecurity。因为这个方法要返回一个userDetails类型的数据,所以我们要接他的接口重写里面的方法,然后可以添加一些我们自己需要的数据类型。

@Data
@NoArgsConstructor
public class LoginUser implements UserDetails {

   private User user;

   //存储权限信息
   private List<String> permissions;

   /**
    *
    * 用户的唯一标识
    */
   private String token;

   public String getToken() {
       return token;
  }

   public void setToken(String token) {
       this.token = token;
  }

   public LoginUser(User user, List<String> permissions) {
       this.user = user;
       this.permissions = permissions;
  }
   public LoginUser(User user) {
       this.user = user;

  }


   //存储SpringSecurity所需要的权限信息的集合
   @JSONField(serialize = false)
   private List<GrantedAuthority> authorities;

   @Override
   public  Collection<? extends GrantedAuthority> getAuthorities() {
       if(authorities!=null){
           return authorities;
      }
       //把permissions中字符串类型的权限信息转换成GrantedAuthority对象存入authorities中
       authorities = permissions.stream().
               map(SimpleGrantedAuthority::new)
              .collect(Collectors.toList());
       return authorities;
  }

//   获取密码
   @Override
   public String getPassword() {
       return user.getPassword();
  }
//   获取用户名
   @Override
   public String getUsername() {
       return user.getUserName();
  }

   @Override
   public boolean isAccountNonExpired() {
       return true;
  }

   @Override
   public boolean isAccountNonLocked() {
       return true;
  }

   @Override
   public boolean isCredentialsNonExpired() {
       return true;
  }

   @Override
   public boolean isEnabled() {
       return true;
  }
}

然后我们继续写上面的createLogin()方法

public  LoginUser createLoginUser(User user){
   //在这里创建返回需要的loginUser并且查询用户的所有权限
   return new LoginUser(user,permissionService.getRolePermision(user));
}

这边我们直接new了一个LoginUser然后调用他的构造方法将其用户权限传给他。

然后写一个用户权限信息的类和其中获取用户权限的方法,然后返回。

/**
* 用户权限的处理
*/
@Component
public class PermissionService {

   @Autowired
   private MenuMapper menuMapper;

   public List<String> getRolePermision(User user){


       List<String> perms = new ArrayList<>();

//     这边直接写死最高级的管理员id就是为1
       if (user.getId() != null && user.getId() == 1){
           perms.add("*:*:*");
      }
//       其他的用户权限
       else {
//         //根据用户id获得该用户所拥有的权限
           List<String> strings = menuMapper.selectPermsByUserId(user.getId());
           perms.addAll(strings);
      }
       return perms;
  }

}

然后我们回到登录接口调用的登录service实现类,用UserdetailsService的实现类获得该用户的权限后我们需要给前端返回一个token以便后面用户返回接口时控制其权限。这边用jwt来生成token。

    @Override
   public ResponseResult login(User user) {

           //是一个将账号密码封装起来的一个方法
           UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user.getUserName(), user.getPassword());
           // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername   返回一个LoginUser里面包含账号密码和对应的权限
           Authentication authenticate = authenticationManager.authenticate(authenticationToken);
           
           
//           获取根据用户名查到的信息包括账号密码与权限
           LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
//         生成一个token并且存入到将其和loginuser信息存入到redis中去
           String token = jwtUtil.createToken(loginUser);
           //把token响应给前端
           HashMap<String, String> map = new HashMap<>();
           map.put("token", token);
           return new ResponseResult(200, "登陆成功", map);

  }

这里的步骤是用uuid生成一个随机值,将uuid以键的形式,将用户信息以值的新式存入redis中,然后将uuid传入jwt中让其生成token。

    public  String createToken(LoginUser loginUser) {
       //随机生成一个uuid到时候将uuid以键的形式,将用户信息以值的新式存入redis中
       String token = UUID.randomUUID().toString();
       loginUser.setToken(token);
//       将loginUser以uuid为键存入redis中去,为了方便后面控制登录的用户这里在uuid前加上前缀,相当于分组吧。
       String userKey = "login_tokens_chen:" + token;
       redisCache.setCacheObject(userKey,loginUser);
//       将uuid传入到下一级根据uuid去生成一个token
       Map<String, Object> claims = new HashMap<>();
       claims.put("login_user_key", userKey);
       
       return JwtUtil.createJWT(claims);
  }
    /**
    * 生成jtw
    *
    * @param claims token中要存放的数据(json格式) 是一个键值对 login_user_key:uuid
    * @return
    */
   public static String createJWT(Map<String, Object> claims) {
//       JwtBuilder builder = getJwtBuilder(subject, null, getUUID());// 设置过期时间

       String token = Jwts.builder()
              .setClaims(claims)  //将信息封装进token中
              .signWith(SignatureAlgorithm.HS256, generalKey()).compact();
       return token;
  }

然后登录到这基本就结束了。

最后写一个登录的controller去调用我们写的

@RestController
public class LoginController {

   @Resource
   private LoginServcie loginServcie;

   @PostMapping("/user/login")
   public ResponseResult login(@RequestBody User user){

       return loginServcie.login(user);
  }
}

然后用postman对其进行测试。

 

登录成功,可以用token拿去jwt官网解析

 

可以发现我们刚刚的uuid就被存入了token中,然后我们用解析出来的login_user_key去redis中查询用户信息

 

可以看到我们要的用户信息都在redis中可以查询到。

 

过滤器

其实就是将前端的请求链接链接然后解析其token获取用户对应权限

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

   @Autowired
   private JwtUtil jwtUtil;

   @Override
   protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException, ServletException, IOException {
       //获取token
       String token = request.getHeader("token");
       //解析token
       if (!StringUtils.hasText(token)) {  //验证该token不能为null不能为空不能为” “
           //放行
           filterChain.doFilter(request, response);
//           throw new RuntimeException("没有token");
           return;
      } else {
           try {
//               解析前端token获取用户信息
               LoginUser loginUser = jwtUtil.getLoginUser(token);
               // 用于在应用程序中获取当前用户的认证信息
//               SecurityContext context = SecurityContextHolder.getContext();
               //存入SecurityContextHolder
               //获取权限信息封装到Authentication中
               UsernamePasswordAuthenticationToken authenticationToken =
                       new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
               SecurityContextHolder.getContext().setAuthentication(authenticationToken);
          } catch (Exception e) {
               System.out.println("error" + e.getMessage());
          }
      }
       //放行
       filterChain.doFilter(request, response);
  }
}

解析前端token获取用户信息的方法

/**
* 解析前端传入的token获取用户信息
*/
public LoginUser getLoginUser(String token ){
   Claims claims = Jwts.parser()
          .setSigningKey(JWT_KEY)
          .parseClaimsJws(token)
          .getBody();
   // 解析对应的权限以及用户信息
   //获取到了claims就是封装token时的map
   String uuid = (String) claims.get("login_user_key");
   LoginUser user = redisCache.getCacheObject(uuid);
   System.out.println("log jwtUtil:"+ user);
   return user;

}
 

 

 

标签:return,String,SpringSecurity,学习,token,user,权限,public
From: https://www.cnblogs.com/youseed/p/17825977.html

相关文章

  • 2023-2024-1-20231317 计算机基础与程序设计第七周学习总结
    这个作业属于哪个课程<班级的链接>(如2022-2023-1-计算机基础与程序设计)这个作业要求在哪里<作业要求的链接>(如2022-2023-1计算机基础与程序设计第七周作业)这个作业的目标<《计算机科学概论第8章》《C语言程序设计第6章》>作业正文https://www.cnblogs.com/Ter......
  • 重新学习算法_Day3-哈希表&2283&str与list转换
    HashTable 感觉从原理上说会用但是实际应用感觉不知道有什么用或者不知道怎么用例如:给你一个下标从 0 开始长度为 n 的字符串 num ,它只包含数字。如果对于 每个 0<=i<n 的下标 i ,都满足数位 i 在 num 中出现了 num[i]次,那么请你返回 true ,否则返回......
  • springboot学习日记(二)
    运行springboot项目报错o.s.b.d.LoggingFailureAnalysisReporter,查资料试着查一下端口占用8080。netstat-aon|findstr8080发现8080端口被进程8768占用。 查找8768进程的程序tasklist|findstr8768发现是腾讯会议。。。 退出了再试试,还是没解决问题。。很好,排除一......
  • 2023-2024-1 20231327《计算机基础与程序设计》第7周学习总结
    学期(2023-2024-1)学号(20231327)《计算机基础与程序设计》第7周学习总结作业信息课程<班级的链接>(2023-2024-1-计算机基础与程序设计)要求<作业要求的链接>(2023-2024-1计算机基础与程序设计第7周作业)目标<了解并使用循环结构>作业正文https://i.cnblogs.com/p......
  • 2023-2024-1 20231302 《计算机基础与程序设计》第七周学习总结
    作业信息这个作业属于哪个课程2023-2024-1-计算机基础与程序设计这个作业要求在哪里2023-2024-1计算机基础与程序设计第七周作业这个作业的目标数组与链表、基于数组和基于链表实现数据结构、无序表与有序表、树、图、子程序与参数作业正文https://www.cnblogs......
  • 2023-2024-1 20231416《计算机基础与程序设计》第7周学习总结
    作业信息这个作业属于哪个课程(https://edu.cnblogs.com/campus/besti/2023-2024-1-CFAP)这个作业要求在哪里https://www.cnblogs.com/rocedu/p/9577842.html#WEEK07这个作业的目标《计算机科学概论》第8章《C语言程序设计》第6章作业正文 https://www.cnblo......
  • SMOGN算法的Python实现:不平衡数据的深度学习回归
      本文介绍基于Python语言中的smogn包,读取.csv格式的Excel表格文件,实现SMOGN算法,对机器学习、深度学习回归中,训练数据集不平衡的情况加以解决的具体方法。  在不平衡回归问题中,样本数量的不均衡性可能导致模型在预测较少类别的样本时表现较差;为了解决这个问题,可以使用SMOTE(Syn......
  • 2023-2024-1 20231402《计算机基础与程序设计》第7周学习总结
    2023-2024-120231402《计算机基础与程序设计》第7周学习总结这个作业属于哪个课程2023-2024-1-计算机基础与程序设计这个作业要求在哪里2023-2024-1计算机基础与程序设计第7周作业这个作业的目标自学计算机科学概论第8章,《C语言程序设计》第6章 教材学习内容......
  • 20211128《信息安全系统设计与实现》第六章学习笔记
    一、任务内容自学教材第6章,提交学习笔记(10分)1.知识点归纳以及自己最有收获的内容,选择至少2个知识点利用chatgpt等工具进行苏格拉底挑战,并提交过程截图,提示过程参考下面内容(4分)“我在学***X知识点,请你以苏格拉底的方式对我进行提问,一次一个问题”核心是要求GPT:“请你以苏格拉......
  • 深度学习笔记
    机器学习流程数据获取特征工程(神经网络可以作为一种特征提取的方法,而非算法)建立模型(用工具包建模很快)评估与应用特征工程是所有机器学习算法中最核心的部分图像分类任务图像300*100*3(像素点数目+通道数(3个通道:如RGB))......