自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Spring Boot自定義注解+參數(shù)解析器,構(gòu)建靈活的安全認(rèn)證機(jī)制

開發(fā) 前端
通過Spring AOP 實(shí)現(xiàn)權(quán)限認(rèn)證,是構(gòu)建安全Java應(yīng)用的一種高效方式。Spring AOP允許開發(fā)者在方法執(zhí)行的前、后或拋出異常時(shí),自動執(zhí)行特定的邏輯,而無需修改原有的業(yè)務(wù)代碼。

環(huán)境:SpringBoot3.4.0

1. 簡介

通過Spring AOP 實(shí)現(xiàn)權(quán)限認(rèn)證,是構(gòu)建安全Java應(yīng)用的一種高效方式。Spring AOP允許開發(fā)者在方法執(zhí)行的前、后或拋出異常時(shí),自動執(zhí)行特定的邏輯,而無需修改原有的業(yè)務(wù)代碼。在權(quán)限認(rèn)證的場景下,開發(fā)者可以利用AOP的攔截機(jī)制,在方法執(zhí)行前檢查用戶是否具備相應(yīng)的權(quán)限。

本篇文章將通過AOP + 自定義注解實(shí)現(xiàn)權(quán)限的認(rèn)證,自定義參數(shù)解析器便捷的獲取當(dāng)前登錄人的信息。

本篇文章將會應(yīng)用到如下的技術(shù)點(diǎn):

  • AOP
    攔截需要權(quán)限校驗(yàn)的方法。
  • 攔截器(Interceptor)
    攔截請求解析token,最后將其保存到當(dāng)前的上下文中。
  • 自定義參數(shù)解析器(HandlerMethodArgumentResolver)
    在Controller接口參數(shù)中通過自定義的注解快捷獲取當(dāng)前登錄人的信息。
  • SpEL表達(dá)式
    以表達(dá)式的方式獲取當(dāng)前登錄人的具體數(shù)據(jù)。

2. 實(shí)戰(zhàn)案例

2.1 自定義注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface AuthUser {
  /**從當(dāng)前登錄信息中獲取用戶信息;支持SpEL表達(dá)式*/
  String value() default "" ;
}

權(quán)限注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAuthorize {
  
  /**權(quán)限*/
  String[] value() default {} ;
  /**權(quán)限邏輯驗(yàn)證; 全部匹配還是部分*/
  Logical logic() default Logical.AND;
  
  public enum Logical {
    AND, OR ;
  }
}

2.2 基礎(chǔ)類定義

解析生成Token工具類

@Component
public class JwtUtil {
  @Value("${jwt.secret}")
  private String secret ;
  @Value("${jwt.expiration}")
  private Long expiration ;
  
  private final ObjectMapper objectMapper ;
  public JwtUtil(ObjectMapper objectMapper) {
    this.objectMapper = new ObjectMapper() ;
  }
  /**生成JWT令牌*/
  public String generateToken(User user) {
    String json = null ;
    try {
      json = this.objectMapper.writeValueAsString(user);
    } catch (JsonProcessingException e) {
      throw new RuntimeException(e) ;
    }
    return createToken(json) ;
  }
  private String createToken(String payload) {
    return Jwts.builder()
        .claims().add("info", payload).subject("pack_xxxooo")
        .issuedAt(new Date(System.currentTimeMillis()))
        .expiration(new Date(System.currentTimeMillis() + expiration * 1000)).and()
        .signWith(Keys.hmacShaKeyFor(secret.getBytes())).compact() ;
  }
  /**從令牌中獲取用戶名*/
  public User getUser(String token) {
    Object ret = getClaimFromToken(token, claims -> claims.get("info")) ;
    User value = null;
    try {
      value = new ObjectMapper().readValue(ret.toString(), User.class);
    } catch (Exception e) {
      throw new RuntimeException(e) ;
    }
    return value ;
  }
  /**從令牌中獲取聲明*/
  private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
    final Claims claims = (Claims) Jwts.parser()
        .verifyWith(Keys.hmacShaKeyFor(secret.getBytes()))
        .build()
        .parse(token)
        .getPayload();
    return claimsResolver.apply(claims);
  }
}

全局異常處理

@RestControllerAdvice
public class GlobalControllerAdvice {
  @ExceptionHandler(AuthException.class)
  public ResponseEntity<Object> authException(AuthException e) {
    return ResponseEntity.ok(Map.of("code", -1, "message", e.getMessage())) ;
  }
}

安全上下文對象

public class SecurityContext {
  private static final ThreadLocal<User> context = new ThreadLocal<>();
  public static void setUser(User user) {
    context.set(user);
  }
  public static User getUser() {
    return context.get();
  }
  public static void clear() {
    context.remove();
  }
}

登錄成功的用戶信息將保存到當(dāng)前線程上下文中。

2.3 核心組件實(shí)現(xiàn)

請求攔截器定義,所有被攔截的請求都會進(jìn)行token的解析。

@Component
public class AuthInterceptor implements HandlerInterceptor {
  private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-._~+/]+=*)$",
      Pattern.CASE_INSENSITIVE);
  
  private final JwtUtil jwtUtil ;
  private final ObjectMapper objectMapper ;
  public AuthInterceptor(JwtUtil jwtUtil, ObjectMapper objectMapper) {
    this.jwtUtil = jwtUtil;
    this.objectMapper = objectMapper ;
  }
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    String authorization = request.getHeader(HttpHeaders.AUTHORIZATION) ;
    if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
      error(response, "缺失token") ;
      return false ;
    }
    Matcher matcher = authorizationPattern.matcher(authorization);
    if (!matcher.matches()) {
      error(response, "無效token") ;
      return false ;
    }
    String token = matcher.group("token") ;
    User user = parseToken(token);
    if (user == null) {
      error(response, "登錄無效,重新登錄") ;
      return false ;
    }
    SecurityContext.setUser(user);
    return true;
  }
  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    SecurityContext.clear();
  }
  private void error(HttpServletResponse response, String message) throws Exception {
    response.setContentType("application/json;charset=utf-8") ;
    response.getWriter().print(this.objectMapper.writeValueAsString(Map.of("code", -1, "message", message))) ;
  }
  
  /**通過token,解析獲取User信息*/
  private User parseToken(String token) {
    return this.jwtUtil.getUser(token) ;
  }
}

如果認(rèn)證沒有通過,那么將直接在該攔截器中返回錯(cuò)誤信息。

切面定義

該切面中將攔截所有使用了@PreAuthorize注解的方法,進(jìn)行權(quán)限的驗(yàn)證。

@Aspect
@Component
public class PermissionAspect {
  @Around("@annotation(authority)")
  public Object checkPermission(ProceedingJoinPoint joinPoint, PreAuthorize authority) throws Throwable {
    User user = SecurityContext.getUser();
    if (user == null) {
      throw new AuthException("請登錄") ;
    }
    Set<String> requiredPerms = Set.of(authority.value()) ;
    Set<String> userPerms = user.getPermissions() ;
    boolean hasPermission = checkLogic(requiredPerms, userPerms, authority.logic());
    if (!hasPermission) {
      throw new AuthException("沒有權(quán)限");
    }
    return joinPoint.proceed();
  }
  private boolean checkLogic(Set<String> required, Set<String> has, Logical logic) {
    if (Logical.AND == logic) {
      return has.containsAll(required);
    } else {
      return !Collections.disjoint(required, has);
    }
  }
}

權(quán)限驗(yàn)證中,會根據(jù)配置的logic屬性邏輯(AND,OR)進(jìn)行不同的驗(yàn)證;要么全部匹配,要么有任何一個(gè)匹配的都算成功。

完成以上核心組件后,接下來就需要進(jìn)行配置:

@Configuration
public class WebConfig implements WebMvcConfigurer {
  private final AuthInterceptor authInterceptor ;
  public WebConfig(AuthInterceptor authInterceptor) {
    this.authInterceptor = authInterceptor;
  }
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(this.authInterceptor).addPathPatterns("/users/**") ;
  }
}

接下來,我們就可以進(jìn)行測試

2.4 測試

@RestController
public class LoginController {
  
  private final JwtUtil jwtUtil ;
  public LoginController(JwtUtil jwtUtil) {
    this.jwtUtil = jwtUtil;
  }
  @GetMapping("/login")
  public ResponseEntity<Object> login(String username) {
    User user = new User() ;
    user.setId(1L) ;
    user.setName("pack") ;
    user.setIdCard("100819883") ;
    user.setUsername(username) ;
    user.setPermissions(Set.of("C", "R", "U", "D")) ;
    return ResponseEntity.ok(Map.of("token", this.jwtUtil.generateToken(user))) ;
  }
}

簡單的登錄接口。

需要權(quán)限認(rèn)證的接口定義如下:

@RestController
@RequestMapping("/users")
public class UserController {
  @GetMapping("")
  public ResponseEntity<Object> query() {
    return ResponseEntity.ok("查詢用戶") ;
  }
  
  @GetMapping("/create")
  @PreAuthorize(value = {"C", "X"}, logic = Logical.OR)
  public ResponseEntity<Object> create() {
    return ResponseEntity.ok("創(chuàng)建成功") ;
  }
}

通過postman測試。

圖片

獲取token成功。

訪問沒有攜帶token的接口/users

圖片

header中輸入token后

圖片

調(diào)用成功。

2.5 自定義參數(shù)解析器

public class AuthUserArgumentResolver implements HandlerMethodArgumentResolver {
  private final ExpressionParser parser = new SpelExpressionParser();
  
  @Override
  public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(AuthUser.class) ;
  }
  @Override
  public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
      NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
    AuthUser authUser = parameter.getParameterAnnotation(AuthUser.class) ;
    String value = authUser.value() ;
    if (!StringUtils.hasLength(value)) {
      User user = SecurityContext.getUser() ;
      if (parameter.getParameterType().isAssignableFrom(user.getClass())) {
        return user ;
      }
      return null ; 
    }
    EvaluationContext context = createEvaluationContext() ;
    return parser.parseExpression(value).getValue(context, parameter.getParameterType()) ;
  }
  
  private EvaluationContext createEvaluationContext() {
    StandardEvaluationContext context = new StandardEvaluationContext(SecurityContext.getUser()) ;
    return context ;
  }
}

該參數(shù)解析器將會處理Controller接口參數(shù)上有@AuthUser注解的參數(shù)。

注冊上面的解析器

@Configuration
public class WebConfig implements WebMvcConfigurer {  
  @Override
  public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    resolvers.add(new AuthUserArgumentResolver()) ;
  }
}

下面就可以通過如下接口進(jìn)行測試了。

@GetMapping("/info")
public ResponseEntity<Object> info(@AuthUser User user) {
  return ResponseEntity.ok(user) ;
}
@GetMapping("/name")
public ResponseEntity<Object> info(@AuthUser("name") String name) {
  return ResponseEntity.ok(name) ;
}

上面2個(gè)接口測試結(jié)果如下:

圖片圖片

圖片圖片

成功獲取登錄信息。

責(zé)任編輯:武曉燕 來源: Springboot實(shí)戰(zhàn)案例錦集
相關(guān)推薦

2025-03-13 07:33:46

Spring項(xiàng)目開發(fā)

2021-03-16 10:39:29

SpringBoot參數(shù)解析器

2022-07-11 10:37:41

MapPart集合

2024-10-14 17:18:27

2013-01-14 11:40:50

IBMdW

2022-05-11 10:45:21

SpringMVC框架Map

2022-11-10 07:53:54

Spring參數(shù)校驗(yàn)

2022-01-06 06:23:49

Swagger參數(shù)解析器

2023-10-07 14:49:45

2024-02-22 08:06:45

JSON策略解析器

2022-03-07 07:33:24

Spring自定義機(jī)制線程池

2020-11-25 11:20:44

Spring注解Java

2017-08-03 17:00:54

Springmvc任務(wù)執(zhí)行器

2018-07-06 15:58:34

SpringSchemaJava

2023-10-23 08:18:50

掃描SpringBean

2011-03-17 09:45:01

Spring

2022-06-27 08:16:34

JSON格式序列化

2023-03-30 16:16:00

Java自定義注解開發(fā)

2021-03-18 10:56:59

SpringMVC參數(shù)解析器

2023-03-31 07:17:16

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號