Spring Boot+Jwt+AOP+自定义注解实现接口的权限控制

前言

之前在项目中通过自定义拦截器+自定义注解进行权限校验,拦截器代码过于臃肿!!!

于是想到了使用面向切面的方法!!

AOP的概念

        Aspect Oreinted Programming 面向切面编程,通过预编译方式或者运行时动态代理的方式,实现程序功能的统一管理和维护的一种技术(AOP是一种思想,并不依赖于某个框架或者编程语言实现)。

为什么使用AOP?

        利用AOP可以对 业务逻辑的各部分进行隔离,使程序员更加专注于业务核心逻辑,从而降低代码的耦合度,提供程序可重用性,提高开发的效率(主要应用场景:权限控制,日志记录,性能统计,事务管理,异常处理)

AOP相关的术语

1.目标对象
        指需要被 增强的对象,Spring Aop通过代理增强实现 (target)

2.连接点(JoinPoint)
        指的是被切面拦截到的点,在Spring当中指的是具体的方法.

3.切入点(pointcut)
        表示一组连接点,通过正则表达式,通配符,aspectj切点表达式来进行定义和集中,定义了通知(advice)将要发生的地方. 简单的说:切入点就是我们对 哪些连接点 进行拦截的 定义。

4.通知:(advice)
        通知指的是 拦截到连接点之后 要做的事情就是通知(功能增强) 按照分类:前置通知,后置通知,异常通知,环绕通知,最终通知。 前置通知:在目标对象的业务逻辑功能执行之前,发生. 通常用于:日志记录 权限控制

        后置返回通知:发生目标功能对象的业务逻辑正常执行完之后,发生. 通常用于:对方法返回值进行处理.
        后置异常通知:在目标对象的业务逻辑发生异常时发生 通常用于:项目的异常日志.
        后置:不管目标对象的业务逻辑是否发异常,都会被执行. 通常用于:资源释放.
        环绕通知:(使用最多),在目标对象的业务逻辑执行之前和之后发生。 通常用于:性能监控,事务管理.
        顺序:环绕前置­­>普通前置­­>目标方法执行­­>环绕正常结束/出现异常­­>环绕后置­­>普通后置­­>普通返回或者异常。

5.切面: 切面指的是切入点(多个)和通知(多个)的结合

具体实现

第一步:引入aop pom依赖

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

第二步:编写一个自定义注解

package com.tg.admin.common.annotation;

import java.lang.annotation.*;

/**
 * @Program: admin
 * @ClassName RequiresPermission 
 * @Author: liutao
 * @Description:
 * @Create: 2023-03-20 08:11
 * @Version 1.0
 **/

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequiresPermission {
     String roles() default " ";
     String permissions() default " ";
}

第三步:编写一个切面

package com.tg.admin.common.aop;

import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.tg.admin.common.Constants;
import com.tg.admin.common.annotation.RequiresPermission;
import com.tg.admin.common.exception.ServiceException;
import com.tg.admin.entity.User;
import com.tg.admin.entity.vo.BtnVo;
import com.tg.admin.service.UserService;
import com.tg.admin.utils.MenuUtil;
import com.tg.admin.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @Program: tg-admin
 * @ClassName PermissionChech
 * @Author: liutao
 * @Description: 角色、权限校验切面
 * @Create: 2023-06-20 18:18
 * @Version 1.0
 **/
@Slf4j
@Aspect
@Component
public class PermissionCheck {
    @Autowired
    private MenuUtil menuUtil;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private UserService userService;


    /***
     * @MethodName: permissionCheckPointCut
     * @description: 定义一个切点
     * @Author: LiuTao
     * @UpdateTime: 2023/6/20 19:34
     **/
    @Pointcut("@annotation(com.tg.admin.common.annotation.RequiresPermission)")
    public void permissionCheckPointCut() {

    }

    /***
     * @MethodName: check
     * @description: 环绕通知
     * @Author: LiuTao
     * @Param: [pjp]
     * @UpdateTime: 2023/6/20 19:34
     * @Return: java.lang.Object
     * @Throw: Throwable
     **/
    @Around("permissionCheckPointCut()")
    public Object check(ProceedingJoinPoint pjp) throws Throwable {
        // 获取请求对象
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        // 记录日志
        log.info("===============系统操作日志===============");
        Signature signature = pjp.getSignature();
        // 请求的类
        String className = pjp.getTarget().getClass().getName();
        String methodName = signature.getName();
        log.info("请求方式:{}", request.getMethod());
        log.info("请求ip:{}", request.getRemoteAddr());
        log.info("请求类方法:{}", signature);
        log.info("请求参数:{}", Arrays.toString(pjp.getArgs()));
        // 权限注解校验
        MethodSignature handlerMethod = (MethodSignature) signature;
        Method method = handlerMethod.getMethod();
        if (method.isAnnotationPresent(RequiresPermission.class)) {
            RequiresPermission auth = method.getAnnotation(RequiresPermission.class);
            String roles = auth.roles();
            String permissions = auth.permissions();

            String token = request.getHeader("token");
            // 认证
            if (StrUtil.isBlank(token)) {
                throw new ServiceException(Constants.CODE_401, "请登录!!!");
            }
            String id;
            try {
                id = JWT.decode(token).getAudience().get(0);
            } catch (JWTDecodeException jwtDecodeException) {
                throw new ServiceException(Constants.CODE_401, "token验证失败,请重新登录");
            }
            User user = userService.getById(id);
            // 校验角色
            if (StrUtil.isNotBlank(roles)) {
                if (!Arrays.asList(roles.split(",")).contains(user.getRole())) {
                    throw new ServiceException(Constants.CODE_403, "当前角色权限不足");
                }
            }
            // 校验权限
            if (StrUtil.isNotBlank(permissions)) {
                List<String> userPermissions = menuUtil
                        .getPermissions(user.getRole())
                        .stream()
                        .map(BtnVo::getPermission)
                        .collect(Collectors.toList());
                if (!new HashSet<>(userPermissions).containsAll(Arrays.asList(permissions.split(",")))) {
                    throw new ServiceException(Constants.CODE_401, "无权限访问资源");
                }
            }
        }
        return pjp.proceed();
    }

}

第四步:在web层使用自定义注解

    @RequiresPermission(roles = "ROLE_ADMIN")
    @ApiOperation(value = "查询所有用户", httpMethod = "GET")
    @GetMapping
    public Result<User> findAll() {
        List<User> list = userService.findAll();
        log.info("{}", list);
        return Result.success(list);
    }

    @RequiresPermission(permissions = "user:list:page")
    @ApiOperation(value = "分页查询所有用户信息", httpMethod = "GET")
    @GetMapping("/page")
    public Result<User> findPage(@RequestParam Integer pageNum,
                                 @RequestParam Integer pageSize,
                                 @RequestParam String username,
                                 @RequestParam String email,
                                 @RequestParam String address) {
        IPage<User> page = new Page<>(pageNum, pageSize);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        if (!"".equals(username)) {
            queryWrapper.like("username", username);
        }
        if (!"".equals(email)) {
            queryWrapper.like("email", email);
        }
        if (!"".equals(address)) {
            queryWrapper.like("address", address);
        }
        User currentUser = JwtUtil.getCurrentUser();
        System.out.println("当前用户------" + currentUser);
        return Result.success(userService.page(page, queryWrapper));
    }

效果图

使用ROLE_USER 用户访问

 

 然后我们用ROLE_ADMIN 用户访问

 

 

 

 结尾

最后完美撒花!!!

  • 4
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
使用Spring BootJwt进行身份验证可以保护您的应用程序免受未经授权的访问。下面是使用Spring BootJwt进行身份验证的步骤: 1. 添加相关依赖 在 `pom.xml` 文件中添加以下依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> ``` 2. 创建JwtUtils类 创建一个 `JwtUtils` 类,用于生成和解析Jwt令牌。以下是一个示例实现: ```java import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Component; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Function; @Component public class JwtUtils { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return createToken(claims, userDetails.getUsername()); } private String createToken(Map<String, Object> claims, String subject) { Date now = new Date(); Date expirationDate = new Date(now.getTime() + expiration); return Jwts.builder() .setClaims(claims) .setSubject(subject) .setIssuedAt(now) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public boolean validateToken(String token, UserDetails userDetails) { final String username = extractUsername(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } public boolean isTokenExpired(String token) { final Date expirationDate = extractExpiration(token); return expirationDate.before(new Date()); } public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } public Date extractExpiration(String token) { return extractClaim(token, Claims::getExpiration); } public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) { final Claims claims = extractAllClaims(token); return claimsResolver.apply(claims); } private Claims extractAllClaims(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } } ``` 3. 创建JwtRequestFilter 创建一个 `JwtRequestFilter` 类,用于拦截所有安全请求并验证Jwt令牌。以下是一个示例实现: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @Component public class JwtRequestFilter extends OncePerRequestFilter { @Autowired private UserDetailsService userDetailsService; @Autowired private JwtUtils jwtUtils; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String authorizationHeader = request.getHeader("Authorization"); String username = null; String jwt = null; if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) { jwt = authorizationHeader.substring(7); username = jwtUtils.extractUsername(jwt); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtUtils.validateToken(jwt, userDetails)) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } } filterChain.doFilter(request, response); } } ``` 4. 创建JwtAuthenticationController 创建一个 `JwtAuthenticationController` 类,用于处理用户身份验证请求并返回Jwt令牌。以下是一个示例实现: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.web.bind.annotation.*; @RestController public class JwtAuthenticationController { @Autowired private AuthenticationManager authenticationManager; @Autowired private UserDetailsService userDetailsService; @Autowired private JwtUtils jwtUtils; @Autowired private PasswordEncoder passwordEncoder; @PostMapping("/authenticate") public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest jwtRequest) throws Exception { try { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(jwtRequest.getUsername(), jwtRequest.getPassword())); } catch (Exception e) { throw new Exception("Incorrect username or password", e); } final UserDetails userDetails = userDetailsService.loadUserByUsername(jwtRequest.getUsername()); final String token = jwtUtils.generateToken(userDetails); return ResponseEntity.ok(new JwtResponse(token)); } @PostMapping("/register") public ResponseEntity<?> saveUser(@RequestBody UserDto userDto) { User user = new User(userDto.getUsername(), passwordEncoder.encode(userDto.getPassword())); userDetailsService.save(user); return ResponseEntity.ok(user); } } ``` 5. 配置WebSecurityConfigurerAdapter 最后,我们需要配置WebSecurityConfigurerAdapter以启用基于Jwt的身份验证。以下是一个示例实现: ```java import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtRequestFilter jwtRequestFilter; @Autowired private UserDetailsService userDetailsService; @Autowired private PasswordEncoder passwordEncoder; @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests().antMatchers("/authenticate", "/register").permitAll() .anyRequest().authenticated() .and().sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS); http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } ``` 现在,您的Spring Boot应用程序已经配置使用Jwt进行身份验证。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

晚上睡不着!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值