Jelajahi Sumber

通用版登录超时,异地登录、超时时间配置化

songxinlu 3 tahun lalu
induk
melakukan
d8144fe244

+ 9 - 0
doc/037.20211129_2.1.3/qc_initv2.1.1.sql

@@ -0,0 +1,9 @@
+
+use `qc`;
+-- 执行前请看注意事项!
+-- 通用版本token有效时间配置化
+/**
+sys_dictionary_info表新增token时间配置
+ */
+INSERT INTO `sys_dictionary_info` (`group_type`, `name`, `val`, `return_type`, `remark`) VALUES ('31', 'accessToken', '86400', '2', 'accessToken有效期(单位秒)');
+INSERT INTO `sys_dictionary_info` (`group_type`, `name`, `val`, `return_type`, `remark`) VALUES ('31', 'refreshToken', '604800', '2', 'refreshToken有效期(单位秒)');

+ 25 - 0
src/main/java/com/diagbot/config/CustomExceptionTranslator.java

@@ -0,0 +1,25 @@
+package com.diagbot.config;
+
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
+import org.springframework.security.oauth2.provider.error.DefaultWebResponseExceptionTranslator;
+
+/**
+ * @Description:
+ * @Author songxl
+ * @Date 2021/11/22
+ */
+public class CustomExceptionTranslator extends DefaultWebResponseExceptionTranslator {
+
+    @Override
+    public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {
+        ResponseEntity<OAuth2Exception> translate = super.translate(e);
+        OAuth2Exception body = translate.getBody();
+        CustomOauthException customOauthException = new CustomOauthException(body.getMessage(),body.getOAuth2ErrorCode(),
+                body.getHttpErrorCode());
+        ResponseEntity<OAuth2Exception> response = new ResponseEntity<>(customOauthException, translate.getHeaders(),
+                translate.getStatusCode());
+        return response;
+    }
+
+}

+ 31 - 0
src/main/java/com/diagbot/config/CustomOauthException.java

@@ -0,0 +1,31 @@
+package com.diagbot.config;
+
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.springframework.security.oauth2.common.exceptions.OAuth2Exception;
+
+/**
+ * @Description:
+ * @Author songxl
+ * @Date 2021/11/22
+ */
+@JsonSerialize(using = CustomOauthExceptionSerializer.class)
+public class CustomOauthException extends OAuth2Exception {
+
+    private String oAuth2ErrorCode;
+
+    private int httpErrorCode;
+
+    public CustomOauthException(String msg, String oAuth2ErrorCode, int httpErrorCode) {
+        super(msg);
+        this.oAuth2ErrorCode = oAuth2ErrorCode;
+        this.httpErrorCode = httpErrorCode;
+    }
+
+    public String getoAuth2ErrorCode() {
+        return oAuth2ErrorCode;
+    }
+
+    public int getHttpErrorCode() {
+        return httpErrorCode;
+    }
+}

+ 35 - 0
src/main/java/com/diagbot/config/CustomOauthExceptionSerializer.java

@@ -0,0 +1,35 @@
+package com.diagbot.config;
+
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.ser.std.StdSerializer;
+
+import java.io.IOException;
+
+/**
+ * @Description:
+ * @Author songxl
+ * @Date 2021/11/22
+ */
+public class CustomOauthExceptionSerializer extends StdSerializer<CustomOauthException> {
+
+    public CustomOauthExceptionSerializer() {
+        super(CustomOauthException.class);
+    }
+
+    @Override
+    public void serialize(CustomOauthException value, JsonGenerator gen, SerializerProvider provider) throws IOException {
+        //value内容适当的做一些错误类型判断
+        if ("invalid_token".equals(value.getoAuth2ErrorCode())) {
+            gen.writeStartObject();
+            gen.writeObjectField("code", "10020011");
+            gen.writeObjectField("msg", "登录超时。为确保您的账户安全,系统已自动退出,请重新登录。");
+            gen.writeEndObject();
+        }else {
+            gen.writeStartObject();
+            gen.writeObjectField("code", "00000001");
+            gen.writeObjectField("msg", "操作失败,请确认请求是否有误!!!");
+            gen.writeEndObject();
+        }
+    }
+}

+ 15 - 2
src/main/java/com/diagbot/config/OAuth2Configurer.java

@@ -1,5 +1,6 @@
 package com.diagbot.config;
 
+import com.diagbot.facade.SysDictionaryFacade;
 import com.diagbot.service.UrlUserService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -33,17 +34,29 @@ import java.util.Arrays;
 public class OAuth2Configurer extends AuthorizationServerConfigurerAdapter {
     @Autowired
     private UrlUserService urlUserService;
+    @Autowired
+    private SysDictionaryFacade sysDictionaryFacade;
 
     @Override
     public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
+        int accessToken = 24 * 3600;
+        int refreshToken = 30 * 24 * 3600;
+        if (sysDictionaryFacade.getDictionaryWithKey() != null
+                && sysDictionaryFacade.getDictionaryWithKey().containsKey("31")
+                && sysDictionaryFacade.getDictionaryWithKey().get("31").containsKey("accessToken")
+                && sysDictionaryFacade.getDictionaryWithKey().get("31").containsKey("refreshToken")) {
+            accessToken = Integer.parseInt(sysDictionaryFacade.getDictionaryWithKey().get("31").get("accessToken"));
+            refreshToken = Integer.parseInt(sysDictionaryFacade.getDictionaryWithKey().get("31").get("refreshToken"));
+        }
+        sysDictionaryFacade.getDictionaryWithKey().get("31").get("");
         clients.inMemory()
                 .withClient("uaa-service")
                 .secret("{noop}123456")
                 .scopes("service")
                 .autoApprove(true)
                 .authorizedGrantTypes("implicit", "refresh_token", "password", "authorization_code")
-                .accessTokenValiditySeconds(24 * 3600)
-                .refreshTokenValiditySeconds(30 * 24 * 3600);
+                .accessTokenValiditySeconds(accessToken)
+                .refreshTokenValiditySeconds(refreshToken);
     }
 
     /**

+ 4 - 0
src/main/java/com/diagbot/config/ResourceServerConfigurer.java

@@ -13,6 +13,7 @@ import org.springframework.security.jwt.crypto.sign.RsaVerifier;
 import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
 import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
 import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
+import org.springframework.security.oauth2.provider.error.OAuth2AuthenticationEntryPoint;
 import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
 import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
 import org.springframework.util.FileCopyUtils;
@@ -271,6 +272,9 @@ public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {
     public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
         log.info("Configuring ResourceServerSecurityConfigurer");
         resources.resourceId("user-service").tokenStore(new JwtTokenStore(jwtTokenEnhancerClient()));
+        OAuth2AuthenticationEntryPoint authenticationEntryPoint = new OAuth2AuthenticationEntryPoint();
+        authenticationEntryPoint.setExceptionTranslator(new CustomExceptionTranslator());
+        resources.authenticationEntryPoint(authenticationEntryPoint);
     }
     @Autowired
     private CustomAccessTokenConverter customAccessTokenConverter;

+ 41 - 3
src/main/java/com/diagbot/config/security/UrlAccessDecisionManager.java

@@ -1,5 +1,8 @@
 package com.diagbot.config.security;
 
+import com.diagbot.exception.CommonErrorCode;
+import com.diagbot.exception.CommonException;
+import com.diagbot.exception.ServiceErrorCode;
 import com.diagbot.facade.TokenFacade;
 import com.diagbot.util.HttpUtils;
 import com.diagbot.util.StringUtil;
@@ -32,17 +35,28 @@ public class UrlAccessDecisionManager implements AccessDecisionManager {
     public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
         HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
         String url, method;
+        String tokenStr = HttpUtils.getHeaders(request).get("Authorization");
+        //用户是否被顶掉校验
+        if (StringUtil.isNotEmpty(tokenStr) && !matchNotCheckUrl(request)) {
+            tokenStr = tokenStr.replaceFirst("Bearer ", "");
+            int res = tokenFacade.newVerifyToken(tokenStr, 1);
+            if (-1 == res) {
+                throw new CommonException(ServiceErrorCode.LONGIN_ERROE);
+            }
+        }
         if (matchPermitAllUrl(request)) {
             return;
         }
         if ("anonymousUser".equals(authentication.getPrincipal())) {
             throw new AccessDeniedException("no right");
         } else {
-            String tokenStr = HttpUtils.getHeaders(request).get("Authorization");
             if (StringUtil.isNotEmpty(tokenStr)) {
                 tokenStr = tokenStr.replaceFirst("Bearer ", "");
-                Boolean res = tokenFacade.verifyToken(tokenStr, 1);
-                if (!res) {
+//                Boolean res = tokenFacade.verifyToken(tokenStr, 1);
+                int res = tokenFacade.newVerifyToken(tokenStr, 1);
+                if (-1 == res) {
+                    throw new CommonException(CommonErrorCode.SERVER_IS_ERROR, "该账号在其他地方登录。");
+                } else if (1 != res) {
                     throw new AccountExpiredException("token expire");
                 }
             }
@@ -321,4 +335,28 @@ public class UrlAccessDecisionManager implements AccessDecisionManager {
         }
         return false;
     }
+    private boolean matchNotCheckUrl(HttpServletRequest request) {
+        if (matchers("/swagger/**", request)
+                || matchers("/v2/**", request)
+                || matchers("/swagger-ui.html/**", request)
+                || matchers("/swagger-resources/**", request)
+                || matchers("/webjars/**", request)
+                || matchers("/druid/**", request)
+                || matchers("/actuator/**", request)
+                || matchers("/hystrix/**", request)
+                || matchers("/sys/user/getJwt", request)
+                || matchers("/sys/user/logout", request)
+                || matchers("/sys/user/getCaptcha", request)
+                || matchers("/sys/user/getHospitalMark", request)
+                || matchers("/sys/user/getJwtNoPass", request)
+                || matchers("/sys/user/refreshJwt", request)
+                || matchers("/sys/dictionaryInfo/getDictionary", request)
+                || matchers("/sys/user/checkToken", request)
+                || matchers("/oauth/token", request)
+                || matchers("/oauth/check_token", request)
+                || matchers("/cache/clear", request)) {
+            return true;
+        }
+        return false;
+    }
 }

+ 13 - 1
src/main/java/com/diagbot/config/security/UrlFilterSecurityInterceptor.java

@@ -1,5 +1,7 @@
 package com.diagbot.config.security;
 
+import com.alibaba.fastjson.JSONObject;
+import com.diagbot.exception.CommonException;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.SecurityMetadataSource;
 import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
@@ -43,7 +45,17 @@ public class UrlFilterSecurityInterceptor extends AbstractSecurityInterceptor im
     public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
 
         FilterInvocation fi = new FilterInvocation(request, response, chain);
-        invoke(fi);
+        try {
+            invoke(fi);
+        }catch (CommonException e)
+        {
+            JSONObject error = new JSONObject();
+            response.setContentType("text/html;charset=utf-8");
+            error.put("code",e.getCode());
+            error.put("msg", e.getMessage());
+            response.getWriter().println(error);
+            response.flushBuffer();
+        }
     }
 
 

+ 1 - 0
src/main/java/com/diagbot/exception/ServiceErrorCode.java

@@ -12,6 +12,7 @@ public enum ServiceErrorCode implements ErrorCode {
     USER_PASSWORD_ERROR("10020001", "账号或密码不正确"),
     GET_TOKEN_FAIL("10020002", "获取token失败"),
     TOKEN_IS_NOT_MATCH_USER("10020003", "请使用自己的token进行接口请求"),
+    LONGIN_ERROE("10020012", "该账号在其他地方登录。"),
 
     SMS_SEND_ERROR("10020004", "短信发送错误"),
     USER_BIND_ERROR("10020005", "用户手机号已经绑定无需再次验证"),

+ 8 - 0
src/main/java/com/diagbot/service/SysTokenService.java

@@ -27,6 +27,14 @@ public interface SysTokenService {
      * @return
      */
     Boolean verifyToken(String token, Integer type);
+    /**
+     * 验证token是否有效
+     *
+     * @param token 待验证的token
+     * @param type  1:accessToken,2:refreshToken
+     * @return
+     */
+    int newVerifyToken(String token, Integer type);
 
     /**
      * 删除用户token

+ 54 - 0
src/main/java/com/diagbot/service/impl/SysTokenServiceImpl.java

@@ -127,6 +127,60 @@ public class SysTokenServiceImpl implements SysTokenService {
         return res;
     }
 
+    /**
+     * 验证token是否有效
+     *
+     * @param token 待验证的token
+     * @param type  1:accessToken,2:refreshToken
+     * @return -1:token无效(与服务器token不一致,异地登录),1:token有效,0:其他
+     */
+    @Override
+    public int newVerifyToken(String token, Integer type) {
+        Integer res = 0;
+        if (null == token) {
+            return 0;
+        }
+        String userId = JwtUtil.getUserId(token);
+        //从redis中取出
+        final byte[] redis_key = getUserTokenKey(userId);
+        JwtStore tokenStore = (JwtStore) redisForToken.execute(new RedisCallback<JwtStore>() {
+            @Override
+            public JwtStore doInRedis(RedisConnection connection) throws DataAccessException {
+                byte[] bytes = connection.get(redis_key);
+                if (bytes == null) {
+                    return null;
+                }
+                return (JwtStore) deserializeValue(bytes);
+            }
+        });
+
+        if (null != tokenStore) {
+            if (type == 1) {
+                if (null != tokenStore.getAccessToken()) {
+                    if (tokenStore.getAccessToken().equals(token)) {
+                        res = 1;
+                    } else {
+                        res = -1;
+                    }
+                }
+            }
+
+            if (type == 2) {
+                if (null != tokenStore.getRefreshToken()) {
+                    if (tokenStore.getRefreshToken().equals(token)) {
+                        res = 1;
+                    } else {
+                        res = -1;
+                    }
+                }
+            }
+        } else {
+            res = -1;
+        }
+
+        return res;
+    }
+
     /**
      * 删除用户token
      *