xieb
2023-07-26 006baff0642ddb99514f268c8876d0cb3766e298
增加单点登录逻辑
8 files modified
3 files added
304 ■■■■ changed files
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/config/MyAuthenticationProvider.java 53 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/endpoint/BladeTokenEndPoint.java 86 ●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/granter/BladeTokenGranter.java 3 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/granter/OAuthTokenGranter.java 74 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/props/LoginUser.java 19 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/props/OAuthProperties.java 36 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-auth/src/main/java/cn/gistack/auth/service/BladeUserDetailsServiceImpl.java 8 ●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-auth/src/main/resources/application-dev.yml 13 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-auth/src/main/resources/application-prod.yml 6 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-common/src/main/java/cn/gistack/common/cache/CacheNames.java 5 ●●●●● patch | view | raw | blame | history
skjcmanager/skjcmanager-gateway/src/main/java/cn/gistack/gateway/provider/AuthProvider.java 1 ●●●● patch | view | raw | blame | history
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");