skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/config/MyAuthenticationProvider.java
@@ -1,5 +1,6 @@ package cn.gistack.auth.config; import cn.gistack.auth.granter.PhoneTokenGranter; import cn.gistack.auth.utils.TokenUtil; import cn.gistack.common.cache.CacheNames; import com.alibaba.nacos.common.utils.StringUtils; @@ -35,6 +36,12 @@ public static final String CUSTOM_LOGIN_SMS = "CUSTOM_LOGIN_SMS"; public static final String CUSTOM_LOGIN_WX = "CUSTOM_LOGIN_WX"; public static final String CUSTOM_LOGIN_OAUTH = "CUSTOM_LOGIN_OAUTH"; public static final String OAUTH = "oauth"; public static final String PHONE = "phone"; public static final String WX = "wx"; @Autowired private BladeRedis bladeRedis; @@ -61,29 +68,35 @@ throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } else { String password = authentication.getCredentials().toString(); // 手机号登录时不验证密码 if(!CUSTOM_LOGIN_SMS.equals(password)&&!CUSTOM_LOGIN_WX.equals(password)){ HttpServletRequest request = WebUtil.getRequest(); String grantType = request.getParameter(TokenUtil.GRANT_TYPE_KEY); // 获取租户ID String headerTenant = request.getHeader(TokenUtil.TENANT_HEADER_KEY); String paramTenant = request.getParameter(TokenUtil.TENANT_PARAM_KEY); // 指定租户ID String tenantId = StringUtils.isBlank(headerTenant) ? paramTenant : headerTenant; String detailsPassword = userDetails.getPassword().substring(7); if (!detailsPassword.equals(DigestUtil.hex(password))) { int count = getFailCount(tenantId, userDetails.getUsername()); // 用户存在但密码错误,超过次数则锁定账号 if (grantType != null && !grantType.equals(TokenUtil.REFRESH_TOKEN_KEY)) { setFailCount(tenantId, userDetails.getUsername(), count); throw new UserDeniedAuthorizationException(TokenUtil.USER_NOT_FOUND); } this.logger.debug("Failed to authenticate since password does not match stored value"); throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } HttpServletRequest request = WebUtil.getRequest(); String grantType = request.getParameter(TokenUtil.GRANT_TYPE_KEY); // 获取租户ID String headerTenant = request.getHeader(TokenUtil.TENANT_HEADER_KEY); String paramTenant = request.getParameter(TokenUtil.TENANT_PARAM_KEY); // 指定租户ID String tenantId = StringUtils.isBlank(headerTenant) ? paramTenant : headerTenant; // 手机号短信登录时不验证密码、微信登录时不验证密码、单点登录时不验证密码 if (grantType.equals(PHONE) || grantType.equals(WX) || grantType.equals(OAUTH)) { // 成功则清除登录错误次数 delFailCount(tenantId, userDetails.getUsername()); return; } String detailsPassword = userDetails.getPassword().substring(7); if (!detailsPassword.equals(DigestUtil.hex(password))) { int count = getFailCount(tenantId, userDetails.getUsername()); // 用户存在但密码错误,超过次数则锁定账号 if (grantType != null && !grantType.equals(TokenUtil.REFRESH_TOKEN_KEY)) { setFailCount(tenantId, userDetails.getUsername(), count); throw new UserDeniedAuthorizationException(TokenUtil.USER_NOT_FOUND); } this.logger.debug("Failed to authenticate since password does not match stored value"); throw new BadCredentialsException(this.messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials")); } // 成功则清除登录错误次数 delFailCount(tenantId, userDetails.getUsername()); } } skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/endpoint/BladeTokenEndPoint.java
@@ -17,14 +17,16 @@ package cn.gistack.auth.endpoint; import cn.gistack.auth.props.LoginUser; import cn.gistack.auth.props.OAuthProperties; import cn.gistack.common.utils.HttpClientUtils; import cn.gistack.system.user.entity.User; import cn.gistack.system.user.feign.IUserClient; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONAware; import com.alibaba.fastjson.JSONObject; import com.wf.captcha.SpecCaptcha; import lombok.AllArgsConstructor; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import cn.gistack.common.cache.CacheNames; import org.springblade.core.cache.utils.CacheUtil; @@ -39,32 +41,28 @@ import org.springblade.core.tool.support.Kv; import org.springblade.core.tool.utils.StringUtil; import org.springblade.core.tool.utils.WebUtil; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.oauth2.common.OAuth2AccessToken; import org.springframework.security.oauth2.common.OAuth2RefreshToken; import org.springframework.security.oauth2.provider.AuthorizationRequest; import org.springframework.security.oauth2.provider.ClientDetailsService; import org.springframework.security.oauth2.provider.token.TokenStore; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import org.springframework.web.servlet.ModelAndView; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.time.Duration; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import static cn.gistack.common.cache.CacheNames.OAUTH_KEY; import static org.springblade.core.cache.constant.CacheConstant.*; /** @@ -90,6 +88,9 @@ private final String WX_APP_ID = "wx521ae71bcde281bc"; private final String WX_SECRET = "c1c93b0c4545f8cdf42752121ea8458c"; @Autowired private final OAuthProperties oauthProperties; /** * 登录页面 */ @@ -99,34 +100,83 @@ return model; } @SneakyThrows @GetMapping("/oauth/tokenLoginUI") public void tokenLoginUI(HttpServletResponse response, String code) { response.sendRedirect(oauthProperties.getRedirectWebUri() + "?code=" + code); } @GetMapping("/oauth/tokenLogin") public R tokenLogin(String code) { // 统一登录获取accessToken String accessToken = getAccessToken(code); System.out.println("accessToken:" + accessToken); LoginUser loginUser = getUsernameByToken(accessToken); loginUser.setCode(code); bladeRedis.setEx(OAUTH_KEY + code,code,5*60L); return R.data(loginUser); } private String getAccessToken(String code) { // 统一登录获取accessToken String url = oauthProperties.getTokenUrl(); Map<String, String> param = new HashMap<>(); param.put("grant_type", "authorization_code"); param.put("redirect_uri", oauthProperties.getRedirectUri()); param.put("client_id", oauthProperties.getClientId()); param.put("client_secret", oauthProperties.getClientSecret()); param.put("AppKey", oauthProperties.getAppKey()); param.put("AppSecret", oauthProperties.getAppSecret()); param.put("code", code); String accessToken = HttpClientUtils.doGet(url, param); Map<String, String> maps = (Map<String, String>) JSON.parse(accessToken); if (maps.containsKey("access_token")) { return maps.get("access_token"); } return ""; } private LoginUser getUsernameByToken(String accessToken) { String url = oauthProperties.getProfileUrl(); Map<String, String> param = new HashMap<>(); param.put("AppKey", oauthProperties.getAppKey()); param.put("AppSecret", oauthProperties.getAppSecret()); param.put("accessToken", accessToken); String resp = HttpClientUtils.doGet(url, param); System.out.println("接收数据:" + resp); LoginUser loginUser = JSON.parseObject(resp, LoginUser.class); return loginUser; } @GetMapping("/oauth/wxLogin") public R wxLogin(String code){ public R wxLogin(String code) { String wxAccessToken = getWxAccessToken(); //参数 String url = WX_GET_PHONE_NUMBER+"?access_token="+wxAccessToken; String url = WX_GET_PHONE_NUMBER + "?access_token=" + wxAccessToken; //向微信接口发起请求获取手机号 RestTemplate template = new RestTemplate(); Map<String, Object> params = new LinkedHashMap<>(); params.put("code", code); String result = template.postForObject(url, params, String.class); JSONObject resultObj = (JSONObject)JSONObject.parse(result); JSONObject phoneInfo = (JSONObject)resultObj.get("phone_info"); JSONObject resultObj = (JSONObject) JSONObject.parse(result); JSONObject phoneInfo = (JSONObject) resultObj.get("phone_info"); String phoneNumber = phoneInfo.get("phoneNumber").toString(); R<User> userR = userClient.userByAccount("000000", phoneNumber); if (userR.getData() != null){ if (userR.getData() != null) { return R.data(phoneNumber); }else { } else { return R.fail("不存在该用户"); } } public String getWxAccessToken(){ public String getWxAccessToken() { //参数 Map<String, String> params = new HashMap<>(); params.put("grant_type","client_credential"); params.put("appid",WX_APP_ID); params.put("secret",WX_SECRET); params.put("grant_type", "client_credential"); params.put("appid", WX_APP_ID); params.put("secret", WX_SECRET); String result = HttpClientUtils.doGet(WX_GET_ACCESS_TOKEN, params); JSONObject jsonObject = JSON.parseObject(result); String accessToken = jsonObject.getString("access_token"); skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/granter/BladeTokenGranter.java
@@ -49,6 +49,9 @@ granters.add(new WxPhoneNumberTokenGranter(authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), bladeRedis)); // 增加第三方登陆模式 granters.add(new SocialTokenGranter(endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), userClient, socialProperties)); //单点登录授权模式 granters.add(new OAuthTokenGranter(authenticationManager, endpoints.getTokenServices(), endpoints.getClientDetailsService(), endpoints.getOAuth2RequestFactory(), bladeRedis)); // 组合tokenGranter集合 return new CompositeTokenGranter(granters); } skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/granter/OAuthTokenGranter.java
New file @@ -0,0 +1,74 @@ package cn.gistack.auth.granter; import cn.gistack.auth.utils.TokenUtil; import cn.gistack.common.cache.CacheNames; import org.springblade.core.redis.cache.BladeRedis; import org.springblade.core.tool.utils.StringUtil; import org.springframework.security.authentication.*; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.common.exceptions.InvalidGrantException; import org.springframework.security.oauth2.common.exceptions.UserDeniedAuthorizationException; import org.springframework.security.oauth2.provider.*; import org.springframework.security.oauth2.provider.token.AbstractTokenGranter; import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices; import java.util.LinkedHashMap; import java.util.Map; /** * 微信授权验证TokenGranter * * @author Guoshilong */ public class OAuthTokenGranter extends AbstractTokenGranter { private static final String GRANT_TYPE = "oauth"; private final AuthenticationManager authenticationManager; private BladeRedis bladeRedis; public OAuthTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, BladeRedis bladeRedis) { this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE); this.bladeRedis = bladeRedis; } protected OAuthTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) { super(tokenServices, clientDetailsService, requestFactory, grantType); this.authenticationManager = authenticationManager; } @Override protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) { Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters()); String phone = parameters.get("username"); String code = parameters.get("password"); // 获取oauth key String redisCode = bladeRedis.get(CacheNames.OAUTH_KEY + code); // key验证 if (code == null || !StringUtil.equalsIgnoreCase(redisCode, code)) { throw new UserDeniedAuthorizationException(TokenUtil.CAPTCHA_NOT_CORRECT); } // 通过手机号查询用户 Authentication userAuth = new UsernamePasswordAuthenticationToken(phone, code); ((AbstractAuthenticationToken) userAuth).setDetails(parameters); try { userAuth = authenticationManager.authenticate(userAuth); } catch (AccountStatusException | BadCredentialsException ase) { //covers expired, locked, disabled cases (mentioned in section 5.2, draft 31) throw new InvalidGrantException(ase.getMessage()); } // If the username/password are wrong the spec says we should send 400/invalid grant if (userAuth == null || !userAuth.isAuthenticated()) { throw new InvalidGrantException("Could not authenticate user: " + phone); } OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); return new OAuth2Authentication(storedOAuth2Request, userAuth); } } skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/props/LoginUser.java
New file @@ -0,0 +1,19 @@ package cn.gistack.auth.props; import lombok.Data; /** * @PROJECT_NAME: skjcmanager * @DESCRIPTION: 统一登陆用户实体类 * @USER: aix * @DATE: 2023/7/24 18:01 */ @Data public class LoginUser { private String accountId; private String account; private String userId; private String phone; private String email; private String code; } skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/props/OAuthProperties.java
New file @@ -0,0 +1,36 @@ package cn.gistack.auth.props; import lombok.Data; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * @PROJECT_NAME: skjcmanager * @DESCRIPTION: * @USER: aix * @DATE: 2023/7/24 15:58 */ @Data @Component @ConfigurationProperties(prefix = "oauth.config") public class OAuthProperties { private String clientId; private String clientSecret; private String appKey; private String appSecret; private String serverUrlPrefix; private String tokenUrl; private String profileUrl; private String redirectUri; private String redirectWebUri; } skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/service/BladeUserDetailsServiceImpl.java
@@ -151,8 +151,12 @@ } //拼接用户所属行政区划、责任人需要返回水库ids R<AttResManagePerson> attRes = attResManagePersonClient.queryListByPhone(user.getPhone()); userInfo.getDetail().set("resIds",attRes.getData()); userInfo.getDetail().set("resIds",""); if (StringUtil.isNotBlank(user.getPhone())) { R<AttResManagePerson> attRes = attResManagePersonClient.queryListByPhone(user.getPhone()); userInfo.getDetail().set("resIds",attRes.getData()); } String deptStr = ""; if (userInfo.getUser().getDeptId().indexOf(",") > 0){ skjcmanager/skjcmanager-auth/src/main/resources/application-dev.yml
@@ -13,3 +13,16 @@ social: enabled: true domain: http://127.0.0.1:1888 #oauth2统一单点登录 oauth: config: client-id: hbxskkey client-secret: hbxsksecret app-key: uaq775d4a7ufmefyf7sm16vs app-secret: qjq1qizyo1baoyvzdlbu226hkhrs4vl2 server-url-prefix: http://10.10.3.99:30000/api/IAM token-url: ${oauth.config.server-url-prefix}/cas/oauth2.0/accessToken profile-url: ${oauth.config.server-url-prefix}/api-auth/inspur/cas/oauth2.0/profile redirect-uri: http://192.168.1.198:8100/oauth/tokenLoginUI redirect-web-uri: http://192.168.1.198:1888/business#/tokenlogin skjcmanager/skjcmanager-auth/src/main/resources/application-prod.yml
@@ -13,3 +13,9 @@ social: enabled: true domain: http://127.0.0.1:1888 #oauth2统一单点登录 oauth: enable: true domain: http://10.10.3.99:30000/api/IAM redirect_uri: http://192.168.1.198:8100/oauth/tokenLogin skjcmanager/skjcmanager-common/src/main/java/cn/gistack/common/cache/CacheNames.java
@@ -57,6 +57,11 @@ String PHONE_KEY = "blade:auth::blade:phone:"; /** * 单点登录验证key */ String OAUTH_KEY = "blade:auth::blade:oauth:"; /** * 登录失败key */ String USER_FAIL_KEY = "blade:user::blade:fail:"; skjcmanager/skjcmanager-gateway/src/main/java/cn/gistack/gateway/provider/AuthProvider.java
@@ -60,6 +60,7 @@ DEFAULT_SKIP_URL.add("/app/manage/getLastInfo"); DEFAULT_SKIP_URL.add("/app/manage/getMiniProgramPath"); DEFAULT_SKIP_URL.add("/oauth/wxLogin"); DEFAULT_SKIP_URL.add("/oauth/tokenLogin/**"); DEFAULT_SKIP_URL.add("/sms/sms/sendCode"); DEFAULT_SKIP_URL.add("/hk/hk/queryHkPreviewUrlCommon");