Commit e9194a2d by tangyi

登录支持微信小程序

parent 85e38885
Version v3.0.0 (2019-7-6)
--------------------------
新功能:
* 支持微信小程序登录
Version v3.0.0 (2019-7-4)
--------------------------
......@@ -19,7 +27,7 @@ Version v3.0.0 (2019-6-23)
新功能:
# 增加短信验证码登录
* 增加短信验证码登录
* 增加消息中心服务
改进:
......
......@@ -149,6 +149,11 @@ public class CommonConstant {
public static final String GRANT_TYPE_MOBILE = "mobile";
/**
* 微信类型
*/
public static final String GRANT_TYPE_WX = "wx";
/**
* 租户编号请求头
*/
public static final String TENANT_CODE_HEADER = "Tenant-Code";
......
package com.github.tangyi.user.config;
package com.github.tangyi.common.core.dto;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.io.Serializable;
/**
* 附件配置
*
* @author tangyi
* @date 2019-02-24 20:06
* @date 2019/07/05 15:05
*/
@Data
@Component
@ConfigurationProperties(prefix = "sys")
public class SysConfig {
public class SysConfigDto implements Serializable {
private static final long serialVersionUID = 1L;
/**
* fastDfs服务器的HTTP地址
......
package com.github.tangyi.auth.properties;
package com.github.tangyi.common.core.properties;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
......@@ -14,7 +15,27 @@ import org.springframework.context.annotation.Configuration;
public class SysProperties {
/**
* fastDfs服务器的HTTP地址
*/
private String fdfsHttpHost;
/**
* 上传地址
*/
private String uploadUrl;
/**
* 默认头像
*/
private String defaultAvatar;
/**
* 管理员账号
*/
private String adminUser;
/**
* 密码加密解密的key
*/
private String key;
}
......@@ -4,6 +4,7 @@ import com.github.tangyi.common.security.constant.SecurityConstant;
import com.github.tangyi.common.security.tenant.TenantContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.bouncycastle.util.encoders.Base64;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
......@@ -13,7 +14,11 @@ import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.security.Principal;
/**
......@@ -25,6 +30,10 @@ import java.security.Principal;
@Slf4j
public class SysUtil {
private static final String KEY_ALGORITHM = "AES";
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/CBC/NOPadding";
/**
* 获取当前登录的用户名
*
......@@ -93,4 +102,21 @@ public class SysUtil {
}
return tenantCode;
}
/**
* des解密
*
* @param data data
* @param pass pass
* @return String
* @author tangyi
* @date 2019/03/18 11:39
*/
public static String decryptAES(String data, String pass) throws Exception {
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(pass.getBytes(), KEY_ALGORITHM), new IvParameterSpec(pass.getBytes()));
byte[] result = cipher.doFinal(Base64.decode(data.getBytes(StandardCharsets.UTF_8)));
return new String(result, StandardCharsets.UTF_8);
}
}
......@@ -85,5 +85,45 @@ public class UserVo extends BaseEntity<UserVo> {
*/
private Integer status;
/**
* 引导注册人
*/
private String parentUid;
/**
* 乡/镇
*/
private String streetId;
/**
* 县
*/
private String countyId;
/**
* 城市
*/
private String cityId;
/**
* 省份
*/
private String provinceId;
/**
* 最近登录时间
*/
private Date loginTime;
/**
* 用户归档时间
*/
private Date lockTime;
/**
* 微信
*/
private String wechat;
}
......@@ -12,7 +12,7 @@ import java.util.LinkedHashMap;
import java.util.Map;
/**
* 服务间调用携带authorization请求头
* 服务间调用携带Authorization、Tenant-Code请求头
*
* @author tangyi
* @date 2019-03-15 14:14
......@@ -22,11 +22,14 @@ public class CustomFeignConfig implements RequestInterceptor {
private static final String TOKEN_HEADER = "authorization";
private static final String TENANT_HEADER = "Tenant-Code";
@Override
public void apply(RequestTemplate requestTemplate) {
HttpServletRequest request = getHttpServletRequest();
if (request != null) {
requestTemplate.header(TOKEN_HEADER, getHeaders(request).get(TOKEN_HEADER));
requestTemplate.header(TENANT_HEADER, getHeaders(request).get(TENANT_HEADER));
}
}
......
......@@ -27,5 +27,12 @@
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
</dependency>
<!-- commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>${commons-io.version}</version>
</dependency>
</dependencies>
</project>
......@@ -2,6 +2,7 @@ package com.github.tangyi.common.security.config;
import com.github.tangyi.common.security.mobile.MobileSecurityConfigurer;
import com.github.tangyi.common.security.properties.FilterIgnorePropertiesConfig;
import com.github.tangyi.common.security.wx.WxSecurityConfigurer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
......@@ -32,10 +33,16 @@ public class CustomResourceServerConfig extends ResourceServerConfigurerAdapter
*/
private final MobileSecurityConfigurer mobileSecurityConfigurer;
/**
* 微信登录配置
*/
private final WxSecurityConfigurer wxSecurityConfigurer;
@Autowired
public CustomResourceServerConfig(FilterIgnorePropertiesConfig filterIgnorePropertiesConfig, MobileSecurityConfigurer mobileSecurityConfigurer) {
public CustomResourceServerConfig(FilterIgnorePropertiesConfig filterIgnorePropertiesConfig, MobileSecurityConfigurer mobileSecurityConfigurer, WxSecurityConfigurer wxSecurityConfigurer) {
this.filterIgnorePropertiesConfig = filterIgnorePropertiesConfig;
this.mobileSecurityConfigurer = mobileSecurityConfigurer;
this.wxSecurityConfigurer = wxSecurityConfigurer;
}
@Override
......@@ -55,5 +62,7 @@ public class CustomResourceServerConfig extends ResourceServerConfigurerAdapter
.and().exceptionHandling().accessDeniedHandler(new OAuth2AccessDeniedHandler());
// 手机号登录
http.apply(mobileSecurityConfigurer);
// 微信登录
http.apply(wxSecurityConfigurer);
}
}
package com.github.tangyi.common.security.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tangyi.common.security.core.CustomUserDetailsService;
import com.github.tangyi.common.security.wx.WxLoginSuccessHandler;
import com.github.tangyi.common.security.wx.WxSecurityConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.ClientDetailsService;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
/**
* 微信登录相关配置
*
* @author tangyi
* @date 2019/07/05 19:44
*/
@Configuration
public class WxLoginConfig {
/**
* 配置微信登录
*
* @return WxSecurityConfigurer
*/
@Bean
public WxSecurityConfigurer wxSecurityConfigurer(@Lazy PasswordEncoder encoder, @Lazy ClientDetailsService clientDetailsService,
@Lazy CustomUserDetailsService userDetailsService, @Lazy ObjectMapper objectMapper,
@Lazy AuthorizationServerTokenServices defaultAuthorizationServerTokenServices) {
WxSecurityConfigurer wxSecurityConfigurer = new WxSecurityConfigurer();
wxSecurityConfigurer.setWxLoginSuccessHandler(wxLoginSuccessHandler(encoder, clientDetailsService, objectMapper, defaultAuthorizationServerTokenServices));
wxSecurityConfigurer.setUserDetailsService(userDetailsService);
return wxSecurityConfigurer;
}
/**
* 微信登录成功后的处理
*
* @return AuthenticationSuccessHandler
*/
@Bean
public AuthenticationSuccessHandler wxLoginSuccessHandler(PasswordEncoder encoder, ClientDetailsService clientDetailsService, ObjectMapper objectMapper,
AuthorizationServerTokenServices defaultAuthorizationServerTokenServices) {
return WxLoginSuccessHandler.builder()
.objectMapper(objectMapper)
.clientDetailsService(clientDetailsService)
.passwordEncoder(encoder)
.defaultAuthorizationServerTokenServices(defaultAuthorizationServerTokenServices).build();
}
}
......@@ -42,6 +42,11 @@ public class SecurityConstant {
public static final String MOBILE_TOKEN_URL = "/mobile/token";
/**
* 微信登录URL
*/
public static final String WX_TOKEN_URL = "/wx/token";
/**
* 租户编号请求头
*/
public static final String TENANT_CODE_HEADER = "Tenant-Code";
......
package com.github.tangyi.common.security.core;
import com.github.tangyi.common.security.wx.WxUser;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
......@@ -20,16 +21,28 @@ public interface CustomUserDetailsService {
* @author tangyi
* @date 2019/05/28 21:06
*/
UserDetails loadUserByUsernameAndTenantCode(String username, String tenantCode) throws UsernameNotFoundException;
UserDetails loadUserByIdentifierAndTenantCode(String username, String tenantCode) throws UsernameNotFoundException;
/**
* 根据社交账号和租户标识查询
*
* @param social social
* @param social social
* @param tenantCode tenantCode
* @return UserDetails
* @author tangyi
* @date 2019/06/22 21:08
*/
UserDetails loadUserBySocialAndTenantCode(String social, String tenantCode) throws UsernameNotFoundException;
/**
* 根据微信openId和租户标识查询
*
* @param code code
* @param tenantCode tenantCode
* @param wxUser wxUser
* @return UserDetails
* @author tangyi
* @date 2019/07/05 20:04:59
*/
UserDetails loadUserByWxCodeAndTenantCode(String code, String tenantCode, WxUser wxUser) throws UsernameNotFoundException;
}
......@@ -13,7 +13,7 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* 手机登录配置,配置
* 手机登录配置
*
* @author tangyi
* @date 2019/6/22 21:26
......
package com.github.tangyi.common.security.utils;
import com.google.gson.Gson;
import java.lang.reflect.Type;
/**
* 基于Gson的json工具类
*
* @author tangyi
* @date 2017-11-23 18:03
*/
public class GsonHelper {
/**
* Gson对象
*/
private static final Gson gson = new Gson();
/**
* 单例
*/
private volatile static GsonHelper instance;
/**
* 获取单例
*
* @author tangyi
* @date 2017/11/23 18:10
*/
public static GsonHelper getInstance() {
if (instance == null) {
synchronized (GsonHelper.class) {
if (instance == null)
instance = new GsonHelper();
}
}
return instance;
}
/**
* 将json转为对象
*
* @param json json
* @param clazz clazz
* @return T
* @author tangyi
* @date 2017/11/23 18:09
*/
public <T> T fromJson(String json, Class<T> clazz) {
return gson.fromJson(json, clazz);
}
/**
* 将json转为对象
*
* @param json json
* @param type type
* @return T
* @author tangyi
* @date 2017/11/28 15:41
*/
public <T> T fromJson(String json, Type type) {
return gson.fromJson(json, type);
}
/**
* 将对象转为json
*
* @param src
* @return String
* @author tangyi
* @date 2017/11/23 18:09
*/
public String toJson(Object src) {
return gson.toJson(src);
}
}
package com.github.tangyi.common.security.wx;
import com.github.tangyi.common.security.constant.SecurityConstant;
import com.github.tangyi.common.security.utils.GsonHelper;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 微信登录filter,登录时支持传入用户的资料,如姓名、电话、性别等
*
* @author tangyi
* @date 2019/07/05 19:32
*/
@Slf4j
public class WxAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
/**
* 微信登录的code参数,后续用code换取openId和sessionKey
*/
private static final String SPRING_SECURITY_FORM_WX_KEY = "code";
@Getter
@Setter
private String wxParameter = SPRING_SECURITY_FORM_WX_KEY;
@Getter
@Setter
private boolean postOnly = true;
@Getter
@Setter
private AuthenticationEventPublisher eventPublisher;
@Getter
@Setter
private AuthenticationEntryPoint authenticationEntryPoint;
public WxAuthenticationFilter() {
super(new AntPathRequestMatcher(SecurityConstant.WX_TOKEN_URL, HttpMethod.POST.name()));
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (postOnly && !request.getMethod().equals(HttpMethod.POST.name()))
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
// 获取code
String code = obtainCode(request);
if (code == null) {
code = "";
}
code = code.trim();
// 封装成token
WxAuthenticationToken wxAuthenticationToken = new WxAuthenticationToken(code);
// 封装其它基本信息
setWxUserDetails(request, wxAuthenticationToken);
setDetails(request, wxAuthenticationToken);
Authentication authResult = null;
try {
// 认证
authResult = this.getAuthenticationManager().authenticate(wxAuthenticationToken);
logger.debug("Authentication success: " + authResult);
// 认证成功
eventPublisher.publishAuthenticationSuccess(authResult);
SecurityContextHolder.getContext().setAuthentication(authResult);
} catch (Exception failed) {
SecurityContextHolder.clearContext();
logger.debug("Authentication request failed: " + failed);
eventPublisher.publishAuthenticationFailure(new BadCredentialsException(failed.getMessage(), failed), new PreAuthenticatedAuthenticationToken("access-token", "N/A"));
try {
authenticationEntryPoint.commence(request, response, new UsernameNotFoundException(failed.getMessage(), failed));
} catch (Exception e) {
logger.error("authenticationEntryPoint handle error:{}", failed);
}
}
return authResult;
}
private String obtainCode(HttpServletRequest request) {
return request.getParameter(wxParameter);
}
private void setDetails(HttpServletRequest request, WxAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
/**
* 设置姓名、性别、头像等信息
*
* @param request request
* @param authRequest authRequest
*/
private void setWxUserDetails(HttpServletRequest request, WxAuthenticationToken authRequest) {
try {
String result = IOUtils.toString(request.getReader());
if (StringUtils.isNotBlank(result)) {
WxUser user = GsonHelper.getInstance().fromJson(result, WxUser.class);
authRequest.setWxUser(user);
}
} catch (IOException e) {
log.error(e.getMessage(), e);
}
}
}
package com.github.tangyi.common.security.wx;
import com.github.tangyi.common.security.core.CustomUserDetailsService;
import com.github.tangyi.common.security.tenant.TenantContextHolder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.SpringSecurityMessageSource;
import org.springframework.security.core.userdetails.UserDetails;
/**
* @author tangyi
* @date 2019/07/05 19:34
*/
@Slf4j
@Data
public class WxAuthenticationProvider implements AuthenticationProvider {
private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();
private CustomUserDetailsService customUserDetailsService;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
WxAuthenticationToken wxAuthenticationToken = (WxAuthenticationToken) authentication;
// 微信的code
String principal = wxAuthenticationToken.getPrincipal().toString();
UserDetails userDetails = customUserDetailsService.loadUserByWxCodeAndTenantCode(principal, TenantContextHolder.getTenantCode(), wxAuthenticationToken.getWxUser());
if (userDetails == null) {
log.debug("Authentication failed: no credentials provided");
throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.noopBindAccount", "Noop Bind Account"));
}
WxAuthenticationToken authenticationToken = new WxAuthenticationToken(userDetails, userDetails.getAuthorities());
authenticationToken.setDetails(wxAuthenticationToken.getDetails());
return authenticationToken;
}
@Override
public boolean supports(Class<?> authentication) {
return WxAuthenticationToken.class.isAssignableFrom(authentication);
}
}
package com.github.tangyi.common.security.wx;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
/**
* 微信登录token
*
* @author tangyi
* @date 2019/07/05 19:29
*/
public class WxAuthenticationToken extends AbstractAuthenticationToken {
/**
* code
*/
private final Object principal;
private WxUser wxUser;
public WxAuthenticationToken(String code) {
super(null);
this.principal = code;
setAuthenticated(false);
}
public WxAuthenticationToken(Object principal, Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.principal = principal;
super.setAuthenticated(true);
}
@Override
public Object getCredentials() {
return null;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
public WxUser getWxUser() {
return wxUser;
}
public void setWxUser(WxUser wxUser) {
this.wxUser = wxUser;
}
}
package com.github.tangyi.common.security.wx;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tangyi.common.security.constant.SecurityConstant;
import com.github.tangyi.common.security.utils.SecurityUtil;
import lombok.Builder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.UnapprovedClientAuthenticationException;
import org.springframework.security.oauth2.provider.*;
import org.springframework.security.oauth2.provider.request.DefaultOAuth2RequestValidator;
import org.springframework.security.oauth2.provider.token.AuthorizationServerTokenServices;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
/**
* 微信登录成功
*
* @author tangyi
* @date 2019/07/05 19:29
*/
@Slf4j
@Builder
public class WxLoginSuccessHandler implements AuthenticationSuccessHandler {
private static final String BASIC_ = "Basic ";
private ObjectMapper objectMapper;
private PasswordEncoder passwordEncoder;
private ClientDetailsService clientDetailsService;
private AuthorizationServerTokenServices defaultAuthorizationServerTokenServices;
/**
* Called when a user has been successfully authenticated.
* 调用spring security oauth API 生成 oAuth2AccessToken
*
* @param request the request which caused the successful authentication
* @param response the response
* @param authentication the <tt>Authentication</tt> object which was created during
*/
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (header == null || !header.startsWith(BASIC_))
throw new UnapprovedClientAuthenticationException("请求头中client信息为空");
try {
String[] tokens = SecurityUtil.extractAndDecodeHeader(header);
assert tokens.length == 2;
String clientId = tokens[0];
ClientDetails clientDetails = clientDetailsService.loadClientByClientId(clientId);
// 校验secret
if (!passwordEncoder.matches(tokens[1], clientDetails.getClientSecret()))
throw new InvalidClientException("Given client ID does not match authenticated client");
TokenRequest tokenRequest = new TokenRequest(new HashMap<>(), clientId, clientDetails.getScope(), "wx");
// 校验scope
new DefaultOAuth2RequestValidator().validateScope(tokenRequest, clientDetails);
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
OAuth2AccessToken oAuth2AccessToken = defaultAuthorizationServerTokenServices.createAccessToken(oAuth2Authentication);
response.setCharacterEncoding("utf-8");
response.setContentType(SecurityConstant.CONTENT_TYPE);
PrintWriter printWriter = response.getWriter();
printWriter.append(objectMapper.writeValueAsString(oAuth2AccessToken));
} catch (IOException e) {
throw new BadCredentialsException("Failed to decode basic authentication token");
}
}
}
package com.github.tangyi.common.security.wx;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.tangyi.common.security.core.CustomUserDetailsService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationEventPublisher;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
/**
* 微信登录配置
*
* @author tangyi
* @date 2019/07/05 19:29
*/
@Data
public class WxSecurityConfigurer extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Autowired
private ObjectMapper objectMapper;
@Autowired
private AuthenticationEventPublisher defaultAuthenticationEventPublisher;
private AuthenticationSuccessHandler wxLoginSuccessHandler;
private CustomUserDetailsService userDetailsService;
@Override
public void configure(HttpSecurity http) {
// 微信登录过滤器
WxAuthenticationFilter wxAuthenticationFilter = new WxAuthenticationFilter();
wxAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
wxAuthenticationFilter.setAuthenticationSuccessHandler(wxLoginSuccessHandler);
wxAuthenticationFilter.setEventPublisher(defaultAuthenticationEventPublisher);
WxAuthenticationProvider wxAuthenticationProvider = new WxAuthenticationProvider();
wxAuthenticationProvider.setCustomUserDetailsService(userDetailsService);
// 增加微信登录的过滤器
http.authenticationProvider(wxAuthenticationProvider).addFilterAfter(wxAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
package com.github.tangyi.common.security.wx;
import lombok.Data;
import java.io.Serializable;
/**
* 微信用户的基本信息
*
* @author tangyi
* @date 2019/07/06 15:58
*/
@Data
public class WxUser implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 姓名
*/
private String name;
/**
* 性别
*/
private Integer sex;
/**
* 头像地址
*/
private String avatarUrl;
/**
* 详细描述
*/
private String userDesc;
/**
* 国家
*/
private String country;
/**
* 省
*/
private String province;
/**
* 市
*/
private String city;
/**
* 语言
*/
private String languang;
}
......@@ -117,6 +117,16 @@ cluster:
# 系统配置
sys:
adminUser: ${ADMIN_USER:admin} # 管理员账号,默认是admin
fdfsHttpHost: ${ATTACHMENT_HOST:http://192.168.0.95}:${ATTACHMENT_PORT:8080} # fastDfs的HTTP配置
uploadUrl: api/user/v1/attachment/upload
defaultAvatar: https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif?imageView2/1/w/80/h/80
key: '1234567887654321'
# 微信配置
wx:
appId: wx597d9f972f991a8c
appSecret: b5d44266271a08c6f75a347712a7334e
grantType: authorization_code
logging:
level:
......
......@@ -28,16 +28,16 @@ spring:
zipkin:
base-url: http://${ZIPKIN_HOST:localhost}:${ZIPKIN_PORT:9411} # 指定了Zipkin服务器的地址
security:
encode:
key: '1234567887654321'
# 系统配置
sys:
adminUser: ${ADMIN_USER:admin} # 管理员账号,默认是admin
fdfsHttpHost: ${ATTACHMENT_HOST:http://192.168.0.95}:${ATTACHMENT_PORT:8080} # fastDfs的HTTP配置
uploadUrl: api/user/v1/attachment/upload
defaultAvatar: https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif?imageView2/1/w/80/h/80
key: '1234567887654321'
# feign相关配置
feign:
httpclient:
enabled: false
okhttp:
enabled: true
hystrix:
enabled: true
......@@ -83,9 +83,10 @@ preview:
- api/msc
# 开启网关token转换
# 暂时关闭,此功能在微信端登录有问题
gateway:
token-transfer:
enabled: ${GATEWAY_TOKEN_TRANSFER:true}
enabled: ${GATEWAY_TOKEN_TRANSFER:false}
# 集群ID生成配置
cluster:
......
......@@ -97,6 +97,7 @@ sys:
fdfsHttpHost: ${ATTACHMENT_HOST:http://192.168.0.95}:${ATTACHMENT_PORT:8080} # fastDfs的HTTP配置
uploadUrl: api/user/v1/attachment/upload
defaultAvatar: https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif?imageView2/1/w/80/h/80
key: '1234567887654321'
# feign相关配置
feign:
......
......@@ -35,17 +35,6 @@
<groupId>de.codecentric</groupId>
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
</dependencies>
<build>
......
package com.github.tangyi.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import static io.netty.handler.codec.http.cookie.CookieHeaderNames.MAX_AGE;
import static org.springframework.web.cors.CorsConfiguration.ALL;
/**
* 跨域配置
*
* @author tangyi
* @date 2019/07/06 17:12
*/
@Configuration
public class CorsConfig {
@Bean
public WebFilter corsFilter() {
return (ServerWebExchange ctx, WebFilterChain chain) -> {
ServerHttpRequest request = ctx.getRequest();
if (!CorsUtils.isCorsRequest(request))
return chain.filter(ctx);
HttpHeaders requestHeaders = request.getHeaders();
ServerHttpResponse response = ctx.getResponse();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
if (requestMethod != null)
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL);
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
return chain.filter(ctx);
};
}
}
......@@ -17,6 +17,11 @@ public class GatewayConstant {
public static final String MOBILE_TOKEN_URL = "/mobile/token";
/**
* 微信登录URL
*/
public static final String WX_TOKEN_URL = "/wx/token";
/**
* 注册
*/
public static final String REGISTER = "/user/register";
......
package com.github.tangyi.gateway.filters;
import cn.hutool.core.codec.Base64;
import cn.hutool.core.util.StrUtil;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.properties.SysProperties;
import com.github.tangyi.common.core.utils.SysUtil;
import com.github.tangyi.gateway.constants.GatewayConstant;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.apache.commons.lang.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Mono;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.nio.charset.StandardCharsets;
/**
* 解密过滤器
......@@ -32,23 +29,16 @@ import java.nio.charset.StandardCharsets;
* @date 2019/3/18 11:30
*/
@Slf4j
@AllArgsConstructor
@Configuration
@ConditionalOnProperty(value = "security.encode.key")
@ConditionalOnProperty(value = "sys.key")
public class DecodePasswordFilter implements GlobalFilter, Ordered {
private static final String KEY_ALGORITHM = "AES";
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/CBC/NOPadding";
private static final String CREDENTIAL = "credential";
private static final String PASSWORD = "password";
/**
* 约定的key
*/
@Value("${security.encode.key}")
private String key;
private final SysProperties sysProperties;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
......@@ -63,27 +53,24 @@ public class DecodePasswordFilter implements GlobalFilter, Ordered {
// 授权类型为密码模式则解密
if (CommonConstant.GRANT_TYPE_PASSWORD.equals(grantType) || StrUtil.containsAnyIgnoreCase(uri.getPath(), GatewayConstant.REGISTER)) {
String credential = request.getQueryParams().getFirst(CREDENTIAL);
if (credential == null || credential.isEmpty()) {
log.info("credential is empty...");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return exchange.getResponse().setComplete();
}
try {
// 开始解密
credential = decryptAES(credential, key);
credential = credential.trim();
log.debug("credential decrypt success:{}", credential);
} catch (Exception e) {
log.error("credential decrypt fail:{}", credential);
if (StringUtils.isNotBlank(credential)) {
try {
// 开始解密
credential = SysUtil.decryptAES(credential, sysProperties.getKey());
credential = credential.trim();
log.debug("credential decrypt success:{}", credential);
} catch (Exception e) {
log.error("credential decrypt fail:{}", credential);
}
URI newUri = UriComponentsBuilder.fromUri(uri)
// 替换password字段
.replaceQueryParam(PASSWORD, credential)
// 替换credential字段
.replaceQueryParam(CREDENTIAL, credential)
.build(true).toUri();
request = request.mutate().uri(newUri).build();
return chain.filter(exchange.mutate().request(request).build());
}
URI newUri = UriComponentsBuilder.fromUri(uri)
// 替换password字段
.replaceQueryParam(PASSWORD, credential)
// 替换credential字段
.replaceQueryParam(CREDENTIAL, credential)
.build(true).toUri();
request = request.mutate().uri(newUri).build();
return chain.filter(exchange.mutate().request(request).build());
}
}
return chain.filter(exchange);
......@@ -93,20 +80,4 @@ public class DecodePasswordFilter implements GlobalFilter, Ordered {
public int getOrder() {
return -100;
}
/**
* des解密
*
* @param data data
* @param pass pass
* @return String
* @author tangyi
* @date 2019/03/18 11:39
*/
private static String decryptAES(String data, String pass) throws Exception {
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(pass.getBytes(), KEY_ALGORITHM), new IvParameterSpec(pass.getBytes()));
byte[] result = cipher.doFinal(Base64.decode(data.getBytes(StandardCharsets.UTF_8)));
return new String(result, StandardCharsets.UTF_8);
}
}
......@@ -25,7 +25,6 @@ import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.TimeUnit;
......@@ -64,54 +63,79 @@ public class TokenRequestGlobalFilter implements GlobalFilter, Ordered {
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 当前请求
ServerHttpRequest request = exchange.getRequest();
// 请求的URI
URI uri = request.getURI();
if (HttpMethod.POST.matches(request.getMethodValue())
&& StrUtil.containsAnyIgnoreCase(uri.getPath(), GatewayConstant.OAUTH_TOKEN_URL, GatewayConstant.REGISTER, GatewayConstant.MOBILE_TOKEN_URL)) {
if (match(request)) {
String grantType = request.getQueryParams().getFirst(GatewayConstant.GRANT_TYPE);
// 授权类型为密码、手机号模式
if (CommonConstant.GRANT_TYPE_PASSWORD.equals(grantType) || CommonConstant.GRANT_TYPE_MOBILE.equals(grantType) || GatewayConstant.GRANT_TYPE_REFRESH_TOKEN.equals(grantType)) {
// 装饰器
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(exchange.getResponse()) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (getStatusCode() != null && getStatusCode().equals(HttpStatus.OK) && body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
long start = System.currentTimeMillis();
List<String> contentList = Lists.newArrayList();
dataBuffers.forEach(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
// 释放缓存
DataBufferUtils.release(dataBuffer);
contentList.add(new String(content, StandardCharsets.UTF_8));
});
// 因为返回的数据量大时,获取到的buffer是不完整的,所以需要连接多个buffer
String accessTokenContent = joiner.join(contentList);
log.trace("生成的accessToken: {}", accessTokenContent);
AccessToken accessToken = JsonMapper.getInstance().fromJson(accessTokenContent, AccessToken.class);
if (accessToken == null || StringUtils.isBlank(accessToken.getJti()))
throw new InvalidAccessTokenException("token转换失败!");
// 真正的access_token和refresh_token
String realAccessToken = accessToken.getAccessToken(), realRefreshToken = accessToken.getRefreshToken();
// 转换token
String converted = JsonMapper.getInstance().toJson(accessToken);
log.trace("转换token结果:{}", converted);
// 将真正的access_token,refresh_token存入Redis,建立jti->access_token的映射关系,失效时间为token的失效时间
// 暂时用Redis
redisTemplate.opsForValue().set(GatewayConstant.GATEWAY_ACCESS_TOKENS + accessToken.getJti(), realAccessToken, accessToken.getExpiresIn(), TimeUnit.SECONDS);
redisTemplate.opsForValue().set(GatewayConstant.GATEWAY_REFRESH_TOKENS + accessToken.getJti(), realRefreshToken, REFRESH_TOKEN_EXPIRE, TimeUnit.SECONDS);
log.trace("转换token和建立映射关系成功,耗时:{}ms", System.currentTimeMillis() - start);
return bufferFactory().wrap(converted.getBytes());
}));
}
return super.writeWith(body);
}
};
return chain.filter(exchange.mutate().response(decoratedResponse).build());
// 授权类型为密码、手机号、微信openId
if (matchGrantType(grantType)) {
return chain.filter(exchange.mutate().response(responseDecorator(exchange)).build());
}
}
return chain.filter(exchange);
}
/**
* 修改响应体
*
* @param exchange exchange
* @return ServerHttpResponseDecorator
*/
private ServerHttpResponseDecorator responseDecorator(ServerWebExchange exchange) {
// 装饰器
return new ServerHttpResponseDecorator(exchange.getResponse()) {
@Override
public Mono<Void> writeWith(Publisher<? extends DataBuffer> body) {
if (getStatusCode() != null && getStatusCode().equals(HttpStatus.OK) && body instanceof Flux) {
Flux<? extends DataBuffer> fluxBody = Flux.from(body);
return super.writeWith(fluxBody.buffer().map(dataBuffers -> {
long start = System.currentTimeMillis();
List<String> contentList = Lists.newArrayList();
dataBuffers.forEach(dataBuffer -> {
byte[] content = new byte[dataBuffer.readableByteCount()];
dataBuffer.read(content);
// 释放缓存
DataBufferUtils.release(dataBuffer);
contentList.add(new String(content, StandardCharsets.UTF_8));
});
// 因为返回的数据量大时,获取到的buffer是不完整的,所以需要连接多个buffer
String accessTokenContent = joiner.join(contentList);
AccessToken accessToken = JsonMapper.getInstance().fromJson(accessTokenContent, AccessToken.class);
if (accessToken == null || StringUtils.isBlank(accessToken.getJti()))
throw new InvalidAccessTokenException("token转换失败!");
// 真正的access_token和refresh_token
String realAccessToken = accessToken.getAccessToken(), realRefreshToken = accessToken.getRefreshToken();
// 转换token
String converted = JsonMapper.getInstance().toJson(accessToken);
// 将真正的access_token,refresh_token存入Redis,建立jti->access_token的映射关系,失效时间为token的失效时间
// 暂时用Redis
redisTemplate.opsForValue().set(GatewayConstant.GATEWAY_ACCESS_TOKENS + accessToken.getJti(), realAccessToken, accessToken.getExpiresIn(), TimeUnit.SECONDS);
redisTemplate.opsForValue().set(GatewayConstant.GATEWAY_REFRESH_TOKENS + accessToken.getJti(), realRefreshToken, REFRESH_TOKEN_EXPIRE, TimeUnit.SECONDS);
log.debug("转换token和建立映射关系成功,耗时:{}ms", System.currentTimeMillis() - start);
return bufferFactory().wrap(converted.getBytes());
}));
}
return super.writeWith(body);
}
};
}
/**
* 是否过滤
*
* @param request request
* @return boolean
*/
private boolean match(ServerHttpRequest request) {
return HttpMethod.POST.matches(request.getMethodValue())
&& StrUtil.containsAnyIgnoreCase(request.getURI().getPath(), GatewayConstant.OAUTH_TOKEN_URL, GatewayConstant.MOBILE_TOKEN_URL, GatewayConstant.WX_TOKEN_URL);
}
/**
* 需要过滤的授权类型
*
* @param grantType grantType
* @return boolean
*/
private boolean matchGrantType(String grantType) {
return CommonConstant.GRANT_TYPE_PASSWORD.equals(grantType) || CommonConstant.GRANT_TYPE_MOBILE.equals(grantType) || CommonConstant.GRANT_TYPE_WX.equals(grantType) || GatewayConstant.GRANT_TYPE_REFRESH_TOKEN.equals(grantType);
}
}
......@@ -47,7 +47,7 @@ public class TokenResponseGlobalFilter implements GlobalFilter, Ordered {
// 获取Authorization请求头
String authorization = request.getHeaders().getFirst(CommonConstant.REQ_HEADER);
// 先判断是否为刷新token请求
if (StrUtil.containsAnyIgnoreCase(uri.getPath(), GatewayConstant.OAUTH_TOKEN_URL, GatewayConstant.MOBILE_TOKEN_URL)) {
if (StrUtil.containsAnyIgnoreCase(uri.getPath(), GatewayConstant.OAUTH_TOKEN_URL, GatewayConstant.MOBILE_TOKEN_URL, GatewayConstant.WX_TOKEN_URL)) {
String grantType = request.getQueryParams().getFirst(GatewayConstant.GRANT_TYPE);
// 授权类型为refresh_token
if (GatewayConstant.GRANT_TYPE_REFRESH_TOKEN.equals(grantType)) {
......@@ -56,16 +56,13 @@ public class TokenResponseGlobalFilter implements GlobalFilter, Ordered {
// 根据jti从Redis里获取真正的refresh_token
Object object = redisTemplate.opsForValue().get(GatewayConstant.GATEWAY_REFRESH_TOKENS + refreshToken);
refreshToken = object == null ? refreshToken : object.toString();
log.trace("refreshToken:{}", refreshToken);
// 替换refresh_token参数
URI newUri = UriComponentsBuilder.fromUri(uri).replaceQueryParam(GatewayConstant.GRANT_TYPE_REFRESH_TOKEN, refreshToken).build(true).toUri();
log.trace("newUri: {}", newUri);
ServerHttpRequest newRequest = exchange.getRequest().mutate().uri(newUri).build();
return chain.filter(exchange.mutate().request(newRequest).build());
}
} else if (StringUtils.isNotBlank(authorization) && authorization.startsWith(CommonConstant.TOKEN_SPLIT)) {
authorization = authorization.replace(CommonConstant.TOKEN_SPLIT, "");
log.trace("jti authorization: {}", authorization);
// 从Redis里获取实际的access_token
Object object = redisTemplate.opsForValue().get(GatewayConstant.GATEWAY_ACCESS_TOKENS + authorization);
// 缓存里没有access_token,抛异常,统一异常处理会返回403,前端自动重新获取access_token
......@@ -73,10 +70,8 @@ public class TokenResponseGlobalFilter implements GlobalFilter, Ordered {
throw new InvalidAccessTokenException("access_token已失效.");
authorization = CommonConstant.TOKEN_SPLIT + object.toString();
String realAuthorization = authorization;
log.trace("jti->token:{}", realAuthorization);
// 更新请求头,参考源码:SetRequestHeaderGatewayFilterFactory
ServerHttpRequest newRequest = request.mutate().headers(httpHeaders -> httpHeaders.set(CommonConstant.REQ_HEADER, realAuthorization)).build();
log.trace("newRequestHeader:{}", newRequest.getHeaders().getFirst(CommonConstant.REQ_HEADER));
return chain.filter(exchange.mutate().request(newRequest).build());
}
return chain.filter(exchange);
......
......@@ -26,11 +26,6 @@ public class AccessToken implements Serializable {
private String accessToken;
/**
* 创建时间
*/
private Integer createTime;
/**
* 超时时间
*/
@JsonProperty("expires_in")
......
......@@ -25,7 +25,8 @@ public class AccessTokenJacksonSerializer extends StdSerializer<AccessToken> {
gen.writeStringField("refresh_token", accessToken.getJti());
gen.writeStringField("scope", accessToken.getScope());
gen.writeStringField("token_type", accessToken.getTokenType());
gen.writeStringField("tenant_code", accessToken.getTenantCode());
gen.writeStringField("tenantCode", accessToken.getTenantCode());
gen.writeStringField("loginType", accessToken.getLoginType());
gen.writeEndObject();
}
}
......@@ -75,6 +75,7 @@
<json.version>20140107</json.version>
<okhttp.version>3.8.1</okhttp.version>
<aliyun.version>4.1.0</aliyun.version>
<weixin.version>3.4.0</weixin.version>
<!-- docker -->
<docker.maven.verion>1.4.3</docker.maven.verion>
......@@ -138,6 +139,17 @@
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
</dependencies>
<!-- spring cloud base -->
......@@ -268,6 +280,20 @@
<artifactId>kaptcha</artifactId>
<version>${kaptcha.version}</version>
</dependency>
<!-- 微信小程序 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
<version>${weixin.version}</version>
</dependency>
<!-- 微信支付 -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-pay</artifactId>
<version>${weixin.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
......
Auth Service
=============
### 登录认证接口说明
支持三种方式登录:
1. 账号密码+图片验证码
2. 手机号+短信验证码
3. 微信小程序code
#### 账号密码+图片验证码登录
POST:`/api/auth/oauth/token?grant_type=password&scope=read&username=admin&credential=lBTqrKS0kZixOFXeZ0HRng==&randomStr=86111562225179514&code=mf3f`
url参数:
```
{
grant_type: password, // 固定为password
scope: read, // 固定为read
username: '', // 账号
credential: '', // 加密后的密码,加密算法在下面的密码加密部分
randomStr: '', // 随机数,生成算法在下面的随机数生成部分
code: '' // 验证码
}
```
1. 先生成随机数:randomStr
通过: `/api/user/v1/code/${randomStr}`获取验证码
2. 获取token
登录示例代码:
```
/**
* 登录
* @param identifier 账号
* @param credential 密码
* @param code 验证码
* @param randomStr 随机数
*/
export function loginByUsername (identifier, credential, code, randomStr) {
const grantType = 'password'
const scope = 'read'
return request({
url: '/api/auth/oauth/token',
headers: {
'Authorization': basicAuthorization
},
method: 'post',
params: {username: identifier, credential, randomStr, code, grant_type: grantType, scope}
})
}
```
其中:
1. `const basicAuthorization = 'Basic ' + btoa('web_app:spring-microservice-exam-secret')`
2. 密码需要加密,具体看**密码加密**部分
3. 请求头要带`Tenant-Code`,即租户标识
#### 二 手机号+验证码登录
接口URL:`/api/auth/mobile/token`
grant_type: mobile
#### 三 微信小程序+code登录
POST:`/api/auth/wx/token?grant_type=wx&scope=read&code=` + code
其中grant_type、scope固定,code为wx.login返回的code
1. 请求头参数:
```
{
'Authorization': 'Basic d2ViX2FwcDpzcHJpbmctbWljcm9zZXJ2aWNlLWV4YW0tc2VjcmV0',
'Tenant-Code': 'gitee' // 租户标识,目前固定为gitee
}
```
Authorization为`clientId:clientSecret`的base64编码,即:`'Basic ' + btoa('web_app:spring-microservice-exam-secret')`
2. 请求体参数:
```
{
name: userInfo.nickName,
sex: userInfo.gender, // 男:0,女:1
avatarUrl: userInfo.avatarUrl
}
```
3. 响应体:
```
{
access_token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpblR5cGUiOiJXWCIsInVzZXJfbmFtZSI6Im92TGw0NUluUm40SHpfanJwRWstZ0Yta0VGZjgiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwidGVuYW50Q29kZSI6ImdpdGVlIiwiZXhwIjoxNTYyNDE1ODUxLCJhdXRob3JpdGllcyI6WyJyb2xlX3VzZXIiXSwianRpIjoiOWE4MzUxNzAtMDA4NS00MGU3LWEzZWQtNTkyODFmZThlYWVmIiwiY2xpZW50X2lkIjoid2ViX2FwcCJ9.WsL4fW4ZsiJqlb-Nvj9RMAgdimOFpR4925OtZDPw2AAKG8daIkRIH5HXb91mS6mdI7ldhtiwEV1Lo5Glka1T3DYYeWaZdLsuHt66JyzGTrunuIP7msXfHEfO5QmvoytX2RwbqaS4riBqcc5Oh-Fry8Negb1wc3nV0-zjVqepPFmdDyU0eGL3g6lZMSpi4QPZUlBV2VHqjHw4bf9M1Z1PbZZmJrEWiY48Zdsm-kaW-uwPo7FGi7jXHrnUI7dJgKXsuVyWi04LdwWy2vhohjl7tZd29q5Wr3kmj8nQf3EYiub_Tx9ielVEApgnNqMwLNnWgcX1YNd5nQcs0Ahf785p0w",
expires_in: 2287,
jti: "9a835170-0085-40e7-a3ed-59281fe8eaef",
loginType: "WX",
refresh_token: "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb2dpblR5cGUiOiJXWCIsInVzZXJfbmFtZSI6Im92TGw0NUluUm40SHpfanJwRWstZ0Yta0VGZjgiLCJzY29wZSI6WyJyZWFkIiwid3JpdGUiXSwiYXRpIjoiOWE4MzUxNzAtMDA4NS00MGU3LWEzZWQtNTkyODFmZThlYWVmIiwidGVuYW50Q29kZSI6ImdpdGVlIiwiZXhwIjoxNTYyNDMzODUxLCJhdXRob3JpdGllcyI6WyJyb2xlX3VzZXIiXSwianRpIjoiNzQ4ZjFhNTMtZGRhYi00OGNjLWFhYzYtZWU5MzMzYzI0NWUyIiwiY2xpZW50X2lkIjoid2ViX2FwcCJ9.WRdOKI0Z2PX9IwxthEf_LSOWAyaz1AoM7T_ytkzlDuislSgMI6cZ7rJozMJdozfXa7xWl1swJ_r6-t2EoDC0gqDFnrIjmhgfJH6yIZSgWUQMTzXP29iHtkgRBvvDwPbDmHHRZjwV3eQvL6tFNJguBoywcCv81Ycy8tPBIWgbkbg98Fs--vbs0HznDHldf_Kzu_0_1Us0UkYPhN6YNJdlNfCxzSbXOrwuem-VIcw-yulw1s5-s_5sjqSRJ6HjVbwRyQrJizAmKkDN5YiSxf61fJDO2quUhc0DXY0OmKphL5EqlrgTCoQYCVoeYOLvcnUX5wIB3YdbP2WJiiYRrtx57g",
scope: "read write",
tenantCode: "gitee", // 租户标识
token_type: "bearer"
}
```
4. 登录示例代码:
```
// 登录
wx.login({
success: res => {
// 发送 res.code 到后台换取 openId, sessionKey, unionId
wx.request({
url: 'http://localhost:8000/api/auth/wx/token?grant_type=wx&scope=read&code=' + res.code,
header: {
'Authorization': 'Basic d2ViX2FwcDpzcHJpbmctbWljcm9zZXJ2aWNlLWV4YW0tc2VjcmV0',
'Tenant-Code': 'gitee'
},
method: 'POST',
data: {
name: userInfo.nickName,
sex: userInfo.gender,
avatarUrl: userInfo.avatarUrl
},
success (res) {
console.log(res.data)
}
})
}
})
```
#### 随机数生成
```
/**
* 生成随机len位数字
*/
export const randomLenNum = (len, date) => {
let random = ''
random = Math.ceil(Math.random() * 100000000000000).toString().substr(0, typeof len === 'number' ? len : 4)
if (date) random = random + Date.now()
return random
}
```
如生成4位长度的随机数: let randomStr = randomLenNum(4)
#### 密码加密
```
const user = encryption({
data: userInfo,
key: '1234567887654321',
param: ['credential']
})
```
```
/**
* 加密处理
*/
export const encryption = (params) => {
var {
data,
type,
param,
key
} = params
const result = JSON.parse(JSON.stringify(data))
if (type === 'Base64') {
param.forEach(ele => {
result[ele] = btoa(result[ele])
})
} else {
param.forEach(ele => {
var data = result[ele]
key = CryptoJS.enc.Latin1.parse(key)
var iv = key
var encrypted = CryptoJS.AES.encrypt(
data,
key,
{
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
})
result[ele] = encrypted.toString()
})
}
return result
}
```
\ No newline at end of file
......@@ -83,15 +83,10 @@
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- swagger -->
<!-- 微信小程序 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-miniapp</artifactId>
</dependency>
</dependencies>
......
package com.github.tangyi.auth.config;
import com.github.tangyi.common.security.constant.SecurityConstant;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -48,31 +49,32 @@ public class SwaggerConfig implements WebMvcConfigurer {
/**
* Authorization 请求头
*
* @return Parameter
*/
private Parameter authorizationParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Authorization")
.defaultValue("bearer authorization参数")
.description("令牌")
.description("access_token")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
.required(false).build();
return tokenBuilder.build();
}
/**
* Tenant-Code 请求头
*
* @return Parameter
*/
private Parameter tenantCodeParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Tenant-Code")
.defaultValue("租户标识")
.defaultValue(SecurityConstant.DEFAULT_TENANT_CODE)
.description("租户标识")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
.required(false).build();
return tokenBuilder.build();
}
......
package com.github.tangyi.auth.config;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;
import cn.binarywang.wx.miniapp.config.WxMaConfig;
import cn.binarywang.wx.miniapp.config.WxMaInMemoryConfig;
import com.github.tangyi.auth.properties.WxProperties;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 微信相关配置
*
* @author tangyi
* @date 2019/07/05 20:29
*/
@Configuration
@AllArgsConstructor
public class WxConfig {
/**
* 微信的配置,如appId,appSecret,sessionHost
*/
private final WxProperties wxProperties;
@Bean
public WxMaConfig wxMaConfig() {
WxMaInMemoryConfig config = new WxMaInMemoryConfig();
config.setAppid(wxProperties.getAppId());
config.setSecret(wxProperties.getAppSecret());
return config;
}
@Bean
public WxMaService wxMaService(WxMaConfig maConfig) {
WxMaService service = new WxMaServiceImpl();
service.setWxMaConfig(maConfig);
return service;
}
}
......@@ -72,7 +72,7 @@ public class OauthClientDetailsController extends BaseController {
* @author tangyi
* @date 2019/03/30 16:54
*/
@RequestMapping("clientList")
@GetMapping("clientList")
@ApiOperation(value = "获取客户端列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
......@@ -97,7 +97,7 @@ public class OauthClientDetailsController extends BaseController {
* @author tangyi
* @date 2019/03/30 23:17
*/
@RequestMapping("clients")
@GetMapping("clients")
@ApiOperation(value = "查询客户端列表", notes = "查询客户端列表")
@ApiImplicitParam(name = "oauthClient", value = "客户端实体oauthClient", required = true, dataType = "OauthClientDetails")
public ResponseBean<List<OauthClientDetails>> findOauthClientList(@RequestBody OauthClientDetails oauthClientDetails) {
......
package com.github.tangyi.auth.properties;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 微信配置
*
* @author tangyi
* @date 2019/07/04 20:25
*/
@Data
@Configuration
@ConfigurationProperties(prefix = "wx")
public class WxProperties {
private String appId;
private String appSecret;
private String grantType;
}
......@@ -30,6 +30,8 @@ public class CustomTokenConverter extends JwtAccessTokenConverter {
additionalInfo.put("loginType", LoginType.PWD.getType());
} else if (grantType.equalsIgnoreCase(CommonConstant.GRANT_TYPE_MOBILE)) {
additionalInfo.put("loginType", LoginType.SMS.getType());
} else if (grantType.equalsIgnoreCase(LoginType.WECHAT.getType())) {
additionalInfo.put("loginType", LoginType.WECHAT.getType());
}
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
return super.enhance(accessToken, authentication);
......
......@@ -70,7 +70,7 @@ public class CustomUserDetailsAuthenticationProvider extends AbstractUserDetails
UserDetails loadedUser;
try {
// 加载用户信息
loadedUser = this.userDetailsService.loadUserByUsernameAndTenantCode(authentication.getPrincipal().toString(), TenantContextHolder.getTenantCode());
loadedUser = this.userDetailsService.loadUserByIdentifierAndTenantCode(authentication.getPrincipal().toString(), TenantContextHolder.getTenantCode());
} catch (TenantNotFoundException tenantNotFound) {
throw tenantNotFound;
} catch (UsernameNotFoundException notFound) {
......
package com.github.tangyi.auth.security;
import com.github.tangyi.auth.api.module.WxSession;
import com.github.tangyi.auth.model.CustomUserDetails;
import com.github.tangyi.auth.properties.SysProperties;
import com.github.tangyi.auth.service.WxSessionService;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.exceptions.CommonException;
import com.github.tangyi.common.core.exceptions.TenantNotFoundException;
import com.github.tangyi.common.core.model.ResponseBean;
import com.github.tangyi.common.core.properties.SysProperties;
import com.github.tangyi.common.core.utils.DateUtils;
import com.github.tangyi.common.core.vo.RoleVo;
import com.github.tangyi.common.core.vo.UserVo;
import com.github.tangyi.common.security.core.CustomUserDetailsService;
import com.github.tangyi.common.security.core.GrantedAuthorityImpl;
import com.github.tangyi.common.security.wx.WxUser;
import com.github.tangyi.user.api.constant.MenuConstant;
import com.github.tangyi.user.api.dto.UserDto;
import com.github.tangyi.user.api.enums.IdentityType;
import com.github.tangyi.user.api.feign.UserServiceClient;
import com.github.tangyi.user.api.module.Menu;
import com.github.tangyi.user.api.module.Tenant;
......@@ -16,11 +24,13 @@ import com.google.common.collect.Lists;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
......@@ -40,6 +50,8 @@ public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {
private final SysProperties sysProperties;
private final WxSessionService wxService;
/**
* 加载用户信息
*
......@@ -48,7 +60,7 @@ public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {
* @throws UsernameNotFoundException,TenantNotFoundException
*/
@Override
public UserDetails loadUserByUsernameAndTenantCode(String username, String tenantCode) throws UsernameNotFoundException, TenantNotFoundException {
public UserDetails loadUserByIdentifierAndTenantCode(String username, String tenantCode) throws UsernameNotFoundException, TenantNotFoundException {
Tenant tenant = this.validateTenantCode(tenantCode);
UserVo userVo = userServiceClient.findUserByIdentifier(username, tenantCode);
if (userVo == null)
......@@ -71,7 +83,54 @@ public class CustomUserDetailsServiceImpl implements CustomUserDetailsService {
UserVo userVo = userServiceClient.findUserBySocial(social, tenantCode);
if (userVo == null)
throw new UsernameNotFoundException("用户手机号未注册.");
return new CustomUserDetails(social, userVo.getCredential(), CommonConstant.STATUS_NORMAL.equals(userVo.getStatus()), getAuthority(userVo), userVo.getTenantCode());
return new CustomUserDetails(userVo.getIdentifier(), userVo.getCredential(), CommonConstant.STATUS_NORMAL.equals(userVo.getStatus()), getAuthority(userVo), userVo.getTenantCode());
}
/**
* 根据微信code和租户标识查询
* 将code换成openId和sessionKey
*
* @param code code
* @param tenantCode tenantCode
* @param wxUser wxUser
* @return UserDetails
* @author tangyi
* @date 2019/07/05 20:05:36
*/
@Override
public UserDetails loadUserByWxCodeAndTenantCode(String code, String tenantCode, WxUser wxUser) throws UsernameNotFoundException {
Tenant tenant = this.validateTenantCode(tenantCode);
// 根据code获取openId和sessionKey
WxSession wxSession = wxService.code2Session(code);
if (wxSession == null)
throw new CommonException("获取openId失败.");
// 获取用户信息
UserVo userVo = userServiceClient.findUserByIdentifier(wxSession.getOpenId(), IdentityType.WE_CHAT.getValue(), tenantCode);
// 为空说明是第一次登录,需要将用户信息增加到数据库里
if (userVo == null) {
UserDto userDto = new UserDto();
// 用户的基本信息
if (wxUser != null)
BeanUtils.copyProperties(wxUser, userDto);
userDto.setIdentifier(wxSession.getOpenId());
userDto.setCredential(wxSession.getOpenId());
userDto.setIdentityType(IdentityType.WE_CHAT.getValue());
userDto.setLoginTime(DateUtils.asDate(LocalDateTime.now()));
// 注册账号
ResponseBean<Boolean> response = userServiceClient.registerUser(userDto);
if (response == null || !response.getData())
throw new CommonException("自动注册用户失败.");
// 重新获取用户信息
userVo = userServiceClient.findUserByIdentifier(wxSession.getOpenId(), IdentityType.WE_CHAT.getValue(), tenantCode);
} else {
// TODO 更新sessionKey,记录登录时间,IP等信息
UserDto userDto = new UserDto();
BeanUtils.copyProperties(userVo, userDto);
//userDto.setCredential(wxSession.getSessionKey());
userDto.setLoginTime(DateUtils.asDate(LocalDateTime.now()));
//userServiceClient.updateUser(userDto);
}
return new CustomUserDetails(userVo.getIdentifier(), userVo.getCredential(), CommonConstant.STATUS_NORMAL.equals(userVo.getStatus()), getAuthority(userVo), userVo.getTenantCode());
}
/**
......
package com.github.tangyi.auth.service;
import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import com.github.tangyi.auth.api.module.WxSession;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
/**
* 微信Service
*
* @author tangyi
* @date 2019/07/05 20:33
*/
@Slf4j
@AllArgsConstructor
@Service
public class WxSessionService {
private final WxMaService wxMaService;
/**
* 根据code获取WxSession
*
* @param code code
* @return WxSession
* @author tangyi
* @date 2019/07/05 20:37:02
*/
public WxSession getSession(String code) {
WxSession session = null;
try {
WxMaJscode2SessionResult result = wxMaService.getUserService().getSessionInfo(code);
session = new WxSession(result.getOpenid(), result.getSessionKey());
log.info("获取wx session成功,openId: {}, sessionKey: {}", session.getOpenId(), session.getSessionKey());
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return session;
}
/**
* 根据code获取WxSession
*
* @param code code
* @return WxSession
* @author tangyi
* @date 2019/07/06 14:01:13
*/
public WxSession code2Session(String code) {
WxSession session = null;
try {
WxMaJscode2SessionResult result = wxMaService.jsCode2SessionInfo(code);
session = new WxSession(result.getOpenid(), result.getSessionKey());
log.info("获取wx session成功,openId: {}, sessionKey: {}", session.getOpenId(), session.getSessionKey());
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return session;
}
}
......@@ -77,17 +77,6 @@
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
</dependencies>
<build>
......
package com.github.tangyi.exam.config;
import com.github.tangyi.common.security.constant.SecurityConstant;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -48,31 +49,32 @@ public class SwaggerConfig implements WebMvcConfigurer {
/**
* Authorization 请求头
*
* @return Parameter
*/
private Parameter authorizationParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Authorization")
.defaultValue("bearer authorization参数")
.description("令牌")
.description("access_token")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
.required(false).build();
return tokenBuilder.build();
}
/**
* Tenant-Code 请求头
*
* @return Parameter
*/
private Parameter tenantCodeParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Tenant-Code")
.defaultValue("租户标识")
.defaultValue(SecurityConstant.DEFAULT_TENANT_CODE)
.description("租户标识")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
.required(false).build();
return tokenBuilder.build();
}
......
......@@ -73,7 +73,7 @@ public class AnswerController extends BaseController {
* @author tangyi
* @date 2018/11/10 21:25
*/
@RequestMapping("answerList")
@GetMapping("answerList")
@ApiOperation(value = "获取答题列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
......
......@@ -66,7 +66,7 @@ public class CourseController extends BaseController {
* @author tangyi
* @date 2018/11/10 21:30
*/
@RequestMapping("courseList")
@GetMapping("courseList")
@ApiOperation(value = "获取课程列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
......
......@@ -91,7 +91,7 @@ public class ExamRecordController extends BaseController {
* @author tangyi
* @date 2018/11/10 21:33
*/
@RequestMapping("examRecordList")
@GetMapping("examRecordList")
@ApiOperation(value = "获取考试记录列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
......
......@@ -77,7 +77,7 @@ public class ExaminationController extends BaseController {
* @author tangyi
* @date 2018/11/10 21:10
*/
@RequestMapping("examinationList")
@GetMapping("examinationList")
@ApiOperation(value = "获取考试列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
......
......@@ -75,7 +75,7 @@ public class KnowledgeController extends BaseController {
* @author tangyi
* @date 2019/1/1 15:15
*/
@RequestMapping("knowledgeList")
@GetMapping("knowledgeList")
@ApiOperation(value = "获取知识列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
......
......@@ -80,7 +80,7 @@ public class SubjectController extends BaseController {
* @author tangyi
* @date 2018/11/10 21:43
*/
@RequestMapping("subjectList")
@GetMapping("subjectList")
@ApiOperation(value = "获取题目列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
......
......@@ -56,6 +56,10 @@ public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler
*/
@ExceptionHandler(CommonException.class)
public ResponseEntity<ResponseBean<String>> handleCommonException(Exception e) {
return new ResponseEntity<>(new ResponseBean<>(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
ResponseBean<String> responseBean = new ResponseBean<>();
responseBean.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
responseBean.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
responseBean.setMsg(e.getMessage());
return new ResponseEntity<>(responseBean, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
......@@ -42,17 +42,6 @@
<artifactId>spring-boot-admin-starter-client</artifactId>
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
<!-- aliyun -->
<dependency>
<groupId>com.aliyun</groupId>
......
package com.github.tangyi.msc.config;
import com.github.tangyi.common.security.constant.SecurityConstant;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -48,31 +49,32 @@ public class SwaggerConfig implements WebMvcConfigurer {
/**
* Authorization 请求头
*
* @return Parameter
*/
private Parameter authorizationParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Authorization")
.defaultValue("bearer authorization参数")
.description("令牌")
.description("access_token")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
.required(false).build();
return tokenBuilder.build();
}
/**
* Tenant-Code 请求头
*
* @return Parameter
*/
private Parameter tenantCodeParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Tenant-Code")
.defaultValue("租户标识")
.defaultValue(SecurityConstant.DEFAULT_TENANT_CODE)
.description("租户标识")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
.required(false).build();
return tokenBuilder.build();
}
......
......@@ -83,17 +83,6 @@
<groupId>com.github.axet</groupId>
<artifactId>kaptcha</artifactId>
</dependency>
<!-- swagger -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
</dependency>
</dependencies>
<build>
......
package com.github.tangyi.user.config;
import com.github.tangyi.common.security.constant.SecurityConstant;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
......@@ -48,31 +49,32 @@ public class SwaggerConfig implements WebMvcConfigurer {
/**
* Authorization 请求头
*
* @return Parameter
*/
private Parameter authorizationParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Authorization")
.defaultValue("bearer authorization参数")
.description("令牌")
.description("access_token")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
.required(false).build();
return tokenBuilder.build();
}
/**
* Tenant-Code 请求头
*
* @return Parameter
*/
private Parameter tenantCodeParameter() {
ParameterBuilder tokenBuilder = new ParameterBuilder();
tokenBuilder.name("Tenant-Code")
.defaultValue("租户标识")
.defaultValue(SecurityConstant.DEFAULT_TENANT_CODE)
.description("租户标识")
.modelRef(new ModelRef("string"))
.parameterType("header")
.required(true).build();
.required(false).build();
return tokenBuilder.build();
}
......
......@@ -76,7 +76,7 @@ public class AttachmentController extends BaseController {
* @author tangyi
* @date 2018/10/30 21:05
*/
@RequestMapping("attachmentList")
@GetMapping("attachmentList")
@ApiOperation(value = "获取附件列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
......@@ -102,7 +102,7 @@ public class AttachmentController extends BaseController {
* @author tangyi
* @date 2018/10/30 21:54
*/
@RequestMapping("upload")
@PostMapping("upload")
@ApiOperation(value = "上传文件", notes = "上传文件")
@ApiImplicitParams({
@ApiImplicitParam(name = "busiType", value = "业务分类", dataType = "String"),
......@@ -124,7 +124,7 @@ public class AttachmentController extends BaseController {
* @author tangyi
* @date 2018/10/30 22:26
*/
@RequestMapping("download")
@GetMapping("download")
@ApiOperation(value = "下载附件", notes = "根据ID下载附件")
@ApiImplicitParam(name = "id", value = "附件ID", required = true, dataType = "String")
public void download(@NotBlank String id, HttpServletRequest request, HttpServletResponse response) {
......@@ -207,7 +207,7 @@ public class AttachmentController extends BaseController {
* @author tangyi
* @date 2019/01/01 22:16
*/
@RequestMapping(value = "findById", method = RequestMethod.POST)
@PostMapping(value = "findById")
@ApiOperation(value = "批量查询附件信息", notes = "根据附件ID批量查询附件信息")
@ApiImplicitParam(name = "attachmentVo", value = "附件信息", dataType = "AttachmentVo")
public ResponseBean<List<AttachmentVo>> findById(@RequestBody AttachmentVo attachmentVo) {
......
......@@ -68,7 +68,7 @@ public class LogController extends BaseController {
* @author tangyi
* @date 2018/10/24 0024 22:13
*/
@RequestMapping("logList")
@GetMapping("logList")
@ApiOperation(value = "获取日志列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
......
......@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import com.github.pagehelper.PageInfo;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.model.ResponseBean;
import com.github.tangyi.common.core.properties.SysProperties;
import com.github.tangyi.common.core.utils.*;
import com.github.tangyi.common.core.vo.MenuVo;
import com.github.tangyi.common.core.web.BaseController;
......@@ -14,7 +15,6 @@ import com.github.tangyi.user.api.constant.MenuConstant;
import com.github.tangyi.user.api.dto.MenuDto;
import com.github.tangyi.user.api.module.Menu;
import com.github.tangyi.user.api.module.Role;
import com.github.tangyi.user.config.SysConfig;
import com.github.tangyi.user.service.MenuService;
import com.github.tangyi.user.utils.MenuUtil;
import com.google.common.collect.Lists;
......@@ -55,7 +55,7 @@ public class MenuController extends BaseController {
private final MenuService menuService;
private final SysConfig sysConfig;
private final SysProperties sysProperties;
/**
* 返回当前用户的树形菜单集合
......@@ -69,7 +69,7 @@ public class MenuController extends BaseController {
String tenantCode = SysUtil.getTenantCode(), identifier = SysUtil.getUser();
List<Menu> userMenus;
// 超级管理员
if (identifier.equals(sysConfig.getAdminUser())) {
if (identifier.equals(sysProperties.getAdminUser())) {
// 获取角色的菜单
Menu menu = new Menu();
menu.setTenantCode(tenantCode);
......@@ -209,7 +209,7 @@ public class MenuController extends BaseController {
* @author tangyi
* @date 2018/8/26 23:17
*/
@RequestMapping("menuList")
@GetMapping("menuList")
@ApiOperation(value = "获取菜单列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
......@@ -324,7 +324,7 @@ public class MenuController extends BaseController {
* @author tangyi
* @date 2018/11/28 12:51
*/
@RequestMapping("import")
@PostMapping("import")
@PreAuthorize("hasAuthority('sys:menu:import') or hasAnyRole('" + SecurityConstant.ROLE_ADMIN + "')")
@ApiOperation(value = "导入菜单", notes = "导入菜单")
@Log("导入菜单")
......
......@@ -5,6 +5,8 @@ import com.github.tangyi.common.core.web.BaseController;
import com.github.tangyi.common.security.constant.SecurityConstant;
import com.github.tangyi.user.service.MobileService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
......@@ -34,6 +36,8 @@ public class MobileController extends BaseController {
* @date 2019/07/02 09:49:05
*/
@GetMapping("sendSms/{mobile}")
@ApiOperation(value = "发送短信", notes = "发送短信到指定的手机号")
@ApiImplicitParam(name = "mobile", value = "mobile", required = true, dataType = "String", paramType = "path")
public ResponseBean<Boolean> sendSms(@PathVariable String mobile, @RequestHeader(SecurityConstant.TENANT_CODE_HEADER) String tenantCode) {
return mobileService.sendSms(mobile, tenantCode);
}
......
......@@ -75,7 +75,7 @@ public class RoleController extends BaseController {
* @author tangyi
* @date 2018/10/24 22:13
*/
@RequestMapping("roleList")
@GetMapping("roleList")
@ApiOperation(value = "获取角色列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
......@@ -101,7 +101,7 @@ public class RoleController extends BaseController {
* @author tangyi
* @date 2019/05/15 23:29
*/
@RequestMapping("allRoles")
@GetMapping("allRoles")
@ApiOperation(value = "获取全部角色列表")
@ApiImplicitParam(name = "role", value = "角色信息", dataType = "RoleVo")
public ResponseBean<List<Role>> allRoles(Role role) {
......
......@@ -77,7 +77,7 @@ public class RouteController extends BaseController {
* @author tangyi
* @date 2019/4/2 15:09
*/
@RequestMapping("routeList")
@GetMapping("routeList")
@ApiOperation(value = "获取路由列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
......
package com.github.tangyi.user.controller;
import com.github.tangyi.common.core.dto.SysConfigDto;
import com.github.tangyi.common.core.model.ResponseBean;
import com.github.tangyi.common.core.properties.SysProperties;
import com.github.tangyi.common.core.web.BaseController;
import com.github.tangyi.user.config.SysConfig;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
......@@ -22,7 +24,7 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/v1/sysConfig")
public class SysConfigController extends BaseController {
private final SysConfig sysConfig;
private final SysProperties sysProperties;
/**
* 获取系统配置
......@@ -33,7 +35,9 @@ public class SysConfigController extends BaseController {
*/
@GetMapping
@ApiOperation(value = "获取系统配置", notes = "获取系统配置")
public ResponseBean<SysConfig> getSysConfig() {
return new ResponseBean<>(sysConfig);
public ResponseBean<SysConfigDto> getSysConfig() {
SysConfigDto sysConfigDto = new SysConfigDto();
BeanUtils.copyProperties(sysProperties, sysConfigDto);
return new ResponseBean<>(sysConfigDto);
}
}
......@@ -80,7 +80,7 @@ public class TenantController extends BaseController {
* @author tangyi
* @date 2019/05/22 23:29
*/
@RequestMapping("tenantList")
@GetMapping("tenantList")
@ApiOperation(value = "获取租户列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
......
......@@ -118,7 +118,7 @@ public class UserController extends BaseController {
* @author tangyi
* @date 2018/8/26 22:56
*/
@RequestMapping("userList")
@GetMapping("userList")
@ApiOperation(value = "获取用户列表")
@ApiImplicitParams({
@ApiImplicitParam(name = CommonConstant.PAGE_NUM, value = "分页页码", defaultValue = CommonConstant.PAGE_NUM_DEFAULT, dataType = "String"),
......@@ -331,7 +331,7 @@ public class UserController extends BaseController {
* @author tangyi
* @date 2018/11/28 12:44
*/
@RequestMapping("import")
@PostMapping("import")
@PreAuthorize("hasAuthority('sys:user:import') or hasAnyRole('" + SecurityConstant.ROLE_ADMIN + "')")
@ApiOperation(value = "导入数据", notes = "导入数据")
@Log("导入用户")
......@@ -382,7 +382,7 @@ public class UserController extends BaseController {
* @author tangyi
* @date 2018/12/31 21:16
*/
@RequestMapping(value = "findById", method = RequestMethod.POST)
@PostMapping(value = "findById")
@ApiOperation(value = "根据ID查询用户", notes = "根据ID查询用户")
@ApiImplicitParam(name = "userVo", value = "用户信息", required = true, paramType = "UserVo")
public ResponseBean<List<UserVo>> findById(@RequestBody UserVo userVo) {
......@@ -398,10 +398,15 @@ public class UserController extends BaseController {
* @date 2019/01/10 22:35
*/
@ApiOperation(value = "注册", notes = "注册")
@ApiImplicitParam(name = "userDto", value = "用户实体user", required = true, dataType = "UserDto")
@ApiImplicitParams({
@ApiImplicitParam(name = "grant_type", value = "授权类型(password、mobile)", required = true, defaultValue = "password", dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "code", value = "验证码", required = true, dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "randomStr", value = "随机数", dataType = "String", paramType = "query"),
@ApiImplicitParam(name = "mobile", value = "手机号", dataType = "String", paramType = "query")
})
@PostMapping("register")
@Log("注册用户")
public ResponseBean<Boolean> register(@Valid UserDto userDto) {
public ResponseBean<Boolean> register(@RequestBody @Valid UserDto userDto) {
return new ResponseBean<>(userService.register(userDto));
}
......
......@@ -42,8 +42,7 @@ public class ValidateCodeController extends BaseController {
*/
@ApiOperation(value = "生成验证码", notes = "生成验证码")
@ApiImplicitParams({
@ApiImplicitParam(name = "random", value = "随机数", required = true, dataType = "String", paramType = "path"),
@ApiImplicitParam(name = "tenantCode", value = "租户标识", required = true, dataType = "String")
@ApiImplicitParam(name = "random", value = "随机数", required = true, dataType = "String", paramType = "path")
})
@GetMapping("/{random}")
public void produceCode(@PathVariable String random, @RequestParam(required = false, defaultValue = SecurityConstant.DEFAULT_TENANT_CODE) String tenantCode, HttpServletResponse response) throws Exception {
......
......@@ -56,6 +56,10 @@ public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler
*/
@ExceptionHandler(CommonException.class)
public ResponseEntity<ResponseBean<String>> handleCommonException(Exception e) {
return new ResponseEntity<>(new ResponseBean<>(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
ResponseBean<String> responseBean = new ResponseBean<>();
responseBean.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
responseBean.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
responseBean.setMsg(e.getMessage());
return new ResponseEntity<>(responseBean, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
package com.github.tangyi.user.service;
import com.github.tangyi.common.core.exceptions.CommonException;
import com.github.tangyi.common.core.properties.SysProperties;
import com.github.tangyi.common.core.service.CrudService;
import com.github.tangyi.common.core.utils.FileUtil;
import com.github.tangyi.common.core.utils.SysUtil;
import com.github.tangyi.user.api.module.Attachment;
import com.github.tangyi.user.config.SysConfig;
import com.github.tangyi.user.mapper.AttachmentMapper;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
......@@ -31,7 +31,7 @@ public class AttachmentService extends CrudService<AttachmentMapper, Attachment>
private final FastDfsService fastDfsService;
private final SysConfig sysConfig;
private final SysProperties sysProperties;
/**
* 根据id查询
......@@ -147,7 +147,7 @@ public class AttachmentService extends CrudService<AttachmentMapper, Attachment>
throw new CommonException("附件不存在.");
String preview = attachment.getPreviewUrl();
if (StringUtils.isBlank(preview))
preview = sysConfig.getFdfsHttpHost() + "/" + attachment.getFastFileId();
preview = sysProperties.getFdfsHttpHost() + "/" + attachment.getFastFileId();
log.debug("id为:{}的附件的预览地址:{}", attachment.getId(), preview);
return preview;
}
......
......@@ -3,13 +3,12 @@ package com.github.tangyi.user.service;
import cn.hutool.core.util.RandomUtil;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.enums.LoginType;
import com.github.tangyi.common.core.exceptions.CommonException;
import com.github.tangyi.common.core.model.ResponseBean;
import com.github.tangyi.common.core.vo.UserVo;
import com.github.tangyi.common.security.constant.SecurityConstant;
import com.github.tangyi.msc.api.constant.SmsConstant;
import com.github.tangyi.msc.api.dto.SmsDto;
import com.github.tangyi.msc.api.feign.MscServiceClient;
import com.github.tangyi.user.api.enums.IdentityType;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
......@@ -44,11 +43,6 @@ public class MobileService {
* @date 2019/07/02 09:36:52
*/
public ResponseBean<Boolean> sendSms(String mobile, String tenantCode) {
UserVo userVo = userService.findUserByIdentifier(IdentityType.PHONE_NUMBER.getValue(), mobile, tenantCode);
if (userVo == null) {
log.info("手机号未注册:{}", mobile);
return new ResponseBean<>(Boolean.FALSE, "手机号未注册.");
}
Object codeObj = redisTemplate.opsForValue().get(CommonConstant.DEFAULT_CODE_KEY + mobile);
if (codeObj != null) {
log.info("手机号验证码未过期:{},{}", mobile, codeObj);
......@@ -62,7 +56,10 @@ public class MobileService {
SmsDto smsDto = new SmsDto();
smsDto.setReceiver(mobile);
smsDto.setContent(String.format(SmsConstant.SMS_TEMPLATE, code));
mscServiceClient.sendSms(smsDto);
// ResponseBean<?> result = mscServiceClient.sendSms(smsDto);
// if (result == null)
// throw new CommonException("发送失败.");
// log.info("发送验证码结果:{}", result.getData());
return new ResponseBean<>(Boolean.TRUE, code);
}
}
......@@ -3,19 +3,20 @@ package com.github.tangyi.user.service;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.common.core.enums.LoginType;
import com.github.tangyi.common.core.exceptions.CommonException;
import com.github.tangyi.common.core.properties.SysProperties;
import com.github.tangyi.common.core.service.CrudService;
import com.github.tangyi.common.core.utils.DateUtils;
import com.github.tangyi.common.core.utils.IdGen;
import com.github.tangyi.common.core.utils.SysUtil;
import com.github.tangyi.common.core.vo.UserVo;
import com.github.tangyi.common.security.constant.SecurityConstant;
import com.github.tangyi.user.api.constant.AttachmentConstant;
import com.github.tangyi.user.api.constant.MenuConstant;
import com.github.tangyi.user.api.constant.RoleConstant;
import com.github.tangyi.user.api.dto.UserDto;
import com.github.tangyi.user.api.dto.UserInfoDto;
import com.github.tangyi.user.api.enums.IdentityType;
import com.github.tangyi.user.api.module.*;
import com.github.tangyi.user.config.SysConfig;
import com.github.tangyi.user.mapper.RoleMapper;
import com.github.tangyi.user.mapper.UserMapper;
import com.github.tangyi.user.mapper.UserRoleMapper;
......@@ -66,7 +67,7 @@ public class UserService extends CrudService<UserMapper, User> {
private final AttachmentService attachmentService;
private final SysConfig sysConfig;
private final SysProperties sysProperties;
private final UserAuthsService userAuthsService;
......@@ -503,7 +504,7 @@ public class UserService extends CrudService<UserMapper, User> {
attachment.setId(user.getAvatarId());
userInfoDto.setAvatarUrl(attachmentService.getPreviewUrl(attachment));
} else {
userInfoDto.setAvatarUrl(sysConfig.getDefaultAvatar());
userInfoDto.setAvatarUrl(sysProperties.getDefaultAvatar());
}
} catch (Exception e) {
log.error(e.getMessage(), e);
......@@ -521,7 +522,6 @@ public class UserService extends CrudService<UserMapper, User> {
@Transactional
@CacheEvict(value = "user", key = "#userDto.identifier")
public boolean resetPassword(UserDto userDto) {
userDto.setCommonValue(SysUtil.getUser(), SysUtil.getSysCode());
UserAuths userAuths = new UserAuths();
userAuths.setIdentifier(userDto.getIdentifier());
userAuths = userAuthsService.getByIdentifier(userAuths);
......@@ -533,7 +533,7 @@ public class UserService extends CrudService<UserMapper, User> {
}
/**
* 注册
* 注册,注意要清除缓存
*
* @param userDto userDto
* @return boolean
......@@ -541,16 +541,25 @@ public class UserService extends CrudService<UserMapper, User> {
* @date 2019/07/03 13:30:03
*/
@Transactional
@CacheEvict(value = "user", key = "#userDto.identifier")
public boolean register(UserDto userDto) {
boolean success = false;
// 解密
String password = this.decryptCredential(userDto.getCredential(), userDto.getIdentityType());
User user = new User();
BeanUtils.copyProperties(userDto, user);
// 初始化用户名,系统编号,租户编号
user.setCommonValue(userDto.getIdentifier(), SysUtil.getSysCode(), SysUtil.getTenantCode());
// 设置默认密码
if (StringUtils.isEmpty(userDto.getCredential()))
userDto.setCredential(CommonConstant.DEFAULT_PASSWORD);
user.setStatus(CommonConstant.DEL_FLAG_NORMAL);
// 初始化头像
if (StringUtils.isNotBlank(userDto.getAvatarUrl())) {
Attachment attachment = new Attachment();
attachment.setCommonValue(userDto.getIdentifier(), SysUtil.getSysCode(), SysUtil.getTenantCode());
attachment.setBusiType(AttachmentConstant.BUSI_TYPE_USER_AVATAR);
attachment.setPreviewUrl(userDto.getAvatarUrl());
if (attachmentService.insert(attachment) > 0)
user.setAvatarId(attachment.getId());
}
// 保存用户基本信息
if (this.insert(user) > 0) {
// 保存账号信息
......@@ -558,10 +567,10 @@ public class UserService extends CrudService<UserMapper, User> {
userAuths.setCommonValue(userDto.getIdentifier(), user.getApplicationCode(), user.getTenantCode());
userAuths.setUserId(user.getId());
userAuths.setIdentifier(userDto.getIdentifier());
if (userDto.getIdentityType() == null)
userAuths.setIdentityType(IdentityType.PASSWORD.getValue());
// 设置密码,授权类型默认
userAuths.setCredential(encoder.encode(userDto.getCredential()));
if (userDto.getIdentityType() != null)
userAuths.setIdentityType(userDto.getIdentityType());
// 设置密码
userAuths.setCredential(encoder.encode(password));
userAuthsService.insert(userAuths);
// 分配默认角色
success = this.defaultRole(user, userDto.getTenantCode(), userDto.getIdentifier());
......@@ -570,6 +579,32 @@ public class UserService extends CrudService<UserMapper, User> {
}
/**
* 解密密码
*
* @param encoded encoded
* @return String
* @author tangyi
* @date 2019/07/05 12:39:13
*/
private String decryptCredential(String encoded, Integer identityType) {
// 返回默认密码
if (StringUtils.isBlank(encoded))
return CommonConstant.DEFAULT_PASSWORD;
// 微信注册不需要解密
if (IdentityType.WE_CHAT.getValue().equals(identityType))
return encoded;
// 解密密码
try {
encoded = SysUtil.decryptAES(encoded, sysProperties.getKey()).trim();
log.info("密码解密结果:{}", encoded);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new CommonException("注册失败,密码非法.");
}
return encoded;
}
/**
* 分配默认角色
*
* @param user user
......
package com.github.tangyi.user.utils;
import com.github.tangyi.common.core.properties.SysProperties;
import com.github.tangyi.common.core.utils.SpringContextHolder;
import com.github.tangyi.common.core.utils.SysUtil;
import com.github.tangyi.common.core.vo.RoleVo;
......@@ -7,7 +8,6 @@ import com.github.tangyi.user.api.dto.UserInfoDto;
import com.github.tangyi.user.api.module.Role;
import com.github.tangyi.user.api.module.User;
import com.github.tangyi.user.api.module.UserAuths;
import com.github.tangyi.user.config.SysConfig;
import org.springframework.beans.BeanUtils;
import java.util.LinkedHashMap;
......@@ -89,8 +89,8 @@ public class UserUtils {
* @date 2019/07/04 00:25:11
*/
public static boolean isAdmin(String identifier) {
SysConfig sysConfig = SpringContextHolder.getApplicationContext().getBean(SysConfig.class);
return identifier.equals(sysConfig.getAdminUser());
SysProperties sysProperties = SpringContextHolder.getApplicationContext().getBean(SysProperties.class);
return identifier.equals(sysProperties.getAdminUser());
}
/**
......
......@@ -4,7 +4,7 @@
<resultMap id="userAuthResultMap" type="com.github.tangyi.user.api.module.UserAuths">
<id column="id" property="id"/>
<result column="user_id" property="userId"/>
<result column="identity_type" property="identityType"/>
<result column="identity_type" property="identityType" jdbcType="INTEGER"/>
<result column="identifier" property="identifier"/>
<result column="credential" property="credential"/>
<result column="creator" property="creator"/>
......@@ -33,8 +33,8 @@
<!-- where 条件 -->
<sql id="whereColumnList">
<if test="identityType != null and identityType != ''">
and a.identity_type = #{identityType}
<if test="identityType != null">
and a.identity_type = #{identityType, jdbcType=INTEGER}
</if>
<if test="tenantCode != null and tenantCode != ''">
and a.tenant_code = #{tenantCode}
......@@ -102,7 +102,7 @@
) values (
#{id},
#{userId},
#{identityType},
#{identityType, jdbcType=INTEGER},
#{identifier},
#{credential},
#{creator},
......@@ -122,7 +122,7 @@
user_id = #{userId} ,
</if>
<if test="identityType != null">
identity_type = #{identityType} ,
identity_type = #{identityType, jdbcType=INTEGER} ,
</if>
<if test="identifier != null">
identifier = #{identifier} ,
......
......@@ -12,6 +12,14 @@
<result column="user_desc" property="userDesc"/>
<result column="dept_id" property="deptId"/>
<result column="status" property="status"/>
<result column="parent_uid" property="parentUid"/>
<result column="street_id" property="streetId"/>
<result column="county_id" property="countyId"/>
<result column="city_id" property="cityId"/>
<result column="province_id" property="provinceId"/>
<result column="login_time" property="loginTime" javaType="java.util.Date" jdbcType="TIMESTAMP"/>
<result column="lock_time" property="lockTime" javaType="java.util.Date" jdbcType="TIMESTAMP"/>
<result column="wechat" property="wechat"/>
<result column="creator" property="creator"/>
<result column="create_date" property="createDate" javaType="java.util.Date" jdbcType="TIMESTAMP"/>
<result column="modifier" property="modifier"/>
......@@ -32,6 +40,14 @@
a.user_desc,
a.dept_id,
a.status,
a.parent_uid,
a.street_id,
a.county_id,
a.city_id,
a.province_id,
a.login_time,
a.lock_time,
a.wechat,
a.creator,
a.create_date,
a.modifier,
......@@ -106,6 +122,14 @@
user_desc,
dept_id,
status,
parent_uid,
street_id,
county_id,
city_id,
province_id,
login_time,
lock_time,
wechat,
creator,
create_date,
modifier,
......@@ -124,6 +148,14 @@
#{userDesc},
#{deptId},
#{status},
#{parentUid},
#{streetId},
#{countyId},
#{cityId},
#{provinceId},
#{loginTime, jdbcType=TIMESTAMP, javaType=java.util.Date},
#{lockTime, jdbcType=TIMESTAMP, javaType=java.util.Date},
#{wechat},
#{creator},
#{createDate, jdbcType=TIMESTAMP, javaType=java.util.Date},
#{modifier},
......@@ -164,6 +196,30 @@
<if test="status != null">
status = #{status} ,
</if>
<if test="parentUid != null">
parent_uid = #{parentUid} ,
</if>
<if test="streetId != null">
street_id = #{streetId} ,
</if>
<if test="countyId != null">
county_id = #{countyId} ,
</if>
<if test="cityId != null">
city_id = #{cityId} ,
</if>
<if test="provinceId != null">
province_id = #{provinceId} ,
</if>
<if test="loginTime != null">
login_time = #{loginTime, jdbcType=TIMESTAMP, javaType=java.util.Date} ,
</if>
<if test="lockTime != null">
lock_time = #{lockTime, jdbcType=TIMESTAMP, javaType=java.util.Date} ,
</if>
<if test="wechat != null">
wechat = #{wechat} ,
</if>
<if test="creator != null">
creator = #{creator} ,
</if>
......@@ -182,17 +238,12 @@
</update>
<update id="delete">
update sys_user set
del_flag = 1,
modifier = #{modifier} ,
modify_date = #{modifyDate, jdbcType=TIMESTAMP, javaType=java.util.Date}
where id = #{id}
DELETE FROM sys_user WHERE id = #{id}
</update>
<delete id="deleteAll">
update sys_user SET
del_flag = 1
where id in
DELETE FROM sys_user
WHERE id in
<foreach item="item" index="index" collection="array" open="("
separator="," close=")">#{item}
</foreach>
......
package com.github.tangyi.auth.api.module;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.io.Serializable;
/**
* 封装微信登录状态
*
* @author tangyi
* @date 2019/07/05 20:35
*/
@Data
@AllArgsConstructor
public class WxSession implements Serializable {
private static final long serialVersionUID = 1L;
private String openId;
private String sessionKey;
}
package com.github.tangyi.user.api.constant;
/**
* @author tangyi
* @date 2019/07/06 16:16
*/
public class AttachmentConstant {
/**
* 普通附件
*/
public static final String BUSI_TYPE_NORMAL_ATTACHMENT = "0";
/**
* 用户头像
*/
public static final String BUSI_TYPE_USER_AVATAR = "1";
/**
* 知识库附件
*/
public static final String BUSI_TYPE_KNOWLEDGE_ATTACHMENT = "2";
}
package com.github.tangyi.user.api.dto;
import com.github.tangyi.common.core.persistence.BaseEntity;
import com.github.tangyi.common.core.constant.CommonConstant;
import com.github.tangyi.user.api.module.Role;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.Pattern;
import java.io.Serializable;
import java.util.Date;
import java.util.List;
......@@ -17,92 +19,183 @@ import java.util.List;
* @date 2018/8/26 14:36
*/
@Data
public class UserDto extends BaseEntity<UserDto> {
public class UserDto implements Serializable {
private static final long serialVersionUID = 1L;
/**
* id
*/
@ApiModelProperty(value = "id", hidden = true)
private String id;
/**
* 授权类型,1:用户名密码,2:手机号,3:邮箱,4:微信,5:QQ
*/
@ApiModelProperty(value = "授权类型,1:用户名密码,2:手机号,3:邮箱,4:微信,5:QQ", dataType = "Integer", example = "1")
private Integer identityType;
/**
* 唯一标识,如用户名、手机号
*/
@ApiModelProperty(value = "账号唯一标识,如用户名、手机号", example = "git")
private String identifier;
/**
* 密码凭证,跟授权类型有关,如密码、第三方系统的token等
*/
@ApiModelProperty(value = "密码,需要使用AES加密", example = "lBTqrKS0kZixOFXeZ0HRng==")
private String credential;
/**
* 角色
*/
@ApiModelProperty(value = "角色", hidden = true)
private List<String> role;
/**
* 部门ID
*/
@ApiModelProperty(value = "部门ID")
private String deptId;
/**
* 部门名称
*/
@ApiModelProperty(value = "部门名称", hidden = true)
private String deptName;
/**
* 旧密码
*/
@ApiModelProperty(value = "旧密码", hidden = true)
private String oldPassword;
/**
* 新密码
*/
@ApiModelProperty(value = "新密码", hidden = true)
private String newPassword;
/**
* 姓名
*/
@ApiModelProperty(value = "姓名", example = "git")
private String name;
/**
* 电话
*/
@ApiModelProperty(value = "电话", example = "15521089184")
@Pattern(regexp = "^\\d{11}$", message = "请输入11位手机号")
private String phone;
/**
* 头像id
*/
@ApiModelProperty(value = "头像id", hidden = true)
private String avatarId;
/**
* 头像URL
*/
@ApiModelProperty(value = "头像URL", hidden = true)
private String avatarUrl;
/**
* 邮箱
*/
@ApiModelProperty(value = "邮箱", example = "1633736729@qq.com")
@Email(message = "邮箱格式不正确")
private String email;
/**
* 性别
*/
@ApiModelProperty(value = "性别,0:男,1:女", dataType = "Integer", example = "0")
private Integer sex;
/**
* 出生日期
*/
@ApiModelProperty(value = "出生日期", dataType = "Date")
private Date born;
/**
* 描述
*/
@ApiModelProperty(value = "描述", example = "git")
private String userDesc;
/**
* 状态
*/
@ApiModelProperty(value = "状态,0:启用,1:禁用", example = "0")
private Integer status;
/**
* 角色列表
*/
@ApiModelProperty(value = "角色列表", hidden = true)
private List<Role> roleList;
/**
* 系统编号
*/
@ApiModelProperty(value = "系统编号", example = "EXAM")
private String applicationCode;
/**
* 租户标识
*/
@ApiModelProperty(value = "租户标识", example = "gitee")
private String tenantCode;
/**
* 引导注册人
*/
@ApiModelProperty(value = "引导注册人")
private String parentUid;
/**
* 乡/镇
*/
@ApiModelProperty(value = "乡/镇")
private String streetId;
/**
* 县
*/
@ApiModelProperty(value = "县")
private String countyId;
/**
* 城市
*/
@ApiModelProperty(value = "城市")
private String cityId;
/**
* 省份
*/
@ApiModelProperty(value = "省份")
private String provinceId;
/**
* 最近登录时间
*/
@ApiModelProperty(value = "最近登录时间", hidden = true)
private Date loginTime;
/**
* 用户归档时间
*/
@ApiModelProperty(value = "用户归档时间", hidden = true)
private Date lockTime;
/**
* 微信
*/
@ApiModelProperty(value = "微信")
private String wechat;
}
......@@ -107,4 +107,44 @@ public class UserInfoDto implements Serializable {
* 租户标识
*/
private String tenantCode;
/**
* 引导注册人
*/
private String parentUid;
/**
* 乡/镇
*/
private String streetId;
/**
* 县
*/
private String countyId;
/**
* 城市
*/
private String cityId;
/**
* 省份
*/
private String provinceId;
/**
* 最近登录时间
*/
private Date loginTime;
/**
* 用户归档时间
*/
private Date lockTime;
/**
* 微信
*/
private String wechat;
}
......@@ -7,6 +7,7 @@ import com.github.tangyi.common.core.vo.AttachmentVo;
import com.github.tangyi.common.core.vo.DeptVo;
import com.github.tangyi.common.core.vo.UserVo;
import com.github.tangyi.common.feign.config.CustomFeignConfig;
import com.github.tangyi.user.api.dto.UserDto;
import com.github.tangyi.user.api.dto.UserInfoDto;
import com.github.tangyi.user.api.feign.factory.UserServiceClientFallbackFactory;
import com.github.tangyi.user.api.module.Menu;
......@@ -38,6 +39,20 @@ public interface UserServiceClient {
UserVo findUserByIdentifier(@PathVariable("identifier") String identifier, @RequestParam("tenantCode") String tenantCode);
/**
* 根据用户名获取用户详细信息
*
* @param identifier identifier
* @param identityType identityType
* @param tenantCode 租户标识
* @return UserVo
* @author tangyi
* @date 2019/07/06 14:14:11
*/
@GetMapping("/v1/user/findUserByIdentifier/{identifier}")
UserVo findUserByIdentifier(@PathVariable("identifier") String identifier, @RequestParam(required = false) Integer identityType, @RequestParam("tenantCode") String tenantCode);
/**
* 获取当前用户的信息
*
* @return ResponseBean
......@@ -152,4 +167,26 @@ public interface UserServiceClient {
*/
@GetMapping("/v1/user/findUserBySocial/{social}")
UserVo findUserBySocial(@PathVariable("social") String social, @RequestParam("tenantCode") String tenantCode);
/**
* 注册用户
*
* @param userDto userDto
* @return ResponseBean
* @author tangyi
* @date 2019/07/05 20:57:31
*/
@PostMapping("/v1/user/register")
ResponseBean<Boolean> registerUser(@RequestBody UserDto userDto);
/**
* 更新用户
*
* @param userDto userDto
* @return ResponseBean
* @author tangyi
* @date 2019/07/05 20:59:06
*/
@PutMapping("/v1/user")
ResponseBean<Boolean> updateUser(UserDto userDto);
}
......@@ -5,6 +5,7 @@ import com.github.tangyi.common.core.model.ResponseBean;
import com.github.tangyi.common.core.vo.AttachmentVo;
import com.github.tangyi.common.core.vo.DeptVo;
import com.github.tangyi.common.core.vo.UserVo;
import com.github.tangyi.user.api.dto.UserDto;
import com.github.tangyi.user.api.dto.UserInfoDto;
import com.github.tangyi.user.api.feign.UserServiceClient;
import com.github.tangyi.user.api.module.Menu;
......@@ -43,6 +44,20 @@ public class UserServiceClientFallbackImpl implements UserServiceClient {
}
/**
* 根据用户名查询用户信息
*
* @param identifier identifier
* @param identityType identityType
* @param tenantCode 租户标识
* @return UserVo
*/
@Override
public UserVo findUserByIdentifier(String identifier, Integer identityType, String tenantCode) {
log.error("feign 查询用户信息失败:{}, {}, {}, {}", tenantCode, identityType, identifier, throwable);
return null;
}
/**
* 查询当前登录的用户信息
*
* @return ResponseBean
......@@ -175,6 +190,30 @@ public class UserServiceClientFallbackImpl implements UserServiceClient {
return null;
}
/**
* 注册用户
*
* @param userDto userDto
* @return ResponseBean
*/
@Override
public ResponseBean<Boolean> registerUser(UserDto userDto) {
log.error("feign 注册用户失败, {}, {}, {}", userDto.getIdentityType(), userDto.getIdentifier(), throwable);
return null;
}
/**
* 更新用户
*
* @param userDto userDto
* @return ResponseBean
*/
@Override
public ResponseBean<Boolean> updateUser(UserDto userDto) {
log.error("feign 更新用户失败, {}, {}, {}", userDto.getIdentityType(), userDto.getIdentifier(), throwable);
return null;
}
public Throwable getThrowable() {
return throwable;
}
......
package com.github.tangyi.user.api.module;
import com.github.tangyi.common.core.persistence.BaseEntity;
import com.github.tangyi.user.api.constant.AttachmentConstant;
import lombok.Data;
import javax.validation.constraints.NotBlank;
......@@ -45,7 +46,7 @@ public class Attachment extends BaseEntity<Attachment> {
/**
* 业务类型
*/
private String busiType;
private String busiType = AttachmentConstant.BUSI_TYPE_NORMAL_ATTACHMENT;
/**
* 业务模块
......
......@@ -73,4 +73,44 @@ public class User extends BaseEntity<User> {
* 角色
*/
private List<String> role;
/**
* 引导注册人
*/
private String parentUid;
/**
* 乡/镇
*/
private String streetId;
/**
* 县
*/
private String countyId;
/**
* 城市
*/
private String cityId;
/**
* 省份
*/
private String provinceId;
/**
* 最近登录时间
*/
private Date loginTime;
/**
* 用户归档时间
*/
private Date lockTime;
/**
* 微信
*/
private String wechat;
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment