feat: sso单点登录

This commit is contained in:
fit2cloud-chenyw 2021-09-14 18:47:33 +08:00
parent ae48fbee41
commit 5f20b3fc8f
10 changed files with 193 additions and 23 deletions

View File

@ -13,7 +13,7 @@ public class TokenInfo implements Serializable {
private Long userId;
private String idToken;
/* private String idToken; */
public String format(){
return username + "," +userId;

View File

@ -22,18 +22,18 @@ import io.dataease.plugins.xpack.ldap.dto.request.LdapValidateRequest;
import io.dataease.plugins.xpack.ldap.dto.response.ValidateResult;
import io.dataease.plugins.xpack.ldap.service.LdapXpackService;
import io.dataease.plugins.xpack.oidc.service.OidcXpackService;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
@RestController
public class AuthServer implements AuthApi {
@ -115,13 +115,13 @@ public class AuthServer implements AuthApi {
@Override
public String logout() {
String token = ServletUtils.getToken();
if (isOpenOidc()) {
HttpServletRequest request = ServletUtils.request();
String idToken = request.getHeader("IdToken");
OidcXpackService oidcXpackService = SpringContextUtil.getBean(OidcXpackService.class);
TokenInfo tokenInfo = JWTUtils.tokenInfoByToken(token);
String idToken = tokenInfo.getIdToken();
oidcXpackService.logout(idToken);
}
// String token = ServletUtils.getToken();
if (StringUtils.isEmpty(token) || StringUtils.equals("null", token) || StringUtils.equals("undefined", token)) {
return "success";
}

View File

@ -60,6 +60,11 @@ public class ShiroServiceImpl implements ShiroService {
filterChainDefinitionMap.put("/api/auth/validateName", ANON);
filterChainDefinitionMap.put("/api/auth/isOpenLdap", ANON);
filterChainDefinitionMap.put("/api/auth/isOpenOidc", ANON);
filterChainDefinitionMap.put("/api/pluginCommon/component/*", ANON);
filterChainDefinitionMap.put("/plugin/oidc/authInfo", ANON);
filterChainDefinitionMap.put("/sso/callBack*", ANON);
filterChainDefinitionMap.put("/unauth", ANON);
filterChainDefinitionMap.put("/display/**", ANON);
filterChainDefinitionMap.put("/tokenExpired", ANON);

View File

@ -8,6 +8,7 @@ import com.auth0.jwt.exceptions.JWTDecodeException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.auth0.jwt.interfaces.Verification;
import io.dataease.auth.entity.TokenInfo;
import io.dataease.auth.entity.TokenInfo.TokenInfoBuilder;
import io.dataease.commons.utils.CommonBeanFactory;
import io.dataease.exception.DataEaseException;
import org.apache.commons.lang3.ObjectUtils;
@ -38,9 +39,9 @@ public class JWTUtils {
Verification verification = JWT.require(algorithm)
.withClaim("username", tokenInfo.getUsername())
.withClaim("userId", tokenInfo.getUserId());
if (StringUtils.isNotBlank(tokenInfo.getIdToken())) {
/* if (StringUtils.isNotBlank(tokenInfo.getIdToken())) {
verification.withClaim("idToken", tokenInfo.getIdToken());
}
} */
JWTVerifier verifier = verification.build();
verifier.verify(token);
return true;
@ -54,10 +55,15 @@ public class JWTUtils {
DecodedJWT jwt = JWT.decode(token);
String username = jwt.getClaim("username").asString();
Long userId = jwt.getClaim("userId").asLong();
// String idToken = jwt.getClaim("idToken").asString();
if (StringUtils.isEmpty(username) || ObjectUtils.isEmpty(userId) ){
DataEaseException.throwException("token格式错误");
}
TokenInfo tokenInfo = TokenInfo.builder().username(username).userId(userId).build();
TokenInfoBuilder tokenInfoBuilder = TokenInfo.builder().username(username).userId(userId);
/* if (StringUtils.isNotBlank(idToken)) {
tokenInfoBuilder.idToken(idToken);
} */
TokenInfo tokenInfo = tokenInfoBuilder.build();
return tokenInfo;
}
@ -114,11 +120,11 @@ public class JWTUtils {
Builder builder = JWT.create()
.withClaim("username", tokenInfo.getUsername())
.withClaim("userId", tokenInfo.getUserId());
if (StringUtils.isNotBlank(tokenInfo.getIdToken())) {
/* if (StringUtils.isNotBlank(tokenInfo.getIdToken())) {
builder.withClaim("idToken", tokenInfo.getIdToken());
}
return builder.withExpiresAt(date)
.sign(algorithm);
} */
return builder.withExpiresAt(date).sign(algorithm);
} catch (Exception e) {
return null;
}

View File

@ -2,6 +2,7 @@ package io.dataease.plugins.server;
import io.dataease.commons.utils.ServletUtils;
import io.dataease.plugins.common.dto.PluginSysMenu;
import io.dataease.plugins.common.service.PluginComponentService;
import io.dataease.plugins.common.service.PluginMenuService;
import io.dataease.plugins.config.SpringContextUtil;
import org.springframework.web.bind.annotation.GetMapping;
@ -9,7 +10,6 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.IOException;
@ -25,7 +25,7 @@ import java.util.concurrent.atomic.AtomicReference;
public class PluginCommonServer {
@GetMapping("/async/{menuId}")
public void componentInfo(@PathVariable Long menuId) {
public void menuInfo(@PathVariable Long menuId) {
Map<String, PluginMenuService> pluginMenuServiceMap = SpringContextUtil.getApplicationContext().getBeansOfType(PluginMenuService.class);
pluginMenuServiceMap.values().stream().forEach(service -> {
AtomicReference<PluginSysMenu> atomicReference = new AtomicReference<>();
@ -65,4 +65,41 @@ public class PluginCommonServer {
return;
});
}
@GetMapping("/component/{componentName}")
public void componentInfo(@PathVariable String componentName) {
Map<String, PluginComponentService> beansOfType = SpringContextUtil.getApplicationContext().getBeansOfType(PluginComponentService.class);
beansOfType.values().stream().forEach(service -> {
List<String> components = service.components();
if (components.contains(componentName)) {
HttpServletResponse response = ServletUtils.response();
BufferedInputStream bis = null;
InputStream inputStream = null;
OutputStream os = null; //输出流
try{
inputStream = service.vueResource(componentName);
byte[] buffer = new byte[1024];
os = response.getOutputStream();
bis = new BufferedInputStream(inputStream);
int i = bis.read(buffer);
while(i != -1){
os.write(buffer, 0, i);
i = bis.read(buffer);
}
os.flush();
}catch (Exception e) {
e.printStackTrace();
}finally {
try {
bis.close();
inputStream.close();
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return;
}
});
}
}

View File

@ -3,6 +3,10 @@ package io.dataease.plugins.server;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
@ -14,6 +18,7 @@ import io.dataease.auth.entity.TokenInfo;
import io.dataease.auth.service.AuthUserService;
import io.dataease.auth.util.JWTUtils;
import io.dataease.commons.exception.DEException;
import io.dataease.commons.utils.CodingUtil;
import io.dataease.commons.utils.ServletUtils;
import io.dataease.plugins.config.SpringContextUtil;
import io.dataease.plugins.xpack.display.dto.response.SysSettingDto;
@ -33,8 +38,7 @@ public class SSOServer {
private SysUserService sysUserService;
@GetMapping("/callBack")
public ModelAndView callBack(@RequestParam("code") String code, @RequestParam("statue") String state) {
public ModelAndView callBack(@RequestParam("code") String code, @RequestParam("state") String state) {
Map<String, OidcXpackService> beansOfType = SpringContextUtil.getApplicationContext().getBeansOfType((OidcXpackService.class));
if(beansOfType.keySet().size() == 0) {
DEException.throwException("缺少oidc插件");
@ -44,18 +48,29 @@ public class SSOServer {
if (!suuportOIDC) {
DEException.throwException("未开启oidc");
}
SSOToken ssoToken = oidcXpackService.requestSsoToken(code, state);
Map<String, String> config = config(oidcXpackService);
SSOToken ssoToken = oidcXpackService.requestSsoToken(config, code, state);
SSOUserInfo ssoUserInfo = oidcXpackService.requestUserInfo(config, ssoToken.getAccessToken());
SysUserEntity sysUserEntity = authUserService.getUserByName(ssoUserInfo.getUserName());
if(null == sysUserEntity){
sysUserService.saveOIDCUser(ssoUserInfo);
sysUserEntity = authUserService.getUserByName(ssoUserInfo.getUserName());
}
TokenInfo tokenInfo = TokenInfo.builder().userId(sysUserEntity.getUserId()).username(sysUserEntity.getUsername()).idToken(ssoToken.getIdToken()).build();
String token = JWTUtils.sign(tokenInfo, sysUserService.defaultPWD());
TokenInfo tokenInfo = TokenInfo.builder().userId(sysUserEntity.getUserId()).username(sysUserEntity.getUsername()).build();
String realPwd = CodingUtil.md5(sysUserService.defaultPWD());
String token = JWTUtils.sign(tokenInfo, realPwd);
ServletUtils.setToken(token);
ModelAndView modelAndView = new ModelAndView("/");
HttpServletResponse response = ServletUtils.response();
Cookie cookie_token = new Cookie("Authorization", token);cookie_token.setPath("/");
Cookie cookie_id_token = new Cookie("IdToken", ssoToken.getIdToken());cookie_id_token.setPath("/");
Cookie cookie_ac_token = new Cookie("AccessToken", ssoToken.getAccessToken());cookie_ac_token.setPath("/");
response.addCookie(cookie_token);
response.addCookie(cookie_id_token);
response.addCookie(cookie_ac_token);
ModelAndView modelAndView = new ModelAndView("redirect:/");
return modelAndView;
}
private Map<String, String> config(OidcXpackService oidcXpackService) {

View File

@ -4,9 +4,11 @@ package io.dataease.plugins.server;
import io.dataease.plugins.config.SpringContextUtil;
import io.dataease.plugins.xpack.display.dto.response.SysSettingDto;
import io.dataease.plugins.xpack.oidc.service.OidcXpackService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RequestMapping("/plugin/oidc")
@RestController
@ -24,4 +26,34 @@ public class XOidcServer {
OidcXpackService oidcXpackService = SpringContextUtil.getBean(OidcXpackService.class);
oidcXpackService.save(settings);
}
@PostMapping(value="/authInfo")
public Map<String, Object> authInfo() {
OidcXpackService oidcXpackService = SpringContextUtil.getBean(OidcXpackService.class);
Map<String, Object> result = new HashMap<String, Object>();
List<SysSettingDto> oidcSettings = oidcXpackService.oidcSettings();
Map<String, String> authParam = new HashMap<>();
authParam.put("response_type", "code");
authParam.put("state", "state");
// authParam.put("redirect_uri", "http://localhost:9528");
oidcSettings.forEach(param -> {
if(StringUtils.isNotBlank(param.getParamKey())) {
if (StringUtils.equals(param.getParamKey(), "oidc.authEndpoint")) {
result.put("authEndpoint", param.getParamValue());
}
if (StringUtils.equals(param.getParamKey(), "oidc.scope")) {
authParam.put("scope", param.getParamValue());
}
if (StringUtils.equals(param.getParamKey(), "oidc.clientId")) {
authParam.put("client_id", param.getParamValue());
}
}
});
result.put("authParam", authParam);
return result;
}
}

View File

@ -56,6 +56,7 @@
</el-col>
</el-row>
</div>
<plugin-com v-if="loginTypes.includes(2) && loginForm.loginType === 2" ref="SSOComponent" component-name="SSOComponent" />
</div>
</template>
@ -64,8 +65,10 @@
import { encrypt } from '@/utils/rsaEncrypt'
import { ldapStatus, oidcStatus } from '@/api/user'
import { getSysUI } from '@/utils/auth'
import PluginCom from '@/views/system/plugin/PluginCom'
export default {
name: 'Login',
components: { PluginCom },
data() {
return {
loginForm: {
@ -156,6 +159,9 @@ export default {
},
changeLoginType(val) {
if (val !== 2) return
this.$nextTick(() => {
})
}
}
}

View File

@ -0,0 +1,69 @@
<template>
<div>
<async-component v-if="showAsync" :url="url" @execute-axios="executeAxios" @on-add-languanges="addLanguages" @plugin-call-back="pluginCallBack" />
<div v-else>
<h1>未知组件无法展示</h1>
</div>
</div>
</template>
<script>
import AsyncComponent from '@/components/AsyncComponent'
import i18n from '@/lang'
import bus from '@/utils/bus'
import { execute } from '@/api/system/dynamic'
export default {
name: 'PluginCom',
components: {
AsyncComponent
},
props: {
componentName: {
type: String,
default: null
}
},
data() {
return {
showAsync: false,
baseUrl: '/api/pluginCommon/component/',
url: null
}
},
created() {
if (this.componentName) {
this.showAsync = true
this.url = this.baseUrl + this.componentName
} else {
this.showAsync = false
}
},
methods: {
// hasLicense
executeAxios(options) {
execute(options).then(res => {
if (options.callBack) {
options.callBack(res)
}
}).catch(e => {
if (options.callBack) {
options.callBack(e)
}
})
},
addLanguages(options) {
for (const key in i18n.messages) {
if (Object.hasOwnProperty.call(i18n.messages, key)) {
const element = options[key]
i18n.mergeLocaleMessage(key, element)
}
}
},
pluginCallBack(param) {
const { eventName, eventParam } = param
bus.$emit(eventName, eventParam)
}
}
}
</script>

View File

@ -22,7 +22,7 @@
<!-- <el-table-column prop="gender" :label="$t('commons.gender')" width="60" /> -->
<el-table-column prop="from" :label="$t('user.source')" width="80">
<template slot-scope="scope">
<div>{{ scope.row.from === 0 ? 'LOCAL' : 'LDAP' }}</div>
<div>{{ scope.row.from === 0 ? 'LOCAL' : scope.row.from === 1 ? 'LDAP' : 'OIDC' }}</div>
</template>
</el-table-column>