@@ -20,7 +20,7 @@ | |||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | ||||
<java.version>1.8</java.version> | <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> | </properties> | ||||
<dependencies> | <dependencies> | ||||
@@ -35,11 +35,22 @@ | |||||
<scope>test</scope> | <scope>test</scope> | ||||
</dependency> | </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工具类 --> | <!-- oauth工具类 --> | ||||
<dependency> | <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> | ||||
<dependency> | <dependency> | ||||
@@ -8,13 +8,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; | |||||
* 启动器 | * 启动器 | ||||
* </p> | * </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 | @SpringBootApplication | ||||
public class SpringBootDemoSocialApplication { | 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; | package com.xkcoding.social.controller; | ||||
import cn.hutool.core.lang.Dict; | |||||
import cn.hutool.core.util.StrUtil; | |||||
import cn.hutool.json.JSONUtil; | import cn.hutool.json.JSONUtil; | ||||
import com.xkcoding.social.props.OAuthProperties; | |||||
import com.xkcoding.justauth.AuthRequestFactory; | |||||
import lombok.RequiredArgsConstructor; | import lombok.RequiredArgsConstructor; | ||||
import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||
import me.zhyd.oauth.config.AuthConfig; | |||||
import me.zhyd.oauth.config.AuthSource; | import me.zhyd.oauth.config.AuthSource; | ||||
import me.zhyd.oauth.model.AuthCallback; | import me.zhyd.oauth.model.AuthCallback; | ||||
import me.zhyd.oauth.model.AuthResponse; | import me.zhyd.oauth.model.AuthResponse; | ||||
import me.zhyd.oauth.request.*; | |||||
import me.zhyd.oauth.request.AuthRequest; | |||||
import me.zhyd.oauth.utils.AuthStateUtils; | import me.zhyd.oauth.utils.AuthStateUtils; | ||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
import org.springframework.web.bind.annotation.GetMapping; | import org.springframework.web.bind.annotation.GetMapping; | ||||
@@ -19,6 +18,9 @@ import org.springframework.web.bind.annotation.RestController; | |||||
import javax.servlet.http.HttpServletResponse; | import javax.servlet.http.HttpServletResponse; | ||||
import java.io.IOException; | import java.io.IOException; | ||||
import java.util.List; | |||||
import java.util.Map; | |||||
import java.util.stream.Collectors; | |||||
/** | /** | ||||
* <p> | * <p> | ||||
@@ -38,14 +40,15 @@ import java.io.IOException; | |||||
@RequestMapping("/oauth") | @RequestMapping("/oauth") | ||||
@RequiredArgsConstructor(onConstructor_ = @Autowired) | @RequiredArgsConstructor(onConstructor_ = @Autowired) | ||||
public class OauthController { | public class OauthController { | ||||
private final OAuthProperties properties; | |||||
private final AuthRequestFactory factory; | |||||
/** | /** | ||||
* 登录类型 | * 登录类型 | ||||
*/ | */ | ||||
@GetMapping | @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}") | @RequestMapping("/login/{oauthType}") | ||||
public void renderAuth(@PathVariable String oauthType, HttpServletResponse response) throws IOException { | 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") | @RequestMapping("/{oauthType}/callback") | ||||
public AuthResponse login(@PathVariable String oauthType, AuthCallback callback) { | public AuthResponse login(@PathVariable String oauthType, AuthCallback callback) { | ||||
AuthRequest authRequest = getAuthRequest(oauthType); | |||||
AuthRequest authRequest = factory.get(getAuthSource(oauthType)); | |||||
AuthResponse response = authRequest.login(callback); | AuthResponse response = authRequest.login(callback); | ||||
log.info("【response】= {}", JSONUtil.toJsonStr(response)); | log.info("【response】= {}", JSONUtil.toJsonStr(response)); | ||||
return 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: | servlet: | ||||
context-path: /demo | 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: | oauth: | ||||
qq: | qq: | ||||
client-id: 1015***** | client-id: 1015***** | ||||
@@ -28,3 +49,8 @@ oauth: | |||||
client-id: 2882303************** | client-id: 2882303************** | ||||
client-secret: nFeTt89Yn************** | client-secret: nFeTt89Yn************** | ||||
redirect-uri: http://oauth.xkcoding.com/demo/oauth/mi/callback | 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 |