diff --git a/changelog.md b/changelog.md index 194c729c..40a1db58 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,9 @@ # changelog +## 1.6.0 + +- 新增应用授权 + ## 1.5.0 - admin新增signType字段 diff --git a/sop-1.6.0.sql b/sop-1.6.0.sql new file mode 100644 index 00000000..e5ea8b4a --- /dev/null +++ b/sop-1.6.0.sql @@ -0,0 +1,16 @@ +DROP TABLE IF EXISTS `user_info`; + +CREATE TABLE `user_info` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `username` varchar(64) NOT NULL DEFAULT '' COMMENT '用户名', + `password` varchar(128) NOT NULL DEFAULT '' COMMENT '密码', + `nickname` varchar(64) NOT NULL DEFAULT '' COMMENT '昵称', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_unamepwd` (`username`,`password`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息表'; + + +INSERT INTO `user_info` ( `username`, `password`, `nickname`, `gmt_create`, `gmt_modified`) VALUES + ('zhangsan','123456','张三','2019-04-27 08:32:57','2019-04-27 08:32:57'); diff --git a/sop-admin/sop-admin-server/pom.xml b/sop-admin/sop-admin-server/pom.xml index 71c4a94d..67b78391 100644 --- a/sop-admin/sop-admin-server/pom.xml +++ b/sop-admin/sop-admin-server/pom.xml @@ -43,7 +43,7 @@ net.oschina.durcframework fastmybatis-spring-boot-starter - 1.6.0 + 1.7.1 mysql diff --git a/sop-common/pom.xml b/sop-common/pom.xml index a55f6ed3..0a62c6a1 100644 --- a/sop-common/pom.xml +++ b/sop-common/pom.xml @@ -5,7 +5,7 @@ 4.0.0 com.gitee.sop sop-common - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT pom diff --git a/sop-common/sop-gateway-common/pom.xml b/sop-common/sop-gateway-common/pom.xml index 7735edd4..56c83526 100644 --- a/sop-common/sop-gateway-common/pom.xml +++ b/sop-common/sop-gateway-common/pom.xml @@ -5,11 +5,11 @@ com.gitee.sop sop-common - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT ../pom.xml sop-gateway-common - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT jar sop-gateway-common diff --git a/sop-common/sop-service-common/pom.xml b/sop-common/sop-service-common/pom.xml index c947ce61..92e6879a 100644 --- a/sop-common/sop-service-common/pom.xml +++ b/sop-common/sop-service-common/pom.xml @@ -6,11 +6,11 @@ com.gitee.sop sop-common - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT ../pom.xml sop-service-common - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT jar sop-service-common diff --git a/sop-example/pom.xml b/sop-example/pom.xml index a3efa857..58a69ccd 100644 --- a/sop-example/pom.xml +++ b/sop-example/pom.xml @@ -9,6 +9,7 @@ pom + sop-auth sop-story sop-book sop-easyopen diff --git a/sop-example/sop-auth/.gitignore b/sop-example/sop-auth/.gitignore new file mode 100644 index 00000000..153c9335 --- /dev/null +++ b/sop-example/sop-auth/.gitignore @@ -0,0 +1,29 @@ +HELP.md +/target/ +!.mvn/wrapper/maven-wrapper.jar + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +/build/ + +### VS Code ### +.vscode/ diff --git a/sop-example/sop-auth/pom.xml b/sop-example/sop-auth/pom.xml new file mode 100644 index 00000000..e5f11959 --- /dev/null +++ b/sop-example/sop-auth/pom.xml @@ -0,0 +1,127 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.1.2.RELEASE + + + com.gitee.sop + sop-auth + 1.0.0-SNAPSHOT + sop-auth + 授权程序 + + + 1.8 + Greenwich.RELEASE + 0.31 + + + + + + + com.gitee.sop + sop-service-common + 1.6.0-SNAPSHOT + + + org.springframework.cloud + spring-cloud-starter-netflix-eureka-client + + + org.springframework.cloud + spring-cloud-starter-openfeign + + + + + com.gitee.sop + sdk-java + 1.0.0-SNAPSHOT + + + com.squareup.okhttp3 + okhttp + 3.14.0 + + + + net.oschina.durcframework + fastmybatis-spring-boot-starter + 1.7.1 + + + mysql + mysql-connector-java + runtime + + + + + org.apache.oltu.oauth2 + org.apache.oltu.oauth2.authzserver + ${oltu.version} + + + org.apache.oltu.oauth2 + org.apache.oltu.oauth2.resourceserver + ${oltu.version} + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-data-redis + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + com.google.guava + guava + 27.1-jre + compile + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/sop-example/sop-auth/readme.md b/sop-example/sop-auth/readme.md new file mode 100644 index 00000000..71f3b53c --- /dev/null +++ b/sop-example/sop-auth/readme.md @@ -0,0 +1,9 @@ +# 应用授权 + +- 启动注册中心、网关、本服务(sop-auth) +- 浏览器访问:http://localhost:8087/oauth2/appToAppAuth?app_id=2019032617262200001&redirect_uri=http%3a%2f%2flocalhost%3a8087%2foauth2callback +- 输入用户名密码登录,这里是`zhangsan/123456` + +授权接口在`OAuth2Controller`中,查看回调在`CallbackController`中 + +token的维护,重点关注`OAuth2ManagerRedis.java` diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/SopAuthApplication.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/SopAuthApplication.java new file mode 100644 index 00000000..29595038 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/SopAuthApplication.java @@ -0,0 +1,15 @@ +package com.gitee.sop.sopauth; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; + +@EnableDiscoveryClient +@SpringBootApplication +public class SopAuthApplication { + + public static void main(String[] args) { + SpringApplication.run(SopAuthApplication.class, args); + } + +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/AccessToken.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/AccessToken.java new file mode 100644 index 00000000..83cc9925 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/AccessToken.java @@ -0,0 +1,44 @@ +package com.gitee.sop.sopauth.auth; + +/** + * @author tanghc + */ +public class AccessToken { + /** 过期时间,毫秒 */ + private long expireTimeMillis; + private OpenUser openUser; + + /** + * @param expireIn 有效时长,秒 + * @param openUser + */ + public AccessToken(long expireIn, OpenUser openUser) { + super(); + if(expireIn <= 0) { + throw new IllegalArgumentException("expireIn必须大于0"); + } + this.expireTimeMillis = System.currentTimeMillis() + (expireIn * 1000); + this.openUser = openUser; + } + + public boolean isExpired() { + return expireTimeMillis < System.currentTimeMillis(); + } + + public long getExpireTimeMillis() { + return expireTimeMillis; + } + + public void setExpireTimeMillis(long expireTimeMillis) { + this.expireTimeMillis = expireTimeMillis; + } + + public OpenUser getOpenUser() { + return openUser; + } + + public void setOpenUser(OpenUser openUser) { + this.openUser = openUser; + } + +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/AppIdManager.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/AppIdManager.java new file mode 100644 index 00000000..4d9eb26d --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/AppIdManager.java @@ -0,0 +1,14 @@ +package com.gitee.sop.sopauth.auth; + +/** + * @author tanghc + */ +public interface AppIdManager { + /** + * 是否是合法的appId + * + * @param appId + * @return true:合法 + */ + boolean isValidAppId(String appId); +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/FetchTokenParam.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/FetchTokenParam.java new file mode 100644 index 00000000..2acc1a16 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/FetchTokenParam.java @@ -0,0 +1,30 @@ +package com.gitee.sop.sopauth.auth; + +import lombok.Data; + +import javax.validation.constraints.NotBlank; + +/** + * 获取accessToken参数对象 + * @author tanghc + */ +@Data +public class FetchTokenParam { + /** + * 授权类型。
+ * 如果使用app_auth_code换取token,则为authorization_code, + * 如果使用refresh_token换取新的token,则为refresh_token + */ + @NotBlank(message = "授权类型不能为空") + private String grant_type; + + /** + * 授权码.与refresh_token二选一,用户对应用授权后得到,即第一步中开发者获取到的app_auth_code值 + */ + private String code; + + /** + * 刷新令牌.与code二选一,可为空,刷新令牌时使用 + */ + private String refresh_token; +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/FetchTokenResult.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/FetchTokenResult.java new file mode 100644 index 00000000..be4360b2 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/FetchTokenResult.java @@ -0,0 +1,39 @@ +package com.gitee.sop.sopauth.auth; + +import lombok.Data; + +/** + * 使用app_auth_code换取app_auth_token返回结果 + * @author tanghc + */ +@Data +public class FetchTokenResult { + /** + * 授权令牌 + */ + private String app_auth_token; + + /** + * 用户id + */ + private String user_id; + + /** + * 令牌有效期. -1:永久有效 + */ + private long expires_in = -1; + + /** + * 刷新令牌有效期. -1:永久有效 + */ + private long re_expires_in = -1; + + /** + * 刷新令牌时使用。 + * 刷新令牌后,我们会保证老的app_auth_token从刷新开始10分钟内可继续使用,请及时替换为最新token + */ + private String app_refresh_token; + + private String error; + private String error_description; +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OAuth2Config.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OAuth2Config.java new file mode 100644 index 00000000..6a021dc0 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OAuth2Config.java @@ -0,0 +1,46 @@ +package com.gitee.sop.sopauth.auth; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class OAuth2Config { + + private static OAuth2Config instance = new OAuth2Config(); + + public static OAuth2Config getInstance() { + return instance; + } + + public static void setInstance(OAuth2Config instance) { + OAuth2Config.instance = instance; + } + + /** + * 应用授权的app_auth_code唯一的;app_auth_code使用一次后失效,一天(从生成app_auth_code开始的24小时)未被使用自动过期; + * app_auth_token永久有效。 + */ + private int codeTimeoutSeconds = 86400; + /** + * accessToken有效时间,单位秒 + * -1 永久有效 + */ + private int accessTokenExpiresIn = -1; + + /** + * app_refresh_token有效时间,单位秒,accessToken有效时间的3倍。当小于0,永久有效 + */ + private int refreshTokenExpiresIn = accessTokenExpiresIn * 3; + + /** + * 刷新令牌后,我们会保证老的app_auth_token从刷新开始10分钟内可继续使用,请及时替换为最新token + */ + private int afterRefreshExpiresIn = 60 * 10; + + /** + * 登录视图页面用于,mvc视图,如:loginView + */ + private String oauth2LoginUri = "/oauth2/login"; +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OAuth2Manager.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OAuth2Manager.java new file mode 100644 index 00000000..9f5d1c06 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OAuth2Manager.java @@ -0,0 +1,93 @@ +package com.gitee.sop.sopauth.auth; + + +import com.gitee.sop.sopauth.auth.exception.LoginErrorException; + +import javax.servlet.http.HttpServletRequest; +import java.util.Map; + +/** + * 认证服务,需要自己实现 + * @author tanghc + * + */ +public interface OAuth2Manager { + + /** + * 添加 auth code + * + * @param authCode + * code值 + * @param authUser + * 用户 + */ + void addAuthCode(String authCode, OpenUser authUser); + + /** + * 添加accessToken + * @param accessToken token值 + * @param refreshToken refreshToken + * @param authUser 用户 + */ + void addAccessToken(String accessToken, String refreshToken, OpenUser authUser); + + + /** + * 删除这个accessToken + * @param accessToken + */ + void removeAccessToken(String accessToken); + + /** + * 删除这个refreshToken + * @param refreshToken + */ + void removeRefreshToken(String refreshToken); + + /** + * 获取RefreshToken + * @param refreshToken + * @return 返回Token信息 + */ + RefreshToken getRefreshToken(String refreshToken); + + /** + * 验证auth code是否有效 + * + * @param authCode + * @return 无效返回false + */ + boolean checkAuthCode(String authCode); + + /** + * 根据auth code获取用户 + * + * @param authCode + * @return 返回用户 + */ + OpenUser getUserByAuthCode(String authCode); + + /** + * 根据access token获取用户名 + * + * @param accessToken + * token值 + * @return 返回用户 + */ + OpenUser getUserByAccessToken(String accessToken); + + /** + * 返回accessToken中追加的参数 + * @param user + * @return 返回追加的参数 + */ + Map getParam(OpenUser user); + + /** + * 用户登录,需判断是否已经登录 + * @param request + * @return 返回用户对象 + * @throws LoginErrorException 登录失败异常 + */ + OpenUser login(HttpServletRequest request) throws LoginErrorException; +} \ No newline at end of file diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OAuth2Service.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OAuth2Service.java new file mode 100644 index 00000000..ecc9d26e --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OAuth2Service.java @@ -0,0 +1,44 @@ +package com.gitee.sop.sopauth.auth; + +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; +import org.apache.oltu.oauth2.common.message.OAuthResponse; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.net.URISyntaxException; + +/** + * @author tanghc + */ +public interface OAuth2Service { + + /** + * oauth2授权,获取code. + *
+     *  1、首先通过如http://localhost:8080/api/authorize?client_id=test&response_type=code&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Foauth2callback访问授权页面;
+        2、该控制器首先检查clientId是否正确;如果错误将返回相应的错误信息;
+        3、然后判断用户是否登录了,如果没有登录首先到登录页面登录;
+        4、登录成功后生成相应的code即授权码,然后重定向到客户端地址,如http://localhost:8080/oauth2callback?code=6d250650831fea227749f49a5b49ccad;在重定向到的地址中会带上code参数(授权码),接着客户端可以根据授权码去换取accessToken。
+     * 
+ * + * @param request req + * @param response resp + * @param authConfig 配置 + * @return 返回响应内容 + * @throws URISyntaxException + * @throws OAuthSystemException + */ + OAuthResponse authorize(HttpServletRequest request, HttpServletResponse response, OAuth2Config authConfig) + throws URISyntaxException, OAuthSystemException; + + + /** + * 通过code获取accessToken. + * @param fetchTokenParam + * @param authConfig 配置项 + * @return 返回响应内容 + * @throws URISyntaxException + * @throws OAuthSystemException + */ + FetchTokenResult accessToken(FetchTokenParam fetchTokenParam, OAuth2Config authConfig); +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OpenUser.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OpenUser.java new file mode 100644 index 00000000..5e7338b3 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/OpenUser.java @@ -0,0 +1,22 @@ +package com.gitee.sop.sopauth.auth; + +import java.io.Serializable; + +/** + * 如果要使用oauth2功能,自己的用户类需要实现这个对象 + * + * @author tanghc + */ +public interface OpenUser extends Serializable { + /** + * 返回用户id + * @return + */ + String getUserId(); + /** + * 返回用户名 + * + * @return 返回用户名 + */ + String getUsername(); +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/RefreshToken.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/RefreshToken.java new file mode 100644 index 00000000..acd04d54 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/RefreshToken.java @@ -0,0 +1,40 @@ +package com.gitee.sop.sopauth.auth; + +import java.io.Serializable; + +/** + * @author tanghc + */ +public class RefreshToken implements Serializable { + private static final long serialVersionUID = 1L; + + private String accessToken; + private OpenUser openUser; + + public RefreshToken(String accessToken, OpenUser openUser) { + super(); + this.accessToken = accessToken; + this.openUser = openUser; + } + + public RefreshToken() { + super(); + } + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public OpenUser getOpenUser() { + return openUser; + } + + public void setOpenUser(OpenUser openUser) { + this.openUser = openUser; + } + +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/TokenPair.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/TokenPair.java new file mode 100644 index 00000000..07e11214 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/TokenPair.java @@ -0,0 +1,29 @@ +package com.gitee.sop.sopauth.auth; + +/** + * 存放accessToken和refreshToken + * + * @author tanghc + * + */ +public class TokenPair { + private String accessToken; + private String refreshToken; + + public String getAccessToken() { + return accessToken; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/exception/LoginErrorException.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/exception/LoginErrorException.java new file mode 100644 index 00000000..92e61d6a --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/exception/LoginErrorException.java @@ -0,0 +1,25 @@ +package com.gitee.sop.sopauth.auth.exception; + +/** + * @author tanghc + */ +public class LoginErrorException extends Exception { + private static final long serialVersionUID = -6721499454527023339L; + + public LoginErrorException() { + super(); + } + + public LoginErrorException(String message, Throwable cause) { + super(message, cause); + } + + public LoginErrorException(String message) { + super(message); + } + + public LoginErrorException(Throwable cause) { + super(cause); + } + +} \ No newline at end of file diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/AppIdManagerImpl.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/AppIdManagerImpl.java new file mode 100644 index 00000000..9c49954b --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/AppIdManagerImpl.java @@ -0,0 +1,33 @@ +package com.gitee.sop.sopauth.auth.impl; + +import com.gitee.sop.sopauth.auth.AppIdManager; +import com.gitee.sop.sopauth.entity.IsvInfo; +import com.gitee.sop.sopauth.mapper.IsvInfoMapper; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +/** + * @author tanghc + */ +@Service +@Slf4j +public class AppIdManagerImpl implements AppIdManager { + + public static final int FORBIDDEN = 2; + @Autowired + private IsvInfoMapper isvInfoMapper; + + @Override + public boolean isValidAppId(String appId) { + IsvInfo isvInfo = isvInfoMapper.getByColumn("app_key", appId); + if (isvInfo == null) { + return false; + } + if (isvInfo.getStatus().intValue() == FORBIDDEN) { + log.error("appId已禁用:{}", appId); + return false; + } + return true; + } +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/OAuth2ManagerCache.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/OAuth2ManagerCache.java new file mode 100644 index 00000000..102ed522 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/OAuth2ManagerCache.java @@ -0,0 +1,135 @@ +package com.gitee.sop.sopauth.auth.impl; + +import com.gitee.fastmybatis.core.query.Query; +import com.gitee.sop.sopauth.auth.OAuth2Config; +import com.gitee.sop.sopauth.auth.OAuth2Manager; +import com.gitee.sop.sopauth.auth.OpenUser; +import com.gitee.sop.sopauth.auth.RefreshToken; +import com.gitee.sop.sopauth.auth.exception.LoginErrorException; +import com.gitee.sop.sopauth.entity.UserInfo; +import com.gitee.sop.sopauth.mapper.UserInfoMapper; +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * oauth2管理,默认谷歌缓存实现,跟redis实现只能用一个。正式环境推荐使用redis保存 + * @author tanghc + * + */ +@Service +public class OAuth2ManagerCache implements OAuth2Manager { + /** + * 应用授权的app_auth_code唯一的;app_auth_code使用一次后失效,一天(从生成app_auth_code开始的24小时)未被使用自动过期; + * app_auth_token永久有效。 + */ + private int codeTimeoutSeconds = OAuth2Config.getInstance().getCodeTimeoutSeconds(); + /** + * accessToken过期时间 + * -1 永久有效 + */ + private int accessTokenTimeoutSeconds = OAuth2Config.getInstance().getAccessTokenExpiresIn(); + + private int refreshTokenTimeoutSeconds = OAuth2Config.getInstance().getRefreshTokenExpiresIn(); + + private LoadingCache codeCache = buildCache(codeTimeoutSeconds); + private LoadingCache accessTokenCache = buildCache(accessTokenTimeoutSeconds); + private LoadingCache refreshTokenCache = buildCache(refreshTokenTimeoutSeconds); + + @Autowired + private UserInfoMapper userInfoMapper; + + + private static LoadingCache buildCache(int timeout) { + CacheBuilder cacheBuilder = CacheBuilder.newBuilder(); + if (timeout > 0) { + cacheBuilder.expireAfterAccess(timeout, TimeUnit.SECONDS); + } + return cacheBuilder + .build(new CacheLoader() { + @Override + public T load(String key) throws Exception { + return null; + } + }); + } + + @Override + public void addAuthCode(String authCode, OpenUser authUser) { + codeCache.put(authCode, authUser); + } + + + @Override + public void addAccessToken(String accessToken, String refreshToken, OpenUser authUser) { + accessTokenCache.put(accessToken, authUser); + refreshTokenCache.put(refreshToken, new RefreshToken(accessToken, authUser)); + } + + @Override + public void removeAccessToken(String accessToken) { + accessTokenCache.asMap().remove(accessToken); + } + + @Override + public void removeRefreshToken(String refreshToken) { + refreshTokenCache.asMap().remove(refreshToken); + } + + @Override + public RefreshToken getRefreshToken(String refreshToken) { + return refreshTokenCache.getIfPresent(refreshToken); + } + + @Override + public boolean checkAuthCode(String authCode) { + return codeCache.asMap().containsKey(authCode); + } + + @Override + public OpenUser getUserByAuthCode(String authCode) { + return codeCache.getIfPresent(authCode); + } + + @Override + public OpenUser getUserByAccessToken(String accessToken) { + return accessTokenCache.getIfPresent(accessToken); + } + + @Override + public Map getParam(OpenUser user) { + Map map = new HashMap<>(); + map.put("username", user.getUsername()); + return map; + } + + @Override + public OpenUser login(HttpServletRequest request) throws LoginErrorException { + // 这里应该先检查用户有没有登录,如果登录直接返回openUser + String username = request.getParameter("username"); + String password = request.getParameter("password"); + if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { + throw new LoginErrorException("用户名密码不能为空"); + } + + Query query = new Query(); + query.eq("username", username) + .eq("password", password); + UserInfo userInfo = userInfoMapper.getByQuery(query); + + if(userInfo == null) { + throw new LoginErrorException("用户名密码不正确"); + } + + return userInfo; + } + +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/OAuth2ManagerRedis.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/OAuth2ManagerRedis.java new file mode 100644 index 00000000..c22d98c2 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/OAuth2ManagerRedis.java @@ -0,0 +1,171 @@ +package com.gitee.sop.sopauth.auth.impl; + +import com.alibaba.fastjson.JSON; +import com.alibaba.fastjson.JSONObject; +import com.gitee.fastmybatis.core.query.Query; +import com.gitee.sop.sopauth.auth.OAuth2Config; +import com.gitee.sop.sopauth.auth.OAuth2Manager; +import com.gitee.sop.sopauth.auth.OpenUser; +import com.gitee.sop.sopauth.auth.RefreshToken; +import com.gitee.sop.sopauth.auth.exception.LoginErrorException; +import com.gitee.sop.sopauth.entity.UserInfo; +import com.gitee.sop.sopauth.mapper.UserInfoMapper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +/** + * + * oauth2管理redis实现,这个类跟OAuth2ManagerCache类只能用一个, + * 如果要用这个类,注释掉OAuth2ManagerCache的@Service + * 启用这个类的@Service + */ +//@Service +public class OAuth2ManagerRedis implements OAuth2Manager { + + private static String CODE_PREFIX = "com.gitee.sop.oauth2_code:"; + private static String ACCESS_TOKEN_PREFIX = "com.gitee.sop.oauth2_access_token:"; + private static String REFRESH_TOKEN_PREFIX = "com.gitee.sop.oauth2_refresh_token:"; + + private int codeTimeoutSeconds = OAuth2Config.getInstance().getCodeTimeoutSeconds(); + + @Autowired + private StringRedisTemplate redisTemplate; + + @Autowired + private UserInfoMapper userInfoMapper; + + public static String getCodeKey(String code) { + return CODE_PREFIX + code; + } + + public static String getAccessTokenKey(String accessToken) { + return ACCESS_TOKEN_PREFIX + accessToken; + } + + public static String getRefreshTokenKey(String refreshToken) { + return REFRESH_TOKEN_PREFIX + refreshToken; + } + + @Override + public void addAuthCode(String authCode, OpenUser authUser) { + redisTemplate.opsForValue().set(getCodeKey(authCode), + JSON.toJSONString(authUser), + codeTimeoutSeconds, + TimeUnit.SECONDS); + } + + + @Override + public void addAccessToken(String accessToken, String refreshToken, OpenUser authUser) { + // 存accessToken + long expiresIn = OAuth2Config.getInstance().getAccessTokenExpiresIn(); + long reExpiresIn = OAuth2Config.getInstance().getRefreshTokenExpiresIn(); + if (expiresIn > 0) { + redisTemplate.opsForValue().set(getAccessTokenKey(accessToken), JSON.toJSONString(authUser), expiresIn, TimeUnit.SECONDS); + } else { + redisTemplate.opsForValue().set(getAccessTokenKey(accessToken), JSON.toJSONString(authUser)); + } + // 存refreshToken + if (reExpiresIn > 0) { + redisTemplate.opsForValue().set( + getRefreshTokenKey(refreshToken), + JSON.toJSONString(new RefreshToken(accessToken, authUser)), + reExpiresIn, // refreshToken过期时间 + TimeUnit.SECONDS); + } else { + redisTemplate.opsForValue().set( + getRefreshTokenKey(refreshToken), + JSON.toJSONString(new RefreshToken(accessToken, authUser))); + } + + } + + @Override + public void removeAccessToken(String accessToken) { + String accessTokenKey = getAccessTokenKey(accessToken); + int afterRefreshExpiresIn = OAuth2Config.getInstance().getAfterRefreshExpiresIn(); + redisTemplate.expire(accessTokenKey, afterRefreshExpiresIn, TimeUnit.SECONDS); + } + + @Override + public void removeRefreshToken(String refreshToken) { + redisTemplate.delete(getRefreshTokenKey(refreshToken)); + } + + @Override + public RefreshToken getRefreshToken(String refreshToken) { + String json = redisTemplate.opsForValue().get(getRefreshTokenKey(refreshToken)); + if(StringUtils.isEmpty(json)) { + return null; + } + JSONObject jsonObj = JSON.parseObject(json); + + String userJson = jsonObj.getString("openUser"); + UserInfo user = JSON.parseObject(userJson, UserInfo.class); + String accessToken = jsonObj.getString("accessToken"); + + return new RefreshToken(accessToken, user); + } + + @Override + public boolean checkAuthCode(String authCode) { + if(StringUtils.isEmpty(authCode)) { + return false; + } + return redisTemplate.hasKey(getCodeKey(authCode)); + } + + @Override + public OpenUser getUserByAuthCode(String authCode) { + String json = redisTemplate.opsForValue().get(getCodeKey(authCode)); + if(StringUtils.isEmpty(json)) { + return null; + } + return JSON.parseObject(json, UserInfo.class); + } + + @Override + public OpenUser getUserByAccessToken(String accessToken) { + String json = redisTemplate.opsForValue().get(getAccessTokenKey(accessToken)); + if(StringUtils.isEmpty(json)) { + return null; + } + return JSON.parseObject(json, UserInfo.class); + } + + @Override + public Map getParam(OpenUser user) { + Map map = new HashMap<>(); + map.put("username", user.getUsername()); + return map; + } + + @Override + public OpenUser login(HttpServletRequest request) throws LoginErrorException { + // 这里应该先检查用户有没有登录,如果登录直接返回openUser + + String username = request.getParameter("username"); + String password = request.getParameter("password"); + if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { + throw new LoginErrorException("用户名密码不能为空"); + } + + Query query = new Query(); + query.eq("username", username) + .eq("password", password); + UserInfo userInfo = userInfoMapper.getByQuery(query); + + if(userInfo == null) { + throw new LoginErrorException("用户名密码不正确"); + } + + return userInfo; + } + +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/OAuth2ServiceImpl.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/OAuth2ServiceImpl.java new file mode 100644 index 00000000..7fdc8674 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/auth/impl/OAuth2ServiceImpl.java @@ -0,0 +1,284 @@ +package com.gitee.sop.sopauth.auth.impl; + +import com.gitee.sop.sopauth.auth.AppIdManager; +import com.gitee.sop.sopauth.auth.FetchTokenParam; +import com.gitee.sop.sopauth.auth.FetchTokenResult; +import com.gitee.sop.sopauth.auth.OAuth2Config; +import com.gitee.sop.sopauth.auth.OAuth2Manager; +import com.gitee.sop.sopauth.auth.OAuth2Service; +import com.gitee.sop.sopauth.auth.OpenUser; +import com.gitee.sop.sopauth.auth.RefreshToken; +import com.gitee.sop.sopauth.auth.TokenPair; +import com.gitee.sop.sopauth.auth.exception.LoginErrorException; +import lombok.extern.slf4j.Slf4j; +import org.apache.oltu.oauth2.as.issuer.MD5Generator; +import org.apache.oltu.oauth2.as.issuer.OAuthIssuer; +import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl; +import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest; +import org.apache.oltu.oauth2.as.response.OAuthASResponse; +import org.apache.oltu.oauth2.as.response.OAuthASResponse.OAuthTokenResponseBuilder; +import org.apache.oltu.oauth2.common.OAuth; +import org.apache.oltu.oauth2.common.error.OAuthError; +import org.apache.oltu.oauth2.common.exception.OAuthProblemException; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; +import org.apache.oltu.oauth2.common.message.OAuthResponse; +import org.apache.oltu.oauth2.common.message.types.GrantType; +import org.apache.oltu.oauth2.common.message.types.ResponseType; +import org.apache.oltu.oauth2.common.utils.OAuthUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.net.URISyntaxException; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +/** + * oauth2服务端默认实现 + * + * @author tanghc + */ +@Service +@Slf4j +public class OAuth2ServiceImpl implements OAuth2Service { + + + private static final String TOKEN_TYPE = "Bearer"; + + private OAuthIssuer oauthIssuer = new OAuthIssuerImpl(new MD5Generator()); + + @Autowired + private OAuth2Manager oauth2Manager; + + @Autowired + private AppIdManager appIdManager; + + @Override + public OAuthResponse authorize(HttpServletRequest request, HttpServletResponse resp, OAuth2Config authConfig) + throws URISyntaxException, OAuthSystemException { + try { + // 构建OAuth 授权请求 + OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request); + String clientId = oauthRequest.getClientId(); + // 检查传入的客户端id是否正确 + if (!appIdManager.isValidAppId(clientId)) { + return OAuthASResponse.errorResponse(HttpServletResponse.SC_BAD_REQUEST) + .setError(OAuthError.TokenResponse.INVALID_CLIENT) + .setErrorDescription(OAuthError.TokenResponse.INVALID_CLIENT).buildJSONMessage(); + } + + // 如果用户没有登录,跳转到登陆页面 + OpenUser user = null; + try { + user = oauth2Manager.login(request); + } catch (LoginErrorException e) { + log.error(e.getMessage(), e); + request.setAttribute("error", e.getMessage()); + try { + request.getRequestDispatcher(authConfig.getOauth2LoginUri()).forward(request, resp); + return null; + } catch (Exception e1) { + log.error(e1.getMessage(), e1); + throw new RuntimeException(e1); + } + } + + // 生成授权码 + String authorizationCode = null; + // responseType目前仅支持CODE,另外还有TOKEN + String responseType = oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE); + if (responseType.equals(ResponseType.CODE.toString())) { + OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator()); + authorizationCode = oauthIssuerImpl.authorizationCode(); + oauth2Manager.addAuthCode(authorizationCode, user); + } + // 进行OAuth响应构建 + OAuthASResponse.OAuthAuthorizationResponseBuilder builder = OAuthASResponse.authorizationResponse(request, + HttpServletResponse.SC_FOUND); + // 设置授权码 + builder.setCode(authorizationCode); + // 得到到客户端重定向地址 + String redirectURI = oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI); + + // 构建响应 + return builder.location(redirectURI).buildQueryMessage(); + } catch (OAuthProblemException e) { + // 出错处理 + String redirectUri = e.getRedirectUri(); + if (OAuthUtils.isEmpty(redirectUri)) { + // 告诉客户端没有传入redirectUri直接报错 + String error = "OAuth redirectUri needs to be provided by client!!!"; + return OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND) + .error(OAuthProblemException.error(error)).location(redirectUri).buildQueryMessage(); + } else { + // 返回错误消息(如?error=) + return OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND).error(e).location(redirectUri) + .buildQueryMessage(); + } + + } + } + + + @Override + public FetchTokenResult accessToken(FetchTokenParam fetchTokenParam, OAuth2Config authConfig) { + try { + // 这里需要检查client_id和client_secret,但是已经通过网关接口访问进来了 + // 表示client_id和client_secret是合法的 + + // 检查验证类型,如果是第一次进来用code换取accessToken + String grant_type = fetchTokenParam.getGrant_type(); + if (GrantType.AUTHORIZATION_CODE.toString().equals(grant_type)) { + String authCode = fetchTokenParam.getCode(); + if (!oauth2Manager.checkAuthCode(authCode)) { + FetchTokenResult errorResult = new FetchTokenResult(); + errorResult.setError(OAuthError.CodeResponse.INVALID_REQUEST); + errorResult.setError_description("invalid request"); + return errorResult; + } + // 生成Access Token + OpenUser user = oauth2Manager.getUserByAuthCode(authCode); + if (user == null) { + throw OAuthProblemException.error("Can not found user by code."); + } + + TokenPair tokenPair = this.createNewToken(user); + + oauth2Manager.addAccessToken(tokenPair.getAccessToken(), tokenPair.getRefreshToken(), user); + + // 生成OAuth响应 + return this.buildTokenResult(tokenPair, user); + } else if (GrantType.REFRESH_TOKEN.toString().equals(grant_type)) { + // 用refreshToken来刷新accessToken + String refreshToken = fetchTokenParam.getRefresh_token(); + if (StringUtils.isEmpty(refreshToken)) { + FetchTokenResult errorResult = new FetchTokenResult(); + errorResult.setError(OAuthError.ResourceResponse.EXPIRED_TOKEN); + errorResult.setError_description("expired token"); + return errorResult; + } + + RefreshToken refreshTokenObj = oauth2Manager.getRefreshToken(refreshToken); + if (refreshTokenObj == null) { + FetchTokenResult errorResult = new FetchTokenResult(); + errorResult.setError(OAuthError.ResourceResponse.INVALID_TOKEN); + errorResult.setError_description("invalid token"); + return errorResult; + } + + OpenUser user = refreshTokenObj.getOpenUser(); + // 老的token对 + TokenPair oldTokenPair = new TokenPair(); + oldTokenPair.setAccessToken(refreshTokenObj.getAccessToken()); + oldTokenPair.setRefreshToken(refreshToken); + // 创建一对新的accessToken和refreshToken + TokenPair newTokenPair = this.createRefreshToken(user, oldTokenPair); + + this.afterRefreshToken(oldTokenPair, newTokenPair, user); + + // 返回新的accessToken和refreshToken + return this.buildTokenResult(newTokenPair, user); + } else { + FetchTokenResult errorResult = new FetchTokenResult(); + errorResult.setError(OAuthError.TokenResponse.INVALID_GRANT); + errorResult.setError_description("invalid grant"); + return errorResult; + } + + } catch (OAuthProblemException e) { + log.error(e.getMessage(), e); + FetchTokenResult errorResult = new FetchTokenResult(); + errorResult.setError(e.getMessage()); + errorResult.setError_description("invalid grant"); + return errorResult; + } + } + + /** + * 刷新token后续操作 + * + * @param oldTokenPair 老的token + * @param newTokenPair 新的token + * @param user 用户 + */ + protected void afterRefreshToken(TokenPair oldTokenPair, TokenPair newTokenPair, OpenUser user) { + // 保存token + oauth2Manager.addAccessToken(newTokenPair.getAccessToken(), newTokenPair.getRefreshToken(), user); + + // 删除老的accessToken + oauth2Manager.removeAccessToken(oldTokenPair.getAccessToken()); + // 删除老的refreshToken + oauth2Manager.removeRefreshToken(oldTokenPair.getRefreshToken()); + } + + /** + * 创建新的token + * + * @param user + * @return 返回新token + */ + protected TokenPair createNewToken(OpenUser user) { + return this.createDefaultTokenPair(); + } + + /** + * 返回刷新后token + * + * @param user 用户 + * @param oldTokenPair 旧的token + * @return 返回新的token + */ + protected TokenPair createRefreshToken(OpenUser user, TokenPair oldTokenPair) { + return this.createDefaultTokenPair(); + } + + private TokenPair createDefaultTokenPair() { + TokenPair tokenPair = new TokenPair(); + try { + String accessToken = oauthIssuer.accessToken(); + String refreshToken = oauthIssuer.refreshToken(); + + tokenPair.setAccessToken(accessToken); + tokenPair.setRefreshToken(refreshToken); + + return tokenPair; + } catch (OAuthSystemException e) { + throw new RuntimeException(e); + } + } + + private FetchTokenResult buildTokenResult(TokenPair tokenPair, OpenUser user) { + OAuth2Config auth2Config = OAuth2Config.getInstance(); + FetchTokenResult fetchTokenResult = new FetchTokenResult(); + fetchTokenResult.setApp_auth_token(tokenPair.getAccessToken()); + fetchTokenResult.setApp_refresh_token(tokenPair.getRefreshToken()); + fetchTokenResult.setUser_id(user.getUserId()); + fetchTokenResult.setExpires_in(auth2Config.getAccessTokenExpiresIn()); + fetchTokenResult.setRe_expires_in(auth2Config.getRefreshTokenExpiresIn()); + return fetchTokenResult; + } + + private OAuthResponse buildAccessTokenResponse(TokenPair tokenPair, long expiresIn, OpenUser user) throws OAuthSystemException { + OAuthTokenResponseBuilder resp = OAuthASResponse.tokenResponse(HttpServletResponse.SC_OK); + + Map param = oauth2Manager.getParam(user); + if (param != null) { + Set> entrySet = param.entrySet(); + for (Entry entry : entrySet) { + resp.setParam(entry.getKey(), entry.getValue()); + } + } + + return resp + .setAccessToken(tokenPair.getAccessToken()) + .setRefreshToken(tokenPair.getRefreshToken()) + .setTokenType(TOKEN_TYPE) + .setExpiresIn(String.valueOf(expiresIn)) + .buildJSONMessage(); + + } + +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/config/OpenServiceConfig.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/config/OpenServiceConfig.java new file mode 100644 index 00000000..94298534 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/config/OpenServiceConfig.java @@ -0,0 +1,20 @@ +package com.gitee.sop.sopauth.config; + +import com.gitee.sop.servercommon.bean.ServiceConfig; +import com.gitee.sop.servercommon.configuration.AlipayServiceConfiguration; +import org.springframework.context.annotation.Configuration; + +/** + * 使用支付宝开放平台功能 + * + * @author tanghc + */ +@Configuration +public class OpenServiceConfig extends AlipayServiceConfiguration { + + + static { + ServiceConfig.getInstance().getI18nModules().add("i18n/isp/goods_error"); + } + +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/controller/CallbackController.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/controller/CallbackController.java new file mode 100644 index 00000000..02018d43 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/controller/CallbackController.java @@ -0,0 +1,54 @@ +package com.gitee.sop.sopauth.controller; + +import com.gitee.sop.sdk.client.OpenClient; +import com.gitee.sop.sdk.model.OpenAuthTokenAppModel; +import com.gitee.sop.sdk.request.OpenAuthTokenAppRequest; +import com.gitee.sop.sdk.response.OpenAuthTokenAppResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * @author tanghc + */ +@Controller +@Slf4j +public class CallbackController { + String url = "http://localhost:8081/api"; // zuul + String appId = "2019032617262200001"; + // 支付宝私钥 + String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ="; + + OpenClient openClient = new OpenClient(url, appId, privateKey); + + /** + * 模拟开发者回调,这里需要开发者自己实现.通过code换取token + * @return + */ + @GetMapping("oauth2callback") + @ResponseBody + public String callback(HttpServletRequest servletRequest, HttpServletResponse servletResponse) { + servletResponse.setCharacterEncoding("UTF-8"); + String app_id = servletRequest.getParameter("app_id"); + String code = servletRequest.getParameter("code"); + + OpenAuthTokenAppRequest request = new OpenAuthTokenAppRequest(); + OpenAuthTokenAppModel model = new OpenAuthTokenAppModel(); + model.setCode(code); + model.setGrant_type("authorization_code"); + request.setBizModel(model); + + // 根据code获取token + OpenAuthTokenAppResponse response = openClient.execute(request); + if (response.isSuccess()) { + // 成功拿到token,开发者在这里保存token信息 + // 后续使用token进行接口访问 + log.info("授权成功,body:{}", response.getBody()); + } + return response.getBody(); + } +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/controller/OAuth2Controller.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/controller/OAuth2Controller.java new file mode 100644 index 00000000..062d593a --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/controller/OAuth2Controller.java @@ -0,0 +1,85 @@ +package com.gitee.sop.sopauth.controller; + +import com.gitee.sop.servercommon.annotation.ApiMapping; +import com.gitee.sop.sopauth.auth.FetchTokenParam; +import com.gitee.sop.sopauth.auth.FetchTokenResult; +import com.gitee.sop.sopauth.auth.OAuth2Config; +import com.gitee.sop.sopauth.auth.OAuth2Service; +import org.apache.oltu.oauth2.common.exception.OAuthSystemException; +import org.apache.oltu.oauth2.common.message.OAuthResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.net.URI; +import java.net.URISyntaxException; + +/** + * 授权认证 + */ +@Controller +@RequestMapping("oauth2") +public class OAuth2Controller { + + @Autowired + private OAuth2Service oAuth2Service; + + // 第一步:授权URL拼装 + // https://localhost:8087/oauth2/appToAppAuth?app_id=2019032617262200001&redirect_uri=http%3a%2f%2flocalhost%3a8087%2foauth2callback + @GetMapping("appToAppAuth") + public String appToAppAuth(HttpServletRequest request, ModelMap modelMap) { + String app_id = request.getParameter("app_id"); + String redirect_uri = request.getParameter("redirect_uri"); + modelMap.put("response_type", "code"); + modelMap.put("client_id", app_id); + modelMap.put("redirect_uri", redirect_uri); + return "oauth2login"; + } + + /** + * 第二步:点击登录,到这里拿code + * oauth2认证获取code + * @param request + * @param resp + * @return 返回code + * @throws URISyntaxException + * @throws OAuthSystemException + */ + @RequestMapping("authorize") + public Object authorize(HttpServletRequest request, HttpServletResponse resp) throws URISyntaxException, OAuthSystemException { + OAuthResponse response = oAuth2Service.authorize(request, resp, OAuth2Config.getInstance()); + if(response == null) { + return null; + } + HttpHeaders headers = new HttpHeaders(); + headers.setLocation(new URI(response.getLocationUri())); + return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus())); + } + + + /** + * 第三步,通过code获取token + * 或者,通过refresh_token换取token + * @param param + * @return + */ + @ApiMapping("open.auth.token.app") + @ResponseBody + public FetchTokenResult fetchToken(FetchTokenParam param) { + FetchTokenResult fetchTokenResult = oAuth2Service.accessToken(param, OAuth2Config.getInstance()); + return fetchTokenResult; + } + + @RequestMapping("login") + public String oauth2login() { + return "oauth2login"; + } +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/entity/IsvInfo.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/entity/IsvInfo.java new file mode 100644 index 00000000..64361500 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/entity/IsvInfo.java @@ -0,0 +1,32 @@ +package com.gitee.sop.sopauth.entity; + +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + + +/** + * 表名:isv_info + * 备注:isv信息表 + * + * @author tanghc + */ +@Table(name = "isv_info") +@Data +public class IsvInfo { + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + /** 数据库字段:id */ + private Long id; + + /** appKey, 数据库字段:app_key */ + private String appKey; + + /** 1启用,2禁用, 数据库字段:status */ + private Byte status; +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/entity/UserInfo.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/entity/UserInfo.java new file mode 100644 index 00000000..e4eaa0a4 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/entity/UserInfo.java @@ -0,0 +1,48 @@ +package com.gitee.sop.sopauth.entity; + +import com.gitee.sop.sopauth.auth.OpenUser; +import lombok.Data; + +import javax.persistence.Column; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Date; + + +/** + * 表名:user_info + * 备注:用户信息表 + * + * @author tanghc + */ +@Table(name = "user_info") +@Data +public class UserInfo implements OpenUser { + @Id + @Column(name = "id") + @GeneratedValue(strategy = GenerationType.IDENTITY) + /** 数据库字段:id */ + private Long id; + + /** 用户名, 数据库字段:username */ + private String username; + + /** 密码, 数据库字段:password */ + private String password; + + /** 昵称, 数据库字段:nickname */ + private String nickname; + + /** 数据库字段:gmt_create */ + private Date gmtCreate; + + /** 数据库字段:gmt_modified */ + private Date gmtModified; + + @Override + public String getUserId() { + return String.valueOf(id); + } +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/mapper/IsvInfoMapper.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/mapper/IsvInfoMapper.java new file mode 100644 index 00000000..64687a97 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/mapper/IsvInfoMapper.java @@ -0,0 +1,11 @@ +package com.gitee.sop.sopauth.mapper; + +import com.gitee.fastmybatis.core.mapper.CrudMapper; +import com.gitee.sop.sopauth.entity.IsvInfo; + + +/** + * @author tanghc + */ +public interface IsvInfoMapper extends CrudMapper { +} diff --git a/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/mapper/UserInfoMapper.java b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/mapper/UserInfoMapper.java new file mode 100644 index 00000000..0c1c8219 --- /dev/null +++ b/sop-example/sop-auth/src/main/java/com/gitee/sop/sopauth/mapper/UserInfoMapper.java @@ -0,0 +1,11 @@ +package com.gitee.sop.sopauth.mapper; + +import com.gitee.fastmybatis.core.mapper.CrudMapper; +import com.gitee.sop.sopauth.entity.UserInfo; + + +/** + * @author tanghc + */ +public interface UserInfoMapper extends CrudMapper { +} diff --git a/sop-example/sop-auth/src/main/resources/application-dev.yml b/sop-example/sop-auth/src/main/resources/application-dev.yml new file mode 100644 index 00000000..636aaf6e --- /dev/null +++ b/sop-example/sop-auth/src/main/resources/application-dev.yml @@ -0,0 +1,35 @@ + +server: + port: 8087 + + +# 注册中心 +eureka: + port: 1111 + host: localhost + client: + serviceUrl: + defaultZone: http://${eureka.host}:${eureka.port}/eureka/ + +spring: + application: + name: sop-auth + + cloud: + zookeeper: + connect-string: localhost:2181 + + # 数据源 + datasource: + driver-class-name: com.mysql.jdbc.Driver + url: jdbc:mysql://localhost:3306/sop?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull + username: root + password: root + + thymeleaf: + cache: false + +logging: + level: + com: + gitee: debug \ No newline at end of file diff --git a/sop-example/sop-auth/src/main/resources/application.yml b/sop-example/sop-auth/src/main/resources/application.yml new file mode 100644 index 00000000..caf4dfcd --- /dev/null +++ b/sop-example/sop-auth/src/main/resources/application.yml @@ -0,0 +1,3 @@ +spring: + profiles: + active: dev \ No newline at end of file diff --git a/sop-example/sop-auth/src/main/resources/templates/oauth2login.html b/sop-example/sop-auth/src/main/resources/templates/oauth2login.html new file mode 100644 index 00000000..5c089835 --- /dev/null +++ b/sop-example/sop-auth/src/main/resources/templates/oauth2login.html @@ -0,0 +1,19 @@ + + + +用户登录 + + +
+
+ + + + 用户名:
+ 密码:
+ +
+ + \ No newline at end of file diff --git a/sop-example/sop-auth/src/test/java/com/gitee/sop/sopauth/RefreshTokenTest.java b/sop-example/sop-auth/src/test/java/com/gitee/sop/sopauth/RefreshTokenTest.java new file mode 100644 index 00000000..199f50b5 --- /dev/null +++ b/sop-example/sop-auth/src/test/java/com/gitee/sop/sopauth/RefreshTokenTest.java @@ -0,0 +1,40 @@ +package com.gitee.sop.sopauth; + +import com.gitee.sop.sdk.client.OpenClient; +import com.gitee.sop.sdk.model.OpenAuthTokenAppModel; +import com.gitee.sop.sdk.request.OpenAuthTokenAppRequest; +import com.gitee.sop.sdk.response.OpenAuthTokenAppResponse; +import junit.framework.TestCase; +import lombok.extern.slf4j.Slf4j; + +/** + * @author tanghc + */ +@Slf4j +public class RefreshTokenTest extends TestCase { + String url = "http://localhost:8081/api"; // zuul + String appId = "2019032617262200001"; + // 支付宝私钥 + String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ="; + + OpenClient openClient = new OpenClient(url, appId, privateKey); + + /** + * 根据refreshToken换取token + */ + public void testRefreshToken() { + OpenAuthTokenAppRequest request = new OpenAuthTokenAppRequest(); + OpenAuthTokenAppModel model = new OpenAuthTokenAppModel(); + model.setGrant_type("refresh_token"); + model.setRefresh_token("856faf8d77d3b985c1073557ce6ea724"); + request.setBizModel(model); + + OpenAuthTokenAppResponse response = openClient.execute(request); + if (response.isSuccess()) { + // 成功拿到token,开发者在这里保存token信息 + // 后续使用token进行接口访问 + log.info("换取token成功,body:{}", response.getBody()); + } + } + +} diff --git a/sop-example/sop-auth/src/test/java/com/gitee/sop/sopauth/SopAuthApplicationTests.java b/sop-example/sop-auth/src/test/java/com/gitee/sop/sopauth/SopAuthApplicationTests.java new file mode 100644 index 00000000..b5902b77 --- /dev/null +++ b/sop-example/sop-auth/src/test/java/com/gitee/sop/sopauth/SopAuthApplicationTests.java @@ -0,0 +1,16 @@ +package com.gitee.sop.sopauth; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class SopAuthApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/sop-example/sop-book/sop-book-web/pom.xml b/sop-example/sop-book/sop-book-web/pom.xml index aa01c9b5..868507db 100644 --- a/sop-example/sop-book/sop-book-web/pom.xml +++ b/sop-example/sop-book/sop-book-web/pom.xml @@ -23,7 +23,7 @@ com.gitee.sop sop-service-common - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT com.gitee.sop diff --git a/sop-example/sop-easyopen/pom.xml b/sop-example/sop-easyopen/pom.xml index 5151c186..9b07d62a 100644 --- a/sop-example/sop-easyopen/pom.xml +++ b/sop-example/sop-easyopen/pom.xml @@ -29,7 +29,7 @@ com.gitee.sop sop-service-common - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT org.springframework.cloud diff --git a/sop-example/sop-springmvc/pom.xml b/sop-example/sop-springmvc/pom.xml index fc4739a7..4479a7f0 100644 --- a/sop-example/sop-springmvc/pom.xml +++ b/sop-example/sop-springmvc/pom.xml @@ -20,7 +20,7 @@ com.gitee.sop sop-service-common - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT diff --git a/sop-example/sop-story/sop-story-web/pom.xml b/sop-example/sop-story/sop-story-web/pom.xml index b912c66e..3b9599bc 100644 --- a/sop-example/sop-story/sop-story-web/pom.xml +++ b/sop-example/sop-story/sop-story-web/pom.xml @@ -23,7 +23,7 @@ com.gitee.sop sop-service-common - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT com.gitee.sop diff --git a/sop-gateway/pom.xml b/sop-gateway/pom.xml index 49d1b6fd..fc932851 100644 --- a/sop-gateway/pom.xml +++ b/sop-gateway/pom.xml @@ -23,7 +23,7 @@ com.gitee.sop sop-gateway-common - 1.5.0-SNAPSHOT + 1.6.0-SNAPSHOT diff --git a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/model/OpenAuthTokenAppModel.java b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/model/OpenAuthTokenAppModel.java new file mode 100644 index 00000000..8fa9c55c --- /dev/null +++ b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/model/OpenAuthTokenAppModel.java @@ -0,0 +1,28 @@ +package com.gitee.sop.sdk.model; + +import lombok.Data; + +/** + * Oauth2认证参数 + * @author tanghc + */ +@Data +public class OpenAuthTokenAppModel { + + /** + * 授权类型。
+ * 如果使用app_auth_code换取token,则为authorization_code, + * 如果使用refresh_token换取新的token,则为refresh_token + */ + private String grant_type; + + /** + * 授权码.与refresh_token二选一,用户对应用授权后得到,即第一步中开发者获取到的app_auth_code值 + */ + private String code; + + /** + * 刷新令牌.与code二选一,可为空,刷新令牌时使用 + */ + private String refresh_token; +} diff --git a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/OpenAuthTokenAppRequest.java b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/OpenAuthTokenAppRequest.java new file mode 100644 index 00000000..0afcfb0b --- /dev/null +++ b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/OpenAuthTokenAppRequest.java @@ -0,0 +1,13 @@ +package com.gitee.sop.sdk.request; + +import com.gitee.sop.sdk.response.OpenAuthTokenAppResponse; + +/** + * @author tanghc + */ +public class OpenAuthTokenAppRequest extends BaseRequest { + @Override + protected String method() { + return "open.auth.token.app"; + } +} diff --git a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/response/GetStoryResponse.java b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/response/GetStoryResponse.java index 69c8b959..ff6d900b 100644 --- a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/response/GetStoryResponse.java +++ b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/response/GetStoryResponse.java @@ -1,10 +1,12 @@ package com.gitee.sop.sdk.response; import lombok.Data; +import lombok.EqualsAndHashCode; import java.util.Date; @Data +@EqualsAndHashCode(callSuper = false) public class GetStoryResponse extends BaseResponse { private Long id; private String name; diff --git a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/response/OpenAuthTokenAppResponse.java b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/response/OpenAuthTokenAppResponse.java new file mode 100644 index 00000000..eb248ccb --- /dev/null +++ b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/response/OpenAuthTokenAppResponse.java @@ -0,0 +1,30 @@ +package com.gitee.sop.sdk.response; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * @author tanghc + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class OpenAuthTokenAppResponse extends BaseResponse { + /** + * 授权令牌 + */ + private String app_auth_token; + + /** + * 用户id + */ + private String user_id; + + /** + * 刷新令牌时使用。 + * 刷新令牌后,我们会保证老的app_auth_token从刷新开始10分钟内可继续使用,请及时替换为最新token + */ + private String app_refresh_token; + + private String error; + private String error_description; +} diff --git a/sop-website/website-front/assets/image/auth.png b/sop-website/website-front/assets/image/auth.png new file mode 100644 index 00000000..6965e7e9 Binary files /dev/null and b/sop-website/website-front/assets/image/auth.png differ diff --git a/sop-website/website-front/pages/doc/auth.html b/sop-website/website-front/pages/doc/auth.html new file mode 100644 index 00000000..e3ef0fe5 --- /dev/null +++ b/sop-website/website-front/pages/doc/auth.html @@ -0,0 +1,288 @@ + + + + + 应用授权 + + + + + + + + + + + + + + + +
+ +
+ + +
+
+ +
+
+

第三方应用授权

+
+
+ 概述 +
+
+
+

1、用户对开发者进行应用授权后,开发者可以帮助用户完成相应的业务逻辑。

+

2、授权采用标准的OAuth 2.0流程。

+
+ +

授权流程

+ + +
+
+ 快速接入 +
+
+
+

第一步:应用授权URL拼装

+

拼接规则:

+

http://openauth.yourdomain.com/oauth2/appToAppAuth?app_id=2019032617262200001&redirect_uri=http%3a%2f%2flocalhost%3a8087%2foauth2callback

+

参数说明:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数参数名称类型必填描述范例
app_id开发者应用的AppIdString开发者应用的AppId2015101400446982
redirect_uri回调页面String参数需要UrlEncodehttp%3A%2F%2Fexample.com
+ +

第二步:获取code

+

授权成功后,客户端会跳转至开发者定义的回调页面(即redirect_uri参数对应的url),在回调页面请求中会带上当次授权的授权码code和开发者的app_id,示例如下:

+

http://devloper.domain.com/oauth2callback?app_id=2015101400446982&code=ca34ea491e7146cc87d25fca24c4cD11

+ +

第三步:使用code换取app_auth_token

+

接口名称:open.auth.token.app

+

开发者通过code可以换取app_auth_token、授权用户的userId。

+

注意:应用授权的code唯一,code使用一次后失效,有效期24小时; app_auth_token永久有效。

+

请求参数说明

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数参数名称类型必填描述范例
grant_type授权类型String如果使用code换取token,则为authorization_code,如果使用refresh_token换取新的token,则为refresh_tokenauthorization_code
code授权码String与refresh_token二选一,用户对应用授权后得到,即第一步中开发者获取到的code值bf67d8d5ed754af297f72cc482287X62
refresh_token刷新令牌String与code二选一,可为空,刷新令牌时使用201510BB0c409dd5758b4d939d4008a525463X62
+ +

接口请求SDK示例

+
+   @GetMapping("oauth2callback")
+    @ResponseBody
+    public String callback(HttpServletRequest servletRequest, HttpServletResponse servletResponse) {
+        String app_id = servletRequest.getParameter("app_id");
+        String code = servletRequest.getParameter("code");
+
+        OpenAuthTokenAppRequest request = new OpenAuthTokenAppRequest();
+        OpenAuthTokenAppModel model = new OpenAuthTokenAppModel();
+        model.setCode(code);
+        model.setGrant_type("authorization_code");
+        request.setBizModel(model);
+
+        // 根据code获取token
+        OpenAuthTokenAppResponse response = openClient.execute(request);
+        if (response.isSuccess()) {
+            // 成功拿到token,开发者在这里保存token信息
+            // 后续使用token进行接口访问
+            log.info("授权成功,body:{}", response.getBody());
+        }
+        return response.getBody();
+    }
+
+
+

同步响应参数说明

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
参数参数名称类型必填描述范例
app_auth_token用户授权令牌String通过该令牌来帮助用户发起请求,完成业务856faf8d77d3b985c1073557ce6ea724
user_id授权用户的IDString授权者id1
expires_in令牌有效期Long负值表示永久有效-1
re_expires_in刷新令牌有效期Long负值表示永久有效-3
app_refresh_token刷新令牌时使用String刷新令牌后,我们会保证老的app_auth_token从刷新开始10分钟内可继续使用,请及时替换为最新token88e68196d2359667f0dc8136e6c86803
+

同步响应结果示例

+
{
+    "open_auth_token_app_response": {
+        "code": "10000",
+        "msg": "Success",
+        "app_auth_token": "88e68196d2359667f0dc8136e6c86803",
+        "app_refresh_token": "856faf8d77d3b985c1073557ce6ea724",
+        "expires_in": -1,
+        "re_expires_in": -3,
+        "user_id": "1"
+    },
+    "sign": "xxx"
+}
+
+ +

刷新token

+

+OpenAuthTokenAppRequest request = new OpenAuthTokenAppRequest();
+OpenAuthTokenAppModel model = new OpenAuthTokenAppModel();
+model.setGrant_type("refresh_token");
+model.setRefresh_token("856faf8d77d3b985c1073557ce6ea724");
+request.setBizModel(model);
+
+OpenAuthTokenAppResponse response = openClient.execute(request);
+if (response.isSuccess()) {
+    // 成功拿到token,开发者在这里保存token信息
+    // 后续使用token进行接口访问
+    log.info("换取token成功,body:{}", response.getBody());
+}
+        
+
+
+ + + + + + + \ No newline at end of file diff --git a/sop-website/website-front/pages/doc/doc.html b/sop-website/website-front/pages/doc/doc.html index 9e491b3d..3d9534ee 100644 --- a/sop-website/website-front/pages/doc/doc.html +++ b/sop-website/website-front/pages/doc/doc.html @@ -222,7 +222,7 @@ String 否 40 - 暂未实现 + 详见应用授权概述 diff --git a/sop.sql b/sop.sql index eb94db0b..d3f07bfb 100644 --- a/sop.sql +++ b/sop.sql @@ -16,6 +16,7 @@ DROP TABLE IF EXISTS `isv_info`; DROP TABLE IF EXISTS `config_route_limit`; DROP TABLE IF EXISTS `config_route_base`; DROP TABLE IF EXISTS `admin_user_info`; +DROP TABLE IF EXISTS `user_info`; CREATE TABLE `admin_user_info` ( @@ -106,7 +107,16 @@ CREATE TABLE `perm_role_permission` ( ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COMMENT='角色权限表'; - +CREATE TABLE `user_info` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `username` varchar(64) NOT NULL DEFAULT '' COMMENT '用户名', + `password` varchar(128) NOT NULL DEFAULT '' COMMENT '密码', + `nickname` varchar(64) NOT NULL DEFAULT '' COMMENT '昵称', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + KEY `idx_unamepwd` (`username`,`password`) USING BTREE +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 COMMENT='用户信息表'; SET FOREIGN_KEY_CHECKS = @PREVIOUS_FOREIGN_KEY_CHECKS; @@ -191,7 +201,12 @@ INSERT INTO `perm_role_permission` (`id`, `role_code`, `route_id`, `gmt_create`, ALTER TABLE `perm_role_permission` ENABLE KEYS; UNLOCK TABLES; - +LOCK TABLES `user_info` WRITE; +ALTER TABLE `user_info` DISABLE KEYS; +INSERT INTO `user_info` (`id`, `username`, `password`, `nickname`, `gmt_create`, `gmt_modified`) VALUES + (1,'zhangsan','123456','张三','2019-04-27 08:32:57','2019-04-27 08:32:57'); +ALTER TABLE `user_info` ENABLE KEYS; +UNLOCK TABLES; SET FOREIGN_KEY_CHECKS = @PREVIOUS_FOREIGN_KEY_CHECKS;