上一篇文章我们集成了服务网关Spring Cloud Gateway,所有的服务请求都可以通过Gateway访问。那我们就可在服务网关这一层对用户的请求进行鉴权,判断是否可以访问路由的API接口。
接下来我们开始增加鉴权,这里我们使用jwt
1. 创建授权服务module
按照第二篇文章创建一个module,起名为app-auth。
2. 修改service-auth的pom文件
<properties> <java.version>1.8</java.version> <spring-cloud.version>Hoxton.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!-- redis --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> <version> 2.6.1</version> </dependency> <!-- jwt鉴权 --> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.0</version> </dependency> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> </dependencies> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement>
3. 修改启动类
@SpringBootApplication@EnableEurekaClient // 开启eureka客户端模式public class AppAuthApplication { public static void main(String[] args) { SpringApplication.run(AppAuthApplication.class, args); }}
4. 增加jwt工具类
这里我们将生成的token和refreshToken都存储到redis中,所以我们也引入了redis依赖。为了保证一定的安全性,我们的token和refreshToken都是有时效的,这里我们将这两个值的有效时间放到配置文件中,以便灵活修改。
在生成token的时候,我们将账号和客户端类型传入,客户端类型主要分为,网页浏览器端,手机app端和桌面程序端。三端都有各自的token。
// 生成token和refreshTokenpublic Map<String, String> getToken(String phone, String type){ //生成refreshToken String refreshToken = UUID.randomUUID().toString().replaceAll("-",""); String token = this.buildJWT(phone, type); String key = SecureUtil.md5(type + phone); //向hash中放入数值 stringRedisTemplate.opsForHash().put(key,"token", token); stringRedisTemplate.opsForHash().put(key,"refreshToken", refreshToken); //设置key过期时间 stringRedisTemplate.expire(key, refreshTokenExpireTime, TimeUnit.MILLISECONDS); Map<String , String> map = new HashMap<>(2); map.put("token", token); map.put("refreshToken", refreshToken); return map; }
5. 编写登录接口
@GetMapping(value = "/login") public ResponseDTO<Map<String, String>> login(@RequestParam("account") String account, @RequestParam("password") String password, HttpServletRequest request){ String clientType = request.getHeader("clientType"); if(StrUtil.isEmpty(clientType)) { return ResponseDTO.error().setMsg("clientType不能为空"); } //账号密码校验 if(StrUtil.isNotEmpty(account) && StrUtil.isNotEmpty(password)){ if ("admin".equals(account) && "123456a".equals(password)){ Map<String, String> map = tokenUtil.getToken(account, clientType); return ResponseDTO.ok().setData(map); }else { return ResponseDTO.error().setMsg("账号或密码错误"); } }else { return ResponseDTO.error().setMsg("账号或密码错误"); } }
此处暂时只展示登录方法,刷新token方法和退出登录方法请查看git仓库。
5. server-gateway增加Filter,用于权限验证
此处代码较多,只展示部分核心代码
创建RequestGlobalFilter类,增加@Component注解,实现GlobalFilter, Ordered这两个接口;实现public Mono
由于这个Filter拦截所有请求,所以我们要提前定义一下不拦截的接口,例如登录接口和刷新token的接口。我们在server-gateway的配置文件中增加ignore.urls,把不拦截的接口用逗号分隔的方式放到这个配置上,例如:
ignore.urls=/auth/login,/authrefresh
对于需要拦截的url,我们需要从header中拿到token和clientType,然后通过jwt工具进行校验该token是否有效,如果无效返回401错误信息。
private String verifyJWT(String token, String clientType) { String userPhone; try { Algorithm algorithm = Algorithm.HMAC256(clientType); JWTVerifier verifier = JWT.require(algorithm) .withIssuer("userPhone") .build(); DecodedJWT jwt = verifier.verify(token); userPhone = jwt.getClaim("phone").asString(); } catch (JWTVerificationException e) { return ""; } return userPhone; }
在此处我们需要引入jwt依赖
<dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.0</version> </dependency>
6. 增加app-auth路由配置
# 路由到app-auth服务spring.cloud.gateway.routes[2].id=app-authspring.cloud.gateway.routes[2].uri=lb://APP-AUTHspring.cloud.gateway.routes[2].predicates[0]=Path=/auth/**spring.cloud.gateway.routes[2].filters[0]=StripPrefix=1
7. 启动服务测试请求
分别启动server-eureka,server-gateway,app-auth,app-order,app-storage
先访问http://127.0.0.1:10010/order/v1/order/placeOrder/commit会提示“认证失败”
然后在请求中增加请求头Authorization和clientType,继续访问会提示“认证失败,请重新登录”。
请求登录接口http://127.0.0.1:10010/auth/login?account=admin&password=123456a ,并添加请求头clientType,得到token和refreshToken
把上一步得到的token和使用clientType放到第一步的请求中,再次请求,会返回true。
由于本文涉及的代码偏多,请大家移步至git仓库查看更多代码。 https://gitee.com/hedavid/spring-cloud-example
本文分享自微信公众号 - 自增程序员(javaipp)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。