首页 > 其他分享 >Springboot简单功能示例-5 使用JWT进行授权认证

Springboot简单功能示例-5 使用JWT进行授权认证

时间:2023-09-18 20:13:45浏览次数:38  
标签:exception java String 示例 JWT ERROR LOGIN Springboot

springboot-sample

介绍

  springboot简单示例-使用JWT进行授权认证 跳转到发行版 查看发行版说明

软件架构(当前发行版)

  • Springboot3.1.3
  • hutool
  • bcprov-jdk18on

安装教程

git clone --branch 自定义加密进行登录验证 [email protected]:simen_net/springboot-sample.git

主要功能

使用JWT认证

    • WebSecurityConfig.java中注册JwtAuthenticationSuccessHandler.javaJwtAuthenticationFailureHandler.java验证处理器

      // 注册验证成功处理器
      httpSecurityFormLoginConfigurer.successHandler(authenticationSuccessHandler);
      // 注册验证失败处理器
      httpSecurityFormLoginConfigurer.failureHandler(authenticationFailureHandler);
       
    • WebSecurityConfig.java中加入异常处理器JwtAuthenticationEntryPoint

      // 加入异常处理器
      httpSecurity.exceptionHandling(httpSecurityExceptionHandlingConfigurer ->
              httpSecurityExceptionHandlingConfigurer.authenticationEntryPoint(authenticationEntryPoint)
      );
       
    • WebSecurityConfig.java中强制session无效

      // 强制session无效,使用jwt认证时建议禁用,正常登录不能禁用session
      httpSecurity.sessionManagement(httpSecuritySessionManagementConfigurer->
              httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
      );
       

代码逻辑说明

    1. JwtUserDetails.java中增加private Map<String, Object> mapProperties,用于保存登录用户的扩展信息,录入用户分组、用户单位等等

    2. JwtUserDetailsService.java中模拟注入用户权限及扩展信息

      listGrantedAuthority.add(new SimpleGrantedAuthority("file_write"));
      mapProperties.put("扩展属性", username + " file_write");
      log.info("读取到已有用户[{}],默认密码123456,file_write权限,扩展属性:[{}]", username, mapProperties);
      
      return new JwtUserDetails(username, SM2_OBJ.signHex("123456", SecurityUtils.STR_UUID), listGrantedAuthority, mapProperties);
       
    3. SecurityUtils.java中定义全局常量Map<String, String> MAP_LOGIN_SUCCESS,用于保存用户登录时的token必,可以在服务器通过简单的匹配防止伪造签名,如不需要可以取消

    4. 登录成功处理器JwtAuthenticationSuccessHandler.java,返回包含jwt编码的标准化json。其中使用Sm2JwtSigner.java进行签名和校验

      @Override
      public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
          if (!response.isCommitted() && authentication != null && authentication.getPrincipal() != null
                  // 获取登录用户信息对象
                  && authentication.getPrincipal() instanceof JwtUserDetails userDetails) {
      
              // 获取30分钟有效的token编码
              String strToken = jwtTokenUtils.getToken30Minute(
                      userDetails.getUsername(),
                      CollUtil.join(userDetails.getAuthorities(), ","),
                      userDetails.getMapProperties()
              );
      
              // 在全局登录成功的map中放入当前用户登录的token
              MAP_LOGIN_SUCCESS.put(userDetails.getUsername(), strToken);
      
              // 包装返回的JWT对象
              ReplyVO<JwtResponseData> replyVO = new ReplyVO<>(
                      new JwtResponseData(strToken, DateUtil.date()), "用户登录成功");
      
              // 将返回字符串写入response
              SecurityUtils.returnReplyJsonResponse(response, HttpServletResponse.SC_OK, replyVO);
          }
      }
       
    5. 登录失败处理器JwtAuthenticationFailureHandler,根据抛出的异常返回对应的json

      @Override
      public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException {
          String strData = LOGIN_ERROR_UNKNOWN;
          String strMessage = "LOGIN_ERROR_UNKNOWN";
      
          if (exception instanceof LockedException) {
              strData = LOGIN_ERROR_ACCOUNT_LOCKING;
              strMessage = exception.getMessage();
          } else if (exception instanceof CredentialsExpiredException) {
              strData = LOGIN_ERROR_PASSWORD_EXPIRED;
              strMessage = exception.getMessage();
          } else if (exception instanceof AccountExpiredException) {
              strData = LOGIN_ERROR_OVERDUE_ACCOUNT;
              strMessage = exception.getMessage();
          } else if (exception instanceof DisabledException) {
              strData = LOGIN_ERROR_ACCOUNT_BANNED;
              strMessage = exception.getMessage();
          } else if (exception instanceof BadCredentialsException) {
              strData = LOGIN_ERROR_USER_CREDENTIAL_EXCEPTION;
              strMessage = exception.getMessage();
          } else if (exception instanceof UsernameNotFoundException) {
              strData = LOGIN_ERROR_USER_NAME_NOT_FOUND;
              strMessage = exception.getMessage();
          }
      
          // exception.printStackTrace();
          SecurityUtils.returnReplyJsonResponse(response, HttpServletResponse.SC_OK,
                  new ReplyVO<>(strData, strMessage, ReplyEnum.ERROR_USER_HAS_NO_PERMISSIONS.getCode()));
      }
       
    6. 异常处理器JwtAuthenticationEntryPoint中根据request头Accept判断请求类型是html还是json,html请求跳转到登录页面,json请求返回异常接送代码

      @Override
      public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
          // 从request头中获取Accept
          String strAccept = request.getHeader("Accept");
          if (StrUtil.isNotBlank(strAccept)) {
              // 对Accept分组为字符串数组
              String[] strsAccept = StrUtil.splitToArray(strAccept, ",");
              // 判断Accept数组中是否存在"text/html"
              if (ArrayUtil.contains(strsAccept, "text/html")) {
                  // 存在"text/html",判断为html访问,则跳转到登录界面
                  response.sendRedirect(STR_URL_LOGIN_URL);
              } else {
                  // 不存在"text/html",判断为json访问,则返回未授权的json
                  SecurityUtils.returnReplyJsonResponse(response, HttpServletResponse.SC_OK,
                          new ReplyVO<>("未授权或登录已超时", ReplyEnum.ERROR_TOKEN_EXPIRED));
              }
          }
      }
       
    7. 登出成功处理器JwtLogoutSuccessHandler

      jwtTokenUtils.verifyToken(strJwtToken);
      // 从token中获取用户名
      String strUserName = jwtTokenUtils.getAudience(strJwtToken);
      // 断言用户名非空
      Assert.notBlank(strUserName, "当前用户不存在");
      // 从全局登录信息Map中移除该用户信息
      MAP_LOGIN_SUCCESS.remove(strUserName);
      
      log.info("[{}]登出成功", strUserName);
       
    8. Sm2JwtSigner.java签名和校验时,将headerBase64payloadBase64使用STR_JWT_SIGN_SPLIT组合成字符串进行签名和校验

      /**
       * 返回签名的Base64代码
       *
       * @param headerBase64  JWT头的JSON字符串的Base64表示
       * @param payloadBase64 JWT载荷的JSON字符串Base64表示
       * @return 签名结果Base64,即JWT的第三部分
       */
      @Override
      public String sign(String headerBase64, String payloadBase64) {
          StringBuilder sbContent = new StringBuilder();
          sbContent.append(headerBase64).append(STR_JWT_SIGN_SPLIT).append(payloadBase64);
          return Base64Encoder.encode(SM2_OBJ.sign(StrUtil.utf8Bytes(sbContent)));
      }
      
      /**
       * 验签
       *
       * @param headerBase64  JWT头的JSON字符串Base64表示
       * @param payloadBase64 JWT载荷的JSON字符串Base64表示
       * @param signBase64    被验证的签名Base64表示
       * @return 签名是否一致
       */
      @Override
      public boolean verify(String headerBase64, String payloadBase64, String signBase64) {
          StringBuilder sbContent = new StringBuilder();
          sbContent.append(headerBase64).append(STR_JWT_SIGN_SPLIT).append(payloadBase64);
          return SM2_OBJ.verify(StrUtil.utf8Bytes(sbContent), Base64Decoder.decode(signBase64));
      }
       
    9. 生成的JWT代码和解密内容

      • JWT Tokens 编码

        eyJ0eXAiOiJKV1QiLCJhbGciOiLlm73lr4ZTTTLpnZ7lr7nnp7Dnrpfms5XvvIzln7rkuo5CQ-W6kyJ9.eyJhdWQiOlsic2ltZW4iXSwiaWF0IjoxNjk1MDIwMzUzLCJleHAiOjE2OTUwMzgzNTMsIlVTRVJfQVVUSE9SSVRZIjoiZmlsZV9yZWFkIiwiTUFQX1VTRVJfUFJPUEVSVElFUyI6eyLmianlsZXlsZ7mgKciOiJzaW1lbiBmaWxlX3JlYWQifX0.MEQCIBr7QHoMdgqt53AM+hlVJfDfSrj8Pdi+dAJ9hg3QMBQuAiAhcFbV26ESehhylWewr467GNWncKruz86NfD68CU105Q==
         
      • Decode 解码后HEADER

        {
            "typ": "JWT",
            "alg": "国密SM2非对称算法,基于BC库"
        }
         
      • Decode 解码后PAYLOAD

        {
           "aud": [
              "simen"
           ],
           "iat": 1695020353,
           "exp": 1695038353,
           "USER_AUTHORITY": "file_read",
           "MAP_USER_PROPERTIES": {
              "扩展属性": "simen file_read"
           }
        }

标签:exception,java,String,示例,JWT,ERROR,LOGIN,Springboot
From: https://www.cnblogs.com/xiuligong-net/p/17712933.html

相关文章

  • 启动一个springboot项目
    最终效果 在idea中  packagecom.fqs.helloworld.controller;importorg.springframework.web.bind.annotation.GetMapping;importorg.springframework.web.bind.annotation.RestController;@RestControllerpublicclassHelloController{@GetMapping("/he......
  • 27、Flink 的SQL之SELECT (SQL Hints 和 Joins)介绍及详细示例(2-2)
    Flink系列文章[1、Flink部署、概念介绍、source、transformation、sink使用示例、四大基石介绍和示例等系列综合文章链接][13、Flink的tableapi与sql的基本概念、通用api介绍及入门示例][14、Flink的tableapi与sql之数据类型:内置数据类型以及它们的属性][15、Flink的t......
  • springboot整合elasticsearch-RestHighLevelClient api查询
    1.依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-elasticsearch</artifactId></dependency><dependency><groupId>org.elasticsearch.client</groupId......
  • springboot vue电子班牌系统源码,以云平台、云服务器为基础,融合课程管理、物联控制、
    随着时代进步,数字信息化不断发展,很多学校都开始了数字化的转变。智慧校园电子班牌系统是电子班牌集合信息化技术、物联网、智能化,电子班牌以云平台、云服务器为基础,融合了班级文化展示、课程管理、物联控制、教务管理、考勤管理、素质评价、资源管理、家校互联等一系列应用。实现了......
  • Spring,SpringMVC,SpringBoot,SpringCloud有什么区别?
    简单介绍Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。Spring使你能够编写更干净、更可管理、并且更易于测试的代码。SpringMVC是Spring的一个模块,一个web框架。通过DispatcherServlet,ModelAndView和ViewResolver,开发web应用变得很容易。主要针对的......
  • 4-微信小程序 相关知识点代码示例
    基于上篇文章的理论文本的介绍来进行相关代码的演示和例子该篇文章需注意,在微信小程序的使用时,应先熟悉里面每个文件的作用,在第二篇文章有详细记载,一般用的比较多的是wxml、wxss、ws.js对应网站的开发就是html、css、js、页面的内容及框架、页面的美化、页面的基本功能1.数据......
  • Springboot简单功能示例-4 自定义加密进行登录验证
    springboot-sample介绍springboot简单示例-自定义加密进行登录验证跳转到发行版软件架构(当前发行版)Springboot3.1.3hutoolbcprov-jdk18on安装教程gitclone--branch自定义加密进行登录验证[email protected]:simen_net/springboot-sample.git主要功能使用SM2库......
  • Springboot简单功能示例-3 实现基本登录验证
    springboot-sample介绍springboot简单示例跳转到发行版软件架构(当前发行版)Springboot3.1.3hutoolbcprov-jdk18on安装教程gitclone--branch基本登录验证[email protected]:simen_net/springboot-sample.git主要功能增加登录验证功能在pom.xml中加入sprin......
  • go脚本示例
    以下是Go脚本的示例,涵盖了不同的主题和用途:Hello,World!packagemainimport"fmt"funcmain(){fmt.Println("Hello,World!")}打印系统时间packagemainimport("fmt""time")funcmain(){currentTime:=time.Now()......
  • springboot中配置类型转换,设置开启矩阵变量
    2023-09-17packagecom.hh.springboot05.config;importcom.hh.springboot05.bean.Pet;importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Configuration;importorg.springframework.core.convert.converter.Conver......