@@ -0,0 +1,35 @@ | |||
package com.hz.pm.api.common.config; | |||
import cn.hutool.core.util.RandomUtil; | |||
import lombok.Data; | |||
import org.springframework.boot.context.properties.ConfigurationProperties; | |||
/** | |||
* <p> | |||
* AuthCodeProperties | |||
* </p> | |||
* | |||
* @author WendyYang | |||
* @since 00:15 2023/12/21 | |||
*/ | |||
@Data | |||
@ConfigurationProperties(prefix = "auth-code") | |||
public class AuthCodeProperties { | |||
private String secretKey; | |||
/** | |||
* authCode失效时间(单位:秒) | |||
*/ | |||
private Integer expireTime = 30; | |||
/** | |||
* authCode长度(最大:16~32) | |||
*/ | |||
private Integer length = 16; | |||
public static void main(String[] args) { | |||
System.out.println("secretKey:" + RandomUtil.randomString(32)); | |||
} | |||
} |
@@ -20,8 +20,15 @@ public class WebProperties { | |||
public static String webUrl; | |||
public static String apiHost; | |||
public static String provincialUrl; | |||
@Value("${api-host:}") | |||
private void setApiHost(String host) { | |||
apiHost = host; | |||
} | |||
@Value("${expert-registration.url:/expertEnroll}") | |||
private void setExpertRegistrationUrl(String url) { | |||
expertRegistrationUrl = url; | |||
@@ -2,9 +2,17 @@ package com.hz.pm.api.user.controller; | |||
import com.fasterxml.jackson.databind.ObjectMapper; | |||
import com.hz.pm.api.common.config.AuthCodeProperties; | |||
import com.hz.pm.api.meeting.entity.config.WebProperties; | |||
import com.hz.pm.api.user.manage.AgentLoginManage; | |||
import com.hz.pm.api.user.manage.AuthCodeManage; | |||
import com.hz.pm.api.user.model.vo.AuthCodeVO; | |||
import com.hz.pm.api.user.util.LoginUserUtil; | |||
import com.ningdatech.basic.exception.BizException; | |||
import com.ningdatech.basic.util.StrPool; | |||
import com.hz.pm.api.common.model.constant.BizConst; | |||
import com.hz.pm.api.user.security.auth.constants.SessionTimeConstant; | |||
import com.ningdatech.log.annotation.WebLog; | |||
import io.swagger.annotations.Api; | |||
import io.swagger.annotations.ApiImplicitParam; | |||
import io.swagger.annotations.ApiImplicitParams; | |||
@@ -35,6 +43,10 @@ import java.io.IOException; | |||
public class UserAuthController { | |||
private final ObjectMapper objectMapper; | |||
private final AuthCodeManage authCodeManage; | |||
private final AgentLoginManage agentLoginManage; | |||
private static final String AGENT_LOGIN_PATH = "/api/v1/user/auth/agent-login"; | |||
@PostMapping(value = "/login", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) | |||
@ApiOperation(value = "登陆") | |||
@@ -81,12 +93,43 @@ public class UserAuthController { | |||
response.getWriter().write(objectMapper.writeValueAsString(BizConst.UNAUTHENTICATED)); | |||
} | |||
@PostMapping(value = "/agent-login", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) | |||
@PostMapping(value = "/proxy/agent-login", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) | |||
@ApiOperation(value = "代登陆") | |||
@ApiImplicitParams({ | |||
@ApiImplicitParam(name = "userId", value = "账号", required = true, paramType = "form", dataType = "String")}) | |||
public void agentLogin(@RequestParam(value = "userId") String userId) { | |||
// 不实现任何内容,只是为了出api文档 | |||
@WebLog("代登录(代理接口)") | |||
public void agentLoginProxy(@RequestParam(value = "userId") Long userId, | |||
@RequestParam(value = "username", required = false, defaultValue = "") String username, | |||
@RequestParam(value = "timestamp") long timestamp, | |||
@RequestParam(value = "sign") String sign, | |||
HttpServletRequest request, | |||
HttpServletResponse response) throws IOException { | |||
if (System.currentTimeMillis() - timestamp > 5000) { | |||
throw BizException.wrap("签名已过期"); | |||
} | |||
if (LoginUserUtil.getUserId().equals(userId)) { | |||
throw BizException.wrap("代登录用户无效"); | |||
} | |||
String targetUserId = String.valueOf(userId); | |||
if (!agentLoginManage.agentLoginProxySignCheck(targetUserId, sign)) { | |||
throw BizException.wrap("签名错误"); | |||
} | |||
String authCode = authCodeManage.generateAuthCode(targetUserId); | |||
String urlParam = "?userId=" + userId + "&username=" + username + "&authCode=" + authCode; | |||
String path = WebProperties.apiHost + request.getContextPath() + AGENT_LOGIN_PATH; | |||
response.sendRedirect(path + urlParam); | |||
} | |||
@PostMapping(value = "/getAuthCode", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) | |||
public AuthCodeVO getAuthCode(@RequestParam(value = "userId") String userId, | |||
@RequestParam(value = "sign") String sign) { | |||
String authCode = authCodeManage.generateAuthCode(userId, sign); | |||
return new AuthCodeVO(authCode); | |||
} | |||
@GetMapping(value = "/agent-login") | |||
@ApiOperation(value = "代登陆") | |||
public void agentLogin(@RequestParam(value = "userId") String userId, | |||
@RequestParam(value = "username") String username, | |||
@RequestParam(value = "authCode") String authCode) { | |||
} | |||
@PostMapping(value = "/mh-login", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) | |||
@@ -0,0 +1,32 @@ | |||
package com.hz.pm.api.user.manage; | |||
import cn.hutool.crypto.SecureUtil; | |||
import cn.hutool.crypto.digest.HMac; | |||
import com.hz.pm.api.user.util.LoginUserUtil; | |||
import org.springframework.beans.factory.annotation.Value; | |||
import org.springframework.stereotype.Component; | |||
/** | |||
* <p> | |||
* AgentLoginProxyManage | |||
* </p> | |||
* | |||
* @author WendyYang | |||
* @since 09:35 2023/12/21 | |||
*/ | |||
@Component | |||
public class AgentLoginManage { | |||
/** | |||
* 代登录代理接口:secretKey | |||
*/ | |||
@Value("${agent-login.proxy.secret-key}") | |||
private String agentLoginProxySecretKey; | |||
public boolean agentLoginProxySignCheck(String userId, String sign) { | |||
HMac hmacMd5 = SecureUtil.hmacMd5(agentLoginProxySecretKey); | |||
String digestHex = hmacMd5.digestHex(userId + "#" + LoginUserUtil.getUserId()); | |||
return digestHex.equals(sign); | |||
} | |||
} |
@@ -0,0 +1,60 @@ | |||
package com.hz.pm.api.user.manage; | |||
import cn.hutool.core.util.RandomUtil; | |||
import cn.hutool.crypto.SecureUtil; | |||
import cn.hutool.crypto.digest.HMac; | |||
import com.hz.pm.api.common.config.AuthCodeProperties; | |||
import com.ningdatech.basic.exception.BizException; | |||
import com.ningdatech.cache.model.cache.CacheKey; | |||
import com.ningdatech.cache.repository.CachePlusOps; | |||
import lombok.RequiredArgsConstructor; | |||
import org.springframework.boot.context.properties.EnableConfigurationProperties; | |||
import org.springframework.stereotype.Component; | |||
import java.time.Duration; | |||
/** | |||
* <p> | |||
* AuthCodeManage | |||
* </p> | |||
* | |||
* @author WendyYang | |||
* @since 23:59 2023/12/20 | |||
*/ | |||
@Component | |||
@RequiredArgsConstructor | |||
@EnableConfigurationProperties(AuthCodeProperties.class) | |||
public class AuthCodeManage { | |||
private final CachePlusOps cachePlusOps; | |||
private final AuthCodeProperties authCodeProperties; | |||
private String generateAuthCode(String userId, boolean checkSign, String sign) { | |||
if (checkSign) { | |||
HMac hmacMd5 = SecureUtil.hmacMd5(authCodeProperties.getSecretKey()); | |||
String digestHex = hmacMd5.digestHex(userId); | |||
if (!digestHex.equals(sign)) { | |||
throw BizException.wrap("获取授权码失败:签名错误"); | |||
} | |||
} | |||
String authCode = RandomUtil.randomString(authCodeProperties.getLength()); | |||
Duration duration = Duration.ofSeconds(authCodeProperties.getExpireTime()); | |||
CacheKey key = new CacheKey(userId + "#" + authCode, duration); | |||
cachePlusOps.set(key, userId); | |||
return authCode; | |||
} | |||
public String generateAuthCode(String userId, String sign) { | |||
return generateAuthCode(userId, true, sign); | |||
} | |||
public String generateAuthCode(String userId) { | |||
return generateAuthCode(userId, false, null); | |||
} | |||
public boolean authCodeCheck(String userId, String authCode) { | |||
CacheKey key = new CacheKey(userId + "#" + authCode); | |||
return cachePlusOps.del(key) > 0; | |||
} | |||
} |
@@ -0,0 +1,20 @@ | |||
package com.hz.pm.api.user.model.vo; | |||
import lombok.AllArgsConstructor; | |||
import lombok.Data; | |||
/** | |||
* <p> | |||
* AuthCodeVO | |||
* </p> | |||
* | |||
* @author WendyYang | |||
* @since 00:06 2023/12/21 | |||
*/ | |||
@Data | |||
@AllArgsConstructor | |||
public class AuthCodeVO { | |||
private String authCode; | |||
} |
@@ -1,5 +1,7 @@ | |||
package com.hz.pm.api.user.security.auth.agent; | |||
import com.hz.pm.api.common.util.StrUtils; | |||
import com.hz.pm.api.user.manage.AuthCodeManage; | |||
import com.hz.pm.api.user.security.model.WebRequestDetails; | |||
import com.ningdatech.basic.exception.BizException; | |||
import org.apache.commons.lang3.StringUtils; | |||
@@ -27,10 +29,15 @@ public class AgentAuthFilter extends AbstractAuthenticationProcessingFilter { | |||
private static final String USER_ID_PARAMETER = "userId"; | |||
private static final String AUTH_CODE = "authCode"; | |||
private final AuthCodeManage authCodeManage; | |||
// =================================================================================================== | |||
public AgentAuthFilter(String processingUrl) { | |||
public AgentAuthFilter(String processingUrl,AuthCodeManage authCodeManage) { | |||
super(new AntPathRequestMatcher(processingUrl, HttpMethod.POST.name())); | |||
this.authCodeManage = authCodeManage; | |||
} | |||
// ======================================================================================================== | |||
@@ -41,12 +48,17 @@ public class AgentAuthFilter extends AbstractAuthenticationProcessingFilter { | |||
if (request.getMethod().equals(HttpMethod.POST.name())) { | |||
throw new AuthenticationServiceException("请求方法错误"); | |||
} | |||
String userId = request.getParameter(USER_ID_PARAMETER); | |||
String userId = StrUtils.trim(request.getParameter(USER_ID_PARAMETER)); | |||
if (StringUtils.isBlank(userId)) { | |||
throw new BadCredentialsException("用户id 不能为空"); | |||
throw new BadCredentialsException("用户ID不能为空"); | |||
} | |||
String authCode = StrUtils.trim(request.getParameter(AUTH_CODE)); | |||
if (StringUtils.isBlank(userId)) { | |||
throw new BadCredentialsException("授权码不能为空"); | |||
} | |||
if (!authCodeManage.authCodeCheck(userId, authCode)) { | |||
throw new BadCredentialsException("授权码已过期"); | |||
} | |||
userId = trim(userId); | |||
try { | |||
AgentAuthToken authRequest = new AgentAuthToken(userId, userId); | |||
authRequest.setDetails(new WebRequestDetails(request)); | |||
@@ -1,5 +1,6 @@ | |||
package com.hz.pm.api.user.security.auth.agent; | |||
import com.hz.pm.api.user.manage.AuthCodeManage; | |||
import com.hz.pm.api.user.security.config.AuthProperties; | |||
import org.springframework.beans.factory.annotation.Qualifier; | |||
import org.springframework.security.authentication.AuthenticationManager; | |||
@@ -28,21 +29,23 @@ public class AgentAuthSecurityConfig extends SecurityConfigurerAdapter<DefaultSe | |||
protected final AuthenticationFailureHandler defaultLoginFailureHandler; | |||
private final UserDetailsService agentLoginUserDetailService; | |||
private final AuthProperties authProperties; | |||
private final AuthCodeManage authCodeManage; | |||
public AgentAuthSecurityConfig(@Qualifier(value = "defaultLoginSuccessHandler") AuthenticationSuccessHandler loginSuccessHandler, | |||
@Qualifier(value = "defaultLoginFailureHandler") AuthenticationFailureHandler loginFailureHandler, | |||
@Qualifier(value = "agentLoginUserDetailService") UserDetailsService agentLoginUserDetailService, | |||
AuthProperties authProperties) { | |||
AuthProperties authProperties, | |||
AuthCodeManage authCodeManage) { | |||
this.defaultLoginSuccessHandler = loginSuccessHandler; | |||
this.defaultLoginFailureHandler = loginFailureHandler; | |||
this.agentLoginUserDetailService = agentLoginUserDetailService; | |||
this.authProperties = authProperties; | |||
this.authCodeManage = authCodeManage; | |||
} | |||
@Override | |||
public void configure(HttpSecurity http) { | |||
AgentAuthFilter agentAuthFilter = | |||
new AgentAuthFilter(authProperties.getAgentLoginUrl()); | |||
AgentAuthFilter agentAuthFilter = new AgentAuthFilter(authProperties.getAgentLoginUrl(), authCodeManage); | |||
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); | |||
agentAuthFilter.setAuthenticationManager(authenticationManager); | |||
agentAuthFilter.setAuthenticationSuccessHandler(defaultLoginSuccessHandler); | |||
@@ -254,4 +254,9 @@ mh: | |||
expert-qr-code-url: https://jiema.wwei.cn/uploads/2023/12/28/658d7a3f15f06.jpg | |||
file: | |||
down-url: https://weixin.hzszxc.hzswb.cn:8443/mh-gateway/oss/oss/previewFileLogin | |||
detail-url: https://weixin.hzszxc.hzswb.cn:8443/mh-gateway/oss/ossfile/getFileInfoList | |||
detail-url: https://weixin.hzszxc.hzswb.cn:8443/mh-gateway/oss/ossfile/getFileInfoList | |||
auth-code: | |||
secret-key: nqkmzqojg5j4eiypr3rb8s7nb4noa8b2 | |||
agent-login: | |||
proxy: | |||
secret-key: nqkwiqojg7g4eiypr3rb8s7nb4noa8b2 |
@@ -258,4 +258,10 @@ sync-mh-user: | |||
sync-mh-expert: | |||
open: false | |||
sync-mh-unit: | |||
open: false | |||
open: false | |||
auth-code: | |||
secret-key: uqrvd2bani4fercnisua1cqxjwk1neym | |||
agent-login: | |||
proxy: | |||
secret-key: tqkwiqojg5j4eiypr3rb8w7nb4noa8b2 |
@@ -19,6 +19,7 @@ security: | |||
- /api/v1/user/auth/login/password | |||
- /api/v1/user/auth/forget-password | |||
- /api/v1/user/auth/common-login | |||
- /api/v1/user/auth/common-login | |||
- /doc.html | |||
- /ok.html | |||
- /open/api/** | |||