应用中心单点登录

This commit is contained in:
黑小马 2025-07-16 16:04:30 +08:00
parent 6c1bc61ece
commit deaa4c4112
18 changed files with 1560 additions and 52 deletions

11
pom.xml
View File

@ -31,6 +31,17 @@
<artifactId>hserver-plugin-web</artifactId>
<groupId>cn.hserver</groupId>
</dependency>
<dependency>
<artifactId>hserver-plugin-forest</artifactId>
<groupId>cn.hserver</groupId>
</dependency>
<dependency>
<artifactId>hserver-plugin-satoken</artifactId>
<groupId>cn.hserver</groupId>
</dependency>
<dependency>
<groupId>me.zhyd.oauth</groupId>
<artifactId>JustAuth</artifactId>

View File

@ -0,0 +1,36 @@
package com.lktx.center.config;
import cn.hserver.core.ioc.annotation.Bean;
import cn.hserver.core.ioc.annotation.Configuration;
import cn.hserver.core.ioc.annotation.Value;
import me.zhyd.oauth.config.AuthConfig;
import java.util.List;
@Configuration
public class AuthRequestConfig {
@Value("oauth.clientId")
private String clientId;
@Value("oauth.clientSecret")
private String clientSecret;
@Value("oauth.redirectUri")
private String redirectUri;
@Value("oauth.url")
private String url;
@Bean
public SsoAuthRequest getAuthRequest() {
return new SsoAuthRequest(AuthConfig.builder()
.clientId(clientId)
.clientSecret(clientSecret)
.redirectUri(redirectUri)
//其他系统可以不要center
.scopes(List.of("userinfo", "center"))
.build(),new SsoSource(url));
}
}

View File

@ -0,0 +1,6 @@
package com.lktx.center.config;
public interface Data {
String AuthToken = "AuthToken";
}

View File

@ -1,6 +1,7 @@
package com.lktx.center.config;
import com.alibaba.fastjson.JSONObject;
import com.lktx.center.domain.vo.SsoUserAppVO;
import me.zhyd.oauth.cache.AuthStateCache;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.enums.AuthResponseStatus;
@ -15,18 +16,14 @@ import me.zhyd.oauth.utils.*;
import java.util.Map;
public class SsoAuthRequest extends AuthDefaultRequest {
public SsoAuthRequest(AuthConfig config) {
super(config, SsoSource.SSO_SOURCE);
}
private final SsoSource source;
public SsoAuthRequest(AuthConfig config, AuthStateCache authStateCache) {
super(config,SsoSource.SSO_SOURCE, authStateCache);
}
public AuthToken getAccessToken(AuthCallback authCallback) {
return AuthToken.builder().accessCode(authCallback.getCode()).build();
public SsoAuthRequest(AuthConfig config,SsoSource source) {
super(config, source);
this.source=source;
}
private AuthToken getAuthToken(JSONObject object) {
@ -39,27 +36,93 @@ public class SsoAuthRequest extends AuthDefaultRequest {
throw new AuthException(object.getString("error_description"));
}
}
@Override
protected String revokeUrl(AuthToken authToken) {
return UrlBuilder.fromBaseUrl(source.revoke())
.queryParam("access_token", authToken.getAccessToken())
.queryParam("client_id", this.config.getClientId())
.queryParam("client_secret", this.config.getClientSecret())
.build();
}
@Override
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(source.authorize()).queryParam("response_type", "code").queryParam("client_id", this.config.getClientId()).queryParam("redirect_uri", this.config.getRedirectUri()).queryParam("scope", String.join(",",this.config.getScopes())).queryParam("state", this.getRealState(state)).build();
}
@Override
public AuthToken getAccessToken(AuthCallback authCallback) {
return AuthToken.builder().accessCode(authCallback.getCode()).build();
}
@Override
public AuthUser getUserInfo(AuthToken authToken) {
String response = this.doPostAuthorizationCode(authToken.getAccessCode());
String response = this.doPostAuthorizationCode(authToken.getAccessCode());
JSONObject accessTokenObject = JSONObject.parseObject(response);
if (accessTokenObject.containsKey("error")) {
throw new AuthException(accessTokenObject.getString("error_description"));
if (accessTokenObject.getIntValue("code") != 200) {
throw new AuthException(accessTokenObject.getString("mssg"));
} else {
authToken = this.getAuthToken(accessTokenObject);
String nick = GlobalAuthUtils.urlDecode(accessTokenObject.getString("taobao_user_nick"));
return AuthUser.builder().rawUserInfo(accessTokenObject).uuid(StringUtils.isEmpty(authToken.getUid()) ? authToken.getOpenId() : authToken.getUid()).username(nick).nickname(nick).gender(AuthUserGender.UNKNOWN).token(authToken).source(this.source.toString()).build();
return AuthUser.builder().rawUserInfo(accessTokenObject)
.uuid(StringUtils.isEmpty(authToken.getUid()) ? authToken.getOpenId() : authToken.getUid())
.username(accessTokenObject.getString("username")).nickname(accessTokenObject.getString("nickname"))
.avatar(accessTokenObject.getString("avatar"))
.email(accessTokenObject.getString("email"))
.gender(AuthUserGender.UNKNOWN)
.token(authToken)
.source(source.toString())
.build();
}
}
@Override
public AuthResponse<AuthToken> refresh(AuthToken oldToken) {
String tokenUrl = this.refreshTokenUrl(oldToken.getRefreshToken());
String response = (new HttpUtils(this.config.getHttpConfig())).post(tokenUrl).getBody();
JSONObject accessTokenObject = JSONObject.parseObject(response);
return AuthResponse.builder().code(AuthResponseStatus.SUCCESS.getCode()).data(this.getAuthToken(accessTokenObject)).build();
AuthResponse<AuthToken> authTokenAuthResponse = new AuthResponse<>();
if (accessTokenObject.getIntValue("code") == 200) {
authTokenAuthResponse.setCode(AuthResponseStatus.SUCCESS.getCode());
authTokenAuthResponse.setData(this.getAuthToken(accessTokenObject));
}else {
authTokenAuthResponse.setCode(AuthResponseStatus.FAILURE.getCode());
authTokenAuthResponse.setMsg(accessTokenObject.getString("msg"));
}
return authTokenAuthResponse;
}
public String authorize(String state) {
return UrlBuilder.fromBaseUrl(this.source.authorize()).queryParam("response_type", "code").queryParam("client_id", this.config.getClientId()).queryParam("redirect_uri", this.config.getRedirectUri()).queryParam("view", "web").queryParam("state", this.getRealState(state)).build();
@Override
public AuthResponse<Void> revoke(AuthToken authToken) {
String tokenUrl = this.revokeUrl(authToken);
String response = (new HttpUtils(this.config.getHttpConfig())).post(tokenUrl).getBody();
JSONObject accessTokenObject = JSONObject.parseObject(response);
AuthResponse<Void> authTokenAuthResponse = new AuthResponse<>();
authTokenAuthResponse.setMsg(accessTokenObject.getString("msg"));
if (accessTokenObject.getIntValue("code") == 200) {
authTokenAuthResponse.setCode(AuthResponseStatus.SUCCESS.getCode());
}else {
authTokenAuthResponse.setCode(AuthResponseStatus.FAILURE.getCode());
}
return authTokenAuthResponse;
}
public AuthResponse<SsoUserAppVO> center(AuthToken authToken) {
String response = (new HttpUtils(this.config.getHttpConfig())).post(source.center(),Map.of("access_token",authToken.getAccessToken()),true).getBody();
JSONObject accessTokenObject = JSONObject.parseObject(response);
AuthResponse<SsoUserAppVO> authTokenAuthResponse = new AuthResponse<>();
authTokenAuthResponse.setMsg(accessTokenObject.getString("msg"));
if (accessTokenObject.getIntValue("code") == 200) {
authTokenAuthResponse.setCode(AuthResponseStatus.SUCCESS.getCode());
authTokenAuthResponse.setData(accessTokenObject.getObject("data", SsoUserAppVO.class));
}else {
authTokenAuthResponse.setCode(AuthResponseStatus.FAILURE.getCode());
}
return authTokenAuthResponse;
}
}

View File

@ -3,25 +3,41 @@ package com.lktx.center.config;
import me.zhyd.oauth.config.AuthSource;
import me.zhyd.oauth.request.AuthDefaultRequest;
public enum SsoSource implements AuthSource {
SSO_SOURCE {
public String authorize() {
return "https://github.com/login/oauth/authorize";
}
public class SsoSource implements AuthSource {
public String accessToken() {
return "https://github.com/login/oauth/access_token";
}
private final String baseUrl;
public String userInfo() {
return "https://api.github.com/user";
}
public SsoSource(String baseUrl) {
this.baseUrl = baseUrl;
}
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return SsoAuthRequest.class;
}
@Override
public String authorize() {
return baseUrl+"oauth2/authorize";
}
@Override
public String accessToken() {
return baseUrl+"oauth2/token";
}
@Override
public String userInfo() {
return baseUrl+"oauth2/userinfo";
}
@Override
public String refresh() {
return baseUrl+"oauth2/refresh";
}
@Override
public String revoke() {
return baseUrl+"oauth2/revoke";
}
},
public String center(){
return baseUrl+"oauth2/center";
}
@Override
public Class<? extends AuthDefaultRequest> getTargetClass() {
return SsoAuthRequest.class;
}
}

View File

@ -0,0 +1,70 @@
package com.lktx.center.controller;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.hserver.core.ioc.annotation.Autowired;
import cn.hserver.plugin.web.annotation.Controller;
import cn.hserver.plugin.web.annotation.GET;
import cn.hserver.plugin.web.interfaces.HttpResponse;
import cn.hutool.json.JSONUtil;
import com.lktx.center.config.Data;
import com.lktx.center.config.SsoAuthRequest;
import com.lktx.center.domain.bean.SsoApp;
import com.lktx.center.domain.vo.SsoUserAppVO;
import lombok.extern.slf4j.Slf4j;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import java.util.Map;
import java.util.stream.Collectors;
@Slf4j
@Controller
public class HomeController {
@Autowired
private SsoAuthRequest authRequest;
@GET("/")
public void index(HttpResponse response) {
if (StpUtil.isLogin()){
try {
SaSession session = StpUtil.getSession();
AuthToken authToken = session.get(Data.AuthToken,null);
if (authToken != null){
AuthResponse<SsoUserAppVO> center = authRequest.center(authToken);
if (center.ok()) {
Map<String, Object> data = Map.of(
"user", center.getData().getSsoUser(),
"appList", center.getData().getSsoAppList(),
"appGroup", center.getData().getSsoAppList().stream().map(SsoApp::getSsoAppGroup).collect(Collectors.toSet())
);
response.sendTemplate("index.ftl",data);
}
}
}catch (Exception e){
//通常是token失效导致的异常返回
response.redirect("/oauth/render");
}
}else {
response.redirect("/oauth/render");
}
}
@GET("/logout")
public void logout(HttpResponse response) {
if (StpUtil.isLogin()){
//可以全局退出
SaSession session = StpUtil.getSession();
AuthToken authToken = session.get(Data.AuthToken,null);
if (authToken != null){
AuthResponse revoke = authRequest.revoke(authToken);
System.out.println(revoke.getMsg());
}
//子系统退出
StpUtil.logout();
}
response.redirect("/");
}
}

View File

@ -1,37 +1,42 @@
package com.lktx.center.controller;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.hserver.core.ioc.annotation.Autowired;
import cn.hserver.plugin.web.annotation.Controller;
import cn.hserver.plugin.web.annotation.RequestMapping;
import cn.hserver.plugin.web.interfaces.HttpResponse;
import com.lktx.center.config.Data;
import com.lktx.center.config.SsoAuthRequest;
import me.zhyd.oauth.config.AuthConfig;
import me.zhyd.oauth.model.AuthCallback;
import me.zhyd.oauth.request.AuthQqRequest;
import me.zhyd.oauth.request.AuthRequest;
import me.zhyd.oauth.model.AuthResponse;
import me.zhyd.oauth.model.AuthToken;
import me.zhyd.oauth.model.AuthUser;
import me.zhyd.oauth.utils.AuthStateUtils;
import java.io.IOException;
@Controller("/oauth")
public class RestAuthController {
@Autowired
private SsoAuthRequest authRequest;
@RequestMapping("/render")
public void renderAuth(HttpResponse response) throws IOException {
AuthRequest authRequest = getAuthRequest();
response.redirect(authRequest.authorize(AuthStateUtils.createState()));
public void renderAuth(HttpResponse response) {
String authorize = authRequest.authorize(AuthStateUtils.createState());
response.redirect(authorize);
}
@RequestMapping("/callback")
public Object login(AuthCallback callback) {
AuthRequest authRequest = getAuthRequest();
return authRequest.login(callback);
}
private AuthRequest getAuthRequest() {
return new SsoAuthRequest(AuthConfig.builder()
.clientId("App ID")
.clientSecret("App Key")
.redirectUri("网站回调域")
.build());
public void login(AuthCallback callback,HttpResponse response) {
try {
AuthResponse<AuthUser> login = authRequest.login(callback);
AuthToken token = login.getData().getToken();
StpUtil.login(login.getData().getUuid());
SaSession session = StpUtil.getSession();
session.set(Data.AuthToken, token);
response.redirect("/");
}catch (Exception e) {
response.redirect("/");
}
}
}

View File

@ -0,0 +1,10 @@
package com.lktx.center.domain.bean;
import lombok.Data;
@Data
public class RemoteRes<T>{
private Integer code;
private String msg;
private T data;
}

View File

@ -0,0 +1,32 @@
package com.lktx.center.domain.bean;
import lombok.Data;
import me.zhyd.oauth.utils.UrlBuilder;
import java.time.LocalDateTime;
/** app分组 - sso_app */
@Data
public class SsoApp {
/**appId*/
private Integer ssoAppId;
/**app分组*/
private Integer ssoAppGroupId;
/**备注*/
private String remark;
/**创建时间*/
private Integer status;
/**客户端ID*/
private String clientId;
/**客户端秘钥*/
private String clientSecret;
/**app名字*/
private String appName;
/**app图片地址*/
private String appIcon;
/**APP类型*/
private String appType;
private String appUrl;
private SsoAppGroup ssoAppGroup;
}

View File

@ -0,0 +1,21 @@
package com.lktx.center.domain.bean;
import lombok.Data;
import java.time.LocalDateTime;
/** app分组 - sso_app_group */
@Data
public class SsoAppGroup {
/**分组ID*/
private Integer ssoAppGroupId;
/**分组名字*/
private String name;
/**备注*/
private String remark;
/**创建时间*/
private LocalDateTime createTime;
/**更新时间*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,40 @@
package com.lktx.center.domain.bean;
import lombok.Data;
import java.time.LocalDateTime;
/** SSO用户表 - sso_user */
@Data
public class SsoUser {
private Integer ssoUserId;
/*组织ID*/
private Integer ssoOrganizationId;
/**头像*/
private String avatar;
/**用户姓名*/
private String name;
/**用户昵称*/
private String nickname;
/**用户登录名*/
private String username;
/**手机号*/
private String phone;
/**邮箱*/
private String email;
/**密码*/
private String password;
/**来源*/
private String source;
/**是否冻结 0 启用 1 禁用*/
private Integer locked;
/**用户描述*/
private String remark;
/**是否逻辑删除*/
private Integer deleted;
/**创建时间*/
private LocalDateTime createTime;
/**更新时间*/
private LocalDateTime updateTime;
}

View File

@ -0,0 +1,13 @@
package com.lktx.center.domain.vo;
import com.lktx.center.domain.bean.SsoApp;
import com.lktx.center.domain.bean.SsoUser;
import lombok.Data;
import java.util.List;
@Data
public class SsoUserAppVO {
private SsoUser ssoUser;
private List<SsoApp> ssoAppList;
}

View File

@ -0,0 +1,5 @@
oauth:
client-id: 65013a3d89d14fab8ff3eb2c0f3981a3
client-secret: 22b5ce70d67f41b79b27cbedb57c976a
redirect-uri: http://127.0.0.1:8981/oauth/callback
url: http://localhost:8888/21/

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff