diff --git a/README.md b/README.md index 1ece5e3..a49b38b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ # open-JSD-9647 -JSD-9647 IAM OAuth2单点 \ No newline at end of file +JSD-9647 IAM OAuth2单点\ +免责说明:该源码为第三方爱好者提供,不保证源码和方案的可靠性,也不提供任何形式的源码教学指导和协助!\ +仅作为开发者学习参考使用!禁止用于任何商业用途!\ +为保护开发者隐私,开发者信息已隐去!若原开发者希望公开自己的信息,可联系hugh处理。 \ No newline at end of file diff --git a/doc/IAM对接代码参考.rar b/doc/IAM对接代码参考.rar new file mode 100644 index 0000000..87e34cf Binary files /dev/null and b/doc/IAM对接代码参考.rar differ diff --git a/doc/IAM对接代码参考/SysOamOauth.java b/doc/IAM对接代码参考/SysOamOauth.java new file mode 100644 index 0000000..0cd6cec --- /dev/null +++ b/doc/IAM对接代码参考/SysOamOauth.java @@ -0,0 +1,275 @@ +package com.fawjiefang.modules.sys.controller; + +import cn.hutool.core.codec.Base64; +import com.fawjiefang.common.cmodules.log.entity.SysLogLoginEntity; +import com.fawjiefang.common.cmodules.log.enums.LoginOperationEnum; +import com.fawjiefang.common.cmodules.log.enums.LoginStatusEnum; +import com.fawjiefang.common.cmodules.log.service.SysLogLoginService; +import com.fawjiefang.common.common.redis.RedisUtils; +import com.fawjiefang.common.common.utils.IpUtils; +import com.fawjiefang.common.common.utils.Result; +import com.fawjiefang.common.entity.UserCache; +import com.fawjiefang.common.utils.AesEncryptUtil; +import com.fawjiefang.modules.security.service.SysUserTokenService; +import com.fawjiefang.modules.sys.dto.SysUserDTO; +import com.fawjiefang.modules.sys.service.SysDictService; +import com.fawjiefang.modules.sys.service.SysUserService; +import io.swagger.annotations.ApiOperation; +import org.activiti.engine.impl.util.json.JSONObject; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.nio.charset.Charset; +import java.util.Date; +import java.util.Map; + +@RestController +@RequestMapping("/idm") +public class SysOamOauth { + + @Autowired + private SysUserTokenService sysUserTokenService; + + @Autowired + private SysLogLoginService sysLogLoginService; + + @Autowired + private RedisUtils redisUtils; + + @Autowired + private SysDictService sysDictService; + + //private static String AUTHORIZATION_URL = "https://www.fawidmdev.com/ms_oauth/oauth2/endpoints/oauthservice/authorize"; + @Value("${jiefang.admin.authorization-url}") + private String AUTHORIZATION_URL; + + @Value("${jiefang.admin.access-token-url}") + private String ACCESS_TOKEN_URL; + + @Value("${jiefang.admin.user-profile-url}") + private String USER_PROFILE_URL; + + @Value("${jiefang.admin.customer-service-url}") + private String CUSTOMER_SERVICE_URL; + + @Value("${jiefang.admin.redirect-uri}") + private String REDIRECT_URI; + + private static String CLIENT_ID = "qakz5cr8r61gzzqq5sqioga8ulrfi483"; + + private static String CLIENT_SECRET = "1aspst1979wz4nt296unf51lvbfng0bs"; + + private static String BASE_64_CREDENTIALS = "Basic " + new String(Base64.encode(CLIENT_ID+":"+ CLIENT_SECRET)); + + @Value("${jiefang.admin.home-url}") + private String HOME_URL; + + private static String RESPONSE_TYPE = "code"; + + private static String OAUTH_SCOPE = "Customer.Info UserProfile.me"; + + private static String GRANT_TYPE = "AUTHORIZATION_CODE"; + + private static CloseableHttpClient httpClient; + + static { + PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(); + cm.setMaxTotal(100); + cm.setDefaultMaxPerRoute(20); + cm.setDefaultMaxPerRoute(50); + httpClient = HttpClients.custom().setConnectionManager(cm).build(); + } + + @Autowired + private SysUserService sysUserService; + + @RequestMapping("validation") + @ApiOperation("idm验证") + public void validation(@RequestParam(value="code",required=false) String code, HttpServletRequest request, HttpServletResponse response) throws Exception { + if(code == null || "".equals(code)){ + //response.sendRedirect(AUTHORIZATION_URL+"?client_id=" + CLIENT_ID + "&response_type=" + RESPONSE_TYPE + "&redirect_uri=" + REDIRECT_URI + "&scope=" + OAUTH_SCOPE + "&domain=IdmDomain"); + //response.sendRedirect(AUTHORIZATION_URL+"?response_type=code&client_id= xcoiv98y2kd22vusuye3kch &domain=IdmDomain &scope=ResServer.Customer.Info ResServer.UserProfile.me&redirect_uri="+REDIRECT_URI); + //response.sendRedirect("http://10.60.25.66/oauth2/rest/authz?response_type=code&client_id="+CLIENT_ID+"&domain=IdmDomain&state=xcoiv98y2kd22vusuye3kch&scope=IdmResServer.Customer.Info%20IdmResServer.UserProfile.me&redirect_uri="+REDIRECT_URI); + // response.sendRedirect("https://iamuat.fawjiefang.com.cn/oauth2/rest/authz?response_type=code&client_id=qakz5cr8r61gzzqq5sqioga8ulrfi483&domain=IdmDomain&state=xyz&scope=IdmResServer.UserProfile.me openid email phone profile&redirect_uri=http://10.58.52.112:8686/jiefang-admin/idm/validation"); + response.sendRedirect(AUTHORIZATION_URL+"?response_type="+RESPONSE_TYPE+"&client_id="+CLIENT_ID+"&domain=IdmDomain&state=xyz&scope=IdmResServer.UserProfile.me openid email phone profile&redirect_uri="+REDIRECT_URI); + }else{ + String accessToken = getAccessToken(code); + System.out.println("accessToken:"+accessToken); + String username = getUserInfo(accessToken); + getCustomerInfo(accessToken,username); + SysUserDTO user = sysUserService.getByUsername(username); + + SysLogLoginEntity log = new SysLogLoginEntity(); + log.setOperation(LoginOperationEnum.LOGIN.value()); + log.setCreateDate(new Date()); + log.setIp(IpUtils.getIpAddr(request)); + log.setUserAgent(request.getHeader(HttpHeaders.USER_AGENT)); + log.setIp(IpUtils.getIpAddr(request)); + + if(user == null){ + log.setStatus(LoginStatusEnum.FAIL.value()); + log.setCreatorName(username); + sysLogLoginService.save(log); + response.sendRedirect(HOME_URL); + }else{ + sysDictService.refRedisDict(); + } + Result r = sysUserTokenService.createToken(user.getId()); + //用户信息 + + Map map = (Map) r.getData(); + SetUserCacheToRedis(user); + redisUtils.hSet("userinfo",user.getId().toString(),user); + String key = String.valueOf(System.currentTimeMillis()); + String aesUserId = AesEncryptUtil.encrypt(new String(map.get("userId").toString().getBytes(),"UTF-8"),"123"+key,"123"+key); + String aesToken = AesEncryptUtil.encrypt(new String(map.get("token").toString().getBytes(),"UTF-8"),"123"+key,"123"+key); + log.setStatus(LoginStatusEnum.SUCCESS.value()); + log.setCreator(user.getId()); + log.setCreatorName(user.getUsername()); + sysLogLoginService.save(log); + response.sendRedirect(HOME_URL+"?userId="+aesUserId+"&token="+aesToken+"&key="+key); + } + } + + private void SetUserCacheToRedis(SysUserDTO user) { + try { + UserCache userCache = new UserCache(); + userCache.setEmail(user.getEmail()); + userCache.setId(user.getId()); + userCache.setMobile(user.getMobile()); + userCache.setUsername(user.getUsername()); + userCache.setSuperAdmin(user.getSuperAdmin()); + redisUtils.hSet("userCache",user.getId().toString(),userCache); + } catch (Exception e) { + } + } + + public void getCustomerInfo(String token ,String uid){ + if(token != null && uid != null){ + for(int i=0;i<100;i++){ + System.out.println("token:"+token); + System.out.println("uid:"+uid); + } + }else{ + if(token == null){ + System.out.println("token空了"); + }else{ + System.out.println("uid空了"); + } + } + } + + public String getAccessToken(String code){ + + String accessToken = null; + + String params = "redirect_uri=" + REDIRECT_URI + "&grant_type=" + GRANT_TYPE + "&code=" + code; + System.out.println("参数:"+params); + byte[] postData = params.getBytes(Charset.forName("UTF-8")); + HttpURLConnection connection = null; + OutputStream wr = null; + try { + URL url = new URL(ACCESS_TOKEN_URL); + connection = (HttpURLConnection) url.openConnection(); + connection.setRequestMethod("POST"); + connection.setRequestProperty("Authorization",BASE_64_CREDENTIALS); + connection.setRequestProperty("cache-control","no-cache"); + connection.setRequestProperty("Content-Type","application/x-www-form-urlencoded;charset=UTF-8"); + connection.setRequestProperty("X-OAUTH-IDENTITY-DOMAIN-NAME","IdmDomain "); + connection.setDoOutput(true); + wr = new DataOutputStream(connection.getOutputStream()); + wr.write(postData); + wr.flush(); + wr.close(); + + BufferedReader rd = new BufferedReader(new InputStreamReader(connection.getInputStream(),Charset.forName("UTF-8"))); + String line; + StringBuffer resp = new StringBuffer(); + while((line = rd.readLine()) != null){ + resp.append(line); + } + rd.close(); + JSONObject obj; + obj = new JSONObject(resp.toString()); + accessToken = obj.getString("access_token"); + + }catch (Exception e){ + e.printStackTrace(); + throw new RuntimeException(); + }finally { + if(connection != null){ + connection.disconnect(); + } + try { + if(wr != null){ + wr.flush(); + wr.close(); + } + + } catch (IOException e) { + e.printStackTrace(); + } + } + return accessToken; + } + + public String getUserInfo(String accessToken){ + String uid = null; + + + CloseableHttpResponse response = null; + BufferedReader in = null; + String result = ""; + + String params = "Authorization=" + accessToken; + byte[] postData = params.getBytes(Charset.forName("UTF-8")); + HttpURLConnection connection = null; + try { + HttpGet httpGet = new HttpGet(USER_PROFILE_URL); + RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(30000).setConnectionRequestTimeout(30000).setSocketTimeout(30000).build(); + httpGet.setConfig(requestConfig); + httpGet.addHeader("X-OAUTH-IDENTITY-DOMAIN-NAME", "IdmDomain"); + httpGet.setHeader("Authorization", "Bearer "+accessToken); + response = httpClient.execute(httpGet); + + BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent())); + String line; + StringBuffer resp = new StringBuffer(); + while((line = rd.readLine()) != null){ + resp.append(line); + } + rd.close(); + JSONObject obj; + obj = new JSONObject(resp.toString()); + uid = obj.getString("sub"); + + }catch (Exception e){ + e.printStackTrace(); + throw new RuntimeException(); + }finally { + if(connection != null){ + connection.disconnect(); + } + } + return uid; + + } + + +} diff --git a/doc/IAM对接代码参考/config-index.js b/doc/IAM对接代码参考/config-index.js new file mode 100644 index 0000000..5bf38b7 --- /dev/null +++ b/doc/IAM对接代码参考/config-index.js @@ -0,0 +1,61 @@ +//根据环境,将apiURL赋不同的值 +let IDM_APPROVE_URL = '' +let IDM_LOGOUT_URL = '' +let IAM_LOGIN = '' +let UPDATE_PASSWORD_URL = '' +let ENV_NOTICE = '' +switch (process.env.VUE_APP_NODE_ENV) { + case 'dev': // + window.SITE_CONFIG['apiURL'] = 'http://localhost:8686/' + ENV_NOTICE = '开发环境' + break; + case 'sit': // + case 'uat': // + window.SITE_CONFIG['apiURL'] = 'http://10.60.205.40:8686/'; + IDM_APPROVE_URL = "https://iam.fawjiefang.com.cn/oauth2/rest/authz?response_type=code&client_id=qakz5cr8r61gzzqq5sqioga8ulrfi483&domain=IdmDomain&state=xyz&scope=IdmResServer.UserProfile.me%20openid%20email%20phone%20profile&redirect_uri=http://10.60.205.40:8686/jiefang-admin/idm/validation"; + IDM_LOGOUT_URL = "https://iam.fawjiefang.com.cn/oam/server/logout?end_url=https://iam.fawjiefang.com.cn/oauth2/rest/authz%3Fresponse_type=code%26client_id=qakz5cr8r61gzzqq5sqioga8ulrfi483%26domain=IdmDomain%26state=xyz%26scope=IdmResServer.UserProfile.me%2bopenid%2bemail%2bphone%2bprofile%26redirect_uri=http://10.60.205.40:8686/jiefang-admin/idm/validation"; + IAM_LOGIN = "https://iam.fawjiefang.com.cn/FawIdmCommonUtils/ssoLoginService/goLogout?client_id=qakz5cr8r61gzzqq5sqioga8ulrfi483"; + UPDATE_PASSWORD_URL = "https://iam.fawjiefang.com.cn/IAMEmployeeExtApp/#/home/user_update_password" + ENV_NOTICE = '生产环境' + break; + default: + window.SITE_CONFIG['apiURL'] = 'http://localhost:8686/'; + break; +} + +const DOWNLOAD_URL = "http://localhost:8686/" +const JMPS_BAS_URL = "jiefang-bas" +const JMPS_ADMIN_URL = "jiefang-admin" +const JMPS_FILE_URL = "jiefang-file" +const JMPS_QTMS_URL = "jiefang-qtms" +const PARTS_SUPPLIER = "http://10.60.22.13:8180/tqm-admin/publicsup/publicsup/findbypartgetsup" +const serviceUrl = window.SITE_CONFIG['apiURL'] + "/" +const serviceUrl1 = window.SITE_CONFIG['apiURL'] +const partsSupplierUrl = "http://10.60.22.13:8180/tqm-admin/publicsup/publicsup/findbypartgetsup?" +// const IDM_APPROVE_URL = "https://iam.fawjiefang.com.cn/oauth2/rest/authz?response_type=code&client_id=qakz5cr8r61gzzqq5sqioga8ulrfi483&domain=IdmDomain&state=xyz&scope=IdmResServer.UserProfile.me%20openid%20email%20phone%20profile&redirect_uri=http://10.60.205.40:8686/jiefang-admin/idm/validation"; +// const IDM_LOGOUT_URL = "https://iam.fawjiefang.com.cn/oam/server/logout?end_url=https://iamuat.fawjiefang.com.cn/oauth2/rest/authz%3Fresponse_type=code%26client_id=qakz5cr8r61gzzqq5sqioga8ulrfi483%26domain=IdmDomain%26state=xyz%26scope=IdmResServer.UserProfile.me%2bopenid%2bemail%2bphone%2bprofile%26redirect_uri=http://10.60.205.40:8686/jiefang-admin/idm/validation"; +// const IAM_LOGIN = "https://iam.fawjiefang.com.cn/FawIdmCommonUtils/ssoLoginService/goLogout?client_id=qakz5cr8r61gzzqq5sqioga8ulrfi483"; +export default { + JMPS_BAS_URL, + JMPS_ADMIN_URL, + JMPS_QTMS_URL, + serviceUrl, + serviceUrl1, + JMPS_FILE_URL, + DOWNLOAD_URL, + PARTS_SUPPLIER, + partsSupplierUrl, + IDM_APPROVE_URL, + IDM_LOGOUT_URL, + IAM_LOGIN, + UPDATE_PASSWORD_URL, + ENV_NOTICE +} \ No newline at end of file diff --git a/doc/IAM对接代码参考/router-index.js b/doc/IAM对接代码参考/router-index.js new file mode 100644 index 0000000..0e9abad --- /dev/null +++ b/doc/IAM对接代码参考/router-index.js @@ -0,0 +1,271 @@ +import Vue from 'vue' +import Router from 'vue-router' +import { getUrlKey } from "@/utils/getUrlKey.js"; +import { Decrypt, Encrypt } from '@/utils/my-aes-crypto.js' +import { getKey } from '../utils/my-aes-crypto'; +import http from '@/utils/request' +import Cookies from "js-cookie"; +import { pageRoutes, childrenRoutes, moduleRoutes } from '@/router/config' +import { + isURL +} from '@/utils/validate' +import baseUrl from '@/config' +Vue.use(Router) + +const router = new Router({ + mode: 'hash', + scrollBehavior: () => ({ + y: 0 + }), + routes: pageRoutes.concat(moduleRoutes) +}) +export function addDynamicRoute(routeParams, router) { + // 组装路由名称, 并判断是否已添加, 如是: 则直接跳转 + var routeName = routeParams.routeName + var dynamicRoute = window.SITE_CONFIG['dynamicRoutes'].filter(item => item.name === routeName)[0] + if (dynamicRoute) { + return router.push({ + name: routeName, + params: routeParams.params + }) + } + // 否则: 添加并全局变量保存, 再跳转 + dynamicRoute = { + path: routeName, + component: () => + import(`@/views/modules/${routeParams.path}`), + name: routeName, + meta: { + ...window.SITE_CONFIG['contentTabDefault'], + menuId: routeParams.menuId, + title: `${routeParams.title}` + } + } + router.addRoutes([{ + ...moduleRoutes, + name: `main-dynamic__${dynamicRoute.name}`, + children: [dynamicRoute] + }]) + window.SITE_CONFIG['dynamicRoutes'].push(dynamicRoute) + router.push({ + name: dynamicRoute.name, + params: routeParams.params + }) +} + +let appendRoutes = (dataList) => { + for (let i = 0; i < childrenRoutes.length; i++) { + concatRoutes(dataList, childrenRoutes[i]); + } + + return dataList; +} + +let concatRoutes = (parentList, appendNode) => { + for (let i = 0; i < parentList.length; i++) { + let value = parentList[i]; + + //向路由的每个结点追加isLastLevel属性,标识是否为最后一级导航结点,按配置到数据库中的算,页面上后append的,默认都是页面内部路由,不算 + if (value.children == null) { + value.children = []; + } + + if (value.isLastLevel == null || value.isLastLevel == false) { + value.isLastLevel = value.children.length == 0; + } + + //依据rootUrl寻找父级结点 + if (value.url && appendNode.rootUrl == value.url) { //找到了,为children赋值,结束循环 + value.children = value.children.concat(appendNode.children); + return; + } else { //没找到,递归 + concatRoutes(value.children, appendNode); + } + } +} + + +router.beforeEach((to, from, next) => { + + var userId = getUrlKey("userId"); + var token = getUrlKey("token"); + var key = getUrlKey("key"); + if (userId && token) { + userId = userId.replace(/\ +/g, "+");//去掉空格 + userId = userId.replace(/[ ]/g, "+"); //去掉空格 + userId = userId.replace(/[\r\n]/g, "+");//去掉回车换行 + token = token.replace(/\ +/g, "+");//去掉空格 + token = token.replace(/[ ]/g, "+"); //去掉空格 + token = token.replace(/[\r\n]/g, "+");//去掉回车换行 + } + var deUserId = ""; + var deToken = ""; + console.log("userId", userId); + console.log("token", token); + console.log("key", key); + + if( Cookies.get('cookieFlag') != '1'){ + if (userId && token && key && Number("123" + getKey()) - Number("123" + key) < 600000) { + deUserId = Decrypt(userId, "123" + key, "123" + key); + deToken = Decrypt(token, "123" + key, "123" + key); + console.log("deUserId", deUserId); + console.log("deToken", deToken); + Cookies.set("token", deToken); + Cookies.set("userId", deUserId); + } else { + console.log("已经超时了"); + } + }else{ + Cookies.remove('cookieFlag'); + } + + if (to.name != "logout") { + // if (!Cookies.get("token") || !Cookies.get("userId")) { + // window.location.replace(baseUrl.IDM_APPROVE_URL) + // } + } + // 添加动态(菜单)路由 + // 已添加或者当前路由为页面路由, 可直接访问 + if (window.SITE_CONFIG['dynamicMenuRoutesHasAdded'] || fnCurrentRouteIsPageRoute(to, pageRoutes)) { + return next() + } + // 获取菜单列表, 添加并全局变量保存 + http.get(baseUrl.JMPS_ADMIN_URL + '/sys/menu/nav').then(({ + data: res + }) => { + console.log("beforeEachbeforeEachbeforeEach"); + + if (res.code !== 0) { + Vue.prototype.$message({ + message: res.msg, + type: "error", + duration: 2000 + }); + return next({ + name: 'login' + }) + // window.location.replace(baseUrl.IDM_APPROVE_URL) + } + window.SITE_CONFIG['menuList'] = appendRoutes(res.data); + fnAddDynamicMenuRoutes(window.SITE_CONFIG['menuList']) + next({ + ...to, + replace: true + }) + }).catch(() => { + next({ + name: 'login' + }) + // window.location.replace(baseUrl.IDM_APPROVE_URL) + }) + + + + + + + +}) + +/** + * 判断当前路由是否为页面路由 + * @param {*} route 当前路由 + * @param {*} pageRoutes 页面路由 + */ +function fnCurrentRouteIsPageRoute(route, pageRoutes = []) { + var temp = [] + for (var i = 0; i < pageRoutes.length; i++) { + if (route.path === pageRoutes[i].path) { + return true + } + if (pageRoutes[i].children && pageRoutes[i].children.length >= 1) { + temp = temp.concat(pageRoutes[i].children) + } + } + return temp.length >= 1 ? fnCurrentRouteIsPageRoute(route, temp) : false +} + +const createRouteConfig = (menu) => { + // 组装路由 + var route = { + url: menu.url, + path: '', + component: null, + name: '', + meta: { + ...window.SITE_CONFIG['contentTabDefault'], + menuId: menu.id, + title: menu.name + } + } + + if (menu.meta && menu.meta.isBreadcrumb) { + route.meta = { + ...route.meta, + ...menu.meta + } + } + + // eslint-disable-next-line + let URL = (menu.url || '').replace(/{{([^}}]+)?}}/g, (s1, s2) => eval(s2)) // URL支持{{ window.xxx }}占位符变量 + if (isURL(URL)) { + route['path'] = route['name'] = `i-${menu.id}` + route['meta']['iframeURL'] = URL + } else { + URL = URL.replace(/^\//, '').replace(/_/g, '-') + route['path'] = route['name'] = URL.replace(/\//g, '-') + route['component'] = () => + import(`@/views/modules/${URL}`) + } + + if (menu.routeName && menu.routeName != "") { + route['name'] = menu.routeName; + } + + return route; +} + +/** + * 添加动态(菜单)路由 + * @param {*} menuList 菜单列表 + * @param {*} routes 递归创建的动态(菜单)路由 + */ +function fnAddDynamicMenuRoutes(menuList = [], routes = []) { + var temp = [] + for (var i = 0; i < menuList.length; i++) { + + //逻辑需要,对于其下配置了children的结点,本身也要能够跳转 + if (menuList[i].url && menuList[i].children && menuList[i].children.length >= 1) { + routes.push(createRouteConfig(menuList[i])); + } + + if (menuList[i].children && menuList[i].children.length >= 1) { + temp = temp.concat(menuList[i].children) + continue + } + + routes.push(createRouteConfig(menuList[i])) + } + if (temp.length >= 1) { + return fnAddDynamicMenuRoutes(temp, routes) + } + // 添加路由 + router.addRoutes([{ + ...moduleRoutes, + name: 'main-dynamic-menu', + children: routes + }, + { + path: '*', + redirect: { + name: '404' + } + } + ]) + window.SITE_CONFIG['dynamicMenuRoutes'] = routes + window.SITE_CONFIG['dynamicMenuRoutesHasAdded'] = true + +} + + +export default router \ No newline at end of file diff --git a/doc/JSD-9647-需求确认书V1.docx b/doc/JSD-9647-需求确认书V1.docx new file mode 100644 index 0000000..3d3ffb4 Binary files /dev/null and b/doc/JSD-9647-需求确认书V1.docx differ diff --git a/doc/JSD-9880-需求确认书V1.docx b/doc/JSD-9880-需求确认书V1.docx new file mode 100644 index 0000000..4c8e56b Binary files /dev/null and b/doc/JSD-9880-需求确认书V1.docx differ diff --git a/doc/jsd9880.postman_collection测试用例.json b/doc/jsd9880.postman_collection测试用例.json new file mode 100644 index 0000000..969f8a6 --- /dev/null +++ b/doc/jsd9880.postman_collection测试用例.json @@ -0,0 +1,62 @@ +{ + "info": { + "_postman_id": "1c37d4fa-6ce7-4829-8a1e-9bc5151032ac", + "name": "jsd9880", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" + }, + "item": [ + { + "name": "getToken", + "protocolProfileBehavior": { + "disabledSystemHeaders": { + "user-agent": true, + "accept": true + } + }, + "request": { + "method": "POST", + "header": [ + { + "key": "Cookie", + "value": "cf_chl_2=8530a6541dd5d4a; cf_chl_prog=x20; cf_clearance=LAn7K16arymRPPFI9SJPfBGWpbln5vCCIZzBrTf_Ibc-1647589093-0-150", + "type": "text" + }, + { + "key": "User-Agent", + "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36 Edg/99.0.1150.39", + "type": "text" + }, + { + "key": "accept", + "value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", + "type": "text" + } + ], + "body": { + "mode": "urlencoded", + "urlencoded": [ + { + "key": "accessToken", + "value": "eyJraWQiOiJJZG1Eb21haW4iLCJ4NXQiOiJ6dy1iN0phQlVPQjBrUEtzTlhyOWdHdi0tX3ciLCJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJodHRwczovL2lhbXVhdC5mYXdqaWVmYW5nLmNvbS5jbjo0NDMvb2F1dGgyIiwiYXVkIjpbIklkbVJlc1NlcnZlciIsIjE0NzI4MTlhMGNmOTQwYzI5NWY0Yjg0YjFjMDIzMWY1Il0sImV4cCI6MTY0ODEwNTMyMiwianRpIjoiVHFoWTJEcFBQQzdVMWRXbkdqQTBvUSIsImlhdCI6MTY0ODEwMTcyMiwic3ViIjoiMTAwMDQyNjIiLCJzZXNzaW9uSWQiOiIyYmYzYmM1Ny1hN2E5LTQyNjctOWZiZS0xZDU4YTEzYzAzNDN8c2V3dkE4U3F5b3pzNXpuTS9uV2x2a2R6YkNISFNveFpHREpyd3ZSVWhnZz0iLCJjdXN0b21lQXR0ciI6IkN1c3RvbVZhbHVlIiwicmVzU3J2QXR0ciI6IlJFU09VUkNFQ09OU1QiLCJjbGllbnQiOiIxNDcyODE5YTBjZjk0MGMyOTVmNGI4NGIxYzAyMzFmNSIsInNjb3BlIjpbIklkbVJlc1NlcnZlci5Vc2VyUHJvZmlsZS5tZSIsIm9wZW5pZCIsImVtYWlsIiwicGhvbmUiLCJwcm9maWxlIl0sImRvbWFpbiI6IklkbURvbWFpbiJ9.fvcih44HOKTCXEfCOVyxugUHJQDN_d8LqW4TDZX4E3tOaks5skh-JVVci8hOjppPAn6ue1-LWplb6WNxu3qQWsQQdQu6WyRsXsY-2TAqAkDoUs_eLr6FRZkzbjZPSLCq6fpcxTaBCpuWYrHyKCZnpSYw2sjrfcBKY22o4jus4FMxxUOrJowMdzY3CVzy9jqVeX0A0TWwWwgqdMaL6cCNuwnLnmUubs1aGHP-mUdlpNxKJnJlE9YAaRtdBsLaOMpHDgqUsl3EQcBy4R1pbMIiox0_61v433AjhuGIPbjsI-OGTxPJmesIuPi9IXvaB7vxxf0raeT73vBoJGF8kxB6Gw", + "type": "text" + } + ] + }, + "url": { + "raw": "localhost:8075/webroot/decision/url/getToken", + "host": [ + "localhost" + ], + "port": "8075", + "path": [ + "webroot", + "decision", + "url", + "getToken" + ] + } + }, + "response": [] + } + ] +} \ No newline at end of file diff --git a/doc/一汽解放_IAM身份管理项目-业务系统登录认证接入规范-v1.5(1).docx b/doc/一汽解放_IAM身份管理项目-业务系统登录认证接入规范-v1.5(1).docx new file mode 100644 index 0000000..fb412ff Binary files /dev/null and b/doc/一汽解放_IAM身份管理项目-业务系统登录认证接入规范-v1.5(1).docx differ diff --git a/doc/解放IAM单点登录插件使用文档.docx b/doc/解放IAM单点登录插件使用文档.docx new file mode 100644 index 0000000..1157302 Binary files /dev/null and b/doc/解放IAM单点登录插件使用文档.docx differ diff --git a/lib/finekit-10.0-20200828.jar b/lib/finekit-10.0-20200828.jar new file mode 100644 index 0000000..47de3ae Binary files /dev/null and b/lib/finekit-10.0-20200828.jar differ diff --git a/plugin.xml b/plugin.xml new file mode 100644 index 0000000..e8cd18c --- /dev/null +++ b/plugin.xml @@ -0,0 +1,21 @@ + + com.eco.plugin.xxxx.ijf.iam + + yes + 1.0.4 + 10.0 + 2020-07-31 + fr.open + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/GetTicket.java b/src/main/java/com/fr/plugin/GetTicket.java new file mode 100644 index 0000000..b47a304 --- /dev/null +++ b/src/main/java/com/fr/plugin/GetTicket.java @@ -0,0 +1,120 @@ +package com.fr.plugin; + +import com.fanruan.api.net.http.HttpKit; +import com.fr.decision.authority.data.User; +import com.fr.decision.fun.impl.BaseHttpHandler; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.context.PluginContexts; +import com.fr.third.org.apache.commons.codec.digest.DigestUtils; +import com.fr.third.org.apache.commons.lang3.StringUtils; +import com.fr.third.springframework.web.bind.annotation.RequestMethod; +import com.fr.web.utils.WebUtils; +import org.dom4j.DocumentException; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +public class GetTicket extends BaseHttpHandler { + @Override + public RequestMethod getMethod() { + return null; + } + + @Override + public String getPath() { + return "/getToken"; + } + + @Override + public boolean isPublic() { + return true; + } + + @Override + public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { + JSONObject entries = new JSONObject(); + if (!PluginContexts.currentContext().isAvailable()) { + entries.put("code", "1"); + entries.put("msg", "授权过期请联系销售人员"); + WebUtils.printAsJSON(httpServletResponse, entries); + return; + } + String accessToken = WebUtils.getHTTPRequestParameter(httpServletRequest, "accessToken"); + if (StringUtils.isBlank(accessToken)) { + entries.put("code", "1"); + entries.put("msg", "accessToken码不存在"); + WebUtils.printAsJSON(httpServletResponse, entries); + return; + } + //第一步获取token +// String accessToken = getAccessToken(code); +// if (StringUtils.isBlank(accessToken)) { +// entries.put("code", "1"); +// entries.put("msg", "授权码无效,请重新授权"); +// WebUtils.printAsJSON(httpServletResponse, entries); +// return; +// } + String uid = getUserInfo(accessToken); + User user = UserService.getInstance().getUserByUserName(uid); + if (user == null) { + entries.put("code", "1"); + entries.put("msg", "登录失败:" + uid + " 在帆软用户体系不存在,请联系管理员添加"); + WebUtils.printAsJSON(httpServletResponse, entries); + return; + } + String token = login(httpServletRequest, httpServletResponse, uid); + entries.put("code", "0"); + entries.put("data", token); + WebUtils.printAsJSON(httpServletResponse, entries); + } + + private String getAccessToken(String code) throws IOException { + Oauth2Config config = Oauth2Config.getInstance(); + String valAddr = config.getValAddr(); + String appId = config.getAppId(); + String clientSecret = config.getClientSecret(); + String frUrl = Oauth2Config.getInstance().getFrUrl(); + String redirectUrl = String.format("%s/url/iam/authCallBack", frUrl); + Map params = new HashMap<>(); + String tokenUrl = String.format("%s/oauth2/rest/token", valAddr); + params.put("redirect_uri", redirectUrl); + params.put("grant_type", "AUTHORIZATION_CODE"); + params.put("code", code); + Map header = new HashMap<>(); + header.put("X-OAUTH-IDENTITY-DOMAIN-NAME", "IdmDomain"); + header.put("Authorization", "Basic " + Base64.getEncoder().encodeToString(String.format("%s:%s", appId, clientSecret).getBytes(StandardCharsets.UTF_8))); + String json = HttpKit.post(tokenUrl, params, "utf-8", "utf-8", header); + FineLoggerFactory.getLogger().info("获取AccessToken 请求返回:{}", json); + JSONObject obj = new JSONObject(json); + return obj.getString("access_token"); + } + + public static String getUserInfo(String accessToken) throws DocumentException, IOException { + Map header = new HashMap<>(); + Oauth2Config config = Oauth2Config.getInstance(); + String valAddr = config.getValAddr(); + String url = String.format("%s/oauth2/rest/userinfo?access_token=%s", valAddr, accessToken); + header.put("Authorization", "Bearer " + accessToken); + String json = HttpKit.get(url, new HashMap<>(), header); + FineLoggerFactory.getLogger().info("获取userinfo 请求返回:{}", json); + JSONObject obj = new JSONObject(json); + return obj.getString("sub"); + } + + private String login(HttpServletRequest req, HttpServletResponse res, String username) throws Exception { + String token = LoginService.getInstance().login(req, res, username); + req.setAttribute("fine_auth_token", token); + FineLoggerFactory.getLogger().info("fr FrFilter is over with username is ###" + username); + return token; + } + +} diff --git a/src/main/java/com/fr/plugin/GoAuthApi.java b/src/main/java/com/fr/plugin/GoAuthApi.java new file mode 100644 index 0000000..2c5752d --- /dev/null +++ b/src/main/java/com/fr/plugin/GoAuthApi.java @@ -0,0 +1,63 @@ +package com.fr.plugin; + +import com.fr.decision.fun.impl.BaseHttpHandler; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.third.org.apache.commons.codec.digest.DigestUtils; +import com.fr.third.org.apache.commons.lang3.StringUtils; +import com.fr.third.springframework.web.bind.annotation.RequestMethod; +import com.fr.web.utils.WebUtils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.net.URLEncoder; +import java.util.HashMap; +import java.util.Map; + +public class GoAuthApi extends BaseHttpHandler { + @Override + public RequestMethod getMethod() { + return null; + } + + @Override + public String getPath() { + return "/goAuth"; + } + + @Override + public boolean isPublic() { + return true; + } + + @Override + public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { + if (isLogin(httpServletRequest)) { + sendRedirect(httpServletResponse, HttpUtils.getDefaultUrl(httpServletRequest)); + return; + } else { + String valAddr = Oauth2Config.getInstance().getValAddr(); + String appId = Oauth2Config.getInstance().getAppId(); + String frUrl = Oauth2Config.getInstance().getFrUrl(); + String redirectUrl = String.format("%s/url/iam/authCallBack", frUrl); + redirectUrl = URLEncoder.encode(redirectUrl, "utf-8"); + String goUrl = String.format("%s/oauth2/rest/authz?response_type=code&client_id=%s&domain=IdmDomain&state=xyz&scope=IdmResServer.UserProfile.me openid email phone profile&redirect_uri=%s", valAddr, appId, redirectUrl); + sendRedirect(httpServletResponse, goUrl); + } + } + + + private void sendRedirect(HttpServletResponse res, String url) throws IOException { + Map params = new HashMap<>(); + params.put("callBack", url); + WebUtils.writeOutTemplate("com/fr/plugin/redirect.html", res, params); + } + + private boolean isLogin(HttpServletRequest req) { + return LoginService.getInstance().isLogged(req); + } + + private String md5(String str) { + return DigestUtils.md5Hex(str); + } +} diff --git a/src/main/java/com/fr/plugin/HttpUtils.java b/src/main/java/com/fr/plugin/HttpUtils.java new file mode 100644 index 0000000..71a3bea --- /dev/null +++ b/src/main/java/com/fr/plugin/HttpUtils.java @@ -0,0 +1,52 @@ +package com.fr.plugin; + +import com.fr.base.FRContext; +import com.fr.base.ServerConfig; +import com.fr.base.TemplateUtils; +import com.fr.log.FineLoggerFactory; +import com.fr.stable.StringUtils; +import com.fr.third.org.apache.commons.io.IOUtils; + +import javax.servlet.http.HttpServletRequest; +import java.io.*; +import java.net.HttpURLConnection; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * http请求工具类 + * + * @author 0246 + */ +public class HttpUtils { + + + + /** + * 返回当前系统的根路径 + * + * @return + */ + public static String getDefaultUrl(HttpServletRequest req) { + StringBuilder url = new StringBuilder(); + try { + url.append(req.getScheme()); + url.append("://"); + url.append(req.getServerName()); + if (req.getServerPort() != 80) { + url.append(":"); + url.append(req.getServerPort()); + } + url.append(TemplateUtils.render("${fineServletURL}")); + } catch (Exception e) { + FineLoggerFactory.getLogger().error(e.getMessage(), e); + } + return url.toString(); + } + +} diff --git a/src/main/java/com/fr/plugin/IAMloginFilter.java b/src/main/java/com/fr/plugin/IAMloginFilter.java new file mode 100644 index 0000000..1939de7 --- /dev/null +++ b/src/main/java/com/fr/plugin/IAMloginFilter.java @@ -0,0 +1,166 @@ +package com.fr.plugin; + +import com.fr.base.ServerConfig; +import com.fr.base.TemplateUtils; +import com.fr.data.NetworkHelper; +import com.fr.decision.fun.impl.AbstractGlobalRequestFilterProvider; +import com.fr.decision.webservice.utils.DecisionStatusService; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.plugin.transform.ExecuteFunctionRecord; +import com.fr.plugin.transform.FunctionRecorder; +import com.fr.stable.StringUtils; +import com.fr.store.StateHubService; +import com.fr.web.utils.WebUtils; + +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.net.URLEncoder; +import java.util.Iterator; +import java.util.Map; + +@FunctionRecorder(localeKey = "fds") +public class IAMloginFilter extends AbstractGlobalRequestFilterProvider { + @Override + public String filterName() { + return "oauth2"; + } + + @Override + public String[] urlPatterns() { + return new String[]{ + "/*" + }; + } + + @Override + public void init(FilterConfig filterConfig) { + Oauth2Config.getInstance(); + super.init(filterConfig); + } + + @Override + @ExecuteFunctionRecord + public void doFilter(HttpServletRequest request, HttpServletResponse httpServletResponse, FilterChain filterChain) { + try { + if (isLogOut(request)) { + delLoginOut(request, httpServletResponse); + return; + } + if (needFilter(request) && !isLogin(request)) { + //跳转到登录界面 + String originalURL =getOriginalUrlIgnoreCode(request); + String frUrl = Oauth2Config.getInstance().getFrUrl(); + String valAddr = Oauth2Config.getInstance().getValAddr(); + String appId = Oauth2Config.getInstance().getAppId(); + StateHubService stateHubService = DecisionStatusService.originUrlStatusService(); + stateHubService.put("loginCallBack", originalURL); + String redirectUrl = String.format("%s/url/iam/authCallBack", frUrl); + redirectUrl = URLEncoder.encode(redirectUrl, "utf-8"); + String goUrl = String.format("%s/oauth2/rest/authz?response_type=code&client_id=%s&domain=IdmDomain&state=xyz&scope=IdmResServer.UserProfile.me openid email phone profile&redirect_uri=%s", valAddr, appId, redirectUrl); + sendRedirect(httpServletResponse, goUrl); + return; + } + filterChain.doFilter(request, httpServletResponse); + } catch (IOException | ServletException e) { + printException2FrLog(e); + } catch (Exception e) { + printException2FrLog(e); + } + } + + public String getOriginalUrlIgnoreCode(HttpServletRequest request) throws Exception { + StringBuffer url = new StringBuffer(request.getRequestURI()); + Map parameterMap = request.getParameterMap(); + Iterator iterator = parameterMap.entrySet().iterator(); + boolean notFirst = url.toString().indexOf("?") == -1; + while (iterator.hasNext()) { + Map.Entry entry = (Map.Entry) iterator.next(); + if (StringUtils.equals("code", entry.getKey().toString())) { + continue; + } + if (notFirst) { + url.append('?'); + notFirst = false; + } else { + url.append('&'); + } + + url.append(entry.getKey().toString()); + url.append('='); + url.append( URLEncoder.encode(request.getParameter(entry.getKey().toString()),"utf-8")); + } + FineLoggerFactory.getLogger().info("重定向到:" + url.toString()); + return url.toString(); + } + + private void delLoginOut(HttpServletRequest req, HttpServletResponse res) { + try { + //执行帆软内部的退出 + LoginService.getInstance().logout(req, res); + Oauth2Config oauth2Config = Oauth2Config.getInstance(); + JSONObject jsonObject = new JSONObject(); + String url = String.format("%s/xxxx/ssoLoginService/goLogout", oauth2Config.getValAddr()); + jsonObject.put("data", url); + //调用外部接口注销accessToken + FineLoggerFactory.getLogger().error("登出成功: ----------------"); + //指定退出之后到他们登录页面 + WebUtils.printAsJSON(res, jsonObject); + } catch (Exception var4) { + } + } + + private boolean isLogOut(HttpServletRequest req) { + String url = WebUtils.getOriginalURL(req); + String servletNamePrefix = "/" + ServerConfig.getInstance().getServletName() + "/logout"; + return url.contains(servletNamePrefix) && req.getMethod().equals("POST"); + } + + private void sendRedirect(HttpServletResponse res, String url) { + res.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY); + res.setHeader("Location", url); + } + + private boolean needFilter(HttpServletRequest request) { + String requestURI = request.getRequestURI(); + String isAdmin = request.getParameter("isAdmin"); + if (StringUtils.equals(isAdmin, "1")) { + return false; + } + if (StringUtils.isNotBlank(requestURI) && request.getMethod().equals("GET")) { + if (requestURI.endsWith("decision")) { + return true; + } + if (requestURI.endsWith("/view/form") || requestURI.endsWith("/view/report")) { + if (StringUtils.isNotBlank(request.getParameter("viewlet"))) { + return true; + } + } + if (requestURI.contains("/v10/entry/access/") && request.getMethod().equals("GET")) { + return true; + } + if (requestURI.contains("/v5/design/report") && (requestURI.endsWith("/edit") || requestURI.endsWith("/view"))) { + return true; + } + } + return false; + } + + public static void printException2FrLog(Throwable e) { + StringWriter writer = new StringWriter(); + e.printStackTrace(new PrintWriter(writer)); + String s = writer.toString(); + FineLoggerFactory.getLogger().error("错误:{}", s); + } + + private boolean isLogin(HttpServletRequest req) { + return LoginService.getInstance().isLogged(req); + } +} diff --git a/src/main/java/com/fr/plugin/Oauth2Config.java b/src/main/java/com/fr/plugin/Oauth2Config.java new file mode 100644 index 0000000..b807bd7 --- /dev/null +++ b/src/main/java/com/fr/plugin/Oauth2Config.java @@ -0,0 +1,70 @@ +package com.fr.plugin; + +import com.fr.config.*; +import com.fr.config.holder.Conf; +import com.fr.config.holder.factory.Holders; + +@Visualization(category = "IAM单点登录配置") +public class Oauth2Config extends DefaultConfiguration { + + private static volatile Oauth2Config config = null; + + public static Oauth2Config getInstance() { + if (config == null) { + config = ConfigContext.getConfigInstance(Oauth2Config.class); + } + return config; + } + + @Identifier(value = "valAddr", name = "接口地址", description = "接口地址", status = Status.SHOW) + private Conf valAddr = Holders.simple(""); + @Identifier(value = "frUrl", name = "报表地址", description = "报表地址", status = Status.SHOW) + private Conf frUrl = Holders.simple("http://localhost:8075/webroot/decision"); + @Identifier(value = "appId", name = "clientId", description = "clientId", status = Status.SHOW) + private Conf appId = Holders.simple(""); + @Identifier(value = "clientSecret", name = "clientSecret", description = "clientSecret", status = Status.SHOW) + private Conf clientSecret = Holders.simple(""); + + public String getFrUrl() { + return frUrl.get(); + } + + public void setFrUrl(String frUrl) { + this.frUrl.set(frUrl); + } + + public String getAppId() { + return appId.get(); + } + + public void setAppId(String appId) { + this.appId.set(appId); + } + + public String getClientSecret() { + return clientSecret.get(); + } + + public void setClientSecret(String clientSecret) { + this.clientSecret.set(clientSecret); + } + + public String getValAddr() { + return valAddr.get(); + } + + public void setValAddr(String valAddr) { + this.valAddr.set(valAddr); + } + + @Override + public Object clone() throws CloneNotSupportedException { + Oauth2Config cloned = (Oauth2Config) super.clone(); + cloned.valAddr = (Conf) valAddr.clone(); + cloned.appId = (Conf) appId.clone(); + cloned.clientSecret = (Conf) clientSecret.clone(); + cloned.frUrl = (Conf) frUrl.clone(); + return cloned; + } + +} \ No newline at end of file diff --git a/src/main/java/com/fr/plugin/TCAuthCallbackApi.java b/src/main/java/com/fr/plugin/TCAuthCallbackApi.java new file mode 100644 index 0000000..83c7f6b --- /dev/null +++ b/src/main/java/com/fr/plugin/TCAuthCallbackApi.java @@ -0,0 +1,122 @@ +package com.fr.plugin; + +import com.fanruan.api.net.http.HttpKit; +import com.fr.decision.authority.data.User; +import com.fr.decision.fun.impl.BaseHttpHandler; +import com.fr.decision.webservice.utils.DecisionStatusService; +import com.fr.decision.webservice.v10.login.LoginService; +import com.fr.decision.webservice.v10.user.UserService; +import com.fr.json.JSONObject; +import com.fr.log.FineLoggerFactory; +import com.fr.store.StateHubService; +import com.fr.third.jodd.util.StringUtil; +import com.fr.third.org.apache.commons.lang3.StringUtils; +import com.fr.third.springframework.web.bind.annotation.RequestMethod; +import com.fr.web.utils.WebUtils; +import org.dom4j.Document; +import org.dom4j.DocumentException; +import org.dom4j.DocumentHelper; +import org.dom4j.Element; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.HashMap; +import java.util.Map; + +public class TCAuthCallbackApi extends BaseHttpHandler { + @Override + public RequestMethod getMethod() { + return null; + } + + @Override + public String getPath() { + return "/authCallBack"; + } + + @Override + public boolean isPublic() { + return true; + } + + @Override + public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception { + String code = WebUtils.getHTTPRequestParameter(httpServletRequest, "code"); + if (StringUtils.isBlank(code)) { + WebUtils.printAsString(httpServletResponse, "code授权码不存在"); + return; + } + //第一步获取token + + String accessToken = getAccessToken(code); + if (StringUtils.isBlank(accessToken)) { + WebUtils.printAsString(httpServletResponse, "授权码无效,请重新授权"); + return; + } + String uid = getUserInfo(accessToken); + User user = UserService.getInstance().getUserByUserName(uid); + if (user == null) { + WebUtils.printAsString(httpServletResponse, "登录失败:" + uid + " 在帆软用户体系不存在,请联系管理员添加"); + return; + } + login(httpServletRequest, httpServletResponse, uid); + StateHubService stateHubService = DecisionStatusService.originUrlStatusService(); + Object callback = stateHubService.get("loginCallBack"); + if (callback != null) { + sendRedirect(httpServletResponse, callback.toString()); + return; + } + sendRedirect(httpServletResponse, HttpUtils.getDefaultUrl(httpServletRequest)); + } + + private String getAccessToken(String code) throws IOException { + Oauth2Config config = Oauth2Config.getInstance(); + String valAddr = config.getValAddr(); + String appId = config.getAppId(); + String clientSecret = config.getClientSecret(); + String frUrl = Oauth2Config.getInstance().getFrUrl(); + String redirectUrl = String.format("%s/url/iam/authCallBack", frUrl); + Map params = new HashMap<>(); + String tokenUrl = String.format("%s/oauth2/rest/token", valAddr); + params.put("redirect_uri", redirectUrl); + params.put("grant_type", "AUTHORIZATION_CODE"); + params.put("code", code); + Map header = new HashMap<>(); + header.put("X-OAUTH-IDENTITY-DOMAIN-NAME", "IdmDomain"); + header.put("Authorization", "Basic " + Base64.getEncoder().encodeToString(String.format("%s:%s", appId, clientSecret).getBytes(StandardCharsets.UTF_8))); + String json = HttpKit.post(tokenUrl, params, "utf-8", "utf-8", header); + FineLoggerFactory.getLogger().info("获取AccessToken 请求返回:{}", json); + JSONObject obj = new JSONObject(json); + return obj.getString("access_token"); + } + + public static String getUserInfo(String accessToken) throws DocumentException, IOException { + Map header = new HashMap<>(); + Oauth2Config config = Oauth2Config.getInstance(); + String valAddr = config.getValAddr(); + String url = String.format("%s/oauth2/rest/userinfo?access_token=%s", valAddr, accessToken); + header.put("Authorization", "Bearer " + accessToken); + String json = HttpKit.get(url, new HashMap<>(), header); + FineLoggerFactory.getLogger().info("获取userinfo 请求返回:{}", json); + JSONObject obj = new JSONObject(json); + return obj.getString("sub"); + } + + + private String login(HttpServletRequest req, HttpServletResponse res, String username) throws Exception { + String token = LoginService.getInstance().login(req, res, username); + req.setAttribute("fine_auth_token", token); + FineLoggerFactory.getLogger().info("fr FrFilter is over with username is ###" + username); + return token; + } + + private void sendRedirect(HttpServletResponse res, String url) throws IOException { + Map params = new HashMap<>(); + params.put("callBack", url); + WebUtils.writeOutTemplate("com/fr/plugin/redirect.html", res, params); + } + +} diff --git a/src/main/java/com/fr/plugin/TCauth2HandlerProvider.java b/src/main/java/com/fr/plugin/TCauth2HandlerProvider.java new file mode 100644 index 0000000..794d77c --- /dev/null +++ b/src/main/java/com/fr/plugin/TCauth2HandlerProvider.java @@ -0,0 +1,25 @@ +package com.fr.plugin; + +import com.fr.decision.fun.HttpHandler; +import com.fr.decision.fun.impl.AbstractHttpHandlerProvider; +import com.fr.plugin.transform.ExecuteFunctionRecord; +import com.fr.plugin.transform.FunctionRecorder; +import com.fr.stable.fun.Authorize; + +@FunctionRecorder +/** + * url处理器需要在这里注册 + */ +@Authorize +public class TCauth2HandlerProvider extends AbstractHttpHandlerProvider { + @Override + @ExecuteFunctionRecord + public HttpHandler[] registerHandlers() { + return new HttpHandler[]{ + new GoAuthApi(), + new GetTicket(), + new TCAuthCallbackApi() + }; + } + +} diff --git a/src/main/java/com/fr/plugin/TCauth2URLAliasBridge.java b/src/main/java/com/fr/plugin/TCauth2URLAliasBridge.java new file mode 100644 index 0000000..d20ab66 --- /dev/null +++ b/src/main/java/com/fr/plugin/TCauth2URLAliasBridge.java @@ -0,0 +1,27 @@ +package com.fr.plugin; + +import com.fr.decision.fun.impl.AbstractURLAliasProvider; +import com.fr.decision.webservice.url.alias.URLAlias; +import com.fr.decision.webservice.url.alias.URLAliasFactory; + +/** + * 将长连接转换为短连接 + * 参考文档: + * https://wiki.fanruan.com/display/PD/com.fr.decision.fun.URLAliasProvider + */ +public class TCauth2URLAliasBridge extends AbstractURLAliasProvider +{ + public TCauth2URLAliasBridge() { + Oauth2Config.getInstance(); + } + + @Override + public URLAlias[] registerAlias() { + //像这样配置之后再访问/api就可以通过http(s)://ip:port/webroot/decision/url/api。 进行访问 + return new URLAlias[]{ + URLAliasFactory.createPluginAlias("/goAuth", "/goAuth", true), + URLAliasFactory.createPluginAlias("/getToken", "/getToken", true), + URLAliasFactory.createPluginAlias("/iam/authCallBack", "/authCallBack", true), + }; + } +} diff --git a/src/main/resources/com/fr/plugin/redirect.html b/src/main/resources/com/fr/plugin/redirect.html new file mode 100644 index 0000000..c6a0e90 --- /dev/null +++ b/src/main/resources/com/fr/plugin/redirect.html @@ -0,0 +1,11 @@ + + + + + + + + + \ No newline at end of file