接上篇《微服务实战spring cloud alibaba(十)为spring cloud gateway增加跨域filter》,我们这里添加了一个filter用于解决跨域,今天我们来个日常中最常用的token校验。
本章主要演示的功能是:
1、user-service添加一个登录接口,使用jwt进行登录
2、user-service添加一个获取账户信息的接口。
3、gateway-service添加一个AuthorizeFilter,用于拦截request header里面的token,并解析jwt。
然后我们阐述下具体的实践过程
1、客户端通过访问127.0.0.1:9000/user/login,gateway不会对此接口进行拦截,会直接转发到userservice 服务进行登录。
2、userservice服务里面模拟用户登录成功,(这里我们没有连接数据库,因此直接是模拟的状态)
3、客户端登录成功后会返回:
{ "errorCode": 200, "errorMsg": "请求成功", "data": { "userId": 1234567890123456789, "userName": "zhangsan", "token": "eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI3MzA1NGZjZi1lOTYzLTRmODYtOGUwNS1jZDQwNTY5NmJjODAiLCJpYXQiOjE2NDk5MTk2NzYsInN1YiI6IntcInVzZXJJZFwiOjEyMzQ1Njc4OTAxMjM0NTY3ODksXCJ1c2VyTmFtZVwiOlwiemhhbmdzYW5cIn0iLCJleHAiOjE2NDk5MjMyNzZ9.HaWQpKa0h5OIYrGViIIuKuAFBnpl5vYfFHmw3Rd_3CQ" } }
4、客户端访问127.0.0.1:9000/user/getAccountId接口,在header参数里面不带token,访问的时候会直接被gateway拦截掉,返回未登录
{ "errorCode": 402, "errorMsg": "用户未登录" }
5、客户端访问127.0.0.1:9000/user/getAccountId接口,在header参数里面带上token,gateway会进行校验,成功后会把这个服务转发给uservice相应。客户端会看到如下情况:
{ "errorCode": 200, "errorMsg": "请求成功", "data": "用户信息1234567890123456789获取完成" }
上诉大致的流程我们就讲解完了。话不多说,直接上代码:
一、在u-common项目里面创建JWTUtils.java文件
package com.umall.common.utils; import java.util.Base64; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.UUID; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import org.springframework.stereotype.Component; import com.alibaba.fastjson.JSON; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @Component public class JWTUtils { // 加密 解密时的密钥 用来生成key public static final String JWT_KEY = "b0baee9d279d34fa1dfd71aadb908c3f"; /** * 生成加密后的秘钥 secretKey * * @return */ public static SecretKey generalKey() { byte[] encodedKey = Base64.getDecoder().decode(JWTUtils.JWT_KEY); SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES"); return key; } public static String createJWT(String id, String subject, long ttlMillis) { SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; // 指定签名的时候使用的签名算法,也就是header那部分,jjwt已经将这部分内容封装好了。 long nowMillis = System.currentTimeMillis();// 生成JWT的时间 Date now = new Date(nowMillis); SecretKey key = generalKey();// 生成签名的时候使用的秘钥secret,这个方法本地封装了的,一般可以从本地配置文件中读取,切记这个秘钥不能外露哦。它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, // 那就意味着客户端是可以自我签发jwt了。 JwtBuilder builder = Jwts.builder() // 这里其实就是new一个JwtBuilder,设置jwt的body // .setClaims(claims) //如果有私有声明,一定要先设置这个自己创建的私有的声明,这个是给builder的claim赋值,一旦写在标准的声明赋值之后,就是覆盖了那些标准的声明的 .setId(id) // 设置jti(JWT ID):是JWT的唯一标识,根据业务需要,这个可以设置为一个不重复的值,主要用来作为一次性token,从而回避重放攻击。 .setIssuedAt(now) // iat: jwt的签发时间 .setSubject(subject) // sub(Subject):代表这个JWT的主体,即它的所有人,这个是一个json格式的字符串,可以存放什么userid,roldid之类的,作为什么用户的唯一标志。 .signWith(signatureAlgorithm, key);// 设置签名使用的签名算法和签名使用的秘钥 if (ttlMillis >= 0) { long expMillis = nowMillis + ttlMillis; Date exp = new Date(expMillis); builder.setExpiration(exp); // 设置过期时间 } return builder.compact(); // 就开始压缩为xxxxxxxxxxxxxx.xxxxxxxxxxxxxxx.xxxxxxxxxxxxx这样的jwt } public static Claims parseJWT(String jwt) { SecretKey key = generalKey(); // 签名秘钥,和生成的签名的秘钥一模一样 Claims claims = Jwts.parser() // 得到DefaultJwtParser .setSigningKey(key) // 设置签名的秘钥 .parseClaimsJws(jwt).getBody();// 设置需要解析的jwt return claims; } }
二、在user-service 项目里面创建UserAccountController.java文件
package com.umall.user.controller; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.bcrypt.BCrypt; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.alibaba.fastjson.JSON; import com.umall.common.enu.HttpResponseStatus; import com.umall.common.model.BaseResponse; import com.umall.common.model.UserAccountPoJo; import com.umall.common.utils.JWTUtils; import com.umall.user.request.UserAccountRequestBody; import com.umall.user.response.UserAccountResponseBody; import lombok.extern.slf4j.Slf4j; @RestController @Slf4j public class UserAccountController { @Autowired JWTUtils jwtutils; /** * 这里只是模拟用户登录,假设用户输入的用户名和密码是正确的 * * @param user * @return */ @RequestMapping(path = "/login") public BaseResponse loginByUserNameAndPassword(@RequestBody UserAccountRequestBody user) { // 从数据库中获取到密码,这里我们不连接数据库,直接模拟即可DB.getPasswordByUserName(user.getUserName()); String password = BCrypt.hashpw(user.getPassword(), BCrypt.gensalt()); log.info("存储到数据库的密码是:{}", password); boolean checkSuccess = BCrypt.checkpw(user.getPassword(), password); if (checkSuccess) { UserAccountPoJo build = UserAccountPoJo.builder().userId(1234567890123456789L).userName(user.getUserName()) .build(); String token = jwtutils.createJWT(UUID.randomUUID().toString(), JSON.toJSONString(build), 3600L * 1000); UserAccountResponseBody body = UserAccountResponseBody.builder().userId(1234567890123456789L) .userName(user.getUserName()).token(token).build(); // 然后token放到redis中存储,这里略过 return BaseResponse.success(body); } else { return BaseResponse.fail(HttpResponseStatus.LOGINERROR); } } @RequestMapping(path = "/getAccountId") public BaseResponse getAccountInfo(@RequestHeader("userId") Long userId) { return BaseResponse.success("用户信息" + userId + "获取完成"); } }
三、在gateway项目里面创建AuthorizeFilter.java文件
package org.gateway.service.gateway.filter; import org.apache.commons.lang.StringUtils; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.Ordered; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.http.server.reactive.ServerHttpResponse; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.google.common.io.BaseEncoding; import com.umall.common.enu.HttpResponseStatus; import com.umall.common.model.BaseResponse; import com.umall.common.model.BaseResponse.BaseResponseBuilder; import com.umall.common.model.UserAccountPoJo; import com.umall.common.utils.JWTUtils; import io.jsonwebtoken.Claims; import lombok.extern.slf4j.Slf4j; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; /** * 鉴权过滤器 验证token */ @Component @Slf4j public class AuthorizeFilter implements GlobalFilter, Ordered { private static final String AUTHORIZE_TOKEN = "token"; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { // 1. 获取请求 ServerHttpRequest request = exchange.getRequest(); // 2. 则获取响应 ServerHttpResponse response = exchange.getResponse(); // 3. 如果是登录请求则放行 if (request.getURI().getPath().contains("/user/login")) { return chain.filter(exchange); } // 4. 获取请求头 HttpHeaders headers = request.getHeaders(); // 5. 请求头中获取令牌 String token = headers.getFirst(AUTHORIZE_TOKEN); // 6. 判断请求头中是否有令牌 if (StringUtils.isEmpty(token)) { return this.userNotLoginAction(response); } // 9. 如果请求头中有令牌则解析令牌 try { String subject = JWTUtils.parseJWT(token).getSubject(); log.info("解析出来的用户信息是:{}", subject); UserAccountPoJo poJo = JSON.parseObject(subject, UserAccountPoJo.class); ServerHttpRequest newRequest = exchange.getRequest().mutate() .header("userId", String.valueOf(poJo.getUserId())).build(); return chain.filter(exchange.mutate().request(newRequest).build()); } catch (Exception e) { log.error(e.getMessage(), e); // 10. 解析jwt令牌出错, 说明令牌过期或者伪造等不合法情况出现 return this.userNotLoginAction(response); } // 12. 放行 } private Mono<Void> userNotLoginAction(ServerHttpResponse response) { BaseResponse resultJson = BaseResponse.fail(HttpResponseStatus.USERNOTLOGIN); byte[] bytes = JSONObject.toJSONBytes(resultJson); response.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_VALUE); DataBuffer buffer = response.bufferFactory().wrap(bytes); return response.writeWith(Flux.just(buffer)); } @Override public int getOrder() { return 0; } }
好了,按照上面的流程进行测试即可。
最后咱们补充下说明这代码里面几个比较重要的知识点:
1、gateway里面如果需要返回错误的信息,并且返回json,需要使用这样子的代码:
BaseResponse resultJson = BaseResponse.fail(HttpResponseStatus.USERNOTLOGIN); byte[] bytes = JSONObject.toJSONBytes(resultJson); response.getHeaders().add("Content-Type", MediaType.APPLICATION_JSON_VALUE); DataBuffer buffer = response.bufferFactory().wrap(bytes); return response.writeWith(Flux.just(buffer));
2、在gateway里面我们模拟了jwt解密,把json解析出来,然后使用网关向下传递userid,这样子在其他的微服务service里面就不用再去解析一遍用户信息,直接拿解析出来的userid使用即可。
3、在gateway里面如果要自定义向下传递参数的话,需要使用如下这样的代码:
UserAccountPoJo poJo = JSON.parseObject(subject, UserAccountPoJo.class); ServerHttpRequest newRequest = exchange.getRequest().mutate() .header("userId", String.valueOf(poJo.getUserId())).build(); return chain.filter(exchange.mutate().request(newRequest).build());
4、gateway一般我们在header头里面转发自定义参数,因此在服务里面,取值的话,需要从header里面取,所以需要用到如下这样的代码:
@RequestHeader("userId") Long userId
好了,今天就介绍到这里,总结一下:
1、实现了jwt的登录,加密,解密 2、实现了gateway进行拦截。 3、实现了gateway转发自定义参数
本项目代码下载:点击下载
还没有评论,来说两句吧...