• ruoyi 前后端分离 增加手机号登录


    增加一下类

    1. package com.zscq.framework.sms;
    2. import org.springframework.security.authentication.AuthenticationProvider;
    3. import org.springframework.security.core.Authentication;
    4. import org.springframework.security.core.AuthenticationException;
    5. import org.springframework.security.core.userdetails.UserDetails;
    6. import org.springframework.security.core.userdetails.UserDetailsService;
    7. /**
    8. * 短信登陆鉴权 Provider,要求实现 AuthenticationProvider 接口
    9. *
    10. * @author gmk
    11. */
    12. public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
    13. private UserDetailsService userDetailsService;
    14. @Override
    15. public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    16. SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;
    17. String telephone = (String) authenticationToken.getPrincipal();
    18. UserDetails userDetails = userDetailsService.loadUserByUsername(telephone);
    19. // 此时鉴权成功后,应当重新 new 一个拥有鉴权的 authenticationResult 返回
    20. SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities());
    21. authenticationResult.setDetails(authenticationToken.getDetails());
    22. return authenticationResult;
    23. }
    24. @Override
    25. public boolean supports(Class authentication) {
    26. // 判断 authentication 是不是 SmsCodeAuthenticationToken 的子类或子接口
    27. return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
    28. }
    29. public UserDetailsService getUserDetailsService() {
    30. return userDetailsService;
    31. }
    32. public void setUserDetailsService(UserDetailsService userDetailsService) {
    33. this.userDetailsService = userDetailsService;
    34. }
    35. }
    1. package com.zscq.framework.sms;
    2. import org.springframework.security.authentication.AbstractAuthenticationToken;
    3. import org.springframework.security.core.GrantedAuthority;
    4. import org.springframework.security.core.SpringSecurityCoreVersion;
    5. import java.util.Collection;
    6. /**
    7. * 短信登录 AuthenticationToken,模仿 UsernamePasswordAuthenticationToken 实现
    8. *
    9. * @author gmk
    10. */
    11. public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
    12. private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
    13. /**
    14. * 在 UsernamePasswordAuthenticationToken 中该字段代表登录的用户名,
    15. * 在这里就代表登录的手机号码
    16. */
    17. private final Object principal;
    18. /**
    19. * 构建一个没有鉴权的 SmsCodeAuthenticationToken
    20. */
    21. public SmsCodeAuthenticationToken(Object principal) {
    22. super(null);
    23. this.principal = principal;
    24. setAuthenticated(false);
    25. }
    26. /**
    27. * 构建拥有鉴权的 SmsCodeAuthenticationToken
    28. */
    29. public SmsCodeAuthenticationToken(Object principal, Collection authorities) {
    30. super(authorities);
    31. this.principal = principal;
    32. // must use super, as we override
    33. super.setAuthenticated(true);
    34. }
    35. @Override
    36. public Object getCredentials() {
    37. return null;
    38. }
    39. @Override
    40. public Object getPrincipal() {
    41. return this.principal;
    42. }
    43. @Override
    44. public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
    45. if (isAuthenticated) {
    46. throw new IllegalArgumentException(
    47. "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
    48. }
    49. super.setAuthenticated(false);
    50. }
    51. @Override
    52. public void eraseCredentials() {
    53. super.eraseCredentials();
    54. }
    55. }
    1. package com.zscq.framework.sms;
    2. import com.zscq.common.core.domain.entity.SysUser;
    3. import com.zscq.common.core.domain.model.LoginUser;
    4. import com.zscq.common.enums.UserStatus;
    5. import com.zscq.common.exception.base.BaseException;
    6. import com.zscq.common.utils.StringUtils;
    7. import com.zscq.framework.web.service.SysPermissionService;
    8. import com.zscq.system.service.ISysUserService;
    9. import org.slf4j.Logger;
    10. import org.slf4j.LoggerFactory;
    11. import org.springframework.beans.factory.annotation.Autowired;
    12. import org.springframework.security.core.userdetails.UserDetails;
    13. import org.springframework.security.core.userdetails.UserDetailsService;
    14. import org.springframework.security.core.userdetails.UsernameNotFoundException;
    15. import org.springframework.stereotype.Service;
    16. /**
    17. * 用户验证处理
    18. *
    19. * @author gmk
    20. */
    21. @Service("userDetailsByTelephoneServiceImpl")
    22. public class UserDetailsByTelephoneServiceImpl implements UserDetailsService {
    23. private static final Logger log = LoggerFactory.getLogger(UserDetailsByTelephoneServiceImpl.class);
    24. @Autowired
    25. private ISysUserService userService;
    26. @Autowired
    27. private SysPermissionService permissionService;
    28. @Override
    29. public UserDetails loadUserByUsername(String phone) throws UsernameNotFoundException {
    30. SysUser user = userService.selectUserByPhonenumber(phone);
    31. if (StringUtils.isNull(user)) {
    32. log.info("登录用户:{} 不存在.", phone);
    33. throw new UsernameNotFoundException("登录用户:" + phone + " 不存在");
    34. } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
    35. log.info("登录用户:{} 已被删除.", phone);
    36. throw new BaseException("对不起,您的账号:" + phone + " 已被删除");
    37. } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
    38. log.info("登录用户:{} 已被停用.", phone);
    39. throw new BaseException("对不起,您的账号:" + phone + " 已停用");
    40. }
    41. return createLoginUser(user);
    42. }
    43. public UserDetails createLoginUser(SysUser user) {
    44. return new LoginUser(user, permissionService.getMenuPermission(user));
    45. }
    46. }
    SecurityConfig.java 修改

     

     

    1. package com.zscq.framework.config;
    2. import com.zscq.framework.sms.SmsCodeAuthenticationProvider;
    3. import org.springframework.beans.factory.annotation.Autowired;
    4. import org.springframework.beans.factory.annotation.Qualifier;
    5. import org.springframework.context.annotation.Bean;
    6. import org.springframework.http.HttpMethod;
    7. import org.springframework.security.authentication.AuthenticationManager;
    8. import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    9. import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
    10. import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    11. import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    12. import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
    13. import org.springframework.security.config.http.SessionCreationPolicy;
    14. import org.springframework.security.core.userdetails.UserDetailsService;
    15. import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    16. import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    17. import org.springframework.security.web.authentication.logout.LogoutFilter;
    18. import org.springframework.web.filter.CorsFilter;
    19. import com.zscq.framework.config.properties.PermitAllUrlProperties;
    20. import com.zscq.framework.security.filter.JwtAuthenticationTokenFilter;
    21. import com.zscq.framework.security.handle.AuthenticationEntryPointImpl;
    22. import com.zscq.framework.security.handle.LogoutSuccessHandlerImpl;
    23. import javax.annotation.Resource;
    24. /**
    25. * spring security配置
    26. *
    27. * @author ruoyi
    28. */
    29. @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
    30. public class SecurityConfig extends WebSecurityConfigurerAdapter
    31. {
    32. /**
    33. * 自定义用户(账号密码)认证逻辑
    34. */
    35. @Autowired
    36. @Qualifier("userDetailsServiceImpl")
    37. private UserDetailsService userDetailsService;
    38. /**
    39. * 自定义用户(手机号验证码)认证逻辑
    40. */
    41. @Autowired
    42. @Qualifier("userDetailsByTelephoneServiceImpl")
    43. private UserDetailsService userDetailsServiceByTelephone;
    44. /**
    45. * 认证失败处理类
    46. */
    47. @Autowired
    48. private AuthenticationEntryPointImpl unauthorizedHandler;
    49. /**
    50. * 退出处理类
    51. */
    52. @Autowired
    53. private LogoutSuccessHandlerImpl logoutSuccessHandler;
    54. /**
    55. * token认证过滤器
    56. */
    57. @Autowired
    58. private JwtAuthenticationTokenFilter authenticationTokenFilter;
    59. /**
    60. * 跨域过滤器
    61. */
    62. @Autowired
    63. private CorsFilter corsFilter;
    64. /**
    65. * 允许匿名访问的地址
    66. */
    67. @Autowired
    68. private PermitAllUrlProperties permitAllUrl;
    69. /**
    70. * 解决 无法直接注入 AuthenticationManager
    71. *
    72. * @return
    73. * @throws Exception
    74. */
    75. @Bean
    76. @Override
    77. public AuthenticationManager authenticationManagerBean() throws Exception
    78. {
    79. return super.authenticationManagerBean();
    80. }
    81. /**
    82. * anyRequest | 匹配所有请求路径
    83. * access | SpringEl表达式结果为true时可以访问
    84. * anonymous | 匿名可以访问
    85. * denyAll | 用户不能访问
    86. * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
    87. * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
    88. * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
    89. * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
    90. * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
    91. * hasRole | 如果有参数,参数表示角色,则其角色可以访问
    92. * permitAll | 用户可以任意访问
    93. * rememberMe | 允许通过remember-me登录的用户访问
    94. * authenticated | 用户登录后可访问
    95. */
    96. @Override
    97. protected void configure(HttpSecurity httpSecurity) throws Exception
    98. {
    99. // 注解标记允许匿名访问的url
    100. ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
    101. permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
    102. SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
    103. smsCodeAuthenticationProvider.setUserDetailsService(userDetailsServiceByTelephone);
    104. httpSecurity
    105. // CSRF禁用,因为不使用session
    106. .csrf().disable()
    107. // 认证失败处理类
    108. .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
    109. // 基于token,所以不需要session
    110. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
    111. // 过滤请求
    112. .authorizeRequests()
    113. // 对于登录login 注册register 验证码captchaImage 短信验证码请求 允许匿名访问
    114. .antMatchers("/login","/loginByPhone", "/register", "/captchaImage","/sendSms").anonymous()
    115. // 静态资源,可匿名访问
    116. .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
    117. .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
    118. // 除上面外的所有请求全部需要鉴权认证
    119. .anyRequest().authenticated()
    120. .and()
    121. .headers().frameOptions().disable();
    122. // 添加Logout filter
    123. httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
    124. // 添加JWT filter
    125. httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    126. // 添加CORS filter
    127. httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
    128. httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class)//手机验证码的provider
    129. .authenticationProvider(smsCodeAuthenticationProvider);
    130. }
    131. /**
    132. * 强散列哈希加密实现
    133. */
    134. @Bean
    135. public BCryptPasswordEncoder bCryptPasswordEncoder()
    136. {
    137. return new BCryptPasswordEncoder();
    138. }
    139. /**
    140. * 身份认证接口
    141. */
    142. @Override
    143. protected void configure(AuthenticationManagerBuilder auth) throws Exception
    144. {
    145. auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    146. }
    147. }
    SysLoginController.java增加 
    1. /**
    2. * 登录方法,手机号登录
    3. *
    4. * @param loginBody 登录信息
    5. * @return 结果
    6. */
    7. @PostMapping("/loginByPhone")
    8. public AjaxResult loginByPhone(@RequestBody LoginBody loginBody)
    9. {
    10. AjaxResult ajax = AjaxResult.success();
    11. // 生成令牌
    12. String token = loginService.loginByPhone(loginBody.getPhonenumber(),loginBody.getCode(),
    13. loginBody.getUuid());
    14. ajax.put(Constants.TOKEN, token);
    15. return ajax;
    16. }
    SysLoginService.java增加 
    1. /**
    2. * 登录验证
    3. *
    4. * @param phonenumber 手机号
    5. * @param code 验证码
    6. * @param uuid 唯一标识
    7. * @return 结果
    8. */
    9. public String loginByPhone(String phonenumber,String code, String uuid)
    10. {
    11. // 校验手机验证码
    12. checkMessageCaptcha(code, uuid);
    13. /// 用户验证
    14. Authentication authentication = null;
    15. try {
    16. // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
    17. authentication = authenticationManager
    18. .authenticate(new SmsCodeAuthenticationToken(phonenumber));
    19. } catch (Exception e) {
    20. if (e instanceof BadCredentialsException) {
    21. AsyncManager.me().execute(AsyncFactory.recordLogininfor(phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
    22. throw new UserPasswordNotMatchException();
    23. } else {
    24. AsyncManager.me().execute(AsyncFactory.recordLogininfor(phonenumber, Constants.LOGIN_FAIL, e.getMessage()));
    25. throw new ServiceException(e.getMessage());
    26. }
    27. }
    28. AsyncManager.me().execute(AsyncFactory.recordLogininfor(phonenumber, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
    29. LoginUser loginUser = (LoginUser) authentication.getPrincipal();
    30. // 生成token
    31. return tokenService.createToken(loginUser);
    32. }
    33. /**
    34. * 校验短信验证码
    35. * @param code
    36. * @param uuid
    37. */
    38. private void checkMessageCaptcha(String code, String uuid) {
    39. // 校验短信验证码是否正确
    40. String verifyKey = CacheConstants.SMS_CODE_KEY + uuid;
    41. String captcha = redisCache.getCacheObject(verifyKey);
    42. if (!code.equalsIgnoreCase(captcha))
    43. {
    44. throw new ServiceException("验证码不正确");
    45. }
    46. }

  • 相关阅读:
    容器化|自建 MySQL 集群迁移到 Kubernetes
    重磅!这本SSCI期刊已解除On Hold状态!警惕目前6本SCIE/ESCI期刊被标记!
    【软件分析第12讲-学习笔记】可满足性模理论 Satisfiability Modulo Theories
    [NewStarCTF 2023 公开赛道]R!C!E!
    医疗机器人技术研究现状
    rocketmq-console-1.0.0启动报错
    为什么大模型计算的时候只会利用KVcache来存放KV矩阵,Q矩阵每次不一样?
    [贪心算法]忍者道具
    深入理解 python 虚拟机:花里胡哨的魔术方法
    想要成为CSS大师?这些技巧是你必须知道的!
  • 原文地址:https://blog.csdn.net/qq_29384639/article/details/128081547