后台权限框架搭建:本项目权限主要依赖
SpringSecurity实现,主要涉及的表有
角色表、
菜单表以及
角色菜单关联表等数据库表。权限部分功能的实现需要使用到自定义配制文件、自定义注解、自定义服务类等等...
1、项目整合SpringSecurity
1.1、引入SpringSecurity依赖
<!--===================== SpringBoot相关依赖版本 =========================-->
<springboot.version>2.5.5</springboot.version>
<!-- security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${springboot.version}</version>
</dependency>
1.2、启动测试
启动项目之后访问接口发现已经被SpringSecurity拦截,出现如下界面说明SpringSecurity已经成功引入
1.3、自定义实体类继承UserDetails
由于SpringSecurity默认提供的登陆接口会执行loadUserByUsername()方法,此方法的返回值为UserDetails,而SpringSecurity会根据返回值中的加密密码进行密码校验,所以我们需要自定义一个实体类来继承UserDetails
/**
* @author 木字楠
* @version 1.0
* @description 用户信息
* @date 2022/8/12
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User implements UserDetails {
/**
* 用户主键
*/
private String id;
/**
* 用户名/OpenId
*/
private String username;
/**
* 用户密码
*/
private String password;
/**
* 用户登录方式(1 用户名 2 邮箱 3 QQ 4 微信)
*/
private Integer loginType;
/**
* 是否开启邮箱登录(0 否 1是)
*/
private Boolean emailLogin;
/**
* 是否禁用(0 正常 1禁用)
*/
private Boolean disabled;
/**
* 用户名
*/
private String nickname;
/**
* 用户头像
*/
private String avatar;
/**
* 用户性别(-1 未知 0 仙女 1帅哥)
*/
private String gender;
/**
* 用户邮箱
*/
private String email;
/**
* 用户个人简介
*/
private String personIntro;
/**
* ip地址
*/
private String ipAddress;
/**
* ip来源
*/
private String ipSource;
/**
* 用户创建时间
*/
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime gmtCreate;
/**
* 用户信息更新时间
*/
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime gmtUpdate;
/**
* 用户最近一次登录时间
*/
@JsonFormat(pattern = "yyyy-MM-dd hh:mm:ss")
private LocalDateTime lastLoginTime;
/**
* 浏览器
*/
private String brower;
/**
* 操作系统
*/
private String os;
/**
* 角色
*/
private String role;
/**
* 权限列表
*/
private Set<String> permissionList;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
SimpleGrantedAuthority list = new SimpleGrantedAuthority(role);
return Collections.singleton(list);
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
1.4、自定义配制文件
引入依赖
<!-- 自定义配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</dependency>
自定义配制信息
# custom Configuration
application:
#Swagger Configuration
swagger:
isEnable: true
#Super_admin Configuration
highest-authority:
highest-role-label: super_admin
highest-role-secret: MuZiNan#$%^&*
highest-permission: <*><*><*>
我们可以使用@Value的注解进行自定义配制信息的提取,但是那样做法太Low,而且不方便。这里我们采用更加优雅的方式来提起自定义注解信息!使用Properties配制文件来提取自定义配制信息!
/**
* @author 木字楠
* @version 1.0
* @description 最高权限配制文件
* @date 2022/8/12
*/
@Data
@ConfigurationProperties(HIGHEST_AUTHORITY)
public class HigestAuthorityProperties {
/**
* 最高权限角色标识
*/
private String highestRoleLabel;
/**
* 最高权限角色秘钥
*/
private String highestRoleSecret;
/**
* 最高权限标识
*/
private String highestPermission;
}
我们是用常量的形式来简化字符串的拼接
# ConfigPropertiesConstant
/**
* @author: 木字楠
* @date: 2022/8/12
* @version: 1.0
*/
public class ConfigPropertiesConstant {
/**
* 应用相关前缀,位于“Application”配置中
*/
public static class Application {
/**
* 默认前缀
*/
private static final String DEFAULT_PREFIX = "application";
/**
* JWT相关配置信息
*/
public static final String JWT = DEFAULT_PREFIX + ".jwt";
/**
* 最高权限配制信息
*/
public static final String HIGHEST_AUTHORITY = DEFAULT_PREFIX + ".highest-authority";
/**
* 阿里云Oss对象存储
*/
public static final String ALIYUN_OSS = DEFAULT_PREFIX + ".aliyun.oss";
/**
* 七牛云Kodo对象存储
*/
public static final String QINIU_KODO = DEFAULT_PREFIX + ".qiniu.kodo";
/**
* 服务器Ftp上传
*/
public static final String ECS_FTP = DEFAULT_PREFIX + ".ecs.ftp";
}
}
开启配制文件的使用
/**
* @author 木字楠
* @version 1.0
* @description 配制文件是否开始使用
* @date 2022/8/12
*/
@Configuration
@EnableConfigurationProperties({
HigestAuthorityProperties.class,
})
public class PropertiesConfig {
}
1.5、重写loadUserByUsername方法
/**
* @author 木字楠
* @version 1.0
* @description 自定义服务实现
* @date 2022/8/12
*/
@Service
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private HttpServletRequest request;
private final UserAuthService userAuthService;
private final UserInfoService userInfoService;
private final RoleService roleService;
private final MenuService menuService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserAuth userAuth = usernamePasswordCheck(username);
return convertToUser(request, userAuth);
}
/**
* 用户数据转护
*
* @param request request
* @param userAuth 用户权限信息
* @return {@link User} 用户信息
*/
private User convertToUser(HttpServletRequest request, UserAuth userAuth) {
User user = BeanCopyUtil.copyObject(userAuth, User.class);
// 查询用户基础信息
UserInfo userInfo = userInfoService.getOne(new LambdaQueryWrapper<UserInfo>().eq(UserInfo::getId, userAuth.getUserInfoId()));
//region 获取用户Ip相关信息
String ipAddress = IpUtil.getIpAddress(request);
String ipSource = IpUtil.getIpSource(ipAddress);
UserAgent userAgent = IpUtil.getUserAgent(request);
//endregion
//region 获取用户权限信息
Set<String> permisstionSet = new HashSet<>(99);
Role role = roleService.getOne(new LambdaQueryWrapper<Role>().eq(Role::getId, userAuth.getUserRoleId()));
if (null != role) {
permisstionSet = menuService.getUserPermissionList(role);
}
//endregion
//数据封装
String visitor = "游客";
user.setNickname(userInfo.getNickname());
user.setAvatar(userInfo.getAvatar());
user.setGender(GenderEnum.getGenderValueByCode(userInfo.getGender()));
user.setEmail(userInfo.getEmail());
user.setPersonIntro(user.getPersonIntro());
user.setIpAddress(ipAddress);
user.setIpSource(ipSource);
user.setGmtCreate(userInfo.getGmtCreate());
user.setGmtUpdate(userInfo.getGmtUpdate());
user.setLastLoginTime(userInfo.getLastLoginTime());
user.setRole(null == role ? visitor : role.getRoleName());
user.setPermissionList(permisstionSet);
user.setOs(userAgent.getOperatingSystem().getName());
user.setBrower(userAgent.getBrowser().getName());
return user;
}
/**
* 用户名密码校验
*/
private UserAuth usernamePasswordCheck(String username) {
// 用户名非空校验
if (StringUtils.isEmpty(username)) {
throw new BaseException(HttpCodeEnum.USERNAME_CAN_NOT_BE_EMPTY.getMessage());
}
// 查询用户信息
UserAuth userAuth = userAuthService.getOne(new LambdaQueryWrapper<UserAuth>().eq(UserAuth::getUsername, username)
.eq(UserAuth::getIsDisabled, SystemConstant.NOT_DISABLED)
.eq(UserAuth::getIsDeleted, SystemConstant.NOT_DELETED));
if (null == userAuth) {
throw new BaseException(HttpCodeEnum.USERNAME_OR_PASSWORD_ERROR.getMessage());
}
return userAuth;
}
}
1.6、自定义匿名访问注解
自定义匿名访问注解,届时我们会使用SpringSecurity拦截所有请求,未登录用户访问接口时将会统一返回
请登陆后再进行访问!,而带有匿名注解的接口将会被放行,无论用户是否登录,都可以正常访问。
/**
* @author 木字楠
* @version 1.0
* @description 匿名访问注解
* @date 2022/8/12
*/
public @interface Anonymous {
}
private final ApplicationContext applicationContext;
/**
* 查找可以匿名访问的接口
*
* @return 匿名访问接口集合
*/
private Set<String> listAnonymous() {
Map<RequestMappingInfo, HandlerMethod> handlerMethods =
applicationContext.getBean(RequestMappingHandlerMapping.class).getHandlerMethods();
Set<String> anonymousUrls = new HashSet<>();
for (Map.Entry<RequestMappingInfo, HandlerMethod> infoEntry : handlerMethods.entrySet()) {
HandlerMethod handlerMethod = infoEntry.getValue();
AnonymousAccess anonymousAccess = handlerMethod.getMethodAnnotation(AnonymousAccess.class);
if (anonymousAccess != null) {
assert infoEntry.getKey().getPatternsCondition() != null;
anonymousUrls.addAll(infoEntry.getKey().getPatternsCondition().getPatterns());
}
}
System.out.println("可以匿名访问的url:{}", anonymousUrls);
return anonymousUrls;
}
1.8、编写SpringSecurity配制类
自定义配制类继承
WebSecurityConfigurerAdapter,并且重写其中的三个
config方法
public void configure(WebSecurity web) 内部重新实现,主要放行静态资源
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().mvcMatchers(
"/js/**", "/css/**", "/img/**", "/fonts/**",
"/", "/index.html", "/favicon.ico", "/doc.html",
"/swagger-ui.html", "/webjars/**", "/swagger-resources/**", "/v3/**");
}
protected void configure(AuthenticationManagerBuilder auth) 内部重新实现,指定UserDetailsService执行方式以及密码加密方式
private final UserDetailsServiceImpl userDetailsService;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}