微服务个人项目1--搭建jwt授权框架全过程

一。依赖引入

jdk8只需引入jjwt--0.9.1即可

但高版本jdk如我的jdk11排除了某些依赖,需要额外导入

<!-- JWT依赖 -->
 <dependency>
 <groupId>io.jsonwebtoken</groupId>
 <artifactId>jjwt</artifactId>
 <version>0.9.1</version>
 </dependency>

 <!-- JWT相关依赖,jdk1.8以上版本还需引⼊以下依赖 -->
 <dependency>
 <groupId>javax.xml.bind</groupId>
 <artifactId>jaxb-api</artifactId>
 <version>2.3.1</version>
 </dependency>
 <dependency>
 <groupId>com.sun.xml.bind</groupId>
 <artifactId>jaxb-impl</artifactId>
 <version>3.0.2</version>
 </dependency>
 <dependency>
 <groupId>com.sun.xml.bind</groupId>
 <artifactId>jaxb-core</artifactId>
 <version>3.0.2</version>
 </dependency>
 <dependency>
 <groupId>javax.activation</groupId>
 <artifactId>activation</artifactId>
 <version>1.1.1</version>
 </dependency>

二。编写jwt工具类

package com.base.utils.common;

import io.jsonwebtoken.*;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.UUID;

public class JwtUtils {
    private static String sign = "356yrhtgbwq2";
    private static long time = 3600000;

//生成jwtToken
    public static String createJwt(long id){
        JwtBuilder builder = Jwts.builder();
        String jwtToken = builder
                .setHeaderParam("typ","JWT")
                .setHeaderParam("alg","HS256")
                .claim("userId",id)
                .setSubject("userToken")
                .setExpiration(new Date(System.currentTimeMillis() + time))
                .setId(UUID.randomUUID().toString())
                .signWith(SignatureAlgorithm.HS256,sign)
                .compact();
        return jwtToken;
    }
//解析token返回claim 后面可用claims.get("userId")获取存入的claim
    public static Claims parseJwt(HttpServletRequest request){
        String token = request.getHeader("Authorization");
        JwtParser jwtParser = Jwts.parser();
        Jws<Claims> claimsJws = jwtParser.setSigningKey(sign).parseClaimsJws(token);
        Claims claims = claimsJws.getBody();
        return claims;
    }
//用户检验解析
    public static Claims parseJwt1(String s){

        JwtParser jwtParser = Jwts.parser();
        Jws<Claims> claimsJws = jwtParser.setSigningKey(sign).parseClaimsJws(s);
        Claims claims = claimsJws.getBody();
        return claims;
    }
//验证token是否有效
    public static Boolean checkToken(HttpServletRequest request){
        String token = request.getHeader("Authorization");
        if(token == null){
            return false;
        }
//如果可以解析成功,证明未过期且有效
        try {
            Jws<Claims> claimsJws = Jwts.parser().setSigningKey(sign).parseClaimsJws(token);
        }
        catch (Exception e){
            return false;
        }

        return true;
    }
}

三。修改swagger配置,使页面有授权按钮选项

package com.base.swagger;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.*;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spi.service.contexts.SecurityContext;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

    @Bean
    public Docket buildDocket() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(buildApiInfo())
                .select()
                // 要扫描的API(Controller)基础包
                .apis(RequestHandlerSelectors.basePackage("com.base"))
                .paths(PathSelectors.any())
                .build()
                .securitySchemes(securitySchemes())
                .securityContexts(Collections.singletonList(securityContext()));
    }

    private ApiInfo buildApiInfo() {
        Contact contact = new Contact("Lcx微服务架构","","");
        return new ApiInfoBuilder()
                .title("GameClub-平台管理API文档")
                .description("GameClub后台api")
                .contact(contact)
                .version("1.0.0").build();
    }
    private List<SecurityScheme> securitySchemes() {
        List<SecurityScheme> apiKeyList= new ArrayList<>();
        //注意,这里应对应登录token鉴权对应的k-v
        //这里的Authorization是request-header中的键名,所以checkToken时也要用此键名获取token
        apiKeyList.add(new ApiKey("Authorization", "Authorization", "header"));
        return apiKeyList;
    }

    /**
     * 这里设置 swagger2 认证的安全上下文
     */
    private SecurityContext securityContext() {
        return SecurityContext.builder()
                .securityReferences(Collections.singletonList(new SecurityReference("Authorization", scopes())))
                .forPaths(PathSelectors.any())
                .build();
    }

    /**
     * 这里是写允许认证的scope
     */
    private AuthorizationScope[] scopes() {
        return new AuthorizationScope[]{
                new AuthorizationScope("web", "All scope is trusted!")
        };
    }

}

四。自定义Authorize与NoAuthorize接口

Authorize用在方法或类上,标记需要验证是否授权

NoAuthorize表示该方法不需要授权

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Authorize {
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface NoAuthorize {
}

五。TokenInterceptor拦截器

拦截标有自定义注解的所有请求,根据注解类型返回响应结果

package com.base.Interceptor;

import com.base.exception.MyLoginException;
import com.base.myInterface.Authorize;
import com.base.myInterface.NoAuthorize;
import com.base.utils.common.JwtUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;

public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws MyLoginException {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 检查方法级别的 @NoAuthorize 注解
            if (handlerMethod.getMethod().isAnnotationPresent(NoAuthorize.class)) {
                // 方法上存在 @NoAuthorize 注解,允许请求继续执行
                return true;
            }

            // 检查类级别的注解
            Class<?> targetClass = handlerMethod.getBeanType();
            if (targetClass.isAnnotationPresent(Authorize.class)) {
                // 执行类级别的授权逻辑,如果有的话
                Authorize classAnnotation = targetClass.getAnnotation(Authorize.class);
                if (classAnnotation != null) {
                    // 在这里添加类级别授权逻辑
                    return checkAuth(request);
                }

                // 检查方法上是否有Authorized注解
                Method method = handlerMethod.getMethod();
                if (method.isAnnotationPresent(Authorize.class)) {
                    return checkAuth(request);
                }
            }
        }
        return true;
    }
    private boolean checkAuth(HttpServletRequest request) throws MyLoginException{
       
        Boolean res = JwtUtils.checkToken(request);
        if (res == false) {
            throw new MyLoginException("未授权的用户认证");
        }
        return res;
    }
}

当未授权时,通过抛出自定义异常得到返回结果

public class MyLoginException extends RuntimeException{
    public MyLoginException(String message) {
        super(message);
    }
}
@ControllerAdvice  //控制器增强类
@Slf4j
public class ExceptionCatch {
    /**
     * 处理不可控异常
     * @param e
     * @return
     */
    @ExceptionHandler(Exception.class)
    @ResponseBody
    public ResponseResult exception(Exception e){
        e.printStackTrace();
        log.error("catch exception:{}",e.getMessage());
        Date date = new Date();
        return ResponseResult.error("服务器内部发生错误" + date);
    }
    @ExceptionHandler(MyLoginException.class)
    @ResponseBody
    public ResponseResult exception(MyLoginException e){
        e.printStackTrace();
        log.error("catch exception:{}",e.getMessage());
        return ResponseResult.errorResult(AppHttpCodeEnum.USER_NOLOGIN);
//这里我的返回结果是json串{code:401 msg:"为授权认证的用户"}
    }

}

添加webconfig类使拦截器生效,指明拦截路径为/**所有请求

当拦截到所有请求后,拦截器内部判断是否拦截/通过

package com.base.config;

import com.base.Interceptor.TokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        InterceptorRegistration registration = registry.addInterceptor(new TokenInterceptor());
        registration.addPathPatterns("/**");

    }
}

六。定义获取claims的方法,在控制器中使用claims获取信息

线程安全的上下文共享:RequestContextHolder 使用线程局部变量来存储请求上下文信息,确保在多线程环境下每个线程访问的上下文信息都是独立的,避免了线程安全问题。(每个web请求都是单独的线程)

@Service
public class AuthenticationFacadeImpl implements AuthenticationFacade {
    private final JwtUtils jwtUtils;

    public AuthenticationFacadeImpl(JwtUtils jwtUtils) {
        this.jwtUtils = jwtUtils;
    }

    @Override
    public Claims getUserClaims() {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        Claims claims = JwtUtils.parseJwt(request);
        return claims;
    }

}

在需要使用claims的地方注入使用就可以获取到

@RestController
@RequestMapping("/api/user")
@Api(value = "user",tags = "user")
@Authorize
public class UserController {
    @Autowired
    private IUserService userService;
    private final AuthenticationFacade authenticationFacade;

    public UserController(AuthenticationFacade authenticationFacade) {
        this.authenticationFacade = authenticationFacade;
    }

    @PostMapping("/login")
    @ApiOperation("login测试")
    @NoAuthorize
    public ResponseResult<String> login(@RequestBody loginDto dto){
        return userService.login(dto);
    }

    @PostMapping("/parseToken")
    @ApiOperation("parseToken测试")
    public ResponseResult getId(){
        Claims claims = authenticationFacade.getUserClaims();
        Object userId = claims.get("userId");
        return ResponseResult.success(userId);
    }
}

后面在公共字段填充部分也用到这里的方法,获取claims中存入的username

至此,从基础的引入jwt生成token-->解析token-->将token通过swagger写入request header-->授权认证接口 的功能就全部完成啦

相关推荐

  1. vue项目---1.基础的框架

    2023-12-09 19:38:01       45 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2023-12-09 19:38:01       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2023-12-09 19:38:01       101 阅读
  3. 在Django里面运行非项目文件

    2023-12-09 19:38:01       82 阅读
  4. Python语言-面向对象

    2023-12-09 19:38:01       91 阅读

热门阅读

  1. 大一C语言作业 12.8

    2023-12-09 19:38:01       52 阅读
  2. 如何配置git的ssh密钥

    2023-12-09 19:38:01       60 阅读
  3. 决策树 ID3 算法

    2023-12-09 19:38:01       48 阅读
  4. day4 移出倒数第n个节点

    2023-12-09 19:38:01       57 阅读
  5. ArchLinux下载链接

    2023-12-09 19:38:01       65 阅读
  6. Python中一些有趣的例题

    2023-12-09 19:38:01       51 阅读
  7. SELinux refpolicy详解(13)

    2023-12-09 19:38:01       63 阅读
  8. 车载蓝牙音乐流程简单分析

    2023-12-09 19:38:01       60 阅读
  9. 装箱 Box 数据类型

    2023-12-09 19:38:01       55 阅读