diff --git a/sop-gateway/pom.xml b/sop-gateway/pom.xml index 385e8a82..dc4ad9b2 100644 --- a/sop-gateway/pom.xml +++ b/sop-gateway/pom.xml @@ -67,6 +67,16 @@ 1.18.4 provided + + + org.springframework.boot + spring-boot-starter-data-redis + + + + org.apache.commons + commons-lang3 + diff --git a/sop-gateway/src/main/java/com/gitee/sop/gateway/config/MyConfig.java b/sop-gateway/src/main/java/com/gitee/sop/gateway/config/MyConfig.java index 9d32bba9..d9d0475a 100644 --- a/sop-gateway/src/main/java/com/gitee/sop/gateway/config/MyConfig.java +++ b/sop-gateway/src/main/java/com/gitee/sop/gateway/config/MyConfig.java @@ -1,23 +1,48 @@ package com.gitee.sop.gateway.config; +import com.alibaba.fastjson.JSONObject; import com.gitee.sop.gatewaycommon.bean.ApiConfig; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.StringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import javax.annotation.PostConstruct; +@Slf4j @Component public class MyConfig { + @Autowired + private StringRedisTemplate redisTemplate; + private static String APP_AUTH_TOKEN_PREFIX = "com.gitee.sop.oauth2_access_token:"; + + public static String getAccessTokenKey(String accessToken) { + return APP_AUTH_TOKEN_PREFIX + accessToken; + } + + @PostConstruct public void after() { ApiConfig.getInstance().setTokenValidator(apiParam -> { // 获取客户端传递过来的token - String token = apiParam.fetchAccessToken(); - return !StringUtils.isBlank(token); - // TODO: 校验token有效性,可以从redis中读取 + try { + String token = apiParam.fetchAccessToken(); + if (StringUtils.isBlank(token)) { + return Boolean.FALSE; + } + String json = redisTemplate.opsForValue().get(getAccessTokenKey(token)); + if(StringUtils.isEmpty(json)) { + return Boolean.FALSE; + } - // 返回true表示这个token真实、有效 + JSONObject userInfo = JSONObject.parseObject(json); + return userInfo != null; + } catch (Exception e) { + log.error("redisError", e); + return Boolean.FALSE; + } }); } } \ No newline at end of file diff --git a/sop-gateway/src/main/resources/application-dev.properties b/sop-gateway/src/main/resources/application-dev.properties index 0ba27c4c..e0420b8f 100644 --- a/sop-gateway/src/main/resources/application-dev.properties +++ b/sop-gateway/src/main/resources/application-dev.properties @@ -13,4 +13,14 @@ register.url=127.0.0.1:8848 # 如果需要传输大文本可以把值设置高一点 spring.codec.max-in-memory-size=262144 -logging.level.com.gitee=debug \ No newline at end of file +logging.level.com.gitee=debug + +spring.redis.host=localhost +spring.redis.port=6379 +spring.redis.jedis.pool.min-idle=1 +spring.redis.jedis.pool.max-idle=5 +spring.redis.jedis.pool.max-wait=-1 +spring.redis.jedis.pool.max-active=8 +spring.redis.timeout=60000 +spring.redis.database=0 +spring.redis.password= \ No newline at end of file diff --git a/sop-openapi/pom.xml b/sop-openapi/pom.xml index 1f74e3c7..f035b864 100644 --- a/sop-openapi/pom.xml +++ b/sop-openapi/pom.xml @@ -80,6 +80,13 @@ net.oschina.durcframework fastmybatis-spring-boot-starter + + + org.projectlombok + lombok + 1.18.4 + provided + diff --git a/sop-openapi/src/main/java/com/gitee/sop/openapi/SopOpenapiApplication.java b/sop-openapi/src/main/java/com/gitee/sop/openapi/SopOpenapiApplication.java index c3479299..29675788 100644 --- a/sop-openapi/src/main/java/com/gitee/sop/openapi/SopOpenapiApplication.java +++ b/sop-openapi/src/main/java/com/gitee/sop/openapi/SopOpenapiApplication.java @@ -1,8 +1,14 @@ package com.gitee.sop.openapi; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; + +@EnableDiscoveryClient +@SpringBootApplication public class SopOpenapiApplication { public static void main(String[] args) { - + SpringApplication.run(SopOpenapiApplication.class, args); } } diff --git a/sop-openapi/src/main/java/com/gitee/sop/openapi/entity/IsvAuthTokenEntity.java b/sop-openapi/src/main/java/com/gitee/sop/openapi/entity/IsvAuthTokenEntity.java index 011b4ea3..2fae85fa 100644 --- a/sop-openapi/src/main/java/com/gitee/sop/openapi/entity/IsvAuthTokenEntity.java +++ b/sop-openapi/src/main/java/com/gitee/sop/openapi/entity/IsvAuthTokenEntity.java @@ -32,6 +32,12 @@ public class IsvAuthTokenEntity { /** 1启用,2禁用, 数据库字段:status */ private Byte status; + /** appAuthCode 数据库字段:app_auth_code */ + private String appAuthCode; + /** appAuthToken 数据库字段:app_auth_token */ private String appAuthToken; + + /** appRefreshToken 数据库字段:app_refresh_token */ + private String appRefreshToken; } diff --git a/sop-openapi/src/main/java/com/gitee/sop/openapi/po/AuthTokePo.java b/sop-openapi/src/main/java/com/gitee/sop/openapi/po/AuthTokePo.java index 446dbcb8..e2d88e23 100644 --- a/sop-openapi/src/main/java/com/gitee/sop/openapi/po/AuthTokePo.java +++ b/sop-openapi/src/main/java/com/gitee/sop/openapi/po/AuthTokePo.java @@ -3,6 +3,7 @@ package com.gitee.sop.openapi.po; import com.gitee.sop.servercommon.param.validation.Group1; import com.gitee.sop.servercommon.param.validation.Group2; import io.swagger.annotations.ApiModelProperty; +import lombok.Data; import org.hibernate.validator.constraints.Length; import javax.validation.constraints.NotBlank; @@ -11,6 +12,8 @@ import javax.validation.constraints.NotBlank; * * @author wangping created on 2020/11/2 18:31 */ + +@Data public class AuthTokePo { @@ -23,18 +26,14 @@ public class AuthTokePo { @Length(min = 1, max = 64, message = "{isp.auth.app_id_length_error}=1,64", groups = Group2.class) private String code; - @NotBlank(message = "{isp.auth.user_token_empty_error}", groups = Group1.class) - @Length(min = 1, max = 64, message = "{isp.auth.user_token_length_error}=1,64", groups = Group2.class) - @ApiModelProperty(value = "用户登录凭证", required = true, example = "1111", position = 2) - private String grant_type; - - /** * 授权类型。
* 如果使用app_auth_code换取token,则为authorization_code, * 如果使用refresh_token换取新的token,则为refresh_token */ - @NotBlank(message = "授权类型不能为空") + @NotBlank(message = "{isp.auth.user_token_empty_error}", groups = Group1.class) + @Length(min = 1, max = 64, message = "{isp.auth.user_token_length_error}=1,64", groups = Group2.class) + @ApiModelProperty(value = "授权类型不能为空", required = true, example = "1111", position = 2) private String grantType; diff --git a/sop-openapi/src/main/java/com/gitee/sop/openapi/service/AuthCodeService.java b/sop-openapi/src/main/java/com/gitee/sop/openapi/service/AuthCodeService.java index 9bb753b4..b904028c 100644 --- a/sop-openapi/src/main/java/com/gitee/sop/openapi/service/AuthCodeService.java +++ b/sop-openapi/src/main/java/com/gitee/sop/openapi/service/AuthCodeService.java @@ -1,5 +1,6 @@ package com.gitee.sop.openapi.service; +import com.alibaba.fastjson.JSONObject; import com.gitee.fastmybatis.core.query.Query; import com.gitee.sop.openapi.config.RedisOperator; import com.gitee.sop.openapi.entity.IsvAuthTokenEntity; @@ -13,6 +14,7 @@ import com.gitee.sop.openapi.util.RedisKeyUtil; import com.gitee.sop.servercommon.exception.ServiceException; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; /*** * @@ -20,16 +22,7 @@ import org.springframework.stereotype.Service; */ @Slf4j @Service -public class AuthCodeService { - public static final int FORBIDDEN = 2; - - /** 0 插件应用未授权 **/ - public static final int UNAUTHORIZED = 0; - /** 1 插件应用已授权 **/ - public static final int AUTHORIZED = 1; - - public static final Byte ENABLE_TOKEN = 1; - public static final Byte DISABLE_TOKEN = 0; +public class AuthCodeService implements AuthService { private final IsvInfoMapper isvInfoMapper; private final IsvAuthTokenMapper isvAuthTokenMapper; @@ -43,6 +36,8 @@ public class AuthCodeService { this.redisOperator = redisOperator; } + + @Transactional public AuthCodeVo getCode(AuthCodePo authCodePo) { IsvInfoEntity isvInfo = isvInfoMapper.getByColumn("app_key", authCodePo.getAppId()); @@ -51,8 +46,6 @@ public class AuthCodeService { throw new ServiceException("应用ID未申请或已被禁用"); } - // todo check userToken and get userId - String authCode = MD5GeneratorUtil.generateValue(); Query query = new Query() @@ -65,23 +58,28 @@ public class AuthCodeService { if (authTokenEntity == null) { authCodeVo.setState(UNAUTHORIZED); authTokenEntity = new IsvAuthTokenEntity(); - authTokenEntity.setAppAuthToken(authCode); + authTokenEntity.setAppAuthCode(authCode); authTokenEntity.setAppKey(authCodePo.getAppId()); authTokenEntity.setUserId(authCodePo.getUserId()); isvAuthTokenMapper.save(authTokenEntity); } else { - authCodeVo.setState(AUTHORIZED); + authTokenEntity.setAppAuthCode(authCode); isvAuthTokenMapper.update(authTokenEntity); } // 写入redis缓存 - String codeKey = RedisKeyUtil.getCodeKey(authCodePo.getAppId(), authCodePo.getUserId()); - redisOperator.set(codeKey, authCodePo.getUserId()); + try { + String codeKey = RedisKeyUtil.getCodeKey(authCode); + redisOperator.set(codeKey, JSONObject.toJSON(authCodePo), CODE_TIMEOUT); + } catch (Exception e) { + throw new ServiceException("获取code失败"); + } return authCodeVo; } + @Transactional public void cancelToken(Long userId) { // todo check userToken and get userId @@ -94,13 +92,21 @@ public class AuthCodeService { throw new ServiceException("用户未授权插件应用"); } - authTokenEntity.setStatus(ENABLE_TOKEN); - isvAuthTokenMapper.update(authTokenEntity); + try { + authTokenEntity.setStatus(ENABLE_TOKEN); + isvAuthTokenMapper.update(authTokenEntity); + } catch (Exception e) { + throw new ServiceException("解除授权失败,系统忙,请稍后!"); + } - String tokenKey = RedisKeyUtil.getAuthTokenKey(authTokenEntity.getAppKey(), authTokenEntity.getUserId()); - String refreshKey = RedisKeyUtil.getRefreshTokenKey(authTokenEntity.getAppKey(), authTokenEntity.getUserId()); - redisOperator.del(tokenKey); - redisOperator.del(refreshKey); + String tokenKey = RedisKeyUtil.getAuthTokenKey(authTokenEntity.getAppAuthToken()); + String refreshKey = RedisKeyUtil.getRefreshTokenKey(authTokenEntity.getAppRefreshToken()); + try { + redisOperator.del(tokenKey); + redisOperator.del(refreshKey); + } catch (Exception e) { + throw new ServiceException("解除授权失败,系统忙,请稍后!"); + } } } diff --git a/sop-openapi/src/main/java/com/gitee/sop/openapi/service/AuthService.java b/sop-openapi/src/main/java/com/gitee/sop/openapi/service/AuthService.java new file mode 100644 index 00000000..28102e8b --- /dev/null +++ b/sop-openapi/src/main/java/com/gitee/sop/openapi/service/AuthService.java @@ -0,0 +1,20 @@ +package com.gitee.sop.openapi.service; + +/*** + * + * @author wangping created on 2020/11/4 15:41 + */ +public interface AuthService { + + int FORBIDDEN = 2; + + /** 0 插件应用未授权 **/ + int UNAUTHORIZED = 0; + /** 1 插件应用已授权 **/ + int AUTHORIZED = 1; + + Byte ENABLE_TOKEN = 1; + Byte DISABLE_TOKEN = 0; + + Long CODE_TIMEOUT = 3600L; +} diff --git a/sop-openapi/src/main/java/com/gitee/sop/openapi/service/AuthTokenService.java b/sop-openapi/src/main/java/com/gitee/sop/openapi/service/AuthTokenService.java new file mode 100644 index 00000000..ca8d13f1 --- /dev/null +++ b/sop-openapi/src/main/java/com/gitee/sop/openapi/service/AuthTokenService.java @@ -0,0 +1,83 @@ +package com.gitee.sop.openapi.service; + +import com.alibaba.fastjson.JSONObject; +import com.gitee.sop.openapi.config.RedisOperator; +import com.gitee.sop.openapi.entity.IsvAuthTokenEntity; +import com.gitee.sop.openapi.mapper.IsvAuthTokenMapper; +import com.gitee.sop.openapi.po.AuthCodePo; +import com.gitee.sop.openapi.po.AuthTokePo; +import com.gitee.sop.openapi.util.GrantType; +import com.gitee.sop.openapi.util.MD5GeneratorUtil; +import com.gitee.sop.openapi.util.RedisKeyUtil; +import com.gitee.sop.openapi.vo.AuthTokenVo; +import com.gitee.sop.servercommon.exception.ServiceException; +import org.springframework.stereotype.Service; + +/*** + * + * @author wangping created on 2020/11/4 15:21 + */ +@Service +public class AuthTokenService implements AuthService { + + private final RedisOperator redisOperator; + private final IsvAuthTokenMapper isvAuthTokenMapper; + + public AuthTokenService(final RedisOperator redisOperator, + final IsvAuthTokenMapper isvAuthTokenMapper) { + this.redisOperator = redisOperator; + this.isvAuthTokenMapper = isvAuthTokenMapper; + } + + + public AuthTokenVo getToken(AuthTokePo authTokePo) { + + AuthTokenVo tokenVo = new AuthTokenVo(); + String grantType = authTokePo.getGrantType(); + if (GrantType.AUTHORIZATION_CODE.name().equals(grantType)) { + String codeKey = RedisKeyUtil.getCodeKey(authTokePo.getCode()); + Object jsonObj = redisOperator.get(codeKey); + if (jsonObj == null) { + throw new ServiceException("Invalid request"); + } + + AuthCodePo codePo = JSONObject.parseObject(jsonObj.toString(), AuthCodePo.class); + if (codePo == null) { + throw new ServiceException("Can not found user by code"); + } + IsvAuthTokenEntity authTokenEntity = isvAuthTokenMapper.getByColumn("app_auth_code", authTokePo.getCode()); + if (authTokenEntity == null) { + throw new ServiceException("Can not found user by code"); + } + + + String authToken = MD5GeneratorUtil.generateValue(); + String refreshToken = MD5GeneratorUtil.generateValue(); + + + authTokenEntity.setStatus((byte) AUTHORIZED); + authTokenEntity.setAppAuthToken(authToken); + authTokenEntity.setAppRefreshToken(refreshToken); + isvAuthTokenMapper.update(authTokenEntity); + + String authTokenKey = RedisKeyUtil.getAuthTokenKey(authToken); + String refreshTokenKey = RedisKeyUtil.getRefreshTokenKey(refreshToken); + redisOperator.set(authTokenKey, codePo); + redisOperator.set(refreshTokenKey, codePo); + + tokenVo.setAppAuthToken(authToken); + tokenVo.setAppRefreshToken(refreshToken); + tokenVo.setUserId(codePo.getUserId()); + return tokenVo; + } else if (GrantType.REFRESH_TOKEN.name().equals(grantType)) { + + + return tokenVo; + } else { + throw new ServiceException("Invalid grant"); + } + + } + + +} diff --git a/sop-openapi/src/main/java/com/gitee/sop/openapi/util/GrantType.java b/sop-openapi/src/main/java/com/gitee/sop/openapi/util/GrantType.java new file mode 100644 index 00000000..de8949ee --- /dev/null +++ b/sop-openapi/src/main/java/com/gitee/sop/openapi/util/GrantType.java @@ -0,0 +1,23 @@ +package com.gitee.sop.openapi.util; + +/*** + * + * @author wangping created on 2020/11/4 15:25 + */ +public enum GrantType { + + AUTHORIZATION_CODE("authorization_code"), + PASSWORD("password"), + REFRESH_TOKEN("refresh_token"), + CLIENT_CREDENTIALS("client_credentials"); + + private String grantType; + + private GrantType(String grantType) { + this.grantType = grantType; + } + + public String toString() { + return this.grantType; + } +} diff --git a/sop-openapi/src/main/java/com/gitee/sop/openapi/util/RedisKeyUtil.java b/sop-openapi/src/main/java/com/gitee/sop/openapi/util/RedisKeyUtil.java index 61ec6687..19bab21c 100644 --- a/sop-openapi/src/main/java/com/gitee/sop/openapi/util/RedisKeyUtil.java +++ b/sop-openapi/src/main/java/com/gitee/sop/openapi/util/RedisKeyUtil.java @@ -16,15 +16,15 @@ public class RedisKeyUtil { private static final Joiner JOINER = Joiner.on(":"); - public static String getCodeKey(String appId, Long userId) { - return JOINER.join(CODE_PREFIX, appId, userId); + public static String getCodeKey(String code) { + return JOINER.join(CODE_PREFIX, code); } - public static String getAuthTokenKey(String appId, Long userId) { - return JOINER.join(AUTH_TOKEN_PREFIX, appId, userId); + public static String getAuthTokenKey(String token) { + return JOINER.join(AUTH_TOKEN_PREFIX, token); } - public static String getRefreshTokenKey(String appId, Long userId) { - return JOINER.join(REFRESH_TOKEN_PREFIX, appId, userId); + public static String getRefreshTokenKey(String refreshToken) { + return JOINER.join(REFRESH_TOKEN_PREFIX, refreshToken); } } diff --git a/sop-openapi/src/main/java/com/gitee/sop/openapi/vo/AuthTokenVo.java b/sop-openapi/src/main/java/com/gitee/sop/openapi/vo/AuthTokenVo.java new file mode 100644 index 00000000..c8476430 --- /dev/null +++ b/sop-openapi/src/main/java/com/gitee/sop/openapi/vo/AuthTokenVo.java @@ -0,0 +1,40 @@ +package com.gitee.sop.openapi.vo; + +import lombok.Data; + +/*** + * + * @author wangping created on 2020/11/4 15:22 + */ +@Data +public class AuthTokenVo { + + /** + * 授权令牌 + */ + private String appAuthToken; + + /** + * 用户id + */ + private Long userId; + + + /** + * 令牌有效期. -1:永久有效 + */ + private long expiresIn = -1; + + /** + * 刷新令牌有效期. -1:永久有效 + */ + private long reExpiresIn = -1; + + + /** + * 刷新令牌时使用。 + * 刷新令牌后,我们会保证老的app_auth_token从刷新开始10分钟内可继续使用,请及时替换为最新token + */ + private String appRefreshToken; + +} diff --git a/sop-openapi/src/main/resources/application-standalone.yml b/sop-openapi/src/main/resources/application-standalone.yml index 12b1ea14..89510048 100644 --- a/sop-openapi/src/main/resources/application-standalone.yml +++ b/sop-openapi/src/main/resources/application-standalone.yml @@ -1,6 +1,6 @@ spring: redis: - host: 10.0.0.39 + host: localhost port: 6379 jedis: pool: @@ -10,4 +10,4 @@ spring: min-idle: 1 database: 0 timeout: 60000 - password: lt@3600 + password: diff --git a/sop-openapi/src/main/resources/application.properties b/sop-openapi/src/main/resources/application.properties index 91aad173..b208c10a 100644 --- a/sop-openapi/src/main/resources/application.properties +++ b/sop-openapi/src/main/resources/application.properties @@ -1,6 +1,6 @@ spring.profiles.active=dev server.port=8089 -spring.application.name=sop-openapi +spring.application.name=open-apis # ע spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848 \ No newline at end of file