@@ -20,7 +20,7 @@ | |||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | |||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | |||
<java.version>1.8</java.version> | |||
<spring.social.version>1.1.6.RELEASE</spring.social.version> | |||
<justauth-spring-boot.version>1.0.0</justauth-spring-boot.version> | |||
</properties> | |||
<dependencies> | |||
@@ -35,11 +35,22 @@ | |||
<scope>test</scope> | |||
</dependency> | |||
<dependency> | |||
<groupId>org.springframework.boot</groupId> | |||
<artifactId>spring-boot-starter-data-redis</artifactId> | |||
</dependency> | |||
<!-- 对象池,使用redis时必须引入 --> | |||
<dependency> | |||
<groupId>org.apache.commons</groupId> | |||
<artifactId>commons-pool2</artifactId> | |||
</dependency> | |||
<!-- oauth工具类 --> | |||
<dependency> | |||
<groupId>me.zhyd.oauth</groupId> | |||
<artifactId>JustAuth</artifactId> | |||
<version>1.9.5</version> | |||
<groupId>com.xkcoding</groupId> | |||
<artifactId>justauth-spring-boot-starter</artifactId> | |||
<version>${justauth-spring-boot.version}</version> | |||
</dependency> | |||
<dependency> | |||
@@ -8,13 +8,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; | |||
* 启动器 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.social | |||
* @description: 启动器 | |||
* @author: yangkai.shen | |||
* @date: Created in 2019-02-19 16:04 | |||
* @copyright: Copyright (c) 2019 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
* @author yangkai.shen | |||
* @date Created in 2019-08-09 13:51 | |||
*/ | |||
@SpringBootApplication | |||
public class SpringBootDemoSocialApplication { | |||
@@ -0,0 +1,39 @@ | |||
package com.xkcoding.social.config.justauth; | |||
import me.zhyd.oauth.cache.AuthStateCache; | |||
import org.springframework.context.annotation.Bean; | |||
import org.springframework.context.annotation.Configuration; | |||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; | |||
import org.springframework.data.redis.core.RedisTemplate; | |||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; | |||
import org.springframework.data.redis.serializer.StringRedisSerializer; | |||
import java.io.Serializable; | |||
/** | |||
* <p> | |||
* JustAuth自动装配 | |||
* </p> | |||
* | |||
* @author yangkai.shen | |||
* @date Created in 2019-08-09 14:21 | |||
*/ | |||
@Configuration | |||
public class JustAuthConfig { | |||
/** | |||
* 默认情况下的模板只能支持RedisTemplate<String, String>,也就是只能存入字符串,因此支持序列化 | |||
*/ | |||
@Bean | |||
public RedisTemplate<String, Serializable> redisCacheTemplate(LettuceConnectionFactory redisConnectionFactory) { | |||
RedisTemplate<String, Serializable> template = new RedisTemplate<>(); | |||
template.setKeySerializer(new StringRedisSerializer()); | |||
template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); | |||
template.setConnectionFactory(redisConnectionFactory); | |||
return template; | |||
} | |||
@Bean | |||
public AuthStateCache authStateCache(RedisTemplate<String, String> redisCacheTemplate) { | |||
return new JustAuthRedisStateCache(redisCacheTemplate); | |||
} | |||
} |
@@ -0,0 +1,70 @@ | |||
package com.xkcoding.social.config.justauth; | |||
import lombok.RequiredArgsConstructor; | |||
import me.zhyd.oauth.cache.AuthStateCache; | |||
import org.springframework.data.redis.core.RedisTemplate; | |||
import java.util.concurrent.TimeUnit; | |||
/** | |||
* <p> | |||
* Redis作为JustAuth的State的缓存 | |||
* </p> | |||
* | |||
* @author yangkai.shen | |||
* @date Created in 2019-08-09 14:22 | |||
*/ | |||
@RequiredArgsConstructor | |||
public class JustAuthRedisStateCache implements AuthStateCache { | |||
private final RedisTemplate<String, String> redisTemplate; | |||
private static final long DEF_TIMEOUT = 3 * 60 * 1000; | |||
/** | |||
* 存入缓存 | |||
* | |||
* @param key 缓存key | |||
* @param value 缓存内容 | |||
*/ | |||
@Override | |||
public void cache(String key, String value) { | |||
this.cache(key, value, DEF_TIMEOUT); | |||
} | |||
/** | |||
* 存入缓存 | |||
* | |||
* @param key 缓存key | |||
* @param value 缓存内容 | |||
* @param timeout 指定缓存过期时间(毫秒) | |||
*/ | |||
@Override | |||
public void cache(String key, String value, long timeout) { | |||
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.MILLISECONDS); | |||
} | |||
/** | |||
* 获取缓存内容 | |||
* | |||
* @param key 缓存key | |||
* @return 缓存内容 | |||
*/ | |||
@Override | |||
public String get(String key) { | |||
return redisTemplate.opsForValue().get(key); | |||
} | |||
/** | |||
* 是否存在key,如果对应key的value值已过期,也返回false | |||
* | |||
* @param key 缓存key | |||
* @return true:存在key,并且value没过期;false:key不存在或者已过期 | |||
*/ | |||
@Override | |||
public boolean containsKey(String key) { | |||
Long expire = redisTemplate.getExpire(key, TimeUnit.MILLISECONDS); | |||
if (expire == null) { | |||
expire = 0L; | |||
} | |||
return expire > 0; | |||
} | |||
} |
@@ -1,15 +1,14 @@ | |||
package com.xkcoding.social.controller; | |||
import cn.hutool.core.lang.Dict; | |||
import cn.hutool.core.util.StrUtil; | |||
import cn.hutool.json.JSONUtil; | |||
import com.xkcoding.social.props.OAuthProperties; | |||
import com.xkcoding.justauth.AuthRequestFactory; | |||
import lombok.RequiredArgsConstructor; | |||
import lombok.extern.slf4j.Slf4j; | |||
import me.zhyd.oauth.config.AuthConfig; | |||
import me.zhyd.oauth.config.AuthSource; | |||
import me.zhyd.oauth.model.AuthCallback; | |||
import me.zhyd.oauth.model.AuthResponse; | |||
import me.zhyd.oauth.request.*; | |||
import me.zhyd.oauth.request.AuthRequest; | |||
import me.zhyd.oauth.utils.AuthStateUtils; | |||
import org.springframework.beans.factory.annotation.Autowired; | |||
import org.springframework.web.bind.annotation.GetMapping; | |||
@@ -19,6 +18,9 @@ import org.springframework.web.bind.annotation.RestController; | |||
import javax.servlet.http.HttpServletResponse; | |||
import java.io.IOException; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.stream.Collectors; | |||
/** | |||
* <p> | |||
@@ -38,14 +40,15 @@ import java.io.IOException; | |||
@RequestMapping("/oauth") | |||
@RequiredArgsConstructor(onConstructor_ = @Autowired) | |||
public class OauthController { | |||
private final OAuthProperties properties; | |||
private final AuthRequestFactory factory; | |||
/** | |||
* 登录类型 | |||
*/ | |||
@GetMapping | |||
public Dict loginType() { | |||
return Dict.create().set("QQ登录", "http://oauth.xkcoding.com/demo/oauth/login/qq").set("GitHub登录", "http://oauth.xkcoding.com/demo/oauth/login/github").set("微信登录", "http://oauth.xkcoding.com/demo/oauth/login/wechat").set("Google登录", "http://oauth.xkcoding.com/demo/oauth/login/google").set("Microsoft 登录", "http://oauth.xkcoding.com/demo/oauth/login/microsoft").set("小米登录", "http://oauth.xkcoding.com/demo/oauth/login/mi"); | |||
public Map<String, String> loginType() { | |||
List<String> oauthList = factory.oauthList(); | |||
return oauthList.stream().collect(Collectors.toMap(oauth -> oauth.toLowerCase() + "登录", oauth -> "http://oauth.xkcoding.com/demo/oauth/login/" + oauth.toLowerCase())); | |||
} | |||
/** | |||
@@ -57,8 +60,8 @@ public class OauthController { | |||
*/ | |||
@RequestMapping("/login/{oauthType}") | |||
public void renderAuth(@PathVariable String oauthType, HttpServletResponse response) throws IOException { | |||
AuthRequest authRequest = getAuthRequest(oauthType); | |||
response.sendRedirect(authRequest.authorize(AuthStateUtils.createState())); | |||
AuthRequest authRequest = factory.get(getAuthSource(oauthType)); | |||
response.sendRedirect(authRequest.authorize(oauthType + "::" + AuthStateUtils.createState())); | |||
} | |||
/** | |||
@@ -70,59 +73,17 @@ public class OauthController { | |||
*/ | |||
@RequestMapping("/{oauthType}/callback") | |||
public AuthResponse login(@PathVariable String oauthType, AuthCallback callback) { | |||
AuthRequest authRequest = getAuthRequest(oauthType); | |||
AuthRequest authRequest = factory.get(getAuthSource(oauthType)); | |||
AuthResponse response = authRequest.login(callback); | |||
log.info("【response】= {}", JSONUtil.toJsonStr(response)); | |||
return response; | |||
} | |||
private AuthRequest getAuthRequest(String oauthType) { | |||
AuthSource authSource = AuthSource.valueOf(oauthType.toUpperCase()); | |||
switch (authSource) { | |||
case QQ: | |||
return getQqAuthRequest(); | |||
case GITHUB: | |||
return getGithubAuthRequest(); | |||
case WECHAT: | |||
return getWechatAuthRequest(); | |||
case GOOGLE: | |||
return getGoogleAuthRequest(); | |||
case MICROSOFT: | |||
return getMicrosoftAuthRequest(); | |||
case MI: | |||
return getMiAuthRequest(); | |||
default: | |||
throw new RuntimeException("暂不支持的第三方登录"); | |||
private AuthSource getAuthSource(String type) { | |||
if (StrUtil.isNotBlank(type)) { | |||
return AuthSource.valueOf(type.toUpperCase()); | |||
} else { | |||
throw new RuntimeException("不支持的类型"); | |||
} | |||
} | |||
private AuthRequest getQqAuthRequest() { | |||
AuthConfig authConfig = properties.getQq(); | |||
return new AuthQqRequest(authConfig); | |||
} | |||
private AuthRequest getGithubAuthRequest() { | |||
AuthConfig authConfig = properties.getGithub(); | |||
return new AuthGithubRequest(authConfig); | |||
} | |||
private AuthRequest getWechatAuthRequest() { | |||
AuthConfig authConfig = properties.getWechat(); | |||
return new AuthWeChatRequest(authConfig); | |||
} | |||
private AuthRequest getGoogleAuthRequest() { | |||
AuthConfig authConfig = properties.getGoogle(); | |||
return new AuthGoogleRequest(authConfig); | |||
} | |||
private AuthRequest getMicrosoftAuthRequest() { | |||
AuthConfig authConfig = properties.getMicrosoft(); | |||
return new AuthMicrosoftRequest(authConfig); | |||
} | |||
private AuthRequest getMiAuthRequest() { | |||
AuthConfig authConfig = properties.getMi(); | |||
return new AuthMiRequest(authConfig); | |||
} | |||
} |
@@ -1,54 +0,0 @@ | |||
package com.xkcoding.social.props; | |||
import lombok.Data; | |||
import me.zhyd.oauth.config.AuthConfig; | |||
import org.springframework.boot.context.properties.ConfigurationProperties; | |||
import org.springframework.stereotype.Component; | |||
/** | |||
* <p> | |||
* 第三方登录配置 | |||
* </p> | |||
* | |||
* @package: com.xkcoding.oauth.config.props | |||
* @description: 第三方登录配置 | |||
* @author: yangkai.shen | |||
* @date: Created in 2019-05-17 15:33 | |||
* @copyright: Copyright (c) 2019 | |||
* @version: V1.0 | |||
* @modified: yangkai.shen | |||
*/ | |||
@Data | |||
@Component | |||
@ConfigurationProperties(prefix = "oauth") | |||
public class OAuthProperties { | |||
/** | |||
* QQ 配置 | |||
*/ | |||
private AuthConfig qq; | |||
/** | |||
* github 配置 | |||
*/ | |||
private AuthConfig github; | |||
/** | |||
* 微信 配置 | |||
*/ | |||
private AuthConfig wechat; | |||
/** | |||
* Google 配置 | |||
*/ | |||
private AuthConfig google; | |||
/** | |||
* Microsoft 配置 | |||
*/ | |||
private AuthConfig microsoft; | |||
/** | |||
* Mi 配置 | |||
*/ | |||
private AuthConfig mi; | |||
} |
@@ -3,6 +3,27 @@ server: | |||
servlet: | |||
context-path: /demo | |||
spring: | |||
redis: | |||
host: localhost | |||
# 连接超时时间(记得添加单位,Duration) | |||
timeout: 10000ms | |||
# Redis默认情况下有16个分片,这里配置具体使用的分片 | |||
# database: 0 | |||
lettuce: | |||
pool: | |||
# 连接池最大连接数(使用负值表示没有限制) 默认 8 | |||
max-active: 8 | |||
# 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1 | |||
max-wait: -1ms | |||
# 连接池中的最大空闲连接 默认 8 | |||
max-idle: 8 | |||
# 连接池中的最小空闲连接 默认 0 | |||
min-idle: 0 | |||
cache: | |||
# 一般来说是不用配置的,Spring Cache 会根据依赖的包自行装配 | |||
type: redis | |||
oauth: | |||
qq: | |||
client-id: 1015***** | |||
@@ -28,3 +49,8 @@ oauth: | |||
client-id: 2882303************** | |||
client-secret: nFeTt89Yn************** | |||
redirect-uri: http://oauth.xkcoding.com/demo/oauth/mi/callback | |||
wechat_enterprise: | |||
client-id: ww58**********6fbc | |||
client-secret: 8G6PCr0****************************yzaPc78 | |||
redirect-uri: http://oauth.xkcoding.com/demo/oauth/wechat_enterprise/callback | |||
agent-id: 10*******02 |