搜索代码库中关于LoginUserVo和UserDetails的使用情况,了解为什么要实现UserDetails接口
**已检索仓库** *LoginUserVo UserDetails usage spring security* 20 个结果
实现UserDetails接口的原因主要有以下几点:
1. **集成Spring Security安全框架**:通过实现UserDetails接口,可以无缝集成Spring Security,利用其强大的认证和授权机制。
2. **标准化用户信息**:UserDetails是Spring Security定义的用户信息标准接口,实现它可以确保用户信息结构符合安全框架要求。
3. **灵活的权限控制**:通过重写getAuthorities()方法,可以根据用户角色动态返回权限列表,实现细粒度的访问控制。
4. **统一认证流程**:结合UserDetailsService使用,可以统一管理用户认证流程,便于维护和扩展。
5. **利用框架特性**:可以使用Spring Security提供的各种安全特性,如会话管理、CSRF保护、记住我等功能。
这种设计遵循了面向接口编程的原则,使系统更加灵活、可扩展,并且能够充分利用Spring Security框架提供的安全机制。
查看项目结构,了解Spring Security相关模块的分布
根据搜索结果,CRMEB项目中Spring Security相关的模块主要包括:
1. **安全配置类**
- `WebSecurityConfig.java` - 核心安全配置类,配置了认证、授权、过滤器链等
2. **认证相关组件**
- `
UserDetailServiceImpl.java` - 用户详情服务,实现UserDetailsService接口
- `
CustomAuthenticationProvider.java` - 自定义认证提供者
- `LoginUserVo.java` - 登录用户信息封装类,实现UserDetails接口
3. **过滤器组件**
- `
JwtAuthenticationTokenFilter.java` - JWT认证过滤器
- `TokenComponent.java` - Token管理组件
4. **异常处理组件**
- `
AuthenticationEntryPointImpl.java` - 认证入口点,处理认证失败
- `
CustomAccessDeniedHandler.java` - 访问拒绝处理器
5. **其他辅助类**
- `
CusAuthenticationManager.java` - 自定义认证管理器
这些组件共同构成了完整的Spring Security认证和授权体系,通过JWT实现无状态认证,支持基于角色的访问控制(RBAC)。
这段代码定义了一个[SystemAdminLoginRequest](file://E:\workplace\crmeb_java-1.4\crmeb\crmeb-common\src\main\java\com\zbkj\common\request\
SystemAdminLoginRequest.java#L15-L39)类,用于封装后台管理员登录请求的数据。主要功能包括:
1. 包含管理员账号([account](file://E:\workplace\crmeb_java-1.4\crmeb\crmeb-common\src\main\java\com\zbkj\common\model\system\SystemAdmin.java#L48-L49))和密码([pwd](file://E:\workplace\crmeb_java-1.4\crmeb\crmeb-common\src\main\java\com\zbkj\common\model\system\SystemAdmin.java#L57-L58))字段,均使用注解进行数据校验
2. 集成行为验证码([captchaVO](file://E:\workplace\crmeb_java-1.4\crmeb\crmeb-common\src\main\java\com\zbkj\common\request\
SystemAdminLoginRequest.java#L37-L38))用于安全验证
3. 使用Swagger注解提供API文档说明和示例值
该类主要用于后台管理系统的登录接口参数接收和校验。
登陆代码
package com.zbkj.admin.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.anji.captcha.model.common.ResponseModel;
import com.zbkj.admin.filter.TokenComponent;
import com.zbkj.admin.service.AdminLoginService;
import com.zbkj.common.constants.Constants;
import com.zbkj.common.constants.SysConfigConstants;
import com.zbkj.common.constants.SysGroupDataConstants;
import com.zbkj.common.exception.CrmebException;
import com.zbkj.common.model.system.SystemAdmin;
import com.zbkj.common.model.system.SystemMenu;
import com.zbkj.common.model.system.SystemPermissions;
import com.zbkj.common.request.LoginAdminUpdatePasswordRequest;
import com.zbkj.common.request.LoginAdminUpdateRequest;
import com.zbkj.common.request.SystemAdminLoginRequest;
import com.zbkj.common.response.MenusResponse;
import com.zbkj.common.response.SystemAdminResponse;
import com.zbkj.common.response.SystemGroupDataAdminLoginBannerResponse;
import com.zbkj.common.response.SystemLoginResponse;
import com.zbkj.common.result.CommonResultCode;
import com.zbkj.common.utils.CrmebUtil;
import com.zbkj.common.utils.RedisUtil;
import com.zbkj.common.utils.SecurityUtil;
import com.zbkj.common.vo.LoginUserVo;
import com.zbkj.common.vo.MenuTree;
import com.zbkj.service.service.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
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.security.core.AuthenticationException;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* 管理端登录服务实现类
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2025 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
@Service
public class AdminLoginServiceImpl implements AdminLoginService {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Resource
private TokenComponent tokenComponent;
@Resource
private AuthenticationManager authenticationManager;
@Autowired
private SystemAdminService systemAdminService;
@Autowired
private SystemConfigService systemConfigService;
@Autowired
private SystemGroupDataService systemGroupDataService;
@Autowired
private SystemMenuService systemMenuService;
@Autowired
private RedisUtil redisUtil;
@Autowired
private SafetyService safetyService;
/**
* PC登录
*/
@Override
public SystemLoginResponse login(SystemAdminLoginRequest systemAdminLoginRequest, String ip) {
Integer errorNum = accountDetection(systemAdminLoginRequest.getAccount());
if (errorNum > 3) {
if (ObjectUtil.isNull(systemAdminLoginRequest.getCaptchaVO())) {
throw new CrmebException("验证码信息不存在");
}
// 校验验证码
ResponseModel responseModel = safetyService.verifySafetyCode(systemAdminLoginRequest.getCaptchaVO());
if (!responseModel.getRepCode().equals("0000")) {
logger.error("验证码登录失败,repCode = {}, repMsg = {}", responseModel.getRepCode(), responseModel.getRepMsg());
accountErrorNumAdd(systemAdminLoginRequest.getAccount());
throw new CrmebException("验证码校验失败");
}
}
// 用户验证
Authentication authentication = null;
// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
try {
// CusAuthenticationManager authenticationManager = new CusAuthenticationManager(new CustomAuthenticationProvider());
authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(systemAdminLoginRequest.getAccount(), systemAdminLoginRequest.getPwd()));
} catch (AuthenticationException e) {
accountErrorNumAdd(systemAdminLoginRequest.getAccount());
if (e instanceof BadCredentialsException) {
throw new CrmebException("用户不存在或密码错误");
}
throw new CrmebException(e.getMessage());
}catch (CrmebException e){
accountErrorNumAdd(systemAdminLoginRequest.getAccount());
throw new CrmebException("账号或密码不正确");
}
LoginUserVo loginUser = (LoginUserVo) authentication.getPrincipal();
SystemAdmin systemAdmin = loginUser.getUser();
String token = tokenComponent.createToken(loginUser);
SystemLoginResponse systemAdminResponse = new SystemLoginResponse();
systemAdminResponse.setToken(token);
BeanUtils.copyProperties(systemAdmin, systemAdminResponse);
//更新最后登录信息
systemAdmin.setUpdateTime(DateUtil.date());
systemAdmin.setLoginCount(systemAdmin.getLoginCount() + 1);
systemAdmin.setLastIp(ip);
systemAdminService.updateById(systemAdmin);
accountErrorNumClear(systemAdminLoginRequest.getAccount());
return systemAdminResponse;
}
/**
* 用户登出
*/
@Override
public Boolean logout() {
LoginUserVo loginUserVo = SecurityUtil.getLoginUserVo();
if (ObjectUtil.isNotNull(loginUserVo)) {
// 删除用户缓存记录
tokenComponent.delLoginUser(loginUserVo.getToken());
}
return true;
}
/**
* 获取登录页图片
*
* @return Map
*/
@Override
public Map<String, Object> getLoginPic() {
Map<String, Object> map = new HashMap<>();
//背景图
map.put("backgroundImage", systemConfigService.getValueByKey(SysConfigConstants.CONFIG_KEY_ADMIN_LOGIN_BACKGROUND_IMAGE));
//logo
map.put("logo", systemConfigService.getValueByKey(SysConfigConstants.CONFIG_KEY_ADMIN_LOGIN_LOGO_LEFT_TOP));
map.put("loginLogo", systemConfigService.getValueByKey(SysConfigConstants.CONFIG_KEY_ADMIN_LOGIN_LOGO_LOGIN));
//轮播图
List<SystemGroupDataAdminLoginBannerResponse> bannerList = systemGroupDataService.getListByGid(SysGroupDataConstants.GROUP_DATA_ID_ADMIN_LOGIN_BANNER_IMAGE_LIST, SystemGroupDataAdminLoginBannerResponse.class);
map.put("banner", bannerList);
map.put("siteName", systemConfigService.getValueByKey(SysConfigConstants.CONFIG_KEY_SITE_NAME));
return map;
}
/**
* 获取管理员可访问目录
*
* @return List<MenusResponse>
*/
@Override
public List<MenusResponse> getMenus() {
LoginUserVo loginUserVo = SecurityUtil.getLoginUserVo();
List<String> roleList = Stream.of(loginUserVo.getUser().getRoles().split(",")).collect(Collectors.toList());
List<SystemMenu> menuList;
if (roleList.contains("1")) {// 超管
menuList = systemMenuService.findAllCatalogue();
} else {
menuList = systemMenuService.getMenusByUserId(loginUserVo.getUser().getId());
}
// 组装前端对象
List<MenusResponse> responseList = menuList.stream().map(e -> {
MenusResponse response = new MenusResponse();
BeanUtils.copyProperties(e, response);
return response;
}).collect(Collectors.toList());
MenuTree menuTree = new MenuTree(responseList);
return menuTree.buildTree();
}
/**
* 根据Token获取对应用户信息
*/
@Override
public SystemAdminResponse getInfoByToken() {
LoginUserVo loginUserVo = SecurityUtil.getLoginUserVo();
SystemAdmin systemAdmin = loginUserVo.getUser();
SystemAdminResponse systemAdminResponse = new SystemAdminResponse();
BeanUtils.copyProperties(systemAdmin, systemAdminResponse);
List<String> roleList = Stream.of(systemAdmin.getRoles().split(",")).collect(Collectors.toList());
List<String> permList = CollUtil.newArrayList();
if (roleList.contains("1")) {
permList.add("*:*:*");
} else {
permList = loginUserVo.getPermissions().stream().map(SystemPermissions::getPath).collect(Collectors.toList());
}
systemAdminResponse.setPermissionsList(permList);
return systemAdminResponse;
}
@Override
public Integer accountDetection(String account) {
SystemAdmin admin = systemAdminService.selectUserByUserName(account);
if (ObjectUtil.isNull(admin)) {
return 0;
}
String key = StrUtil.format(Constants.ADMIN_ACCOUNT_LOGIN_ERROR_NUM_KEY, account);
if (!redisUtil.exists(key)) {
return 0;
}
Integer num = redisUtil.get(key);
return num;
}
/**
* 修改登录用户信息
*
* @param request 请求参数
* @return Boolean
*/
@Override
public Boolean loginAdminUpdate(LoginAdminUpdateRequest request) {
SystemAdmin admin = SecurityUtil.getLoginUserVo().getUser();
SystemAdmin systemAdmin = new SystemAdmin();
systemAdmin.setId(admin.getId());
systemAdmin.setRealName(request.getRealName());
systemAdmin.setUpdateTime(DateUtil.date());
return systemAdminService.updateById(systemAdmin);
}
/**
* 修改登录用户密码
*/
@Override
public Boolean loginAdminUpdatePwd(LoginAdminUpdatePasswordRequest request) {
SystemAdmin admin = SecurityUtil.getLoginUserVo().getUser();
SystemAdmin systemAdmin = systemAdminService.getById(admin.getId());
String encryptPassword = CrmebUtil.encryptPassword(request.getOldPassword(), systemAdmin.getAccount());
if (!systemAdmin.getPwd().equals(encryptPassword)) {
throw new CrmebException(CommonResultCode.VALIDATE_FAILED, "原密码不正确");
}
SystemAdmin newAdmin = new SystemAdmin();
newAdmin.setId(admin.getId());
String pwd = CrmebUtil.encryptPassword(request.getPassword(), admin.getAccount());
newAdmin.setPwd(pwd);
newAdmin.setUpdateTime(DateUtil.date());
return systemAdminService.updateById(newAdmin);
}
private void accountErrorNumAdd(String account) {
redisUtil.incr(StrUtil.format(Constants.ADMIN_ACCOUNT_LOGIN_ERROR_NUM_KEY, account), 1);
}
private void accountErrorNumClear(String account) {
String key = StrUtil.format(Constants.ADMIN_ACCOUNT_LOGIN_ERROR_NUM_KEY, account);
if (redisUtil.exists(key)) {
redisUtil.delete(StrUtil.format(Constants.ADMIN_ACCOUNT_LOGIN_ERROR_NUM_KEY, account));
}
}
}
安全配置文件
package com.zbkj.admin.config;
import com.zbkj.admin.filter.JwtAuthenticationTokenFilter;
import com.zbkj.admin.manager.AuthenticationEntryPointImpl;
import com.zbkj.admin.manager.CustomAccessDeniedHandler;
import com.zbkj.admin.manager.CustomAuthenticationProvider;
import com.zbkj.common.constants.Constants;
import com.zbkj.common.constants.UploadConstants;
import com.zbkj.service.service.impl.UserDetailServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutFilter;
import org.springframework.web.filter.CorsFilter;
/**
* Security配置
* +----------------------------------------------------------------------
* | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
* +----------------------------------------------------------------------
* | Copyright (c) 2016~2025 https://www.crmeb.com All rights reserved.
* +----------------------------------------------------------------------
* | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
* +----------------------------------------------------------------------
* | Author: CRMEB Team <admin@crmeb.com>
* +----------------------------------------------------------------------
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 跨域过滤器
*/
@Autowired
private CorsFilter corsFilter;
/**
* token认证过滤器
*/
@Bean
public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilter();
}
/**
* 认证失败处理类
*/
@Bean
public AuthenticationEntryPointImpl unauthorizedHandler() {
return new AuthenticationEntryPointImpl();
}
/**
* 鉴权失败处理类
*/
@Bean
public CustomAccessDeniedHandler accessDeniedHandler() {
return new CustomAccessDeniedHandler();
}
/**
* 这里将Spring Security自带的authenticationManager声明成Bean,声明它的作用是用它帮我们进行认证操作,
* 调用这个Bean的authenticate方法会由Spring Security自动帮我们做认证。
*/
// @Bean
// public AuthenticationManager authenticationManager() throws Exception {
// return new CusAuthenticationManager(customAuthenticationProvider);
// }
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(new CustomAuthenticationProvider(new UserDetailServiceImpl()));
}
/**
* 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 http) throws Exception {
// CRSF禁用,因为不使用session
http.cors().and().csrf().disable()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler())
.accessDeniedHandler(accessDeniedHandler()).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 过滤请求
.authorizeRequests()
// 跨域预检请求
// .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// 对于登录login 验证码captchaImage 和其他放行的目录 允许匿名访问"/citylife/front/**"
.antMatchers("/api/admin/login", "/api/admin/validate/code/get").permitAll()
.antMatchers("/api/admin/getLoginPic").permitAll()
.antMatchers("/api/admin/login/account/detection").permitAll()
.antMatchers("/api/admin/validate/code/getcaptchaconfig").permitAll()
// 放行资源路径
.antMatchers("/"+ UploadConstants.UPLOAD_FILE_KEYWORD +"/**").permitAll()
.antMatchers("/"+ UploadConstants.DOWNLOAD_FILE_KEYWORD +"/**").permitAll()
.antMatchers("/"+ UploadConstants.UPLOAD_AFTER_FILE_KEYWORD +"/**").permitAll()
// 放行图片、文件上传
.antMatchers("/api/admin/upload/image").permitAll()
.antMatchers("/api/admin/upload/file").permitAll()
// .antMatchers("/wx/user/*/login","/citylife/nocheck/**").anonymous()
.antMatchers(
HttpMethod.GET,
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js"
).permitAll()
.antMatchers("/profile/**").anonymous()
.antMatchers("/common/download**").anonymous()
.antMatchers("/common/download/resource**").anonymous()
.antMatchers("/doc.html").permitAll()
.antMatchers("/swagger-resources/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/v2/**").permitAll()
.antMatchers("/swagger-ui.html/**").permitAll()
.antMatchers("/*/api-docs").anonymous()
.antMatchers("/druid/**").anonymous()
.antMatchers("/captcha/get", "/captcha/check").anonymous()
.antMatchers("/api/admin/payment/callback/**").anonymous()
.antMatchers("/api/public/**").permitAll()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();// 防止iframe 造成跨域
// http.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
// 开启登录认证流程过滤器
http.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
http.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
http.addFilterBefore(corsFilter, LogoutFilter.class);
}
}