测试环境配置及带租房的登录功能

This commit is contained in:
2026-07-06 09:28:20 +08:00
parent 7c319a80a4
commit c43915105c
40 changed files with 1975 additions and 468 deletions

View File

@@ -0,0 +1,131 @@
package com.zf.foodpurchase.framework.config;
import cn.hutool.core.lang.Assert;
import com.zf.foodpurchase.framework.service.ClientUserDetailsService;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
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.UserDetailsPasswordService;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
public class ClientLoginAuthProvider extends AbstractUserDetailsAuthenticationProvider {
private static final String USER_NOT_FOUND_PASSWORD = "userNotFoundPassword";
private PasswordEncoder passwordEncoder;
/**
* The password used to perform {@link PasswordEncoder#matches(CharSequence, String)}
* on when the user is not found to avoid SEC-2056. This is necessary, because some
* {@link PasswordEncoder} implementations will short circuit if the password is not
* in a valid format.
*/
private volatile String userNotFoundEncodedPassword;
private ClientUserDetailsService userDetailsService;
private UserDetailsPasswordService userDetailsPasswordService;
public ClientLoginAuthProvider(ClientUserDetailsService clientUserDetailsService) {
this.userDetailsService = clientUserDetailsService;
setPasswordEncoder(PasswordEncoderFactories.createDelegatingPasswordEncoder());
}
@Override
@SuppressWarnings("deprecation")
protected void additionalAuthenticationChecks(UserDetails userDetails,
UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
if (authentication.getCredentials() == null) {
this.logger.debug("Failed to authenticate since no credentials provided");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
String presentedPassword = authentication.getCredentials().toString();
if (!this.passwordEncoder.matches(presentedPassword, userDetails.getPassword())) {
this.logger.debug("Failed to authenticate since password does not match stored value");
throw new BadCredentialsException(this.messages
.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
}
}
@Override
protected void doAfterPropertiesSet() {
Assert.notNull(this.userDetailsService, "A UserDetailsService must be set");
}
@Override
protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
CustomUsernamePasswordAuthenticationToken auth = (CustomUsernamePasswordAuthenticationToken) authentication;
//多个参数
UserDetails loadedUser = userDetailsService.loadUserByUsername(username,auth.getTenantId());
if (loadedUser == null) {
throw new InternalAuthenticationServiceException(
"UserDetailsService returned null, which is an interface contract violation");
}
return loadedUser;
}
@Override
protected Authentication createSuccessAuthentication(Object principal, Authentication authentication,
UserDetails user) {
boolean upgradeEncoding = this.userDetailsPasswordService != null
&& this.passwordEncoder.upgradeEncoding(user.getPassword());
if (upgradeEncoding) {
String presentedPassword = authentication.getCredentials().toString();
String newPassword = this.passwordEncoder.encode(presentedPassword);
user = this.userDetailsPasswordService.updatePassword(user, newPassword);
}
return super.createSuccessAuthentication(principal, authentication, user);
}
private void prepareTimingAttackProtection() {
if (this.userNotFoundEncodedPassword == null) {
this.userNotFoundEncodedPassword = this.passwordEncoder.encode(USER_NOT_FOUND_PASSWORD);
}
}
private void mitigateAgainstTimingAttack(UsernamePasswordAuthenticationToken authentication) {
if (authentication.getCredentials() != null) {
String presentedPassword = authentication.getCredentials().toString();
this.passwordEncoder.matches(presentedPassword, this.userNotFoundEncodedPassword);
}
}
/**
* Sets the PasswordEncoder instance to be used to encode and validate passwords. If
* not set, the password will be compared using
* {@link PasswordEncoderFactories#createDelegatingPasswordEncoder()}
* @param passwordEncoder must be an instance of one of the {@code PasswordEncoder}
* types.
*/
public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
Assert.notNull(passwordEncoder, "passwordEncoder cannot be null");
this.passwordEncoder = passwordEncoder;
this.userNotFoundEncodedPassword = null;
}
protected PasswordEncoder getPasswordEncoder() {
return this.passwordEncoder;
}
public void setUserDetailsService(ClientUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
protected ClientUserDetailsService getUserDetailsService() {
return this.userDetailsService;
}
public void setUserDetailsPasswordService(UserDetailsPasswordService userDetailsPasswordService) {
this.userDetailsPasswordService = userDetailsPasswordService;
}
}

View File

@@ -0,0 +1,17 @@
package com.zf.foodpurchase.framework.config;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
public class CustomUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken {
private final String tenantId;
public CustomUsernamePasswordAuthenticationToken(Object principal, Object credentials, String tenantId) {
super(principal, credentials);
this.tenantId = tenantId;
}
public String getTenantId() {
return tenantId;
}
}

View File

@@ -1,5 +1,6 @@
package com.zf.foodpurchase.framework.config;
import com.zf.foodpurchase.framework.service.ClientUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
@@ -9,8 +10,12 @@ import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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;
@@ -23,28 +28,15 @@ import com.zf.foodpurchase.framework.security.filter.JwtAuthenticationTokenFilte
import com.zf.foodpurchase.framework.security.handle.AuthenticationEntryPointImpl;
import com.zf.foodpurchase.framework.security.handle.LogoutSuccessHandlerImpl;
import javax.annotation.Resource;
/**
* spring security配置
*
* @author ruoyi
*/
@EnableMethodSecurity(prePostEnabled = true, securedEnabled = true)
@Configuration
public class SecurityConfig
{
/**
* 自定义用户认证逻辑
*/
@Autowired
@Qualifier("userDetailsServiceImpl")
private UserDetailsService userDetailsService;
/**
* 自定义微信小程序/APP认证逻辑类
*/
@Autowired
@Qualifier("wxAppUserDetailsService")
private UserDetailsService wxAppUserDetailsService;
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 认证失败处理类
@@ -75,31 +67,22 @@ public class SecurityConfig
*/
@Autowired
private PermitAllUrlProperties permitAllUrl;
/**
* 身份验证实现
*/
@Bean("authenticationManager")
@Primary
public AuthenticationManager authenticationManager()
{
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(userDetailsService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
AuthenticationManager manager=new ProviderManager(daoAuthenticationProvider);
return manager;
@Resource
private ClientUserDetailsService clientUserDetailsService;
@Bean
public ClientLoginAuthProvider clientLoginAuthProvider(){
ClientLoginAuthProvider provider = new ClientLoginAuthProvider(clientUserDetailsService);
provider.setPasswordEncoder(bCryptPasswordEncoder());
return provider;
}
/**
* wxApp登录身份验证实现
*/
@Bean("wxAppAuthenticationManager")
public AuthenticationManager wxAppAuthenticationManager()
{
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService(wxAppUserDetailsService);
daoAuthenticationProvider.setPasswordEncoder(bCryptPasswordEncoder());
AuthenticationManager manager=new ProviderManager(daoAuthenticationProvider);
return manager;
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
@@ -117,49 +100,45 @@ public class SecurityConfig
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Bean
protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception
{
return httpSecurity
// CSRF禁用,因为不使用session
.csrf(csrf -> csrf.disable())
// 禁用HTTP响应标头
.headers((headersCustomizer) -> {
headersCustomizer.cacheControl(cache -> cache.disable()).frameOptions(options -> options.sameOrigin());
})
// 认证失败处理类
.exceptionHandling(exception -> exception.authenticationEntryPoint(unauthorizedHandler))
// 基于token,所以不需要session
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 注解标记允许匿名访问的url
.authorizeHttpRequests((requests) -> {
permitAllUrl.getUrls().forEach(url -> requests.antMatchers(url).permitAll());
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 注解标记允许匿名访问的url
ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry registry = httpSecurity.authorizeRequests();
permitAllUrl.getUrls().forEach(url -> registry.antMatchers(url).permitAll());
httpSecurity
// CSRF禁用,因为不使用session
.csrf().disable()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
requests.antMatchers("/login", "/register", "/captchaImage","/wx/user/mockLogin","/wx/user/login","/wxapp/userLogin/login").permitAll()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 微信静态资源访问不被拦截
.antMatchers("/wx/static/**","/wx/payApi/").permitAll()
// 部分接口开放,不受权限访问限制
.antMatchers("/wx/area/**").permitAll()
// 微信体验功能,部分接口权限放开
// 首页查询商品查询,轮播图查询,类别查询,按类别查询商品接口
.antMatchers("/wx/products/indexProducts","/wx/dict/type/product_category","/wx/products/category","/wx/carousel/list").permitAll()
// 临时放开权限校验,共开发测试使用
// .antMatchers("/wx/**").permitAll()
.antMatchers("/wx/payApi/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
})
// 添加Logout filter
.logout(logout -> logout.logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler))
// 添加JWT filter
.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class)
// 添加CORS filter
.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class)
.addFilterBefore(corsFilter, LogoutFilter.class)
.build();
.antMatchers("/login", "/register", "/captchaImage","/wx/user/mockLogin","/wx/user/login","/wxapp/userLogin/login").permitAll()
// 静态资源,可匿名访问
.antMatchers(HttpMethod.GET, "/", "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "/profile/**").permitAll()
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/webjars/**", "/*/api-docs", "/druid/**").permitAll()
// 微信静态资源访问不被拦截
.antMatchers("/wx/static/**","/wx/payApi/").permitAll()
// 部分接口开放,不受权限访问限制
.antMatchers("/wx/area/**").permitAll()
// 微信体验功能,部分接口权限放开
// 首页查询商品查询,轮播图查询,类别查询,按类别查询商品接口
.antMatchers("/wx/products/indexProducts","/wx/dict/type/product_category","/wx/products/category","/wx/carousel/list").permitAll()
// 临时放开权限校验,共开发测试使用
// .antMatchers("/wx/**").permitAll()
.antMatchers("/wx/payApi/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated();
// 添加Logout filter
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
}
/**
@@ -170,4 +149,11 @@ public class SecurityConfig
{
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication().passwordEncoder(bCryptPasswordEncoder());
//auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
auth.authenticationProvider(clientLoginAuthProvider());
}
}

View File

@@ -0,0 +1,9 @@
package com.zf.foodpurchase.framework.service;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
public interface ClientUserDetailsService {
UserDetails loadUserByUsername(String username, String tenantId) throws UsernameNotFoundException;
}

View File

@@ -0,0 +1,73 @@
package com.zf.foodpurchase.framework.service.impl;
import com.zf.foodpurchase.common.core.domain.entity.SysUser;
import com.zf.foodpurchase.common.core.domain.model.LoginUser;
import com.zf.foodpurchase.common.enums.UserStatus;
import com.zf.foodpurchase.common.exception.ServiceException;
import com.zf.foodpurchase.common.utils.StringUtils;
import com.zf.foodpurchase.framework.web.service.SysPasswordService;
import com.zf.foodpurchase.framework.web.service.SysPermissionService;
import com.zf.foodpurchase.system.mapper.SysDeptMapper;
import com.zf.foodpurchase.system.mapper.SysRoleDeptMapper;
import com.zf.foodpurchase.system.mapper.SysUserMapper;
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.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import com.zf.foodpurchase.framework.service.ClientUserDetailsService;
import javax.annotation.Resource;
@Service("clientUserDetailsService")
public class ClientUserDetailsServiceImpl implements ClientUserDetailsService {
private static final Logger log = LoggerFactory.getLogger(ClientUserDetailsServiceImpl.class);
@Resource
private SysUserMapper sysUserMapper;
@Autowired
private SysPasswordService passwordService;
@Autowired
private SysPermissionService permissionService;
@Autowired
private SysRoleDeptMapper roleDeptMapper;
@Autowired
private SysDeptMapper deptMapper;
@Override
public UserDetails loadUserByUsername(String username, String tenantId) throws UsernameNotFoundException {
// 此处即可自定义查询用户
SysUser user = sysUserMapper.selectUserByUserNameAndTenantId(username,tenantId);
if (StringUtils.isNull(user))
{
log.info("登录用户:{} 不存在.", username);
throw new ServiceException("登录用户:" + username + " 不存在");
}
else if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
log.info("登录用户:{} 已被删除.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
}
else if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
log.info("登录用户:{} 已被停用.", username);
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
}
passwordService.validate(user);
return createLoginUser(user);
}
public UserDetails createLoginUser(SysUser user)
{
return new LoginUser(user.getUserId(), user.getDeptId(), user, permissionService.getMenuPermission(user));
}
}

View File

@@ -1,8 +1,11 @@
package com.zf.foodpurchase.framework.web.service;
import cn.hutool.core.util.ObjectUtil;
import com.zf.foodpurchase.common.constant.CacheConstants;
import com.zf.foodpurchase.common.constant.Constants;
import com.zf.foodpurchase.common.constant.UserConstants;
import com.zf.foodpurchase.common.core.domain.AjaxResult;
import com.zf.foodpurchase.common.core.domain.entity.SysTenantVo;
import com.zf.foodpurchase.common.core.domain.entity.SysUser;
import com.zf.foodpurchase.common.core.domain.model.LoginUser;
import com.zf.foodpurchase.common.core.redis.RedisCache;
@@ -12,10 +15,12 @@ import com.zf.foodpurchase.common.utils.DateUtils;
import com.zf.foodpurchase.common.utils.MessageUtils;
import com.zf.foodpurchase.common.utils.StringUtils;
import com.zf.foodpurchase.common.utils.ip.IpUtils;
import com.zf.foodpurchase.framework.config.CustomUsernamePasswordAuthenticationToken;
import com.zf.foodpurchase.framework.manager.AsyncManager;
import com.zf.foodpurchase.framework.manager.factory.AsyncFactory;
import com.zf.foodpurchase.framework.security.context.AuthenticationContextHolder;
import com.zf.foodpurchase.system.service.ISysConfigService;
import com.zf.foodpurchase.system.service.ISysTenantService;
import com.zf.foodpurchase.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
@@ -26,6 +31,9 @@ import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/**
* 登录校验方法
@@ -39,7 +47,6 @@ public class SysLoginService
private TokenService tokenService;
@Resource
@Qualifier("authenticationManager")
private AuthenticationManager authenticationManager;
@Autowired
@@ -51,26 +58,39 @@ public class SysLoginService
@Autowired
private ISysConfigService configService;
@Autowired
private ISysTenantService tenantService;
/**
* 登录验证
*
* @param username 用户名
* @param password 密码
* @param code 验证码
* @param uuid 唯一标识
* @return 结果
*/
public String login(String username, String password, String code, String uuid)
public AjaxResult login(String tenantId, String username, String password)
{
// 验证码校验
validateCaptcha(username, code, uuid);
//validateCaptcha(username, code, uuid);
// 登录前置校验
loginPreCheck(username, password);
//loginPreCheck(username, password);
if (StringUtils.isBlank(tenantId)) {
return AjaxResult.error("请选择租户");
}
SysTenantVo tenant = tenantService.queryByTenantId(tenantId);
if (ObjectUtil.isNull(tenant)) {
return AjaxResult.error("租户不存在");
} else if ("1".equals(tenant.getStatus())) {
return AjaxResult.error("租户已停用");
} else if (ObjectUtil.isNotNull(tenant.getExpireTime())
&& new Date().after(tenant.getExpireTime())) {
return AjaxResult.error("租户已过期");
}
// 用户验证
Authentication authentication = null;
try
{
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
CustomUsernamePasswordAuthenticationToken authenticationToken = new CustomUsernamePasswordAuthenticationToken(username, password,tenantId);
AuthenticationContextHolder.setContext(authenticationToken);
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager.authenticate(authenticationToken);
@@ -96,7 +116,12 @@ public class SysLoginService
LoginUser loginUser = (LoginUser) authentication.getPrincipal();
recordLoginInfo(loginUser.getUserId());
// 生成token
return tokenService.createToken(loginUser);
String token = tokenService.createToken(loginUser);
Map<String, Object> map = new HashMap<String, Object>();
map.put(Constants.TOKEN, token);
map.put("userId",loginUser.getUserId());
return AjaxResult.success(map);
}
/**

View File

@@ -1,185 +0,0 @@
package com.zf.foodpurchase.framework.web.service;
import com.zf.foodpurchase.common.constant.CacheConstants;
import com.zf.foodpurchase.common.constant.Constants;
import com.zf.foodpurchase.common.constant.UserConstants;
import com.zf.foodpurchase.common.core.domain.entity.SysUser;
import com.zf.foodpurchase.common.core.domain.entity.WxAppUserVO;
import com.zf.foodpurchase.common.core.domain.model.LoginUser;
import com.zf.foodpurchase.common.core.domain.model.WxAppLoginBody;
import com.zf.foodpurchase.common.core.domain.model.WxAppLoginUser;
import com.zf.foodpurchase.common.core.redis.RedisCache;
import com.zf.foodpurchase.common.exception.ServiceException;
import com.zf.foodpurchase.common.exception.user.*;
import com.zf.foodpurchase.common.utils.DateUtils;
import com.zf.foodpurchase.common.utils.MessageUtils;
import com.zf.foodpurchase.common.utils.StringUtils;
import com.zf.foodpurchase.common.utils.ip.IpUtils;
import com.zf.foodpurchase.fdsafe.entity.WxAppUser;
import com.zf.foodpurchase.fdsafe.service.IWxAppUserService;
import com.zf.foodpurchase.framework.manager.AsyncManager;
import com.zf.foodpurchase.framework.manager.factory.AsyncFactory;
import com.zf.foodpurchase.framework.security.context.AuthenticationContextHolder;
import com.zf.foodpurchase.system.service.ISysConfigService;
import com.zf.foodpurchase.system.service.ISysUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 登录校验方法
*
* @author ruoyi
*/
@Component
public class WxAppLoginService
{
@Autowired
private WxAppTokenService tokenService;
@Autowired
@Qualifier("wxAppAuthenticationManager")
private AuthenticationManager authenticationManager;
@Autowired
private RedisCache redisCache;
@Autowired
private IWxAppUserService wxAppUserService;
@Autowired
private ISysConfigService configService;
/**
* 登录验证
*
* @param wxAppLoginBody 登录对象
* @return 结果
*/
public String login(WxAppLoginBody wxAppLoginBody)
{
String username = wxAppLoginBody.getUsername();
String password = wxAppLoginBody.getPassword();
// 验证码校验
// validateCaptcha(username, code, uuid);
// 登录前置校验
loginPreCheck(username, password);
// 用户验证
Authentication authentication = null;
try
{
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
AuthenticationContextHolder.setContext(authenticationToken);
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
authentication = authenticationManager.authenticate(authenticationToken);
}
catch (Exception e)
{
if (e instanceof BadCredentialsException)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
else
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
throw new ServiceException(e.getMessage());
}
}
finally
{
AuthenticationContextHolder.clearContext();
}
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
WxAppLoginUser loginUser = (WxAppLoginUser) authentication.getPrincipal();
recordLoginInfo(loginUser.getUserId());
// 生成token
return tokenService.createWxAppToken(loginUser);
}
/**
* 校验验证码
*
* @param username 用户名
* @param code 验证码
* @param uuid 唯一标识
* @return 结果
*/
public void validateCaptcha(String username, String code, String uuid)
{
boolean captchaEnabled = configService.selectCaptchaEnabled();
if (captchaEnabled)
{
String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
String captcha = redisCache.getCacheObject(verifyKey);
if (captcha == null)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
throw new CaptchaExpireException();
}
redisCache.deleteObject(verifyKey);
if (!code.equalsIgnoreCase(captcha))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
throw new CaptchaException();
}
}
}
/**
* 登录前置校验
* @param username 用户名
* @param password 用户密码
*/
public void loginPreCheck(String username, String password)
{
// 用户名或密码为空 错误
if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
throw new UserNotExistsException();
}
// 密码如果不在指定范围内 错误
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
// 用户名不在指定范围内 错误
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
throw new UserPasswordNotMatchException();
}
// IP黑名单校验
String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr()))
{
AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
throw new BlackListException();
}
}
/**
* 记录登录信息
*
* @param userId 用户ID
*/
public void recordLoginInfo(Long userId)
{
WxAppUser user = new WxAppUser();
user.setId(userId);
user.setLoginTime(DateUtils.getNowDate());
user.setLoginIp(IpUtils.getIpAddr());
user.setUpdateTime(DateUtils.getNowDate());
wxAppUserService.updateById(user);
}
}