一。依赖引入
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-->授权认证接口 的功能就全部完成啦