Merge remote-tracking branch 'origin/dev' into test
This commit is contained in:
commit
efb8e44a14
14
pom.xml
14
pom.xml
@ -161,6 +161,19 @@
|
|||||||
<version>${sf.version}</version>
|
<version>${sf.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 三方授权模块-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.smarterFramework</groupId>
|
||||||
|
<artifactId>sf-oauth</artifactId>
|
||||||
|
<version>${sf.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.smarterFramework</groupId>
|
||||||
|
<artifactId>sf-order</artifactId>
|
||||||
|
<version>${sf.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- 通用工具-->
|
<!-- 通用工具-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.smarterFramework</groupId>
|
<groupId>com.smarterFramework</groupId>
|
||||||
@ -200,6 +213,7 @@
|
|||||||
<module>sf-file</module>
|
<module>sf-file</module>
|
||||||
<module>sf-common</module>
|
<module>sf-common</module>
|
||||||
<module>sf-apijson</module>
|
<module>sf-apijson</module>
|
||||||
|
<module>sf-oauth</module>
|
||||||
</modules>
|
</modules>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
@ -60,11 +60,15 @@
|
|||||||
<groupId>com.smarterFramework</groupId>
|
<groupId>com.smarterFramework</groupId>
|
||||||
<artifactId>sf-file</artifactId>
|
<artifactId>sf-file</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.smarterFramework</groupId>
|
||||||
|
<artifactId>sf-oauth</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.smarterFramework</groupId>
|
<groupId>com.smarterFramework</groupId>
|
||||||
<artifactId>sf-order</artifactId>
|
<artifactId>sf-order</artifactId>
|
||||||
<version>1.0.0</version>
|
|
||||||
<scope>compile</scope>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -48,9 +48,10 @@ public class SysLoginController
|
|||||||
public AjaxResult login(@RequestBody LoginBody loginBody, HttpSession session)
|
public AjaxResult login(@RequestBody LoginBody loginBody, HttpSession session)
|
||||||
{
|
{
|
||||||
AjaxResult ajax = AjaxResult.success();
|
AjaxResult ajax = AjaxResult.success();
|
||||||
|
loginService.validateCaptcha(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode());
|
||||||
// 生成令牌
|
// 生成令牌
|
||||||
String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(), loginBody.getCode(),
|
String token = loginService.login(loginBody.getUsername(), loginBody.getPassword(),
|
||||||
loginBody.getUuid(), session);
|
session);
|
||||||
ajax.put(Constants.TOKEN, token);
|
ajax.put(Constants.TOKEN, token);
|
||||||
return ajax;
|
return ajax;
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter
|
|||||||
// 过滤请求
|
// 过滤请求
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
|
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
|
||||||
.antMatchers("/login", "/register", "/captchaImage").permitAll()
|
.antMatchers("/login", "/register", "/captchaImage","/oauth/**").permitAll()
|
||||||
// 静态资源,可匿名访问
|
// 静态资源,可匿名访问
|
||||||
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
|
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
|
||||||
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
|
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package com.sf.framework.web.service;
|
package com.sf.framework.web.service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import javax.security.auth.message.AuthException;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -32,6 +33,7 @@ import com.sf.framework.manager.factory.AsyncFactory;
|
|||||||
import com.sf.framework.security.context.AuthenticationContextHolder;
|
import com.sf.framework.security.context.AuthenticationContextHolder;
|
||||||
import com.sf.system.service.ISysConfigService;
|
import com.sf.system.service.ISysConfigService;
|
||||||
import com.sf.system.service.ISysUserService;
|
import com.sf.system.service.ISysUserService;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录校验方法
|
* 登录校验方法
|
||||||
@ -56,19 +58,18 @@ public class SysLoginService
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ISysConfigService configService;
|
private ISysConfigService configService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SysPermissionService permissionService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录验证
|
* 登录验证
|
||||||
*
|
*
|
||||||
* @param username 用户名
|
* @param username 用户名
|
||||||
* @param password 密码
|
* @param password 密码
|
||||||
* @param code 验证码
|
|
||||||
* @param uuid 唯一标识
|
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
public String login(String username, String password, String code, String uuid, HttpSession session)
|
public String login(String username, String password, HttpSession session)
|
||||||
{
|
{
|
||||||
// 验证码校验
|
|
||||||
validateCaptcha(username, code, uuid);
|
|
||||||
// 登录前置校验
|
// 登录前置校验
|
||||||
loginPreCheck(username, password);
|
loginPreCheck(username, password);
|
||||||
// 用户验证
|
// 用户验证
|
||||||
@ -105,6 +106,25 @@ public class SysLoginService
|
|||||||
return tokenService.createToken(loginUser);
|
return tokenService.createToken(loginUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 无密码登录
|
||||||
|
* @param userName
|
||||||
|
* @return
|
||||||
|
* @author zoukun
|
||||||
|
*/
|
||||||
|
public String noPwdLogin(String userName, HttpSession session){
|
||||||
|
SysUser user = userService.selectUserByUserName(userName);
|
||||||
|
Assert.notNull(user,MessageUtils.message("user.not.exists"));
|
||||||
|
LoginUser loginUser = new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
|
||||||
|
// 记录登陆信息
|
||||||
|
AsyncManager.me().execute(AsyncFactory.recordLogininfor(userName, Constants.LOGIN_SUCCESS,
|
||||||
|
MessageUtils.message("user.login.success")));
|
||||||
|
recordLoginInfo(loginUser.getUserId());
|
||||||
|
ApijsonUtils.buildFormSession(session, String.valueOf(loginUser.getUserId()));
|
||||||
|
return tokenService.createToken(loginUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 校验验证码
|
* 校验验证码
|
||||||
*
|
*
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.sf.framework.web.service;
|
package com.sf.framework.web.service;
|
||||||
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.security.core.Authentication;
|
import org.springframework.security.core.Authentication;
|
||||||
@ -23,6 +25,7 @@ import com.sf.framework.security.context.AuthenticationContextHolder;
|
|||||||
*
|
*
|
||||||
* @author ztzh
|
* @author ztzh
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
public class SysPasswordService
|
public class SysPasswordService
|
||||||
{
|
{
|
||||||
|
32
sf-oauth/pom.xml
Normal file
32
sf-oauth/pom.xml
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>smarterFramework</artifactId>
|
||||||
|
<groupId>com.smarterFramework</groupId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>sf-oauth</artifactId>
|
||||||
|
|
||||||
|
<description>
|
||||||
|
oauth三方登录认证模块
|
||||||
|
</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
|
||||||
|
<!-- 通用工具-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.smarterFramework</groupId>
|
||||||
|
<artifactId>sf-common</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.smarterFramework</groupId>
|
||||||
|
<artifactId>sf-framework</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
41
sf-oauth/src/main/java/com/sf/oauth/config/AuthConfig.java
Normal file
41
sf-oauth/src/main/java/com/sf/oauth/config/AuthConfig.java
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package com.sf.oauth.config;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置类
|
||||||
|
*
|
||||||
|
* @author zoukun
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class AuthConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端id:对应各平台的appKey
|
||||||
|
*/
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端Secret:对应各平台的appSecret
|
||||||
|
*/
|
||||||
|
private String clientSecret;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 登录成功后的回调地址
|
||||||
|
*/
|
||||||
|
private String redirectUri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支持自定义授权平台的 scope 内容
|
||||||
|
*/
|
||||||
|
private List<String> scopes;
|
||||||
|
|
||||||
|
private boolean ignoreCheckState;
|
||||||
|
|
||||||
|
}
|
76
sf-oauth/src/main/java/com/sf/oauth/config/AuthSource.java
Normal file
76
sf-oauth/src/main/java/com/sf/oauth/config/AuthSource.java
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package com.sf.oauth.config;
|
||||||
|
|
||||||
|
import com.sf.oauth.exception.AuthException;
|
||||||
|
import com.sf.oauth.request.AuthDefaultRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OAuth平台的API地址的统一接口,提供以下方法:
|
||||||
|
* 1) {@link AuthSource#authorize()}: 获取授权url. 必须实现
|
||||||
|
* 2) {@link AuthSource#accessToken()}: 获取accessToken的url. 必须实现
|
||||||
|
* 3) {@link AuthSource#userInfo()}: 获取用户信息的url. 必须实现
|
||||||
|
* 4) {@link AuthSource#revoke()}: 获取取消授权的url. 非必须实现接口(部分平台不支持)
|
||||||
|
* 5) {@link AuthSource#refresh()}: 获取刷新授权的url. 非必须实现接口(部分平台不支持)
|
||||||
|
* <p>
|
||||||
|
*
|
||||||
|
* @author zoukun
|
||||||
|
*/
|
||||||
|
public interface AuthSource {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权的api
|
||||||
|
*
|
||||||
|
* @return url
|
||||||
|
*/
|
||||||
|
String authorize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取accessToken的api
|
||||||
|
*
|
||||||
|
* @return url
|
||||||
|
*/
|
||||||
|
String accessToken();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户信息的api
|
||||||
|
*
|
||||||
|
* @return url
|
||||||
|
*/
|
||||||
|
String userInfo();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取消授权的api
|
||||||
|
*
|
||||||
|
* @return url
|
||||||
|
*/
|
||||||
|
default String revoke() {
|
||||||
|
throw new AuthException("UNSUPPORTED");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新授权的api
|
||||||
|
*
|
||||||
|
* @return url
|
||||||
|
*/
|
||||||
|
default String refresh() {
|
||||||
|
throw new AuthException("UNSUPPORTED");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取Source的字符串名字
|
||||||
|
*
|
||||||
|
* @return name
|
||||||
|
*/
|
||||||
|
default String getName() {
|
||||||
|
if (this instanceof Enum) {
|
||||||
|
return String.valueOf(this);
|
||||||
|
}
|
||||||
|
return this.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台对应的 AuthRequest 实现类,必须继承自 {@link AuthDefaultRequest}
|
||||||
|
*
|
||||||
|
* @return class
|
||||||
|
*/
|
||||||
|
Class<? extends AuthDefaultRequest> getTargetClass();
|
||||||
|
}
|
@ -0,0 +1,143 @@
|
|||||||
|
package com.sf.oauth.controller;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.UUID;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.sf.common.constant.Constants;
|
||||||
|
import com.sf.common.constant.HttpStatus;
|
||||||
|
import com.sf.common.core.domain.AjaxResult;
|
||||||
|
import com.sf.oauth.config.AuthConfig;
|
||||||
|
import com.sf.oauth.domain.AuthCallback;
|
||||||
|
import com.sf.oauth.domain.AuthUser;
|
||||||
|
import com.sf.oauth.enums.scope.AuthHuaweiScope;
|
||||||
|
import com.sf.oauth.exception.AuthException;
|
||||||
|
import com.sf.oauth.request.AuthHuaweiRequest;
|
||||||
|
import com.sf.oauth.request.AuthRequest;
|
||||||
|
import com.sf.oauth.service.IAuthService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.EMPTY;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zoukun
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/oauth")
|
||||||
|
public class RestAuthController {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private IAuthService authService;
|
||||||
|
|
||||||
|
@GetMapping("/authorize/{source}")
|
||||||
|
public void authorize(@PathVariable("source") String source, HttpServletResponse response) throws IOException {
|
||||||
|
log.info("进入render:" + source);
|
||||||
|
AuthRequest authRequest = getAuthRequest(source, EMPTY, EMPTY);
|
||||||
|
String authorizeUrl = authRequest.authorize(UUID.fastUUID().toString());
|
||||||
|
log.info(authorizeUrl);
|
||||||
|
response.sendRedirect(authorizeUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* oauth平台中配置的授权回调地址
|
||||||
|
* 如在创建华为授权应用时的回调地址为:http://127.0.0.1:8080/oauth/callback/huawei
|
||||||
|
*/
|
||||||
|
@RequestMapping("/callback/{source}")
|
||||||
|
public AjaxResult callback(@PathVariable("source") String source, @RequestBody AuthCallback callback, HttpSession session) {
|
||||||
|
log.info("进入callback:" + source + " callback params:" + JSONObject.toJSONString(callback));
|
||||||
|
AuthRequest authRequest = getAuthRequest(source, callback.getClientId(), callback.getClientSecret());
|
||||||
|
AuthUser authUser = authRequest.callback(callback);
|
||||||
|
log.info(JSONObject.toJSONString(authUser));
|
||||||
|
AjaxResult ajax = AjaxResult.success();
|
||||||
|
String token = authService.authLogin(authUser,session);
|
||||||
|
ajax.put(Constants.TOKEN, token);
|
||||||
|
return ajax;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
@RequestMapping("/revoke/{source}/{userId}")
|
||||||
|
@ResponseBody
|
||||||
|
public AjaxResult revokeAuth(@PathVariable("source") String source, @PathVariable("userId") String userId) throws IOException {
|
||||||
|
AuthRequest authRequest = getAuthRequest(source.toLowerCase());
|
||||||
|
|
||||||
|
AuthUser user = userService.getByUuid(uuid);
|
||||||
|
if (null == user) {
|
||||||
|
return Response.error("用户不存在");
|
||||||
|
}
|
||||||
|
AjaxResult<AuthToken> response = null;
|
||||||
|
try {
|
||||||
|
response = authRequest.revoke(user.getToken());
|
||||||
|
if (response.ok()) {
|
||||||
|
userService.remove(user.getUuid());
|
||||||
|
return Response.success("用户 [" + user.getUsername() + "] 的 授权状态 已收回!");
|
||||||
|
}
|
||||||
|
return Response.error("用户 [" + user.getUsername() + "] 的 授权状态 收回失败!" + response.getMsg());
|
||||||
|
} catch (AuthException e) {
|
||||||
|
return Response.error(e.getErrorMsg());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/refresh/{source}/{userId}")
|
||||||
|
@ResponseBody
|
||||||
|
public Object refreshAuth(@PathVariable("source") String source, @PathVariable("userId") String userId) {
|
||||||
|
AuthRequest authRequest = getAuthRequest(source.toLowerCase());
|
||||||
|
|
||||||
|
AuthUser user = userService.getByUuid(userId);
|
||||||
|
if (null == user) {
|
||||||
|
return Response.error("用户不存在");
|
||||||
|
}
|
||||||
|
AjaxResult<AuthToken> response = null;
|
||||||
|
try {
|
||||||
|
response = authRequest.refresh(user.getToken());
|
||||||
|
if (response.ok()) {
|
||||||
|
user.setToken(response.getData());
|
||||||
|
userService.save(user);
|
||||||
|
return Response.success("用户 [" + user.getUsername() + "] 的 access token 已刷新!新的 callback: " + response.getData().getAccessToken());
|
||||||
|
}
|
||||||
|
return Response.error("用户 [" + user.getUsername() + "] 的 access token 刷新失败!" + response.getMsg());
|
||||||
|
} catch (AuthException e) {
|
||||||
|
return Response.error(e.getErrorMsg());
|
||||||
|
}
|
||||||
|
}*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据具体的授权来源,获取授权请求工具类
|
||||||
|
*
|
||||||
|
* @param source
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
private AuthRequest getAuthRequest(String source, String clientId, String clientSecret) {
|
||||||
|
AuthRequest authRequest = null;
|
||||||
|
switch (source.toLowerCase()) {
|
||||||
|
case "huawei":
|
||||||
|
authRequest = new AuthHuaweiRequest(AuthConfig.builder()
|
||||||
|
.clientId(StrUtil.isBlank(clientId) ? "110693217" : clientId)
|
||||||
|
.clientSecret(StrUtil.isBlank(clientSecret) ? "1410c01bc71c7ba587175ae79e500137c70945acc1416a38127cf98a09a6f8ba" : clientSecret)
|
||||||
|
.redirectUri("")
|
||||||
|
.ignoreCheckState(true)
|
||||||
|
.scopes(Arrays.asList(
|
||||||
|
AuthHuaweiScope.BASE_PROFILE.getScope(),
|
||||||
|
AuthHuaweiScope.MOBILE_NUMBER.getScope(),
|
||||||
|
AuthHuaweiScope.ACCOUNTLIST.getScope(),
|
||||||
|
AuthHuaweiScope.SCOPE_DRIVE_FILE.getScope(),
|
||||||
|
AuthHuaweiScope.SCOPE_DRIVE_APPDATA.getScope()
|
||||||
|
))
|
||||||
|
.build());
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (null == authRequest) {
|
||||||
|
throw new AuthException(HttpStatus.BAD_REQUEST, "未获取到有效的Auth配置");
|
||||||
|
}
|
||||||
|
return authRequest;
|
||||||
|
}
|
||||||
|
}
|
42
sf-oauth/src/main/java/com/sf/oauth/domain/AuthCallback.java
Normal file
42
sf-oauth/src/main/java/com/sf/oauth/domain/AuthCallback.java
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package com.sf.oauth.domain;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权回调时的参数类
|
||||||
|
*
|
||||||
|
* @author zk
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Builder
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class AuthCallback implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问AuthorizeUrl后回调时带的参数code
|
||||||
|
*/
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问AuthorizeUrl后回调时带的参数state,用于和请求AuthorizeUrl前的state比较,防止CSRF攻击
|
||||||
|
*/
|
||||||
|
private String state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 华为授权登录
|
||||||
|
*
|
||||||
|
* 客户端id:对应各平台的appKey
|
||||||
|
*/
|
||||||
|
private String clientId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端Secret:对应各平台的appSecret
|
||||||
|
*/
|
||||||
|
private String clientSecret;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
42
sf-oauth/src/main/java/com/sf/oauth/domain/AuthToken.java
Normal file
42
sf-oauth/src/main/java/com/sf/oauth/domain/AuthToken.java
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
package com.sf.oauth.domain;
|
||||||
|
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权所需的token
|
||||||
|
*
|
||||||
|
* @author zoukun
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class AuthToken implements Serializable {
|
||||||
|
private String accessToken;
|
||||||
|
private int expireIn;
|
||||||
|
private String refreshToken;
|
||||||
|
private int refreshTokenExpireIn;
|
||||||
|
private String uid;
|
||||||
|
private String openId;
|
||||||
|
private String accessCode;
|
||||||
|
private String unionId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 华为返回 生成的Access Token中包含的scope。
|
||||||
|
*/
|
||||||
|
private String scope;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 华为返回 固定返回Bearer,标识返回Access Token的类型
|
||||||
|
*/
|
||||||
|
private String tokenType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 华为返回 返回JWT格式数据,包含用户基本帐号、用户邮箱等信息。
|
||||||
|
* 参照https://developer.huawei.com/consumer/cn/doc/HMSCore-References/account-verify-id-token_hms_reference-0000001050050577#section3142132691914
|
||||||
|
*/
|
||||||
|
private String idToken;
|
||||||
|
}
|
79
sf-oauth/src/main/java/com/sf/oauth/domain/AuthUser.java
Normal file
79
sf-oauth/src/main/java/com/sf/oauth/domain/AuthUser.java
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package com.sf.oauth.domain;
|
||||||
|
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.sf.oauth.enums.AuthUserGender;
|
||||||
|
import lombok.*;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权成功后的用户信息,根据授权平台的不同,获取的数据完整性也不同
|
||||||
|
*
|
||||||
|
* @author zoukun
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@Builder
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class AuthUser implements Serializable {
|
||||||
|
/**
|
||||||
|
* 用户第三方系统的唯一id。
|
||||||
|
*/
|
||||||
|
private String uuid;
|
||||||
|
/**
|
||||||
|
* 用户名
|
||||||
|
*/
|
||||||
|
private String username;
|
||||||
|
/**
|
||||||
|
* 用户昵称
|
||||||
|
*/
|
||||||
|
private String nickname;
|
||||||
|
/**
|
||||||
|
* 用户头像
|
||||||
|
*/
|
||||||
|
private String avatar;
|
||||||
|
/**
|
||||||
|
* 用户网址
|
||||||
|
*/
|
||||||
|
private String blog;
|
||||||
|
/**
|
||||||
|
* 所在公司
|
||||||
|
*/
|
||||||
|
private String company;
|
||||||
|
/**
|
||||||
|
* 位置
|
||||||
|
*/
|
||||||
|
private String location;
|
||||||
|
/**
|
||||||
|
* 用户邮箱
|
||||||
|
*/
|
||||||
|
private String email;
|
||||||
|
/**
|
||||||
|
* 用户手机号
|
||||||
|
*/
|
||||||
|
private String mobileNumber;
|
||||||
|
/**
|
||||||
|
* 用户备注(各平台中的用户个人介绍)
|
||||||
|
*/
|
||||||
|
private String remark;
|
||||||
|
/**
|
||||||
|
* 性别
|
||||||
|
*/
|
||||||
|
private AuthUserGender gender;
|
||||||
|
/**
|
||||||
|
* 用户来源
|
||||||
|
*/
|
||||||
|
private String source;
|
||||||
|
/**
|
||||||
|
* 用户授权的token信息
|
||||||
|
*/
|
||||||
|
private AuthToken token;
|
||||||
|
/**
|
||||||
|
* 第三方平台返回的原始用户信息
|
||||||
|
*/
|
||||||
|
private JSONObject rawUserInfo;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package com.sf.oauth.enums;
|
||||||
|
|
||||||
|
import com.sf.oauth.config.AuthSource;
|
||||||
|
import com.sf.oauth.request.AuthDefaultRequest;
|
||||||
|
import com.sf.oauth.request.AuthHuaweiRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 内置的各api需要的url, 用枚举类分平台类型管理
|
||||||
|
*
|
||||||
|
* @author zoukun
|
||||||
|
*/
|
||||||
|
public enum AuthDefaultSource implements AuthSource {
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 华为oauth2/v3
|
||||||
|
*/
|
||||||
|
HUAWEI {
|
||||||
|
@Override
|
||||||
|
public String authorize() {
|
||||||
|
return "https://oauth-login.cloud.huawei.com/oauth2/v3/authorize";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String accessToken() {
|
||||||
|
return "https://oauth-login.cloud.huawei.com/oauth2/v3/token";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String userInfo() {
|
||||||
|
return "https://account.cloud.huawei.com/rest.php";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String refresh() {
|
||||||
|
return "https://oauth-login.cloud.huawei.com/oauth2/v3/token";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends AuthDefaultRequest> getTargetClass() {
|
||||||
|
return AuthHuaweiRequest.class;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package com.sf.oauth.enums;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author zoukun
|
||||||
|
*/
|
||||||
|
public enum AuthPlatformInfo {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台
|
||||||
|
*/
|
||||||
|
HUAWEI("华为", "huawei", "https://developer.huawei.com/consumer/cn/doc/HMSCore-Guides/open-platform-oauth-0000001053629189"),
|
||||||
|
;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 平台名
|
||||||
|
*/
|
||||||
|
private final String name;
|
||||||
|
/**
|
||||||
|
* 平台编码
|
||||||
|
*/
|
||||||
|
private final String code;
|
||||||
|
/**
|
||||||
|
* 官网api文档
|
||||||
|
*/
|
||||||
|
private final String apiDoc;
|
||||||
|
|
||||||
|
AuthPlatformInfo(String name, String code, String apiDoc) {
|
||||||
|
this.name = name;
|
||||||
|
this.code = code;
|
||||||
|
this.apiDoc = apiDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<AuthPlatformInfo> getPlatformInfos() {
|
||||||
|
return Arrays.asList(AuthPlatformInfo.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getApiDoc() {
|
||||||
|
return apiDoc;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package com.sf.oauth.enums;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户性别
|
||||||
|
*
|
||||||
|
* @author zoukun
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum AuthUserGender {
|
||||||
|
/**
|
||||||
|
* MALE/FAMALE为正常值,通过{@link AuthUserGender#getRealGender(String)}方法获取真实的性别
|
||||||
|
* UNKNOWN为容错值,部分平台不会返回用户性别,为了方便统一,使用UNKNOWN标记所有未知或不可测的用户性别信息
|
||||||
|
*/
|
||||||
|
MALE("0", "男"),
|
||||||
|
FEMALE("1", "女"),
|
||||||
|
UNKNOWN("2", "未知");
|
||||||
|
|
||||||
|
private String code;
|
||||||
|
private String desc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户的实际性别,常规网站
|
||||||
|
*
|
||||||
|
* @param originalGender 用户第三方标注的原始性别
|
||||||
|
* @return 用户性别
|
||||||
|
*/
|
||||||
|
public static AuthUserGender getRealGender(String originalGender) {
|
||||||
|
if (null == originalGender || UNKNOWN.getCode().equals(originalGender)) {
|
||||||
|
return UNKNOWN;
|
||||||
|
}
|
||||||
|
String[] males = {"m", "男", "0", "male"};
|
||||||
|
if (Arrays.asList(males).contains(originalGender.toLowerCase())) {
|
||||||
|
return MALE;
|
||||||
|
}
|
||||||
|
return FEMALE;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.sf.oauth.enums.scope;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 华为平台 OAuth 授权范围
|
||||||
|
*
|
||||||
|
* @author zoukun
|
||||||
|
*/
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum AuthHuaweiScope implements AuthScope {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code scope} 含义,以{@code description} 为准
|
||||||
|
*/
|
||||||
|
BASE_PROFILE("https://www.huawei.com/auth/account/base.profile", "获取用户的基本信息", true),
|
||||||
|
MOBILE_NUMBER("https://www.huawei.com/auth/account/mobile.number", "获取用户的手机号", false),
|
||||||
|
ACCOUNTLIST("https://www.huawei.com/auth/account/accountlist", "获取用户的账单列表", false),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 以下两个 scope 不需要经过华为评估和验证
|
||||||
|
*/
|
||||||
|
SCOPE_DRIVE_FILE("https://www.huawei.com/auth/drive.file", "只允许访问由应用程序创建或打开的文件", false),
|
||||||
|
SCOPE_DRIVE_APPDATA("https://www.huawei.com/auth/drive.appdata", "只允许访问由应用程序创建或打开的文件", false),
|
||||||
|
/**
|
||||||
|
* 以下四个 scope 使用前需要向drivekit@huawei.com提交申请
|
||||||
|
* <p>
|
||||||
|
* 参考:https://developer.huawei.com/consumer/cn/doc/development/HMSCore-Guides-V5/server-dev-0000001050039664-V5#ZH-CN_TOPIC_0000001050039664__section1618418855716
|
||||||
|
*/
|
||||||
|
SCOPE_DRIVE("https://www.huawei.com/auth/drive", "只允许访问由应用程序创建或打开的文件", false),
|
||||||
|
SCOPE_DRIVE_READONLY("https://www.huawei.com/auth/drive.readonly", "只允许访问由应用程序创建或打开的文件", false),
|
||||||
|
SCOPE_DRIVE_METADATA("https://www.huawei.com/auth/drive.metadata", "只允许访问由应用程序创建或打开的文件", false),
|
||||||
|
SCOPE_DRIVE_METADATA_READONLY("https://www.huawei.com/auth/drive.metadata.readonly", "只允许访问由应用程序创建或打开的文件", false),
|
||||||
|
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
private final String scope;
|
||||||
|
private final String description;
|
||||||
|
private final boolean isDefault;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package com.sf.oauth.enums.scope;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 各个平台 scope 类的统一接口
|
||||||
|
*
|
||||||
|
* @author zoukun
|
||||||
|
*/
|
||||||
|
public interface AuthScope {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取字符串 {@code scope},对应为各平台实际使用的 {@code scope}
|
||||||
|
*
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String getScope();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断当前 {@code scope} 是否为各平台默认启用的
|
||||||
|
*
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
boolean isDefault();
|
||||||
|
}
|
@ -0,0 +1,46 @@
|
|||||||
|
package com.sf.oauth.exception;
|
||||||
|
|
||||||
|
|
||||||
|
import com.sf.oauth.config.AuthSource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权异常
|
||||||
|
*
|
||||||
|
* @author zoukun
|
||||||
|
*/
|
||||||
|
public class AuthException extends RuntimeException {
|
||||||
|
|
||||||
|
private int errorCode;
|
||||||
|
private String errorMsg;
|
||||||
|
|
||||||
|
public AuthException(String errorMsg) {
|
||||||
|
super(errorMsg);
|
||||||
|
this.errorMsg = errorMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthException(int errorCode, String errorMsg) {
|
||||||
|
super(errorMsg);
|
||||||
|
this.errorCode = errorCode;
|
||||||
|
this.errorMsg = errorMsg;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthException(int errorCode, String errorMsg, AuthSource source) {
|
||||||
|
this(errorCode, String.format("%s [%s]", errorMsg, source.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getErrorCode() {
|
||||||
|
return errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getErrorMsg() {
|
||||||
|
return errorMsg;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,177 @@
|
|||||||
|
package com.sf.oauth.request;
|
||||||
|
|
||||||
|
import cn.hutool.core.lang.UUID;
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import cn.hutool.core.util.URLUtil;
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import com.sf.common.constant.HttpStatus;
|
||||||
|
import com.sf.oauth.config.AuthConfig;
|
||||||
|
import com.sf.oauth.config.AuthSource;
|
||||||
|
import com.sf.oauth.exception.AuthException;
|
||||||
|
import com.sf.oauth.domain.AuthCallback;
|
||||||
|
import com.sf.oauth.domain.AuthToken;
|
||||||
|
import com.sf.oauth.domain.AuthUser;
|
||||||
|
import com.sf.oauth.utils.AuthChecker;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 默认的request处理类
|
||||||
|
*
|
||||||
|
* @author zoukun
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public abstract class AuthDefaultRequest implements AuthRequest {
|
||||||
|
protected AuthConfig config;
|
||||||
|
protected AuthSource source;
|
||||||
|
|
||||||
|
public AuthDefaultRequest(AuthConfig config, AuthSource source) {
|
||||||
|
this.config = config;
|
||||||
|
this.source = source;
|
||||||
|
if (!AuthChecker.isSupportedAuth(config, source)) {
|
||||||
|
throw new AuthException(HttpStatus.ERROR,"Parameter incomplete", source);
|
||||||
|
}
|
||||||
|
// 校验配置合法性
|
||||||
|
// AuthChecker.checkConfig(config, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取access token
|
||||||
|
*
|
||||||
|
* @param authCallback 授权成功后的回调参数
|
||||||
|
* @return token
|
||||||
|
* @see AuthDefaultRequest#authorize()
|
||||||
|
* @see AuthDefaultRequest#authorize(String)
|
||||||
|
*/
|
||||||
|
protected abstract AuthToken getAccessToken(AuthCallback authCallback);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用token换取用户信息
|
||||||
|
*
|
||||||
|
* @param authToken token信息
|
||||||
|
* @return 用户信息
|
||||||
|
* @see AuthDefaultRequest#getAccessToken(AuthCallback)
|
||||||
|
*/
|
||||||
|
protected abstract AuthUser getUserInfo(AuthToken authToken);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一的登录入口。当通过{@link AuthDefaultRequest#authorize(String)}授权成功后,会跳转到调用方的相关回调方法中
|
||||||
|
* 方法的入参可以使用{@code AuthCallback},{@code AuthCallback}类中封装好了OAuth2授权回调所需要的参数
|
||||||
|
*
|
||||||
|
* @param authCallback 用于接收回调参数的实体
|
||||||
|
* @return AuthResponse
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AuthUser callback(AuthCallback authCallback) {
|
||||||
|
try {
|
||||||
|
checkCode(authCallback);
|
||||||
|
if (!config.isIgnoreCheckState()) {
|
||||||
|
AuthChecker.checkState(authCallback.getState(), source);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthToken authToken = this.getAccessToken(authCallback);
|
||||||
|
return this.getUserInfo(authToken);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to callback with oauth authorization", e);
|
||||||
|
throw new AuthException(HttpStatus.UNAUTHORIZED,"Failed to callback with oauth authorization");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkCode(AuthCallback authCallback) {
|
||||||
|
AuthChecker.checkCode(source, authCallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回授权url,可自行跳转页面
|
||||||
|
* <p>
|
||||||
|
* 不建议使用该方式获取授权地址,不带{@code state}的授权地址,容易受到csrf攻击。
|
||||||
|
* 建议使用{@link AuthDefaultRequest#authorize(String)}方法生成授权地址,在回调方法中对{@code state}进行校验
|
||||||
|
*
|
||||||
|
* @return 返回授权地址
|
||||||
|
* @see AuthDefaultRequest#authorize(String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String authorize() {
|
||||||
|
return this.authorize(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||||
|
*
|
||||||
|
* @param state state 验证授权流程的参数,可以防止csrf
|
||||||
|
* @return 返回授权地址
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String authorize(String state) {
|
||||||
|
Map<String, Object> form = new HashMap<>(8);
|
||||||
|
form.put("client_id", config.getClientId());
|
||||||
|
form.put("client_secret", config.getClientSecret());
|
||||||
|
form.put("redirect_uri", config.getRedirectUri());
|
||||||
|
form.put("state", getRealState(state));
|
||||||
|
form.put("response_type","code");
|
||||||
|
return HttpUtil.get(source.authorize(), form);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回获取accessToken的url
|
||||||
|
*
|
||||||
|
* @param code 授权码
|
||||||
|
* @return 返回获取accessToken的url
|
||||||
|
*/
|
||||||
|
protected String accessTokenUrl(String code) {
|
||||||
|
Map<String, Object> form = new HashMap<>(8);
|
||||||
|
form.put("code", code);
|
||||||
|
form.put("client_id", config.getClientId());
|
||||||
|
form.put("client_secret", config.getClientSecret());
|
||||||
|
form.put("grant_type", "authorization_code");
|
||||||
|
form.put("redirect_uri", config.getRedirectUri());
|
||||||
|
return HttpUtil.get(source.accessToken(),form);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取state,如果为空, 则默认取当前日期的时间戳
|
||||||
|
*
|
||||||
|
* @param state 原始的state
|
||||||
|
* @return 返回不为null的state
|
||||||
|
*/
|
||||||
|
protected String getRealState(String state) {
|
||||||
|
if (StrUtil.isEmpty(state)) {
|
||||||
|
state = UUID.fastUUID().toString();
|
||||||
|
}
|
||||||
|
// todo 需要缓存state
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取以 {@code separator}分割过后的 scope 信息
|
||||||
|
*
|
||||||
|
* @param separator 多个 {@code scope} 间的分隔符
|
||||||
|
* @param encode 是否 encode 编码
|
||||||
|
* @param defaultScopes 默认的 scope, 当客户端没有配置 {@code scopes} 时启用
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
protected String getScopes(String separator, boolean encode, List<String> defaultScopes) {
|
||||||
|
List<String> scopes = config.getScopes();
|
||||||
|
if (null == scopes || scopes.isEmpty()) {
|
||||||
|
if (null == defaultScopes || defaultScopes.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
scopes = defaultScopes;
|
||||||
|
}
|
||||||
|
if (null == separator) {
|
||||||
|
// 默认为空格
|
||||||
|
separator = " ";
|
||||||
|
}
|
||||||
|
String scopeStr = String.join(separator, scopes);
|
||||||
|
return encode ? URLUtil.encode(scopeStr) : scopeStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
package com.sf.oauth.request;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.http.HttpUtil;
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.sf.common.constant.HttpStatus;
|
||||||
|
import com.sf.common.core.domain.AjaxResult;
|
||||||
|
import com.sf.oauth.config.AuthConfig;
|
||||||
|
import com.sf.oauth.enums.AuthDefaultSource;
|
||||||
|
import com.sf.oauth.enums.AuthUserGender;
|
||||||
|
import com.sf.oauth.enums.scope.AuthHuaweiScope;
|
||||||
|
import com.sf.oauth.exception.AuthException;
|
||||||
|
import com.sf.oauth.domain.AuthCallback;
|
||||||
|
import com.sf.oauth.domain.AuthToken;
|
||||||
|
import com.sf.oauth.domain.AuthUser;
|
||||||
|
import com.sf.oauth.utils.AuthScopeUtils;
|
||||||
|
import io.jsonwebtoken.Jwts;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 华为授权登录
|
||||||
|
*
|
||||||
|
* @author zkun
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class AuthHuaweiRequest extends AuthDefaultRequest {
|
||||||
|
|
||||||
|
public AuthHuaweiRequest(AuthConfig config) {
|
||||||
|
super(config, AuthDefaultSource.HUAWEI);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取access token
|
||||||
|
*
|
||||||
|
* @param authCallback 授权成功后的回调参数
|
||||||
|
* @return token
|
||||||
|
* @see AuthDefaultRequest#authorize()
|
||||||
|
* @see AuthDefaultRequest#authorize(String)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected AuthToken getAccessToken(AuthCallback authCallback) {
|
||||||
|
Map<String, Object> form = new HashMap<>(8);
|
||||||
|
form.put("grant_type", "authorization_code");
|
||||||
|
form.put("code", authCallback.getCode());
|
||||||
|
form.put("client_id", config.getClientId());
|
||||||
|
form.put("client_secret", config.getClientSecret());
|
||||||
|
form.put("redirect_uri", config.getRedirectUri());
|
||||||
|
String response = HttpUtil.post(source.accessToken(), form);
|
||||||
|
return getAuthToken(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 使用token换取用户信息
|
||||||
|
*
|
||||||
|
* @param authToken token信息
|
||||||
|
* @return 用户信息
|
||||||
|
* @see AuthDefaultRequest#getAccessToken(AuthCallback)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected AuthUser getUserInfo(AuthToken authToken) {
|
||||||
|
Map<String, Object> form = new HashMap<>(8);
|
||||||
|
form.put("nsp_ts", System.currentTimeMillis() + "");
|
||||||
|
form.put("access_token", authToken.getAccessToken());
|
||||||
|
form.put("nsp_svc", "GOpen.User.getInfo");
|
||||||
|
form.put("getNickName", "1");
|
||||||
|
String response = HttpUtil.post(source.userInfo(), form);
|
||||||
|
log.info(response);
|
||||||
|
|
||||||
|
JSONObject object = JSONObject.parseObject(response);
|
||||||
|
this.checkResponse(object);
|
||||||
|
AuthUserGender gender = getRealGender(object);
|
||||||
|
return AuthUser.builder()
|
||||||
|
.rawUserInfo(object)
|
||||||
|
.uuid(object.getString("unionID"))
|
||||||
|
.username(object.getString("displayName"))
|
||||||
|
.nickname(object.getString("displayName"))
|
||||||
|
.gender(gender)
|
||||||
|
.avatar(object.getString("headPictureURL"))
|
||||||
|
.mobileNumber(object.getString("mobileNumber"))
|
||||||
|
.email(object.getString("email"))
|
||||||
|
.token(authToken)
|
||||||
|
.source(source.toString())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新access token (续期)
|
||||||
|
*
|
||||||
|
* @param authToken 登录成功后返回的Token信息
|
||||||
|
* @return AuthResponse
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public AjaxResult refresh(AuthToken authToken) {
|
||||||
|
Map<String, Object> form = new HashMap<>(8);
|
||||||
|
form.put("client_id", config.getClientId());
|
||||||
|
form.put("client_secret", config.getClientSecret());
|
||||||
|
form.put("refresh_token", authToken.getRefreshToken());
|
||||||
|
form.put("grant_type", "refresh_token");
|
||||||
|
String response = HttpUtil.post(source.refresh(), form);
|
||||||
|
return AjaxResult.success(getAuthToken(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
private AuthToken getAuthToken(String response) {
|
||||||
|
JSONObject object = JSONObject.parseObject(response);
|
||||||
|
|
||||||
|
this.checkResponse(object);
|
||||||
|
|
||||||
|
return AuthToken.builder()
|
||||||
|
.accessToken(object.getString("access_token"))
|
||||||
|
.expireIn(object.getIntValue("expires_in"))
|
||||||
|
.refreshToken(object.getString("refresh_token"))
|
||||||
|
.scope(object.getString("scope"))
|
||||||
|
.tokenType(object.getString("token_type"))
|
||||||
|
.idToken(object.getString("id_token"))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||||
|
*
|
||||||
|
* @param state state 验证授权流程的参数,可以防止csrf
|
||||||
|
* @return 返回授权地址
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String authorize(String state) {
|
||||||
|
Map<String, Object> form = new HashMap<>(8);
|
||||||
|
form.put("client_id", config.getClientId());
|
||||||
|
form.put("client_secret", config.getClientSecret());
|
||||||
|
form.put("redirect_uri", config.getRedirectUri());
|
||||||
|
form.put("state", getRealState(state));
|
||||||
|
form.put("response_type", "code");
|
||||||
|
form.put("access_type", "offline");
|
||||||
|
form.put("scope", this.getScopes(" ", true, AuthScopeUtils.getDefaultScopes(AuthHuaweiScope.values())));
|
||||||
|
return HttpUtil.get(source.authorize(), form);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取用户的实际性别。华为系统中,用户的性别:1表示女,0表示男
|
||||||
|
*
|
||||||
|
* @param object obj
|
||||||
|
* @return AuthUserGender
|
||||||
|
*/
|
||||||
|
private AuthUserGender getRealGender(JSONObject object) {
|
||||||
|
int genderCodeInt = object.getIntValue("gender");
|
||||||
|
String genderCode = genderCodeInt == 0 ? "0" : (genderCodeInt == 1) ? "1" : genderCodeInt + "";
|
||||||
|
return AuthUserGender.getRealGender(genderCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验响应结果
|
||||||
|
*
|
||||||
|
* @param object 接口返回的结果
|
||||||
|
*/
|
||||||
|
private void checkResponse(JSONObject object) {
|
||||||
|
if (object.containsKey("NSP_STATUS")) {
|
||||||
|
throw new AuthException(object.getString("error"));
|
||||||
|
}
|
||||||
|
if (object.containsKey("error")) {
|
||||||
|
throw new AuthException(object.getString("sub_error") + ":" + object.getString("error_description"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
76
sf-oauth/src/main/java/com/sf/oauth/request/AuthRequest.java
Normal file
76
sf-oauth/src/main/java/com/sf/oauth/request/AuthRequest.java
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package com.sf.oauth.request;
|
||||||
|
|
||||||
|
|
||||||
|
import com.sf.common.constant.HttpStatus;
|
||||||
|
import com.sf.common.core.domain.AjaxResult;
|
||||||
|
import com.sf.oauth.domain.AuthUser;
|
||||||
|
import com.sf.oauth.exception.AuthException;
|
||||||
|
import com.sf.oauth.domain.AuthCallback;
|
||||||
|
import com.sf.oauth.domain.AuthToken;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code Request}公共接口,所有平台的{@code Request}都需要实现该接口
|
||||||
|
* <p>
|
||||||
|
* {@link AuthRequest#authorize()}
|
||||||
|
* {@link AuthRequest#authorize(String)}
|
||||||
|
* {@link AuthRequest#callback(AuthCallback)}
|
||||||
|
* {@link AuthRequest#revoke(AuthToken)}
|
||||||
|
* {@link AuthRequest#refresh(AuthToken)}
|
||||||
|
*
|
||||||
|
* @author zoukun
|
||||||
|
*/
|
||||||
|
public interface AuthRequest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回授权url,可自行跳转页面
|
||||||
|
* <p>
|
||||||
|
* 不建议使用该方式获取授权地址,不带{@code state}的授权地址,容易受到csrf攻击。
|
||||||
|
* 建议使用{@link AuthDefaultRequest#authorize(String)}方法生成授权地址,在回调方法中对{@code state}进行校验
|
||||||
|
*
|
||||||
|
* @return 返回授权地址
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
default String authorize() {
|
||||||
|
throw new AuthException(HttpStatus.UNAUTHORIZED,"Not Implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 返回带{@code state}参数的授权url,授权回调时会带上这个{@code state}
|
||||||
|
*
|
||||||
|
* @param state state 验证授权流程的参数,可以防止csrf
|
||||||
|
* @return 返回授权地址
|
||||||
|
*/
|
||||||
|
default String authorize(String state) {
|
||||||
|
throw new AuthException(HttpStatus.UNAUTHORIZED,"Not Implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 第三方登录
|
||||||
|
*
|
||||||
|
* @param authCallback 用于接收回调参数的实体
|
||||||
|
* @return 返回登录成功后的用户信息
|
||||||
|
*/
|
||||||
|
default AuthUser callback(AuthCallback authCallback) {
|
||||||
|
throw new AuthException(HttpStatus.UNAUTHORIZED,"Not Implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 撤销授权
|
||||||
|
*
|
||||||
|
* @param authToken 登录成功后返回的Token信息
|
||||||
|
* @return AjaxResult
|
||||||
|
*/
|
||||||
|
default AjaxResult revoke(AuthToken authToken) {
|
||||||
|
throw new AuthException(HttpStatus.UNAUTHORIZED,"Not Implemented");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新access token (续期)
|
||||||
|
*
|
||||||
|
* @param authToken 登录成功后返回的Token信息
|
||||||
|
* @return AjaxResult
|
||||||
|
*/
|
||||||
|
default AjaxResult refresh(AuthToken authToken) {
|
||||||
|
throw new AuthException(HttpStatus.UNAUTHORIZED,"Not Implemented");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package com.sf.oauth.service;
|
||||||
|
|
||||||
|
import com.sf.oauth.domain.AuthUser;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能描述:
|
||||||
|
*
|
||||||
|
* @author a_kun
|
||||||
|
* @date 2024/4/12 10:21
|
||||||
|
*/
|
||||||
|
public interface IAuthService {
|
||||||
|
String authLogin(AuthUser authUser, HttpSession session);
|
||||||
|
}
|
@ -0,0 +1,62 @@
|
|||||||
|
package com.sf.oauth.service.impl;
|
||||||
|
|
||||||
|
import com.sf.common.core.domain.AjaxResult;
|
||||||
|
import com.sf.common.core.domain.entity.SysUser;
|
||||||
|
import com.sf.common.enums.UserStatus;
|
||||||
|
import com.sf.common.utils.SecurityUtils;
|
||||||
|
import com.sf.common.utils.ip.IpUtils;
|
||||||
|
import com.sf.framework.web.service.SysLoginService;
|
||||||
|
import com.sf.oauth.domain.AuthUser;
|
||||||
|
import com.sf.oauth.exception.AuthException;
|
||||||
|
import com.sf.oauth.service.IAuthService;
|
||||||
|
import com.sf.system.service.ISysUserService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpSession;
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 功能描述:
|
||||||
|
*
|
||||||
|
* @author a_kun
|
||||||
|
* @date 2024/4/12 10:21
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class IAuthServiceImpl implements IAuthService {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private SysLoginService loginService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ISysUserService userService;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String authLogin(AuthUser authUser, HttpSession session) {
|
||||||
|
// TODO 应该要登录后绑定一个平台账号,没有平台账号要创建,保存用户的三方来源,及用户的三方唯一标识和绑定的用户
|
||||||
|
if (authUser != null) {
|
||||||
|
SysUser sysUser = new SysUser();
|
||||||
|
sysUser.setUserName(authUser.getUsername());
|
||||||
|
if (userService.checkUserNameUnique(sysUser)) {
|
||||||
|
// 注册用户后登录
|
||||||
|
sysUser.setNickName(authUser.getNickname());
|
||||||
|
sysUser.setEmail(authUser.getEmail());
|
||||||
|
sysUser.setPhonenumber(authUser.getMobileNumber());
|
||||||
|
sysUser.setSex(authUser.getGender().getCode());
|
||||||
|
sysUser.setAvatar(authUser.getAvatar());
|
||||||
|
sysUser.setPassword(SecurityUtils.encryptPassword("ztzh@sac123"));
|
||||||
|
sysUser.setStatus(UserStatus.OK.getCode());
|
||||||
|
sysUser.setDelFlag("0");
|
||||||
|
sysUser.setLoginIp(IpUtils.getHostIp());
|
||||||
|
sysUser.setLoginDate(new Date());
|
||||||
|
userService.insertUser(sysUser);
|
||||||
|
}
|
||||||
|
// 生成令牌
|
||||||
|
return loginService.noPwdLogin(authUser.getUsername(), session);
|
||||||
|
}
|
||||||
|
throw new AuthException("login error");
|
||||||
|
}
|
||||||
|
}
|
79
sf-oauth/src/main/java/com/sf/oauth/utils/AuthChecker.java
Normal file
79
sf-oauth/src/main/java/com/sf/oauth/utils/AuthChecker.java
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
package com.sf.oauth.utils;
|
||||||
|
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.sf.common.constant.HttpStatus;
|
||||||
|
import com.sf.oauth.config.AuthConfig;
|
||||||
|
import com.sf.oauth.enums.AuthDefaultSource;
|
||||||
|
import com.sf.oauth.config.AuthSource;
|
||||||
|
import com.sf.oauth.exception.AuthException;
|
||||||
|
import com.sf.oauth.domain.AuthCallback;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 授权配置类的校验器
|
||||||
|
*
|
||||||
|
* @author ZK
|
||||||
|
*/
|
||||||
|
public class AuthChecker {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否支持第三方登录
|
||||||
|
*
|
||||||
|
* @param config config
|
||||||
|
* @param source source
|
||||||
|
* @return true or false
|
||||||
|
*/
|
||||||
|
public static boolean isSupportedAuth(AuthConfig config, AuthSource source) {
|
||||||
|
|
||||||
|
return StrUtil.isNotEmpty(config.getClientId())
|
||||||
|
&& StrUtil.isNotEmpty(config.getClientSecret())
|
||||||
|
&& null != source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查配置合法性。针对部分平台, 对redirect uri有特定要求。一般来说redirect uri都是http://,
|
||||||
|
* 而对于部分平台, redirect uri 必须是https的链接
|
||||||
|
*
|
||||||
|
* @param config config
|
||||||
|
* @param source source
|
||||||
|
*/
|
||||||
|
public static void checkConfig(AuthConfig config, AuthSource source) {
|
||||||
|
String redirectUri = config.getRedirectUri();
|
||||||
|
if (StrUtil.isEmpty(redirectUri)) {
|
||||||
|
throw new AuthException(HttpStatus.BAD_REQUEST, "Illegal redirect uri", source);
|
||||||
|
}
|
||||||
|
if (!AuthUtils.isHttpProtocol(redirectUri) && !AuthUtils.isHttpsProtocol(redirectUri)) {
|
||||||
|
throw new AuthException(HttpStatus.BAD_REQUEST, "Illegal redirect uri", source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验回调传回的code
|
||||||
|
* <p>
|
||||||
|
* {@code v1.10.0}版本中改为传入{@code source}和{@code callback},对于不同平台使用不同参数接受code的情况统一做处理
|
||||||
|
*
|
||||||
|
* @param source 当前授权平台
|
||||||
|
* @param callback 从第三方授权回调回来时传入的参数集合
|
||||||
|
*/
|
||||||
|
public static void checkCode(AuthSource source, AuthCallback callback) {
|
||||||
|
if (StrUtil.isEmpty(callback.getCode())) {
|
||||||
|
throw new AuthException(HttpStatus.UNAUTHORIZED,"Illegal code", source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 校验回调传回的{@code state},为空或者不存在
|
||||||
|
* <p>
|
||||||
|
* {@code state}不存在的情况只有两种:
|
||||||
|
* 1. {@code state}已使用,被正常清除
|
||||||
|
* 2. {@code state}为前端伪造,本身就不存在
|
||||||
|
*
|
||||||
|
* @param state {@code state}一定不为空
|
||||||
|
* @param source {@code source}当前授权平台
|
||||||
|
*/
|
||||||
|
public static void checkState(String state, AuthSource source) {
|
||||||
|
if (StrUtil.isEmpty(state)) {
|
||||||
|
throw new AuthException(HttpStatus.UNAUTHORIZED,"Illegal state", source);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package com.sf.oauth.utils;
|
||||||
|
|
||||||
|
import com.sf.oauth.enums.scope.AuthScope;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scope 工具类,提供对 scope 类的统一操作
|
||||||
|
*
|
||||||
|
* @author zoukun
|
||||||
|
*/
|
||||||
|
public class AuthScopeUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 {@link com.sf.oauth.enums.scope.AuthScope} 数组中所有的被标记为 {@code default} 的 scope
|
||||||
|
*
|
||||||
|
* @param scopes scopes
|
||||||
|
* @return List
|
||||||
|
*/
|
||||||
|
public static List<String> getDefaultScopes(AuthScope[] scopes) {
|
||||||
|
if (null == scopes || scopes.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Arrays.stream(scopes)
|
||||||
|
.filter((AuthScope::isDefault))
|
||||||
|
.map(AuthScope::getScope)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 {@link com.sf.oauth.enums.scope.AuthScope} 数组中获取实际的 scope 字符串
|
||||||
|
*
|
||||||
|
* @param scopes 可变参数,支持传任意 {@link com.sf.oauth.enums.scope.AuthScope}
|
||||||
|
* @return List
|
||||||
|
*/
|
||||||
|
public static List<String> getScopes(AuthScope... scopes) {
|
||||||
|
if (null == scopes || scopes.length == 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return Arrays.stream(scopes).map(AuthScope::getScope).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
}
|
127
sf-oauth/src/main/java/com/sf/oauth/utils/AuthUtils.java
Normal file
127
sf-oauth/src/main/java/com/sf/oauth/utils/AuthUtils.java
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
|
||||||
|
package com.sf.oauth.utils;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.sf.oauth.exception.AuthException;
|
||||||
|
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.net.URLDecoder;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class AuthUtils {
|
||||||
|
private static final Charset DEFAULT_ENCODING;
|
||||||
|
private static final String HMAC_SHA1 = "HmacSHA1";
|
||||||
|
private static final String HMAC_SHA_256 = "HmacSHA256";
|
||||||
|
|
||||||
|
public AuthUtils() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String urlEncode(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
String encoded = URLEncoder.encode(value, DEFAULT_ENCODING.displayName());
|
||||||
|
return encoded.replace("+", "%20").replace("*", "%2A").replace("~", "%7E").replace("/", "%2F");
|
||||||
|
} catch (UnsupportedEncodingException var2) {
|
||||||
|
throw new AuthException("Failed To Encode Uri", var2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String urlDecode(String value) {
|
||||||
|
if (value == null) {
|
||||||
|
return "";
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
return URLDecoder.decode(value, DEFAULT_ENCODING.displayName());
|
||||||
|
} catch (UnsupportedEncodingException var2) {
|
||||||
|
throw new AuthException("Failed To Decode Uri", var2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String parseMapToString(Map<String, String> params, boolean encode) {
|
||||||
|
if (null != params && !params.isEmpty()) {
|
||||||
|
List<String> paramList = new ArrayList();
|
||||||
|
params.forEach((k, v) -> {
|
||||||
|
if (null == v) {
|
||||||
|
paramList.add(k + "=");
|
||||||
|
} else {
|
||||||
|
paramList.add(k + "=" + (encode ? urlEncode(v) : v));
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
return String.join("&", paramList);
|
||||||
|
} else {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isHttpProtocol(String url) {
|
||||||
|
if (StrUtil.isEmpty(url)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return url.startsWith("http://") || url.startsWith("http%3A%2F%2F");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isHttpsProtocol(String url) {
|
||||||
|
if (StrUtil.isEmpty(url)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return url.startsWith("https://") || url.startsWith("https%3A%2F%2F");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isLocalHost(String url) {
|
||||||
|
return StrUtil.isEmpty(url) || url.contains("127.0.0.1") || url.contains("localhost");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isHttpsProtocolOrLocalHost(String url) {
|
||||||
|
if (StrUtil.isEmpty(url)) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return isHttpsProtocol(url) || isLocalHost(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String getTimestamp() {
|
||||||
|
return String.valueOf(System.currentTimeMillis() / 1000L);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static String md5(String str) {
|
||||||
|
MessageDigest md = null;
|
||||||
|
StringBuilder buffer = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
md = MessageDigest.getInstance("MD5");
|
||||||
|
md.update(str.getBytes(StandardCharsets.UTF_8));
|
||||||
|
byte[] byteData = md.digest();
|
||||||
|
buffer = new StringBuilder();
|
||||||
|
byte[] var4 = byteData;
|
||||||
|
int var5 = byteData.length;
|
||||||
|
|
||||||
|
for(int var6 = 0; var6 < var5; ++var6) {
|
||||||
|
byte byteDatum = var4[var6];
|
||||||
|
buffer.append(Integer.toString((byteDatum & 255) + 256, 16).substring(1));
|
||||||
|
}
|
||||||
|
} catch (Exception var8) {
|
||||||
|
}
|
||||||
|
|
||||||
|
return null == buffer ? "" : buffer.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
static {
|
||||||
|
DEFAULT_ENCODING = StandardCharsets.UTF_8;
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user