最近在搭建阿里巴巴的微服务框架,这次是引入jwt实现鉴权,主要包括以下功能
(1)登录。接收用户名,密码,校验密码是否正确,若正确则返回token(jwt生成),若错误返回提示信息。
(2)请求网关时校验token。
(3)登出。接收token,将指定token置为失效的状态。
(4)续签。对前端服务部署服务器发过来的请求对过期的token直接返回新的token,并提示更换token。
功能实现涉及两个服务,网关服务,鉴权服务。
一、在鉴权服务中
pom关键配置:
<properties> <jjwt.version>0.11.5</jjwt.version> </properties> <dependencies> <!--springboot基本场景启动器--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--引入JWT--> <dependency> <groupId>com.auth0</groupId> <artifactId>java-jwt</artifactId> <version>3.10.0</version> <exclusions></exclusions> </dependency> <!--引入JWT--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>${jjwt.version}</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>${jjwt.version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>${jjwt.version}</version> <scope>runtime</scope> </dependency> <!--引入redis--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <!--处理token过期时间--> <dependency> <groupId>joda-time</groupId> <artifactId>joda-time</artifactId> <version>2.9.6</version> </dependency> </dependencies>
配置文件关键配置:
auth: expireMinutes: 20 #过期分钟数 key: 0123456789_0123456789_0123456789 #token生成
#省略网关,redis等其他常规设置
工具类(关键代码)
package com.example.auth.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import com.auth0.jwt.interfaces.Claim; import com.auth0.jwt.interfaces.DecodedJWT; import com.example.auth.constant.JWTConstants; import com.example.auth.model.AuthUser; import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Map; /** * jwt工具类 */ @Component public class JwtUtils { @Value("${auth.expireMinutes:30}") private int expireMinutes; /** * 生成密钥 * * @return 生成密钥 */ Algorithm getAlgorithm() { return Algorithm.HMAC256(JWTConstants.JWT_KEY); } /** * 生成token * * @param userId 用户id * @param userName 用户名 * @param userRole 用户的角色 * @return token jwtToken */ public String generateToken(String userId, String userName, String userRole) { return JWT.create() .withClaim(JWTConstants.JWT_KEY_USER_NAME, userName) .withClaim(JWTConstants.JWT_KEY_ROLE, userRole) .withClaim(JWTConstants.JWT_KEY_ID, userId) .withExpiresAt(DateTime.now().plusSeconds(expireMinutes).toDate()) .sign(getAlgorithm()); } /** * 根据用户信息生成用户 * * @param user 用户信息 * @return token jwtT oken */ public String generateToken(AuthUser user) { return generateToken(user.getId(), user.getUsername(), user.getRole()); } /** * 解码token * * @param token jwtToken * @return 用户信息 */ public AuthUser decode(String token) { AuthUser authUser = new AuthUser(); DecodedJWT decodedJWT = JWT.require(getAlgorithm()).build().verify(token); Map<String, Claim> jwt = decodedJWT.getClaims(); String userName = jwt.get(JWTConstants.JWT_KEY_USER_NAME).asString(); String userId = jwt.get(JWTConstants.JWT_KEY_ID).asString(); authUser.setId(userId); authUser.setUsername(userName); return authUser; } }
登录接口
package com.example.auth.controller; import com.alibaba.nacos.common.model.RestResult; import com.example.auth.model.AuthUser; import com.example.auth.model.LoginReturn; import com.example.auth.utils.JwtUtils; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.annotation.Resource; @RestController @RequestMapping("auth") @RefreshScope public class AuthController { @Resource JwtUtils jwtUtils; @PostMapping("/login") public RestResult<String> login(@RequestBody AuthUser user) { //模拟数据库查询的用户 AuthUser tagetUser = new AuthUser(); tagetUser.setPassword("123456"); //密码校验(demo密码未加密) if (!tagetUser.getPassword().equals(user.getPassword())) { return new RestResult<>(-1, "用户名与密码不正确"); } String token = jwtUtils.generateToken(user); return new RestResult(1, "认证成功", new LoginReturn(user,token)); } }
其他实体类
package com.example.auth.model; import lombok.*; /** * @author songyan */ @Data @NoArgsConstructor @AllArgsConstructor @Getter @Setter public class AuthUser { /** * 主键 */ private String id; /** * 用户名 */ private String username; /** * 密码 */ private String password; /** * 角色 */ private String role; }AuthUser LoginReturn
package com.example.auth.constant; public class JWTConstants { public static final String JWT_REQUEST_HEADER_KEY = "Authorization"; public static final String JWT_KEY = "0123456789_0123456789_0123456789"; public static final String JWT_KEY_ID = "user_id"; public static final String JWT_KEY_USER_NAME = "user_name"; public static final String JWT_KEY_ROLE = "user_role"; public static final String JWT_REQUEST_KEY_ID = JWT_KEY_ID; public static final String JWT_REQUEST_KEY_USER_NAME = JWT_KEY_USER_NAME; public static final String JWT_REQUEST_KEY_ROLE = JWT_KEY_ROLE; }JWTConstants
package com.example.auth.configuration; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfiguration { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }RedisConfiguration
package com.example.auth.utils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.CollectionUtils; import java.util.Collection; import java.util.concurrent.TimeUnit; @Component public class RedisUtil { @Autowired private RedisTemplate<String, Object> redisTemplate; /** * 指定缓存失效时间 * * @param key 键 * @param time 时间(秒) * @return */ public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 根据key 获取过期时间 * * @param key 键 不能为null * @return 时间(秒) 返回0代表为永久有效 */ public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } /** * 判断key是否存在 * * @param key 键 * @return true 存在 false不存在 */ public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } /** * 删除缓存 * * @param key 可以传一个值 或多个 */ public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key)); } } } // ============================String============================= /** * 普通缓存获取 * * @param key 键 * @return 值 */ public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } /** * 普通缓存放入 * * @param key 键 * @param value 值 * @return true成功 false失败 */ public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 普通缓存放入并设置时间 * * @param key 键 * @param value 值 * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 * @return true成功 false 失败 */ public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * 递增 * * @param key 键 * @param delta 要增加几(大于0) */ public long incr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } return redisTemplate.opsForValue().increment(key, delta); } /** * 递减 * * @param key 键 * @param delta 要减少几(小于0) */ public long decr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递减因子必须大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } }RedisUtil
二、网关服务
依赖
<!--依赖权限模块--> <dependency> <groupId>com.example</groupId> <artifactId>auth</artifactId> <version>0.0.1-SNAPSHOT</version> <scope>compile</scope> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </exclusion> </exclusions> </dependency>
JwtTokenFilter 全局过滤器
package com.luoxun.gateway.filter; import com.alibaba.cloud.commons.lang.StringUtils; import com.alibaba.fastjson.JSONObject; import com.alibaba.nacos.common.model.RestResult; import com.auth0.jwt.exceptions.TokenExpiredException; import com.example.auth.constant.JWTConstants; import com.example.auth.model.AuthUser; import com.example.auth.utils.JwtUtils; import com.example.auth.utils.RedisUtil; import com.luoxun.gateway.constant.AuthConstant; import com.luoxun.gateway.constant.AuthReturnMessage; import com.luoxun.gateway.constant.BEANOrder; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.cloud.gateway.filter.GlobalFilter; import org.springframework.core.annotation.Order; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpStatus; 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 reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import javax.annotation.Resource; import java.nio.charset.StandardCharsets; /** * JwtToken 过滤器 */ @Order(BEANOrder.JWT_TOKEN_FILTER) @Component @Slf4j public class JwtTokenFilter implements GlobalFilter { @Value("${auth.tt}") private String skipAuthUrls; @Value("${auth.overduceTime}") private int overdueTime; @Value("${auth.webIp}") private String webIp; @Resource private JwtUtils jwtUtils; @Resource private RedisUtil redisUtil; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String url = exchange.getRequest().getURI().getPath(); //跳过不需要验证的路径 if (skipAuthUrls != null && url.equals(skipAuthUrls)) { //继续路由 return chain.filter(exchange); } //获取token String token = exchange.getRequest().getHeaders().getFirst(JWTConstants.JWT_REQUEST_HEADER_KEY); ServerHttpResponse resp = exchange.getResponse(); if (StringUtils.isBlank(token)) { return authError(resp, AuthReturnMessage.AUTH_EXCEPTION_NULL); } else { try { //校验token并解析token AuthUser loginUser = jwtUtils. decode(token); //登出 if (url.equals(AuthConstant.URL_LOGOUT)) { redisUtil.set(AuthConstant.PREFIX_TOKEN_LAPSED + loginUser.getUsername(), token, overdueTime); return authSuccess(resp, AuthReturnMessage.AUTH_SUCCESS_LOGOUT); } //判断token是否已弃用 String sig = redisUtil.get(AuthConstant.PREFIX_TOKEN_LAPSED + loginUser.getUsername()) + ""; if (token.equals(sig)) { return authError(resp, AuthReturnMessage.AUTH_EXCEPTION_LAPSED); } //继续路由 return chain.filter(exchange); } catch (TokenExpiredException tokenExpiredException) { //处理过期的token return expiredToken(tokenExpiredException, exchange, url, token); } catch (Exception e) { log.error(e.getMessage(), e); return authError(resp, AuthReturnMessage.AUTH_EXCEPTION_FAIL); } } } /** * token过期的处理 * * @param tokenExpiredException * @param exchange * @param url * @param token * @return */ private Mono<Void> expiredToken(TokenExpiredException tokenExpiredException, ServerWebExchange exchange, String url, String token) { log.error(tokenExpiredException.getMessage(), tokenExpiredException); String userName = exchange.getRequest().getHeaders().getFirst(JWTConstants.JWT_REQUEST_KEY_USER_NAME); String userId = exchange.getRequest().getHeaders().getFirst(JWTConstants.JWT_REQUEST_KEY_ID); String userRole = exchange.getRequest().getHeaders().getFirst(JWTConstants.JWT_REQUEST_KEY_ROLE); ServerHttpRequest request = exchange.getRequest(); String ip = request.getURI().getHost(); ServerHttpResponse resp = exchange.getResponse(); if (webIp.equals(ip)) { //登出 if (url.equals(AuthConstant.URL_LOGOUT)) { return authError(resp, AuthReturnMessage.AUTH_EXCEPTION_EXPIRED); } //判断token是否已弃用 String sig = redisUtil.get(AuthConstant.PREFIX_TOKEN_LAPSED + userName) + ""; if (token.equals(sig)) { return authError(resp, AuthReturnMessage.AUTH_EXCEPTION_EXPIRED); } //续签 String newToken = jwtUtils.generateToken(userId, userName, userRole); return authSuccess(resp, new RestResult(HttpStatus.OK.value(), AuthReturnMessage.AUTH_SUCCESS_TIP_REFRESH, newToken)); } return authError(resp, AuthReturnMessage.AUTH_EXCEPTION_EXPIRED); } /** * 认证错误输出 * * @param resp 响应对象 * @param mess 错误信息 * @return */ private Mono<Void> authError(ServerHttpResponse resp, String mess) { resp.setStatusCode(HttpStatus.UNAUTHORIZED); resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); DataBuffer buffer = resp.bufferFactory().wrap(mess.getBytes(StandardCharsets.UTF_8)); return resp.writeWith(Flux.just(buffer)); } private Mono<Void> authSuccess(ServerHttpResponse resp, RestResult mess) { resp.setStatusCode(HttpStatus.OK); resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); DataBuffer buffer = resp.bufferFactory().wrap(JSONObject.toJSONString(mess).getBytes(StandardCharsets.UTF_8)); return resp.writeWith(Flux.just(buffer)); } private Mono<Void> authSuccess(ServerHttpResponse resp, String mess) { resp.setStatusCode(HttpStatus.OK); resp.getHeaders().add("Content-Type", "application/json;charset=UTF-8"); DataBuffer buffer = resp.bufferFactory().wrap(mess.getBytes(StandardCharsets.UTF_8)); return resp.writeWith(Flux.just(buffer)); } }
其他
package com.luoxun.gateway.configuration; import com.example.auth.utils.JwtUtils; import com.example.auth.utils.RedisUtil; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class CommonConfiguration { /** * 注入jwt工具类 * * @return jwt工具类 */ @Bean public JwtUtils jwtUtils() { return new JwtUtils(); } @Bean public RedisUtil redisUtil() { return new RedisUtil(); } }CommonConfiguration
package com.luoxun.gateway.configuration; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.PropertyAccessor; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfiguration { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL); jackson2JsonRedisSerializer.setObjectMapper(om); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); // key采用String的序列化方式 template.setKeySerializer(stringRedisSerializer); // hash的key也采用String的序列化方式 template.setHashKeySerializer(stringRedisSerializer); // value序列化方式采用jackson template.setValueSerializer(jackson2JsonRedisSerializer); // hash的value序列化方式采用jackson template.setHashValueSerializer(jackson2JsonRedisSerializer); template.afterPropertiesSet(); return template; } }RedisConfiguration
package com.luoxun.gateway.constant; public class AuthConstant { public final static String PREFIX_TOKEN_LAPSED = "token_lapsed_"; public final static String URL_LOGOUT = "/auth/loginOut"; }AuthConstant
package com.luoxun.gateway.constant; public class AuthReturnMessage { public final static String AUTH_EXCEPTION_FAIL = "认证失败"; public final static String AUTH_EXCEPTION_LAPSED = "认证失效"; public final static String AUTH_EXCEPTION_EXPIRED = "认证过期"; public final static String AUTH_EXCEPTION_NULL = "认证信息不可为空"; public final static String AUTH_SUCCESS_LOGOUT = "已登出"; public final static String AUTH_SUCCESS_TIP_REFRESH = "请更换新token"; }AuthReturnMessage
package com.luoxun.gateway.constant; public class BEANOrder { public static final int JWT_TOKEN_FILTER = -100; }BEANOrder
标签:return,String,spring,jwt,alibaba,key,import,com,public From: https://www.cnblogs.com/excellencesy/p/16942109.html