增加一下类
- package com.zscq.framework.sms;
-
- import org.springframework.security.authentication.AuthenticationProvider;
- import org.springframework.security.core.Authentication;
- import org.springframework.security.core.AuthenticationException;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
-
- /**
- * 短信登陆鉴权 Provider,要求实现 AuthenticationProvider 接口
- *
- * @author gmk
- */
- public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
-
- private UserDetailsService userDetailsService;
-
- @Override
- public Authentication authenticate(Authentication authentication) throws AuthenticationException {
- SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;
-
- String telephone = (String) authenticationToken.getPrincipal();
-
- UserDetails userDetails = userDetailsService.loadUserByUsername(telephone);
-
- // 此时鉴权成功后,应当重新 new 一个拥有鉴权的 authenticationResult 返回
- SmsCodeAuthenticationToken authenticationResult = new SmsCodeAuthenticationToken(userDetails, userDetails.getAuthorities());
-
- authenticationResult.setDetails(authenticationToken.getDetails());
-
- return authenticationResult;
- }
-
-
- @Override
- public boolean supports(Class> authentication) {
- // 判断 authentication 是不是 SmsCodeAuthenticationToken 的子类或子接口
- return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
- }
-
- public UserDetailsService getUserDetailsService() {
- return userDetailsService;
- }
-
- public void setUserDetailsService(UserDetailsService userDetailsService) {
- this.userDetailsService = userDetailsService;
- }
- }
- package com.zscq.framework.sms;
-
- import org.springframework.security.authentication.AbstractAuthenticationToken;
- import org.springframework.security.core.GrantedAuthority;
- import org.springframework.security.core.SpringSecurityCoreVersion;
-
- import java.util.Collection;
-
- /**
- * 短信登录 AuthenticationToken,模仿 UsernamePasswordAuthenticationToken 实现
- *
- * @author gmk
- */
- public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
-
- private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
-
- /**
- * 在 UsernamePasswordAuthenticationToken 中该字段代表登录的用户名,
- * 在这里就代表登录的手机号码
- */
- private final Object principal;
-
- /**
- * 构建一个没有鉴权的 SmsCodeAuthenticationToken
- */
- public SmsCodeAuthenticationToken(Object principal) {
- super(null);
- this.principal = principal;
- setAuthenticated(false);
- }
-
- /**
- * 构建拥有鉴权的 SmsCodeAuthenticationToken
- */
- public SmsCodeAuthenticationToken(Object principal, Collection extends GrantedAuthority> authorities) {
- super(authorities);
- this.principal = principal;
- // must use super, as we override
- super.setAuthenticated(true);
- }
-
- @Override
- public Object getCredentials() {
- return null;
- }
-
- @Override
- public Object getPrincipal() {
- return this.principal;
- }
-
- @Override
- public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
- if (isAuthenticated) {
- throw new IllegalArgumentException(
- "Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
- }
-
- super.setAuthenticated(false);
- }
-
- @Override
- public void eraseCredentials() {
- super.eraseCredentials();
- }
-
- }
- package com.zscq.framework.sms;
-
- import com.zscq.common.core.domain.entity.SysUser;
- import com.zscq.common.core.domain.model.LoginUser;
- import com.zscq.common.enums.UserStatus;
- import com.zscq.common.exception.base.BaseException;
- import com.zscq.common.utils.StringUtils;
- import com.zscq.framework.web.service.SysPermissionService;
- import com.zscq.system.service.ISysUserService;
- import org.slf4j.Logger;
- import org.slf4j.LoggerFactory;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.core.userdetails.UsernameNotFoundException;
- import org.springframework.stereotype.Service;
-
- /**
- * 用户验证处理
- *
- * @author gmk
- */
- @Service("userDetailsByTelephoneServiceImpl")
- public class UserDetailsByTelephoneServiceImpl implements UserDetailsService {
- private static final Logger log = LoggerFactory.getLogger(UserDetailsByTelephoneServiceImpl.class);
-
- @Autowired
- private ISysUserService userService;
-
- @Autowired
- private SysPermissionService permissionService;
-
- @Override
- public UserDetails loadUserByUsername(String phone) throws UsernameNotFoundException {
- SysUser user = userService.selectUserByPhonenumber(phone);
- if (StringUtils.isNull(user)) {
- log.info("登录用户:{} 不存在.", phone);
- throw new UsernameNotFoundException("登录用户:" + phone + " 不存在");
- } else if (UserStatus.DELETED.getCode().equals(user.getDelFlag())) {
- log.info("登录用户:{} 已被删除.", phone);
- throw new BaseException("对不起,您的账号:" + phone + " 已被删除");
- } else if (UserStatus.DISABLE.getCode().equals(user.getStatus())) {
- log.info("登录用户:{} 已被停用.", phone);
- throw new BaseException("对不起,您的账号:" + phone + " 已停用");
- }
-
- return createLoginUser(user);
- }
-
- public UserDetails createLoginUser(SysUser user) {
- return new LoginUser(user, permissionService.getMenuPermission(user));
- }
- }
SecurityConfig.java 修改



- package com.zscq.framework.config;
-
- import com.zscq.framework.sms.SmsCodeAuthenticationProvider;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
- import org.springframework.context.annotation.Bean;
- import org.springframework.http.HttpMethod;
- import org.springframework.security.authentication.AuthenticationManager;
- import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
- import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
- import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
- import org.springframework.security.config.http.SessionCreationPolicy;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
- import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
- import org.springframework.security.web.authentication.logout.LogoutFilter;
- import org.springframework.web.filter.CorsFilter;
- import com.zscq.framework.config.properties.PermitAllUrlProperties;
- import com.zscq.framework.security.filter.JwtAuthenticationTokenFilter;
- import com.zscq.framework.security.handle.AuthenticationEntryPointImpl;
- import com.zscq.framework.security.handle.LogoutSuccessHandlerImpl;
-
- import javax.annotation.Resource;
-
- /**
- * spring security配置
- *
- * @author ruoyi
- */
- @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
- public class SecurityConfig extends WebSecurityConfigurerAdapter
- {
- /**
- * 自定义用户(账号密码)认证逻辑
- */
- @Autowired
- @Qualifier("userDetailsServiceImpl")
- private UserDetailsService userDetailsService;
-
- /**
- * 自定义用户(手机号验证码)认证逻辑
- */
- @Autowired
- @Qualifier("userDetailsByTelephoneServiceImpl")
- private UserDetailsService userDetailsServiceByTelephone;
-
-
- /**
- * 认证失败处理类
- */
- @Autowired
- private AuthenticationEntryPointImpl unauthorizedHandler;
-
- /**
- * 退出处理类
- */
- @Autowired
- private LogoutSuccessHandlerImpl logoutSuccessHandler;
-
- /**
- * token认证过滤器
- */
- @Autowired
- private JwtAuthenticationTokenFilter authenticationTokenFilter;
-
- /**
- * 跨域过滤器
- */
- @Autowired
- private CorsFilter corsFilter;
-
- /**
- * 允许匿名访问的地址
- */
- @Autowired
- private PermitAllUrlProperties permitAllUrl;
-
- /**
- * 解决 无法直接注入 AuthenticationManager
- *
- * @return
- * @throws Exception
- */
- @Bean
- @Override
- public AuthenticationManager authenticationManagerBean() throws Exception
- {
- return super.authenticationManagerBean();
- }
-
- /**
- * anyRequest | 匹配所有请求路径
- * access | SpringEl表达式结果为true时可以访问
- * anonymous | 匿名可以访问
- * denyAll | 用户不能访问
- * fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
- * hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
- * hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
- * hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
- * hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
- * hasRole | 如果有参数,参数表示角色,则其角色可以访问
- * permitAll | 用户可以任意访问
- * rememberMe | 允许通过remember-me登录的用户访问
- * authenticated | 用户登录后可访问
- */
- @Override
- protected void configure(HttpSecurity httpSecurity) throws Exception
- {
- // 注解标记允许匿名访问的url
- ExpressionUrlAuthorizationConfigurer
.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests(); - permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
-
- SmsCodeAuthenticationProvider smsCodeAuthenticationProvider = new SmsCodeAuthenticationProvider();
- smsCodeAuthenticationProvider.setUserDetailsService(userDetailsServiceByTelephone);
-
- httpSecurity
- // CSRF禁用,因为不使用session
- .csrf().disable()
- // 认证失败处理类
- .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
- // 基于token,所以不需要session
- .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
- // 过滤请求
- .authorizeRequests()
- // 对于登录login 注册register 验证码captchaImage 短信验证码请求 允许匿名访问
- .antMatchers("/login","/loginByPhone", "/register", "/captchaImage","/sendSms").anonymous()
- // 静态资源,可匿名访问
- .antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
- .antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
- // 除上面外的所有请求全部需要鉴权认证
- .anyRequest().authenticated()
- .and()
- .headers().frameOptions().disable();
- // 添加Logout filter
- httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
- // 添加JWT filter
- httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
- // 添加CORS filter
- httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
- httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class)//手机验证码的provider
- .authenticationProvider(smsCodeAuthenticationProvider);
- }
-
- /**
- * 强散列哈希加密实现
- */
- @Bean
- public BCryptPasswordEncoder bCryptPasswordEncoder()
- {
- return new BCryptPasswordEncoder();
- }
-
- /**
- * 身份认证接口
- */
- @Override
- protected void configure(AuthenticationManagerBuilder auth) throws Exception
- {
- auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
- }
- }
SysLoginController.java增加
- /**
- * 登录方法,手机号登录
- *
- * @param loginBody 登录信息
- * @return 结果
- */
- @PostMapping("/loginByPhone")
- public AjaxResult loginByPhone(@RequestBody LoginBody loginBody)
- {
- AjaxResult ajax = AjaxResult.success();
- // 生成令牌
- String token = loginService.loginByPhone(loginBody.getPhonenumber(),loginBody.getCode(),
- loginBody.getUuid());
- ajax.put(Constants.TOKEN, token);
- return ajax;
- }
SysLoginService.java增加
- /**
- * 登录验证
- *
- * @param phonenumber 手机号
- * @param code 验证码
- * @param uuid 唯一标识
- * @return 结果
- */
- public String loginByPhone(String phonenumber,String code, String uuid)
- {
- // 校验手机验证码
- checkMessageCaptcha(code, uuid);
- /// 用户验证
- Authentication authentication = null;
- try {
- // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
- authentication = authenticationManager
- .authenticate(new SmsCodeAuthenticationToken(phonenumber));
- } catch (Exception e) {
- if (e instanceof BadCredentialsException) {
- AsyncManager.me().execute(AsyncFactory.recordLogininfor(phonenumber, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
- throw new UserPasswordNotMatchException();
- } else {
- AsyncManager.me().execute(AsyncFactory.recordLogininfor(phonenumber, Constants.LOGIN_FAIL, e.getMessage()));
- throw new ServiceException(e.getMessage());
- }
- }
- AsyncManager.me().execute(AsyncFactory.recordLogininfor(phonenumber, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
- LoginUser loginUser = (LoginUser) authentication.getPrincipal();
- // 生成token
- return tokenService.createToken(loginUser);
- }
-
- /**
- * 校验短信验证码
- * @param code
- * @param uuid
- */
- private void checkMessageCaptcha(String code, String uuid) {
- // 校验短信验证码是否正确
- String verifyKey = CacheConstants.SMS_CODE_KEY + uuid;
- String captcha = redisCache.getCacheObject(verifyKey);
-
- if (!code.equalsIgnoreCase(captcha))
- {
- throw new ServiceException("验证码不正确");
- }
- }