diff --git a/doc/SECURITY.md b/doc/SECURITY.md index 0ad013c..8eeee24 100644 --- a/doc/SECURITY.md +++ b/doc/SECURITY.md @@ -1,5 +1,7 @@ -Security框架封装集成登录 使用指南 +Security框架封装集成多方登录 使用指南 ------------- +#### 先上[效果图](Securtity效果图.md) + 单独使用例子工程:[https://github.com/yexuejc/springboot-security-login-simple](https://github.com/yexuejc/springboot-security-login-simple) * 本项目依赖不向下传递 @@ -15,7 +17,8 @@ Security框架封装集成登录 使用指南 ``` > **相关文件说明** 所有核心文件都在 com.yexuejc.springboot.base.security 包下 - +#### 现附上系统实现逻辑图 +![多方登录系统实现逻辑图](多方登录设计.jpg) 1.com.yexuejc.springboot.base.security.SecurityConfig
@@ -26,6 +29,38 @@ Security框架封装集成登录 使用指南 * 继承configure(HttpSecurity http) 完善更多security过滤配置 * 例子[com.yexuejc.springboot.base.security.MySecurityConfig](../yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/MySecurityConfig.java) +#### 注: 代码中抛出的相关异常拦截在filter.setAuthenticationFailureHandler()中处理,参考[MySecurityConfig](../yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/MySecurityConfig.java) +``` +filter.setAuthenticationFailureHandler((request, response, exception) -> { + response.setContentType("application/json;charset=UTF-8"); + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + Resps resps = new Resps(); + if (exception instanceof DisabledException) { + resps.setErr(RespsConsts.CODE_FAIL, new String[]{BizConsts.BASE_IS_LOCK_MSG}); + } else if (exception instanceof AccountExpiredException) { + resps.setErr(RespsConsts.CODE_FAIL, new String[]{BizConsts.BASE_IS_EXPIRE_MSG}); + } else if (exception instanceof CredentialsExpiredException) { + resps.setErr(BizConsts.BASE_LOGIN_IS_EXPIRE_CODE, new String[]{BizConsts.BASE_LOGIN_IS_EXPIRE_MSG}); + } else if (exception instanceof LockedException) { + resps.setErr(RespsConsts.CODE_FAIL, new String[]{BizConsts.BASE_IS_LOCKED_MSG}); + } else if (exception instanceof AuthenticationCredentialsNotFoundException) { + resps.setErr(RespsConsts.CODE_FAIL, new String[]{BizConsts.BASE_CREDENTIALS_NOT_FOUND_MSG}); + } else if (exception instanceof ThirdPartyAuthorizationException) { + resps.setErr(RespsConsts.CODE_FAIL, new String[]{exception.getMessage()}); + } else if (exception instanceof BadCredentialsException) { + resps.setErr(RespsConsts.CODE_FAIL, new String[]{BizConsts.BASE_PWD_IS_ERR_MSG}); + } else if (exception instanceof UsernameNotFoundException) { + resps.setErr(RespsConsts.CODE_FAIL, new String[]{BizConsts.BASE_ACCOUNT_NOT_FOUND_MSG}); + } else if (exception instanceof UserNotAuthoriayException) { + resps.setErr(RespsConsts.CODE_FAIL, new String[]{exception.getMessage()}); + } else { + resps.setErr(RespsConsts.CODE_FAIL, new String[]{BizConsts.BASE_SYS_ERR_MSG}); + } + response.getWriter().write(JsonUtil.obj2Json(resps)); + response.getWriter().close(); + }); +``` + 2.com.yexuejc.springboot.base.security.UserDetailsManager
**获取登录用户信息** diff --git a/doc/Securtity效果图.md b/doc/Securtity效果图.md new file mode 100644 index 0000000..dcdf1af --- /dev/null +++ b/doc/Securtity效果图.md @@ -0,0 +1,24 @@ +Security 多方登录封装使用效果图 +--------------- +### 账号登录 +密码错误 +![](sl/sl_02.png) +正确 +![](sl/sl_01.png) + +### 短信登录 +发送短信 +![](sl/sl_ss.png) +短信错误 +![](sl/sl_err.jpg) +短信过期 +![](sl/sl_gq.png) +正确 +![](sl/sl_10.png) + + +### 第三方登录 +第一次登录,需要绑定手机号 +![](sl/sl_t3.png) +绑定过手机号的第三方账号登录(绑定相关业务需要自己实现) +![](sl/sl_t4.png) \ No newline at end of file diff --git a/doc/sl/sl_01.png b/doc/sl/sl_01.png new file mode 100644 index 0000000..4301520 Binary files /dev/null and b/doc/sl/sl_01.png differ diff --git a/doc/sl/sl_02.png b/doc/sl/sl_02.png new file mode 100644 index 0000000..6325819 Binary files /dev/null and b/doc/sl/sl_02.png differ diff --git a/doc/sl/sl_10.png b/doc/sl/sl_10.png new file mode 100644 index 0000000..f77936a Binary files /dev/null and b/doc/sl/sl_10.png differ diff --git a/doc/sl/sl_err.jpg b/doc/sl/sl_err.jpg new file mode 100644 index 0000000..1910cf0 Binary files /dev/null and b/doc/sl/sl_err.jpg differ diff --git a/doc/sl/sl_gq.png b/doc/sl/sl_gq.png new file mode 100644 index 0000000..9079cd5 Binary files /dev/null and b/doc/sl/sl_gq.png differ diff --git a/doc/sl/sl_ss.png b/doc/sl/sl_ss.png new file mode 100644 index 0000000..842a277 Binary files /dev/null and b/doc/sl/sl_ss.png differ diff --git a/doc/sl/sl_t3.png b/doc/sl/sl_t3.png new file mode 100644 index 0000000..e9ffd05 Binary files /dev/null and b/doc/sl/sl_t3.png differ diff --git a/doc/sl/sl_t4.png b/doc/sl/sl_t4.png new file mode 100644 index 0000000..850d1d8 Binary files /dev/null and b/doc/sl/sl_t4.png differ diff --git a/doc/多方登录设计.jpg b/doc/多方登录设计.jpg new file mode 100644 index 0000000..9f116ab Binary files /dev/null and b/doc/多方登录设计.jpg differ diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/exception/ClassConvertExeption.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/exception/ClassConvertExeption.java new file mode 100644 index 0000000..50c6df2 --- /dev/null +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/exception/ClassConvertExeption.java @@ -0,0 +1,24 @@ +package com.yexuejc.springboot.base.exception; + +/** + * 类型转换异常 + * + * @author: maxf + * @date: 2018/5/12 18:36 + */ +public class ClassConvertExeption extends RuntimeException { + + private static final long serialVersionUID = -2390195902982826130L; + /** + * 错误码 + */ + private String code = "E"; + + public ClassConvertExeption() { + super("类型转换异常"); + } + + public ClassConvertExeption(String message) { + super(message); + } +} diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ParamsRequestBodyAdvice.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ParamsRequestBodyAdvice.java index 8c9225b..ba6b606 100644 --- a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ParamsRequestBodyAdvice.java +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ParamsRequestBodyAdvice.java @@ -8,6 +8,7 @@ import com.yexuejc.base.util.StrUtil; import com.yexuejc.springboot.base.exception.GatewayException; import com.yexuejc.springboot.base.util.LogUtil; import org.apache.commons.io.IOUtils; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.core.MethodParameter; import org.springframework.http.HttpHeaders; @@ -32,6 +33,7 @@ import java.security.interfaces.RSAPrivateKey; * @date: 2018/5/12 22:49 */ @ControllerAdvice +@ConditionalOnClass({RequestBodyAdvice.class, HttpHeaders.class, HttpInputMessage.class, HttpMessageConverter.class}) @EnableConfigurationProperties(RsaProperties.class) public class ParamsRequestBodyAdvice implements RequestBodyAdvice { diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ParamsResponseBodyAdvice.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ParamsResponseBodyAdvice.java index e8b6655..4933958 100644 --- a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ParamsResponseBodyAdvice.java +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/filter/ParamsResponseBodyAdvice.java @@ -6,6 +6,7 @@ import com.yexuejc.base.http.Resps; import com.yexuejc.base.util.JsonUtil; import com.yexuejc.base.util.StrUtil; import com.yexuejc.springboot.base.util.LogUtil; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.core.MethodParameter; import org.springframework.http.MediaType; @@ -29,6 +30,7 @@ import java.util.Map; * @date: 2018/5/12 22:50 */ @ControllerAdvice +@ConditionalOnClass({ResponseBodyAdvice.class, ServerHttpRequest.class, ServerHttpResponse.class, MediaType.class}) @EnableConfigurationProperties(RsaProperties.class) public class ParamsResponseBodyAdvice implements ResponseBodyAdvice { diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/ConsumerAuthenticationProcessingFilter.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/ConsumerAuthenticationProcessingFilter.java index eed2a24..27c37ec 100644 --- a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/ConsumerAuthenticationProcessingFilter.java +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/ConsumerAuthenticationProcessingFilter.java @@ -20,37 +20,37 @@ public class ConsumerAuthenticationProcessingFilter extends AbstractAuthenticati // ~ Static fields/initializers // ===================================================================================== - public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; - public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; + protected static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; + protected static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; /** * 登录方式 */ - public static final String SPRING_SECURITY_FORM_LOGTYPE_KEY = "logtype"; - public static final String SPRING_SECURITY_FORM_OPENID_KEY = "openid"; + protected static final String SPRING_SECURITY_FORM_LOGTYPE_KEY = "logtype"; + protected static final String SPRING_SECURITY_FORM_OPENID_KEY = "openid"; /********************************** 第三方登录时附带信息*************************************/ /** * 头像 */ - public static final String SPRING_SECURITY_FORM_HEAD_KEY = "head"; + protected static final String SPRING_SECURITY_FORM_HEAD_KEY = "head"; /** * 昵称 */ - public static final String SPRING_SECURITY_FORM_NICKNAME_KEY = "nickname"; + protected static final String SPRING_SECURITY_FORM_NICKNAME_KEY = "nickname"; /** * 性别 */ - public static final String SPRING_SECURITY_FORM_SEX_KEY = "sex"; + protected static final String SPRING_SECURITY_FORM_SEX_KEY = "sex"; /********************************** 第三方登录时附带信息*************************************/ - private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY; - private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY; - private String logtypeParameter = SPRING_SECURITY_FORM_LOGTYPE_KEY; - private String openidParameter = SPRING_SECURITY_FORM_OPENID_KEY; - private String headParameter = SPRING_SECURITY_FORM_HEAD_KEY; - private String nicknameParameter = SPRING_SECURITY_FORM_NICKNAME_KEY; - private String sexParameter = SPRING_SECURITY_FORM_SEX_KEY; - private boolean postOnly = true; - private boolean reverse = true; + protected String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY; + protected String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY; + protected String logtypeParameter = SPRING_SECURITY_FORM_LOGTYPE_KEY; + protected String openidParameter = SPRING_SECURITY_FORM_OPENID_KEY; + protected String headParameter = SPRING_SECURITY_FORM_HEAD_KEY; + protected String nicknameParameter = SPRING_SECURITY_FORM_NICKNAME_KEY; + protected String sexParameter = SPRING_SECURITY_FORM_SEX_KEY; + protected boolean postOnly = true; + protected boolean reverse = true; // ~ Constructors // =================================================================================================== diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/ConsumerAuthenticationProvider.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/ConsumerAuthenticationProvider.java index 09494a1..7b78577 100644 --- a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/ConsumerAuthenticationProvider.java +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/ConsumerAuthenticationProvider.java @@ -4,6 +4,7 @@ import com.yexuejc.base.pojo.ApiVO; import com.yexuejc.base.util.StrUtil; import com.yexuejc.springboot.base.constant.BizConsts; import com.yexuejc.springboot.base.constant.LogTypeConsts; +import com.yexuejc.springboot.base.exception.ClassConvertExeption; import com.yexuejc.springboot.base.exception.ThirdPartyAuthorizationException; import com.yexuejc.springboot.base.security.inte.User; import com.yexuejc.springboot.base.security.inte.UserService; @@ -50,12 +51,12 @@ public class ConsumerAuthenticationProvider extends AbstractUserDetailsAuthentic * PasswordEncoder#matches(CharSequence, String)} on when the user is * not found to avoid SEC-2056. */ - private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword"; + protected static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword"; // ~ Instance fields // ================================================================================================ - private PasswordEncoder passwordEncoder; + protected PasswordEncoder passwordEncoder; /** * The password used to perform @@ -64,10 +65,10 @@ public class ConsumerAuthenticationProvider extends AbstractUserDetailsAuthentic * {@link PasswordEncoder} implementations will short circuit if the password is not * in a valid format. */ - private volatile String userNotFoundEncodedPassword; + protected volatile String userNotFoundEncodedPassword; - private UserDetailsService userDetailsService; - private final UserService accountView; + protected UserDetailsService userDetailsService; + protected final UserService accountView; public ConsumerAuthenticationProvider(UserDetailsService userDetailsService, UserService accountView) { @@ -177,46 +178,14 @@ public class ConsumerAuthenticationProvider extends AbstractUserDetailsAuthentic throw notFound; } else { try { - //其他方式登录:查询账号 没有->创建账号 - //第三方登录 - if (consumerToken != null && StrUtil.isNotEmpty(consumerToken.getOpenid())) { - ApiVO apiVO = accountView.checkOpenId(consumerToken); - if (apiVO.isSucc()) { - //已有账号 - User consumer = apiVO.getObject1(User.class); - // 处理用户权限 - List authorities = new ArrayList<>(); - for (String role : consumer.getRoles()) { - authorities.add(new SimpleGrantedAuthority(role)); - } - loadedUser = new ConsumerUser( - StrUtil.isEmpty(consumer.getMobile()) ? consumerToken.getOpenid() : consumer.getMobile(), - consumer.getPwd(), consumer.getEnable(), consumer.getNonExpire(), - true, consumer.getNonLock(), authorities, consumer.getConsumerId(), - logtype, System.currentTimeMillis()); - return loadedUser; - } - } - //第三方登录+短信登录 - if (consumerToken != null) { - //没有->创建账号 - consumerToken.isReg = true; - ApiVO apiVO = accountView.addConsumer(consumerToken); - if (apiVO.isSucc()) { - loadedUser = display(consumerToken, apiVO.getObject1(User.class)); - return loadedUser; - } else { - throw new ThirdPartyAuthorizationException(apiVO.getMsg()); - } - } + return third(consumerToken, loadedUser, logtype); } catch (Exception e) { e.printStackTrace(); if (e instanceof ThirdPartyAuthorizationException) { throw e; } - throw new ThirdPartyAuthorizationException("登录失败,请稍后重试"); + throw new ThirdPartyAuthorizationException(e.getMessage()); } - throw notFound; } } catch (Exception repositoryProblem) { throw new InternalAuthenticationServiceException( @@ -229,13 +198,78 @@ public class ConsumerAuthenticationProvider extends AbstractUserDetailsAuthentic return loadedUser; } - private void prepareTimingAttackProtection() { + /** + * 第三方登录处理=>登录用户为空,此方法处理返回登录用户 + * + * @param consumerToken 登录信息 + * @param loadedUser 登录用户(为空时进入此方法) + * @param logtype 登录方式 + * @return 登录用户 + */ + protected UserDetails third(ConsumerToken consumerToken, UserDetails loadedUser, String logtype) { + //其他方式登录:查询账号 没有->创建账号 + //第三方登录 + if (consumerToken != null && StrUtil.isNotEmpty(consumerToken.getOpenid())) { + Object obj = accountView.checkOpenId(consumerToken); + if (obj != null) { + + //已有账号 + if (obj instanceof User) { + User consumer = (User) obj; + // 处理用户权限 + List authorities = new ArrayList<>(); + for (String role : consumer.getRoles()) { + authorities.add(new SimpleGrantedAuthority(role)); + } + loadedUser = new ConsumerUser( + StrUtil.isEmpty(consumer.getMobile()) ? consumerToken.getOpenid() : consumer.getMobile(), + consumer.getPwd(), consumer.getEnable(), consumer.getNonExpire(), + true, consumer.getNonLock(), authorities, consumer.getConsumerId(), + logtype, System.currentTimeMillis()); + return loadedUser; + } else if (obj instanceof UserDetails) { + loadedUser = (UserDetails) obj; + return loadedUser; + } else { + throw new ClassConvertExeption("获取登录用户信息返回结果类型必须是com.yexuejc.springboot.base.security.inte.User实现类" + + "或者org.springframework.security.core.userdetails.UserDetails实现类" + + "或者com.yexuejc.springboot.base.security.ConsumerUser继承类"); + } + } + } + //第三方登录+短信登录 + if (consumerToken != null) { + //没有->创建账号 + consumerToken.isReg = true; + Object obj = accountView.addConsumer(consumerToken); + if (obj != null) { + if (obj instanceof User) { + User consumer = (User) obj; + loadedUser = display(consumerToken, consumer); + return loadedUser; + } else if (obj instanceof UserDetails) { + loadedUser = (UserDetails) obj; + return loadedUser; + } else { + throw new ClassConvertExeption("获取登录用户信息返回结果类型必须是com.yexuejc.springboot.base.security.inte.User实现类" + + "或者org.springframework.security.core.userdetails.UserDetails实现类" + + "或者com.yexuejc.springboot.base.security.ConsumerUser继承类"); + } + } else { + throw new ThirdPartyAuthorizationException("第三方登录失败"); + } + } else { + throw new ThirdPartyAuthorizationException(); + } + } + + protected void prepareTimingAttackProtection() { if (this.userNotFoundEncodedPassword == null) { this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD); } } - private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) { + protected void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) { if (authentication.getCredentials() != null) { String presentedPassword = authentication.getCredentials().toString(); this.passwordEncoder.matches(presentedPassword, "{MD5}" + this.userNotFoundEncodedPassword); @@ -249,7 +283,7 @@ public class ConsumerAuthenticationProvider extends AbstractUserDetailsAuthentic * @param consumer 实际用户信息 * @return response User */ - private UserDetails display(ConsumerToken consumerToken, User consumer) { + protected UserDetails display(ConsumerToken consumerToken, User consumer) { // 处理用户权限 List authorities = new ArrayList<>(); for (String role : consumer.getRoles()) { diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/ConsumerSecurityContextRepository.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/ConsumerSecurityContextRepository.java index 43d8782..6f751a6 100644 --- a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/ConsumerSecurityContextRepository.java +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/ConsumerSecurityContextRepository.java @@ -34,10 +34,10 @@ import java.util.Map; */ public class ConsumerSecurityContextRepository implements SecurityContextRepository { protected final Log logger = LogFactory.getLog(this.getClass()); - private static final String TOKEN = "token"; - private static final String ROLES = "roles"; + protected static final String TOKEN = "token"; + protected static final String ROLES = "roles"; - private final RedisTemplate redisTemplate0; + protected final RedisTemplate redisTemplate0; public ConsumerSecurityContextRepository(RedisTemplate redisTemplate0) { this.redisTemplate0 = redisTemplate0; diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/SecurityConfig.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/SecurityConfig.java index 4b2c4d8..bbede7b 100644 --- a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/SecurityConfig.java +++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/SecurityConfig.java @@ -1,12 +1,10 @@ package com.yexuejc.springboot.base.security; -import com.yexuejc.springboot.base.autoconfigure.MutiRedisAutoConfiguration; import com.yexuejc.springboot.base.security.inte.UserService; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.config.BeanIds; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -17,6 +15,7 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.NoOpPasswordEncoder; import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.context.SecurityContextRepository; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; @@ -39,10 +38,6 @@ public abstract class SecurityConfig extends WebSecurityConfigurerAdapter { return super.authenticationManagerBean(); } - @Autowired - @Qualifier(MutiRedisAutoConfiguration.BEAN_REDIS_TEMPLATE0) - private RedisTemplate redisTemplate0; - @Bean public static NoOpPasswordEncoder passwordEncoder() { @@ -58,22 +53,51 @@ public abstract class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { - UserDetailsService userDetailsService = new UserDetailsManager(getUserService()); - auth.authenticationProvider(new ConsumerAuthenticationProvider(userDetailsService, getUserService())); + UserDetailsService userDetailsService = createUserDetailsManager(); + AuthenticationProvider authenticationProvider = createConsumerAuthenticationProvider(userDetailsService); + auth.authenticationProvider(authenticationProvider); auth.userDetailsService(userDetailsService); } + /** + * 初始化 AuthenticationProvider + * + * @param userDetailsService + * @return + */ + protected AuthenticationProvider createConsumerAuthenticationProvider(UserDetailsService userDetailsService) { + return new ConsumerAuthenticationProvider(userDetailsService, getUserService()); + } + + /** + * 初始化 UserDetailsService + * + * @return + */ + protected UserDetailsService createUserDetailsManager() { + return new UserDetailsManager(getUserService()); + } + @Bean public ConsumerAuthenticationProcessingFilter consumerAuthenticationProcessingFilter( AuthenticationManager authenticationManager) throws Exception { - ConsumerAuthenticationProcessingFilter filter = new ConsumerAuthenticationProcessingFilter - (authenticationManager); + ConsumerAuthenticationProcessingFilter filter = createConsumerAuthenticationProcessingFilter(authenticationManager); filter.setAuthenticationManager(this.authenticationManager()); loginHodler(filter); return filter; } + /** + * 初始化 ConsumerAuthenticationProcessingFilter + * + * @param authenticationManager + * @return + */ + protected ConsumerAuthenticationProcessingFilter createConsumerAuthenticationProcessingFilter(AuthenticationManager authenticationManager) { + return new ConsumerAuthenticationProcessingFilter(authenticationManager); + } + /** *
      * 处理登录
@@ -88,8 +112,7 @@ public abstract class SecurityConfig extends WebSecurityConfigurerAdapter {
 
     @Bean
     public LoginUrlAuthenticationEntryPoint loginUrlAuthenticationEntryPoint() {
-        LoginUrlAuthenticationEntryPoint loginUrlAuthenticationEntryPoint = new LoginUrlAuthenticationEntryPoint
-                ("/login");
+        LoginUrlAuthenticationEntryPoint loginUrlAuthenticationEntryPoint = new LoginUrlAuthenticationEntryPoint("/login");
         return loginUrlAuthenticationEntryPoint;
     }
 
@@ -134,7 +157,7 @@ public abstract class SecurityConfig extends WebSecurityConfigurerAdapter {
                 .cors()
                 .and().servletApi().disable()
                 .requestCache().disable()
-                .securityContext().securityContextRepository(new ConsumerSecurityContextRepository(redisTemplate0))
+                .securityContext().securityContextRepository(createConsumerSecurityContextRepository())
                 .and()
                 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
 
@@ -142,6 +165,22 @@ public abstract class SecurityConfig extends WebSecurityConfigurerAdapter {
                 UsernamePasswordAuthenticationFilter.class);
     }
 
+    /**
+     * 创建 SecurityContextRepository
+     *
+     * @return
+     */
+    protected SecurityContextRepository createConsumerSecurityContextRepository() {
+        return new ConsumerSecurityContextRepository(getRedisDB());
+    }
+
+    /**
+     * 初始化放置用户信息的reids库
+     *
+     * @return
+     */
+    protected abstract RedisTemplate getRedisDB();
+
 
     @Override
     public void configure(WebSecurity web) throws Exception {
diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/UserDetailsManager.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/UserDetailsManager.java
index 9f89b4a..42a563f 100644
--- a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/UserDetailsManager.java
+++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/UserDetailsManager.java
@@ -1,6 +1,7 @@
 package com.yexuejc.springboot.base.security;
 
 import com.yexuejc.base.util.StrUtil;
+import com.yexuejc.springboot.base.exception.ClassConvertExeption;
 import com.yexuejc.springboot.base.exception.UserNotAuthoriayException;
 import com.yexuejc.springboot.base.security.inte.User;
 import com.yexuejc.springboot.base.security.inte.UserService;
@@ -23,7 +24,7 @@ import java.util.List;
  */
 public class UserDetailsManager extends InMemoryUserDetailsManager {
 
-    private final UserService userService;
+    protected final UserService userService;
 
     public UserDetailsManager(UserService userService) {
         this.userService = userService;
@@ -31,22 +32,34 @@ public class UserDetailsManager extends InMemoryUserDetailsManager {
 
     @Override
     public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
-        User consumer = userService.getConsumerByUserName(username);
-        if (StrUtil.isEmpty(consumer)) {
-            throw new UsernameNotFoundException(username);
+        return loadUser(username);
+    }
+
+    protected ConsumerUser loadUser(String username) throws UsernameNotFoundException{
+        Object user = userService.getConsumerByUserName(username);
+        if (user instanceof User) {
+            User consumer = (User) user;
+            if (StrUtil.isEmpty(consumer)) {
+                throw new UsernameNotFoundException(username);
+            }
+            // 处理用户权限
+            List authorities = new ArrayList<>();
+            if (StrUtil.isEmpty(consumer.getRoles())) {
+                throw new UserNotAuthoriayException("用户" + username + "缺少权限");
+            }
+            for (String role : consumer.getRoles()) {
+                authorities.add(new SimpleGrantedAuthority(role));
+            }
+            ConsumerUser consumerUser = new ConsumerUser(consumer.getMobile(), consumer.getPwd(),
+                    consumer.getEnable(), consumer.getNonExpire(), true, consumer.getNonLock(),
+                    authorities, consumer.getConsumerId(), null, System.currentTimeMillis());
+            return consumerUser;
+        } else if (user instanceof ConsumerUser) {
+            return (ConsumerUser) user;
+        } else {
+            throw new ClassConvertExeption("获取登录用户信息返回结果类型必须是com.yexuejc.springboot.base.security.inte.User实现类" +
+                    "或者com.yexuejc.springboot.base.security.ConsumerUser继承类");
         }
-        // 处理用户权限
-        List authorities = new ArrayList<>();
-        if (StrUtil.isEmpty(consumer.getRoles())) {
-            throw new UserNotAuthoriayException("用户" + username + "缺少权限");
-        }
-        for (String role : consumer.getRoles()) {
-            authorities.add(new SimpleGrantedAuthority(role));
-        }
-        ConsumerUser consumerUser = new ConsumerUser(consumer.getMobile(), consumer.getPwd(),
-                consumer.getEnable(), consumer.getNonExpire(), true, consumer.getNonLock(),
-                authorities, consumer.getConsumerId(), null, System.currentTimeMillis());
-        return consumerUser;
     }
 
 }
diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/inte/User.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/inte/User.java
index c0aba9a..a3baddb 100644
--- a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/inte/User.java
+++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/inte/User.java
@@ -3,6 +3,8 @@ package com.yexuejc.springboot.base.security.inte;
 import java.util.List;
 
 /**
+ * 登录用户接口
+ *
  * @author maxf
  * @version 1.0
  * @ClassName User
diff --git a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/inte/UserService.java b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/inte/UserService.java
index 599f78f..57bb6f9 100644
--- a/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/inte/UserService.java
+++ b/yexuejc-springboot-base/src/main/java/com/yexuejc/springboot/base/security/inte/UserService.java
@@ -2,9 +2,13 @@ package com.yexuejc.springboot.base.security.inte;
 
 import com.yexuejc.base.pojo.ApiVO;
 import com.yexuejc.springboot.base.constant.BizConsts;
+import com.yexuejc.springboot.base.exception.UserNotAuthoriayException;
 import com.yexuejc.springboot.base.security.ConsumerToken;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
 
 /**
+ * 用户登录处理接口
+ *
  * @author maxf
  * @version 1.0
  * @ClassName UserService
@@ -14,11 +18,18 @@ import com.yexuejc.springboot.base.security.ConsumerToken;
 public interface UserService {
     /**
      * 根据用户名到数据库查询用户
+     * 

+ *

+ *

+ * 获取登录用户信息返回结果类型必须是 + * {@link com.yexuejc.springboot.base.security.inte.User}实现类
+ * 或者{@link com.yexuejc.springboot.base.security.ConsumerUser}继承类 + *

* * @param username 登录账号 * @return */ - User getConsumerByUserName(String username); + Object getConsumerByUserName(String username) throws UserNotAuthoriayException, UsernameNotFoundException; /** * 校验短信验证码=>短信登录 @@ -32,17 +43,29 @@ public interface UserService { /** * 校验第三方登录openid + *

+ * 获取登录用户信息返回结果类型必须是 + * {@link com.yexuejc.springboot.base.security.inte.User}实现类
+ * 或者{@link org.springframework.security.core.userdetails.UserDetails}实现类
+ * 或者{@link com.yexuejc.springboot.base.security.ConsumerUser}继承类 + *

* * @param consumerToken 登录信息 - * @return apiVO.setObject1(User.class) 自己封装登录用户信息 + * @return 自己封装登录用户信息 */ - ApiVO checkOpenId(ConsumerToken consumerToken); + Object checkOpenId(ConsumerToken consumerToken); /** * 没有账号时根据登录信息创建账号 + *

+ * 获取登录用户信息返回结果类型必须是 + * {@link com.yexuejc.springboot.base.security.inte.User}实现类
+ * 或者{@link org.springframework.security.core.userdetails.UserDetails}实现类
+ * 或者{@link com.yexuejc.springboot.base.security.ConsumerUser}继承类 + *

* * @param consumerToken 登录信息 - * @return + * @return 自己封装登录用户信息 */ - ApiVO addConsumer(ConsumerToken consumerToken); + Object addConsumer(ConsumerToken consumerToken); } diff --git a/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/MySecurityConfig.java b/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/MySecurityConfig.java index 1cfb026..c59006f 100644 --- a/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/MySecurityConfig.java +++ b/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/MySecurityConfig.java @@ -47,6 +47,11 @@ public class MySecurityConfig extends SecurityConfig { @Qualifier(MutiRedisAutoConfiguration.BEAN_REDIS_TEMPLATE0) private RedisTemplate redisTemplate0; + @Override + protected RedisTemplate getRedisDB() { + return redisTemplate0; + } + /** * 保存登录信息至redis * @@ -184,4 +189,5 @@ public class MySecurityConfig extends SecurityConfig { response.getWriter().close(); }); } + } diff --git a/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/UserServiceImpl.java b/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/UserServiceImpl.java index 22a82eb..d8b7623 100644 --- a/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/UserServiceImpl.java +++ b/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/UserServiceImpl.java @@ -13,11 +13,13 @@ import com.yexuejc.springboot.base.constant.LogTypeConsts; import com.yexuejc.springboot.base.exception.ThirdPartyAuthorizationException; import com.yexuejc.springboot.base.mapper.ConsumerMapper; import com.yexuejc.springboot.base.security.domain.Consumer; -import com.yexuejc.springboot.base.security.inte.User; import com.yexuejc.springboot.base.security.inte.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import java.util.ArrayList; @@ -48,14 +50,37 @@ public class UserServiceImpl implements UserService { * @return */ @Override - public User getConsumerByUserName(String username) { + public Object getConsumerByUserName(String username) { + if (StrUtil.isEmpty(username)) { + throw new UsernameNotFoundException("username为空,一般是第三方登录来的,直接抛出UsernameNotFoundException就是"); + } QueryWrapper queryWrapper = new QueryWrapper(); queryWrapper.eq("mobile", username); Consumer consumer = consumerMapper.selectOne(queryWrapper); - ArrayList roles = new ArrayList<>(); + if (null == consumer) { + /** + * 1.抛出UsernameNotFoundException这个异常如果是第三方登录会走 {@link #checkOpenId(ConsumerToken)} + * 2.抛出其他Exception可以自己到{@link MySecurityConfig#loginHodler(ConsumerAuthenticationProcessingFilter)} + * 里面的filter.setAuthenticationFailureHandler()中做特殊处理 + */ + throw new UsernameNotFoundException("没有该账号相关信息"); + } + //h2不支持json,人为处理角色 + ArrayList roles = new ArrayList<>(); roles.add("ROLE_CONSUMER"); consumer.setRoles(roles); - return consumer; + //1.consumer为User的实现类 +// return consumer; + + //2. 自己创建ConsumerUser,直接返回 + List authorities = new ArrayList<>(); + for (String role : consumer.getRoles()) { + authorities.add(new SimpleGrantedAuthority(role)); + } + ConsumerUser consumerUser = new ConsumerUser(consumer.getMobile(), consumer.getPwd(), + consumer.getEnable(), consumer.getNonExpire(), true, consumer.getNonLock(), + authorities, consumer.getConsumerId(), null, System.currentTimeMillis()); + return consumerUser; } /** @@ -88,15 +113,14 @@ public class UserServiceImpl implements UserService { } /** + * 第三方登录 * 校验openid 根据自己业务做判断 - *
- * 返回:封装登录用户信息到 apiVO.setObject1(User.class) 自己封装登录用户信息 * * @param consumerToken 登录信息 * @return */ @Override - public ApiVO checkOpenId(ConsumerToken consumerToken) { + public Object checkOpenId(ConsumerToken consumerToken) { ApiVO apiVO = new ApiVO(ApiVO.STATUS.F, "没有找到用户信息"); switch (consumerToken.getLogtype()) { case LogTypeConsts.QQ: @@ -111,9 +135,89 @@ public class UserServiceImpl implements UserService { default: break; } - return apiVO; + if (apiVO.isFail()) { + /** + * 未查到: + * 1.返回null会走(数据库没有这个openid[第三方账号]信息)新增流程 {@link #addConsumer(ConsumerToken)} + * 2.也可以自己创建一个带有特殊标识的ConsumerUser,然后在 {@link MySecurityConfig#loginHodler(ConsumerAuthenticationProcessingFilter)} + * 里面的filter.setAuthenticationSuccessHandler()中做特殊处理 ps:假装登录成功 :) + */ + return null; + } + //h2不支持json,人为处理角色 + ArrayList roles = new ArrayList<>(); + roles.add("ROLE_CONSUMER"); + apiVO.getObject1(Consumer.class).setRoles(roles); + //根据openid到数据库查到consumer返回 + return apiVO.getObject1(Consumer.class); } + /** + * {@link #checkOpenId(ConsumerToken)} 返回null会走该方法 + * 没有账号时处理自己的业务,此处必须返回 构造出的登录用户,否则会抛出{@link ThirdPartyAuthorizationException 第三方授权异常} + *
+ * + * @param consumerToken 登录信息 + * @return + */ + @Override + public Object addConsumer(ConsumerToken consumerToken) { + Consumer consumer = new Consumer(); + consumer.setConsumerId(StrUtil.genUUID()); + consumer.setMobile(StrUtil.isNotEmpty(consumerToken.getUsername()) ? consumerToken.getUsername() : consumerToken.getOpenid()); + consumer.setPwd(StrUtil.toMD5("123456")); + consumer.setEnable(true); + consumer.setNonExpire(true); + consumer.setNonLock(true); + List roles = new ArrayList<>(); + roles.add("ROLE_CONSUMER"); + consumer.setRoles(roles); + switch (consumerToken.getLogtype()) { + case LogTypeConsts.SMS: + ApiVO apiVO = checkSmsCode2Redis(BizConsts.CONSUMER_LOGIN_SMS, consumerToken.getUsername(), + consumerToken.getSmscode()); + if (apiVO.isFail()) { + throw new ThirdPartyAuthorizationException("短信验证码错误"); + } + consumer.setNickname(consumerToken.getUsername()); + consumer.setHead("/head/def.png"); + consumer.setRegType(DictRegTypeConsts.DICT_MOBILE); + break; + case LogTypeConsts.QQ: + consumer.setQqId(consumerToken.getOpenid()); + consumer.setNickname(consumerToken.getNickname()); + setHeader(consumerToken, consumer, false); + setSex(consumerToken, consumer); + consumer.setRegType(DictRegTypeConsts.DICT_QQ); + break; + case LogTypeConsts.WECHAT: + consumer.setWechatId(consumerToken.getOpenid()); + consumer.setNickname(consumerToken.getNickname()); + setHeader(consumerToken, consumer, false); + setSex(consumerToken, consumer); + consumer.setRegType(DictRegTypeConsts.DICT_WECHAT); + break; + case LogTypeConsts.WEIBO: + consumer.setWeiboId(consumerToken.getOpenid()); + consumer.setNickname(consumerToken.getNickname()); + setHeader(consumerToken, consumer, false); + setSex(consumerToken, consumer); + consumer.setRegType(DictRegTypeConsts.DICT_WEIBO); + break; + default: + throw new ThirdPartyAuthorizationException("暂不支持该第三方授权"); + } + Integer result = consumerMapper.insert(consumer); + if (result < 1) { + /** + * 会抛出{@link ThirdPartyAuthorizationException 第三方授权异常} + */ + return null; + } + return consumer; + } + + /** * 第三方登录 QQ登录 * @@ -173,69 +277,6 @@ public class UserServiceImpl implements UserService { } - /** - * 没有账号时处理自己的业务,此次必须返回 构造出的登录用户,否则会抛出{@link ThirdPartyAuthorizationException 第三方授权异常} - *
- * 返回:封装登录用户信息到 apiVO.setObject1(User.class) 自己封装登录用户信息 - * - * @param consumerToken 登录信息 - * @return - */ - @Override - public ApiVO addConsumer(ConsumerToken consumerToken) { - Consumer consumer = new Consumer(); - consumer.setConsumerId(StrUtil.genUUID()); - consumer.setMobile(StrUtil.isNotEmpty(consumerToken.getUsername()) ? consumerToken.getUsername() : consumerToken.getOpenid()); - consumer.setPwd(StrUtil.toMD5("123456")); - consumer.setEnable(true); - consumer.setNonExpire(true); - consumer.setNonLock(true); - List roles = new ArrayList<>(); - roles.add("ROLE_CONSUMER"); - consumer.setRoles(roles); - switch (consumerToken.getLogtype()) { - case LogTypeConsts.SMS: - ApiVO apiVO = checkSmsCode2Redis(BizConsts.CONSUMER_LOGIN_SMS, consumerToken.getUsername(), - consumerToken.getSmscode()); - if (apiVO.isFail()) { - return apiVO; - } - consumer.setNickname(consumerToken.getUsername()); - consumer.setHead("/head/def.png"); - consumer.setRegType(DictRegTypeConsts.DICT_MOBILE); - break; - case LogTypeConsts.QQ: - consumer.setQqId(consumerToken.getOpenid()); - consumer.setNickname(consumerToken.getNickname()); - setHeader(consumerToken, consumer, false); - setSex(consumerToken, consumer); - consumer.setRegType(DictRegTypeConsts.DICT_QQ); - break; - case LogTypeConsts.WECHAT: - consumer.setWechatId(consumerToken.getOpenid()); - consumer.setNickname(consumerToken.getNickname()); - setHeader(consumerToken, consumer, false); - setSex(consumerToken, consumer); - consumer.setRegType(DictRegTypeConsts.DICT_WECHAT); - break; - case LogTypeConsts.WEIBO: - consumer.setWeiboId(consumerToken.getOpenid()); - consumer.setNickname(consumerToken.getNickname()); - setHeader(consumerToken, consumer, false); - setSex(consumerToken, consumer); - consumer.setRegType(DictRegTypeConsts.DICT_WEIBO); - break; - default: - return new ApiVO(ApiVO.STATUS.F, "暂不支持的登录方式"); - } - Integer result = consumerMapper.insert(consumer); - if (result < 1) { - return new ApiVO(ApiVO.STATUS.F, RespsConsts.CODE_FAIL, "登录失败"); - } - return new ApiVO(ApiVO.STATUS.S).setObject1(consumer); - } - - public Consumer getConsumerByQQOpenid(String openid) { QueryWrapper queryWrapper = new QueryWrapper<>(); queryWrapper.eq("qq_id", openid); diff --git a/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/domain/Consumer.java b/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/domain/Consumer.java index 5ab3da0..bf7d8d2 100644 --- a/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/domain/Consumer.java +++ b/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/security/domain/Consumer.java @@ -238,6 +238,7 @@ public class Consumer extends Model implements User { return this; } + @Override public List getRoles() { return roles; } diff --git a/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/web/SecurityCtrl.java b/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/web/SecurityCtrl.java index 8f5ea6f..549faf9 100644 --- a/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/web/SecurityCtrl.java +++ b/yexuejc-springboot-base/src/test/java/com/yexuejc/springboot/base/web/SecurityCtrl.java @@ -1,7 +1,23 @@ package com.yexuejc.springboot.base.web; +import com.yexuejc.base.http.Resps; +import com.yexuejc.base.pojo.ApiVO; +import com.yexuejc.base.util.RegexUtil; +import com.yexuejc.base.util.StrUtil; +import com.yexuejc.springboot.base.autoconfigure.MutiRedisAutoConfiguration; +import com.yexuejc.springboot.base.constant.BizConsts; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.TimeUnit; + /** *
  * Security 登录注册相关controller
@@ -19,5 +35,49 @@ import org.springframework.web.bind.annotation.RestController;
 @RestController
 public class SecurityCtrl {
 
+    @Autowired
+    @Qualifier(MutiRedisAutoConfiguration.BEAN_REDIS_TEMPLATE1)
+    RedisTemplate redisTemplate;
+
+    /**
+     * 登录发送短信
+     *
+     * @param mobile
+     * @return
+     */
+    @RequestMapping(value = "/login/{mobile}", method = RequestMethod.POST)
+    public Resps login(@PathVariable String mobile) {
+        if (!RegexUtil.regex(mobile, RegexUtil.REGEX_MOBILE)) {
+            return Resps.fail("手机号不正确");
+        }
+        ApiVO apiVO = sendSmsCode(BizConsts.CONSUMER_LOGIN_SMS, mobile);
+        if (apiVO.isFail()) {
+            return Resps.fail(apiVO.getMsg());
+        }
+        return Resps.success(apiVO.getMsg());
+    }
+
+    private ApiVO sendSmsCode(String smsType, String mobile) {
+        String smsId = StrUtil.genUUID(30);
+        String code = StrUtil.genNum().substring(2, 8);
+        //自己接入短信发送
+        boolean result = true;
+        if (result) {
+            //成功
+            //存reids
+            Map map = new HashMap<>();
+            map.put("smsId", smsId);
+            map.put("code", code);
+            map.put("trade_id", "短信返回id");
+            map.put("validatedNums", 0);
+            redisTemplate.afterPropertiesSet();
+            redisTemplate.opsForHash().putAll(smsType + "." + mobile, map);
+            // 过期时间:5分钟
+            redisTemplate.expire(smsType + "." + mobile, 5 * 60, TimeUnit.SECONDS);
+            return new ApiVO(ApiVO.STATUS.S, "短信发送成功");
+        } else {
+            return new ApiVO(ApiVO.STATUS.F, "短信发送失败");
+        }
+    }
 
 }