Spring boot +swagger+token
电脑版发表于:2021/11/3 15:23
文章借鉴于://https://www.jianshu.com/p/6e5ee9dd5a61
一、pom的相关依赖
<!--版本控制2.5.5--> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.5.5</version> <!--版本控制2.5.5--> <relativePath/> </parent> <!--web--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--lombok--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--swagger--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <!--JWT--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.0</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version> </dependency> <dependency> <groupId>commons-codec</groupId> <artifactId>commons-codec</artifactId> <version>1.9</version> </dependency>
二、swagger 的配置
导入相关的包
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.List;
@Configuration @EnableSwagger2 public class SwaggerConfig2 { //https://www.jianshu.com/p/6e5ee9dd5a61 @Bean public Docket api(){ return new Docket(DocumentationType.SWAGGER_2). useDefaultResponseMessages(false) .select() .apis(RequestHandlerSelectors.any()) .paths(PathSelectors.regex("^(?!auth).*$")) .build() .securitySchemes(securitySchemes()) .securityContexts(securityContexts());// } private List<ApiKey> securitySchemes() { return newArrayList( new ApiKey("Authorization", "Authorization","header")); } private List<SecurityContext> securityContexts() { return newArrayList( SecurityContext.builder() .securityReferences(defaultAuth()) .forPaths(PathSelectors.regex("^(?!auth).*$"))//含有auth路径的接口可以没有令牌 .build() ); } List<SecurityReference> defaultAuth() { AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything"); AuthorizationScope[] authorizationScopes = new AuthorizationScope[1]; authorizationScopes[0] = authorizationScope; return newArrayList( new SecurityReference("Authorization", authorizationScopes)); } }
1、配完后会出现这个
2、下图是这个按钮的内容
三、配置toke令牌的加密、解密
1、类Constant的字段设置
public final static String TOKEN_HEADER_STRING="Authorization"; public final static String TOKEN_PERFIX="Bearer"; public final static String CURRENT_USER="name"; public final static String BASE_PATH_PERFIX="/api/practice/v1";
2、toke的配置
导入的包
import com.example.My1101.pojo.Constant; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.apache.commons.codec.binary.Base64; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.util.Calendar; import java.util.Date; import java.util.HashMap;
//SpringBoot中组件类需要使用@Component进行组件注册才能使用(不知道该类属于什么层就可以用它,如:服务处、Dao等) @Component public class TokenUtils { /** * 由字符串生成加密key * @return */ public static SecretKey generalKey() { String stringKey = "thisisasecretkey"; //随机写的 // 本地的密码解码 byte[] encodedKey = Base64.decodeBase64(stringKey); // 根据给定的字节数组使用AES加密算法构造一个密钥 SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } /** * 生成JWT * @return */ public static String createToken(String userName) { //设置JWT的header HashMap<String, Object> map = new HashMap<String, Object>(); //Head map.put("alg", "HS256"); map.put("typ", "jwt"); //Payload map.put("username", userName); //根据userName生成jwt //设置JWT的过期时间 Calendar now = Calendar.getInstance(); now.add(Calendar.MINUTE, 20);//当前时间+20mins Date expireDate = now.getTime();//Calendar转Date //设置JWT生效时间 Date nowDate = new Date();//系统当前时间 SecretKey key = generalKey(); //密钥(服务端专有,面向客户端隐藏) JwtBuilder jwtBuilder = Jwts.builder() .setClaims(map) .setExpiration(expireDate) .setNotBefore(nowDate) //Signature .signWith(SignatureAlgorithm.HS256, key);//设置签发算法和密钥 return Constant.TOKEN_PERFIX + jwtBuilder.compact();//jwt前面一般会加上Bearer } /** * 解析token * @param token * @return */ public static Claims parseToken(String token) { SecretKey key = generalKey(); try { Claims claims = Jwts.parser() .setSigningKey(key) .parseClaimsJws(token.replace("Bearer", "")).getBody();//TOKEN_PERFIX = "Bearer" return claims; } catch (Exception e) { throw new IllegalStateException("Invalid token." + e.getMessage()); } } }
四、拦截的具体逻辑
导入的包
import com.example.My1101.pojo.Constant; import io.jsonwebtoken.Claims; import io.swagger.models.HttpMethod; import org.springframework.stereotype.Component; import org.springframework.web.servlet.HandlerInterceptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.util.Date;
@Component //拦截器也是一个组件,需要加@Component注解进行组件注册 public class AuthInterceptor implements HandlerInterceptor { //声明一个static final的Logger对象 private static final Logger logger = LoggerFactory.getLogger(AuthInterceptor.class); /** * 预处理回调方法,实现处理器的预处理 * 返回值:true表示继续流程;false表示流程中断,不会继续调用其他的拦截器或处理器 */ @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // return HandlerInterceptor.super.preHandle(request, response, handler); System.out.println("开始拦截........."); //设置response的编码格式 response.setContentType("text/html;charset=utf-8"); //获取请求的url String url = request.getServletPath().toString(); System.out.println("url:" + url); //判断放行的url if(url.contains("/user/login")){ return true; } if(url.contains("/user/test2")){ return true; } if(url.contains("/swagger-resource")){ return true; } if(url.contains("/v2/api-docs")){ return true; } //第一次请求放行(因为它是探路用的) if (HttpMethod.OPTIONS.toString().equals(request.getMethod())) { System.out.println("OPTIONS请求,放行"); return true; } //获取request中的参数token String token = request.getHeader(Constant.TOKEN_HEADER_STRING); //如果token为空或不存在 if(token==null || "".equals(token) || !token.startsWith(Constant.TOKEN_PERFIX)){ logger.info("{} : Unknown token", request.getServletPath()); //将结果打印返回到前端 response.getWriter().print("The resource requires authentication, which was not supplied with the request"); return false;//拦截成功 } //解析token Claims claims = TokenUtils.parseToken(token); String userName = (String)claims.get("username"); Date expireTime = claims.getExpiration(); //如果token的username不存在 ,具体的逻辑自己发挥 if(userName.equals(null)){ logger.info("{} : token user not found", request.getServletPath()); response.getWriter().print("ERROR Permission denied"); return false; } //如果token过期 if(expireTime.before(new Date())){ logger.info("{} : token expired", request.getServletPath()); response.getWriter().print("The token expired, please apply for a new one"); return false; } //token匹配成功,放行 request.setAttribute(Constant.CURRENT_USER, userName); System.out.println("放行..........."); return true; } /** * 后处理回调方法,实现处理器(controller)的后处理,但在渲染视图之前 * 此时我们可以通过modelAndView对模型数据进行处理或对视图进行处理 */ @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { // HandlerInterceptor.super.postHandle(request, response, handler, modelAndView); } /** * 整个请求处理完毕回调方法,即在视图渲染完毕时回调, * 如性能监控中我们可以在此记录结束时间并输出消耗时间, * 还可以进行一些资源清理,类似于try-catch-finally中的finally, * 但仅调用处理器执行链中 */ @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { // HandlerInterceptor.super.afterCompletion(request, response, handler, ex); } }
五、配置mvc 实现拦截
导入的包
import com.example.My1101.pojo.Constant; import com.example.My1101.utils.AuthInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Autowired AuthInterceptor authInterceptor; /** * 添加拦截器 * addPathPatterns 用于添加拦截规则,/**表示拦截所有请求 * excludePathPatterns 排除拦截 * @param registry */ @Override public void addInterceptors(InterceptorRegistry registry) { //注册AuthInterceptor拦截器 registry.addInterceptor(authInterceptor).addPathPatterns(Constant.BASE_PATH_PERFIX +"/**") //拦截/api/practice/v1/user/test .excludePathPatterns(Constant.BASE_PATH_PERFIX + "/user/login"); //放行/api/practice/v1/user/login } // 这个方法是用来配置静态资源的,比如html,js,css,等等 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { } }
六、controller层的应用
@Api("用户操作接口") @RestController("user") @RequestMapping("/api/practice/v1" + "/user") //配置网址前缀 BASE_PATH_PERFIX = "/api/practice/v1" public class UserController { @Autowired UserService userService; @ApiOperation(value = "登录", notes = "通过用户名和密码登录系统") //配置方法操作在swagger中的名和曾 notes为备注 @PostMapping(value = "login") public String login(@RequestBody User user) { if (user == null) { return "用户名或密码为空!"; } User loginUser = userService.login(user); if (loginUser == null) { return "用户名或密码错误!"; } //生成token(用户拿着这个token+请求返回给服务端,服务端匹配token,如果匹配上了则处理用户发来的请求,如果匹配不上则用户验证失败不处理请求) String jwt = TokenUtils.createToken(user.getUserName()); HashMap<String, String> map = new HashMap<>(); map.put("token", jwt); return ResponseEntity.ok("loginUser").toString() + map; } @ApiOperation(value = "测试", notes = "通过用户名和密码测试token") @PostMapping(value = "test/{toke}") public String testToken(User user,String toke){ return user.toString(); } @ApiOperation(value = "测试2", notes = "通过用户名和密码测试token") @PostMapping(value = "test2") public String testToken2(User user){ return user.toString(); } }