Spring Security初识用户认证流程

用户认证流程

  1. 客户端第一次发送请求,由于此前未登录认证,因此会被spring security过滤器拦截;若此次请求为登录请求,由于在SpringSecurity配置类声明的白名单(即不需要被认证的请求)里配置了登录路径,因此可以被接收。

  2. 客户端接收登录请求后到service层处理登录业务,此请求参数DTO携带用户名和密码信息,然后把其声明在认证信息里面:

    Authentication authentication//声明一个认证信息
                    = new UsernamePasswordAuthenticationToken(
                            adminLoginDTO.getUsername(),adminLoginDTO.getPassword());
    

    接下来用认证管理器对认证信息进行处理:

     Authentication authenticateResult              //调用认证管理器对认证信息进行处理
                    = authenticationManager.authenticate(authentication);//并获取认证信息把他放在上下文里面
    

    此认证过程实质上主要是调用了UserDetailsService里面被重写的loadUserByUsername(参数为用户名)方法进行查询数据库中是否存在此用户的处理,若无(即登录的用户不存在)则抛出异常,若存在则返回UserDetails对象:

    • 根据用户名查询出用户对象

    • 获取用权限并把权限放入GrantedAuthority里面

      List<GrantedAuthority> authorities = new ArrayList<>();
              //把权限放入GrantedAuthority里面
              for (String permission : admin.getPermissions()){
                  GrantedAuthority authority=new SimpleGrantedAuthority(permission);
                  authorities.add(authority);
              }
      
    • 自定义的含有id的认证结果当事人对象

      AdminDetails adminDetails = new AdminDetails(
                      admin.getId(),
                      admin.getUsername(),
                      admin.getPassword(),
                      admin.getEnable() == 1,
                      authorities);
      

    图解初识认证流程:

    img

    • 返回adminDetails对象(AdminDetails extends User,User implements UserDetails)

    返回的adminDetails对象实质上就是authenticateResult认证结果里面的当事人信息(认证结果包含Principal当事人、Credentials凭证、Authorities权限清单;其他俩是框架生成的);

    //从认证结果里得到adminDetails当事人信息
    Object principal = authenticateResult.getPrincipal();
    AdminDetails adminDetails = (AdminDetails) principal;
    

    得到当事人信息接下来就需要生成jwt令牌:

    • 获取其用户名、id、权限(需要传换成json字符串)

    • jwt包含三大部分:头部信息Header、载荷Payload、签名Signature,其中载荷存放用户有效信息

    • 把有效信息存放到载荷里:

       Map<String, Object> claims = new HashMap<>();
              // claims.put();放入到jwt载荷有效信息里面
              claims.put("username", username);
              claims.put("id",id);
              claims.put("authoritiesJsonString", authoritiesJsonString);
      
    • 生成jwt

       String jwt = Jwts.builder()
                      // Header
                      .setHeaderParam("alg", "HS256")
                      .setHeaderParam("typ", "JWT")
                      // Payload
                      .setClaims(claims)
                      // Signature
                      .setExpiration(date)
           					//生成签名时需要的secretKey,再被解析时会用到
                      .signWith(SignatureAlgorithm.HS256, secretKey)
                      .compact();
      

    登录业务处理完毕响应jwt到客户端。

  3. 客户端(以浏览器为例)接收到jwt后存放到localstorage中,此后发送请求都要携带jwt。当请求发送到服务器时,会先经过框架的过滤器组件(因为组件类会在项目启动时就被实例化到容器中以备框架随时调用),在我们自定义的过滤器中对jwt进行过滤。

    (过滤器需要做的: 解析JWT、创建认证对象、将认证对象存入到SecurityContext上下文):

    • 获取请求携带的jwt并判断其是否有效

      String jwt = request.getHeader("Authorization");
      //StringUtils.hasText()调用String工具类判断字符串是否为null或empty
      if (!StringUtils.hasText(jwt) || jwt.length() < JWT_MIN_LENGTH) {
                 // 对于无效的JWT,直接放行,交由后续的组件进行处理
                 log.debug("获取到的JWT被视为无效,当前过滤器将放行……");
                 filterChain.doFilter(request, response);
                 return;
             }
      
    • 解析获取到的有效的jwt有效信息(载荷)

      claims = Jwts.parser()
                          .setSigningKey(secretKey)
                          .parseClaimsJws(jwt)
                          .getBody();
      
    • 在解析时可能会出现各种异常:ExpiredJwtException、MalformedJwtException、SignatureException、Throwable等,需要对其catch处理,例:

       catch (ExpiredJwtException e) {
              log.debug("解析JWT时出现ExpiredJwtException");
              String message = "登录信息已过期,请重新登录!";
              JsonResult<Void> jsonResult = 		JsonResult.fail(ServiceCode.ERR_JWT_Expired, message);
              String jsonResultString = JSON.toJSONString(jsonResult);
              PrintWriter writer = response.getWriter();
              writer.println(jsonResultString);
              return;
          } 
      
    • 获取jwt中的用户信息(id、用户名、权限)

      String username = claims.get("username", String.class);
      Long id = claims.get("id",Long.class);
      String authoritiesJsonString=claims.get("authoritiesJsonString",String.class);
      
    • 接下来需要创建认证对象

       LoginPincipal loginPincipal = new LoginPincipal(id,username);
              Authentication authentication
                      = new UsernamePasswordAuthenticationToken(
                      loginPincipal, null, authorities);
      
    • 再将认证对象Authentication存入到SecurityContext上下文里,之后交给框架自动去查

      SecurityContextHolder.getContext().setAuthentication(authentication);
      
    • 过滤器链继续向后传递,放行

      filterChain.doFilter(request,response);
      
    • 请求接下来到spring security框架自己的过滤器里,框架会检查上下文里面是否存在有效当事人信息,有的话放行,请求正常访问控制器,无的话拦截请求(若此次请求不携带jwt,仍有可能通过认证,原因是此前有携带有效jwt的请求通过后,使得security上下文里面存在之前的缓存)。

      为实现以上操作,要让咱自定义的过滤器在框架之前被执行,需要把jwt过滤器添加到spring security框架的过滤器链中,并且在其之前:

       http.addFilterBefore(
           jwtAuthorizationFilter, UsernamePasswordAuthenticationFilter.class);
      
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值