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 包下 - +#### 现附上系统实现逻辑图 + 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 多方登录封装使用效果图 +--------------- +### 账号登录 +密码错误 + +正确 + + +### 短信登录 +发送短信 + +短信错误 + +短信过期 + +正确 + + + +### 第三方登录 +第一次登录,需要绑定手机号 + +绑定过手机号的第三方账号登录(绑定相关业务需要自己实现) + \ 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, "短信发送失败"); + } + } }
* 处理登录 @@ -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, "短信发送失败"); + } + } }
+ *
+ * 获取登录用户信息返回结果类型必须是 + * {@link com.yexuejc.springboot.base.security.inte.User}实现类 + * 或者{@link com.yexuejc.springboot.base.security.ConsumerUser}继承类 + *
+ * 获取登录用户信息返回结果类型必须是 + * {@link com.yexuejc.springboot.base.security.inte.User}实现类 + * 或者{@link org.springframework.security.core.userdetails.UserDetails}实现类 + * 或者{@link com.yexuejc.springboot.base.security.ConsumerUser}继承类 + *
* 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, "短信发送失败"); + } + } }