背景
前文对整体实现方案做了说明,接下来,我们来让这一方案落地。
主流安全框架
在java开源领域,主流安全框架有两个,分别是Spring Security和Apache Shiro,以下是它们的一些比较:
- 集成和生态系统
Spring Security:
与Spring框架紧密集成,提供了对Spring应用程序的全面安全支持。
作为Spring生态系统的一部分,它可以很容易地与Spring Data、Spring Boot等其他Spring项目集成。
Apache Shiro:
独立于Spring,可以轻松集成到任何Java应用程序中,不仅限于Spring。
提供了与Java EE环境的集成,如Servlets和Java EE realms。
2. 配置和易用性
Spring Security:
配置相对复杂,尤其是对于初学者。它提供了XML配置和Java配置两种方式。
随着Spring Boot的出现,Spring Security的配置变得更加简洁,许多配置可以自动完成。
Apache Shiro:
配置相对简单直观,易于理解和使用。
提供了基于INI配置文件的方式,使得配置变得非常灵活和可读。
3. 功能和特性
Spring Security:
提供了丰富的安全特性,包括OAuth2、LDAP、X.509、CAS等。
支持多种认证方式,如表单登录、HTTP Basic、Digest、OAuth等。
Apache Shiro:
提供了核心的安全功能,包括认证、授权、加密和会话管理。
支持自定义的Realms,可以轻松扩展以支持各种认证和授权机制。
4. 性能
Spring Security:
由于其丰富的特性和Spring框架的重量级,可能在性能上不如Apache Shiro轻量。
Apache Shiro:
以轻量级著称,性能优异,适合对性能要求较高的应用。
5. 社区和文档
Spring Security:
拥有庞大的社区和丰富的文档资源,易于获取帮助和最佳实践。
Apache Shiro:
社区相对较小,但文档齐全,对于熟悉Apache项目的用户来说,学习和使用起来也很方便。
6. 学习曲线
Spring Security:
学习曲线较陡峭,尤其是对于Spring Security的高级特性。
Apache Shiro:
学习曲线较为平缓,易于上手。
总结
选择Spring Security还是Apache Shiro取决于项目需求、团队熟悉度以及对性能的要求。如果你的项目已经在使用Spring框架,并且需要一个功能丰富且与Spring生态系统紧密集成的安全解决方案,Spring Security可能是更好的选择。如果你需要一个轻量级、易于配置且性能优异的安全框架,Apache Shiro可能更适合你的需求。
简单来说,Apache Shiro更轻量级,配置简单,而Spring Security功能更强大,配置也相对更复杂一些,门槛较高。
考虑到平台的扩展性,这里我最终选择了Spring Security。
实现
首先说明一点,Spring Security是一个安全框架,基于RBAC模型实现了功能权限控制,但是对于基础的关系表(用户、角色、权限项)及对应关系(用户与角色、角色与权限项),Spring Security并不负责创建和管理,仍需要平台或系统自行实现。
Spring Security提供了框架,通过预置的接口和服务,来实现最终的功能权限控制功能。
控制器层
需要在具体的控制器类的方法上,添加注解,如下示例:
/**
* 列表
*/
@GetMapping("/list")
@SystemLog(value = "用户-列表")
@PreAuthorize("hasPermission(null,'system:user:query')")
public ResponseEntity<Result> list(UserVO queryVO, SortInfo sortInfo) {
// 构造查询条件
QueryWrapper<User> queryWrapper = QueryGenerator.generateQueryWrapper(User.class, queryVO, sortInfo);
List<User> list = userService.list(queryWrapper);
// 转换vo
List<UserVO> userVOList = convert2VO(list);
return ResultUtil.success(userVOList);
}
Spring Security提供了权限注解为@PreAuthorize:
@PreAuthorize("hasPermission(null,'system:user:query')")
在Spring Security中,@PreAuthorize 注解用于在方法执行之前进行权限检查。如果检查通过,则方法可以正常执行;如果检查不通过,则会抛出异常,阻止方法的执行。
@PreAuthorize("hasPermission(null,'system:user:query')") 这行代码的含义是:
@PreAuthorize:这是一个Spring Security的注解,用于在方法执行前进行权限检查。hasPermission:这是一个自定义的权限检查表达式,用于确定当前认证的用户是否具有执行该方法的权限。null:在这里,null通常表示当前请求中的域对象(domain object),在这个表达式中没有使用具体的域对象,所以传入null。'system:user:query':这是一个权限字符串,表示具体的权限。在这个例子中,它可能表示查询用户信息的权限。
简而言之,@PreAuthorize("hasPermission(null,'system:user:query')") 表示在执行被注解的方法之前,需要检查当前用户是否具有system:user:query这个权限。如果用户没有这个权限,方法将不会被执行,并且通常会返回一个HTTP状态码403(禁止访问)。
请注意,hasPermission 表达式的确切行为取决于你的应用程序如何配置和实现权限检查逻辑。在Spring Security中,默认情况下,你可能需要使用@PreAuthorize("hasAuthority('system:user:query')") 或者 @PreAuthorize("hasRole('SYSTEM_USER_QUERY')") 这样的表达式,具体取决于你的权限配置和角色设置。hasPermission 可能需要自定义的权限评估器(PermissionEvaluator)来解析和验证权限表达式。
自定义权限评估器
上一章节,用到了一个hasPermission的表达式,这个表达式是我们自行定义的,继承Spring Security框架的PermissionEvaluator接口,如下所示:
package tech.abc.platform.framework.security;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import tech.abc.platform.common.enums.StatusEnum;
import tech.abc.platform.common.exception.CustomException;
import tech.abc.platform.common.utils.UserUtil;
import tech.abc.platform.framework.config.PlatformConfig;
import tech.abc.platform.system.entity.PermissionItem;
import tech.abc.platform.system.exception.PermissionItemExceptionEnum;
import tech.abc.platform.system.service.PermissionItemService;
import tech.abc.platform.system.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.io.Serializable;
/**
* 自定义权限表达式
*
* @author wqliu
* @date 2023-03-08
*/
@Component
public class MyPermissionEvaluator implements PermissionEvaluator {
@Autowired
private PermissionItemService permissionItemService;
@Autowired
private PlatformConfig appConfig;
@Autowired
private UserService userService;
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
return hasPermission(authentication, permission);
}
private boolean hasPermission(Authentication authentication, Object permission) {
if (appConfig.getSystem().isEnablePermission()) {
// 首先判断该资源
QueryWrapper<PermissionItem> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda().eq(PermissionItem::getPermissionCode, permission.toString());
PermissionItem entity = permissionItemService.getOne(queryWrapper);
// 不存在
if (entity == null) {
throw new CustomException(PermissionItemExceptionEnum.NOT_EXIST);
}
// 停用
if (entity.getStatus().equals(StatusEnum.DEAD.name())) {
throw new CustomException(PermissionItemExceptionEnum.STATUS_IS_DEAD);
}
// 权限验证
userService.checkPermission(UserUtil.getId(), entity.getPermissionCode());
return true;
} else {
return true;
}
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
return false;
}
}
内部控制逻辑比较简单,先判断全局配置项,是否启用的权限控制,若开启,则先判断权限编码是否存在,如存在且状态为正常,则调用用户服务userService的权限验证方法checkPermission,验证是否有权限。
具体验证逻辑是验证当前用户拥有的用户组,是否具备权限编码对应的权限,sql如下:
<select id="checkPermission" resultType="java.lang.Integer">
select count(1) as count
from sys_permission_item p
left join sys_group_permission_item rp
on p.id=rp.PERMISSION_ITEM_ID
left join sys_user_group r on r.id=rp.group_ID
left join sys_group_user ru on ru.group_id=r.id
left join sys_user u on ru.user_id=u.id
where u.id=#{id}
and r.status='NORMAL'
and p.status='NORMAL'
and p.permission_code=#{permissionCode}
</select>
配置权限评估器
上一章节,我们自定义了权限评估器,需要注意的是,并不是定义了就会生效,需要额外配置,继承Spring Security框架类WebSecurityConfigurerAdapter的情况下,覆写其中的方法,如下:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class Spring SecurityConfig extends WebSecurityConfigurerAdapter{
@Override
public void configure(WebSecurity web) {
web.expressionHandler(new DefaultWebSecurityExpressionHandler() {
@Override
protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, FilterInvocation fi) {
WebSecurityExpressionRoot root = (WebSecurityExpressionRoot) super.createSecurityExpressionRoot(authentication, fi);
root.setPermissionEvaluator(myPermissionEvaluator);
return root;
}
});
}
}
同时注意该类,需要添加注解@EnableGlobalMethodSecurity(prePostEnabled = true)。
在Spring Security中,@EnableGlobalMethodSecurity 注解用于开启全局方法安全性控制,即启用方法级别的安全性控制。当你使用这个注解时,Spring Security将开始处理所有被安全注解(如@PreAuthorize、@PostAuthorize、@Secured等)标记的方法调用。
配置权限控制
除了上一章节要做的配置外,还需要对权限控制进行配置,依然是继承Spring Security框架类WebSecurityConfigurerAdapter的情况下,覆写其中的方法,如下:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class Spring SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// 身份认证配置部分 略
// 配置允许访问页面
http.authorizeRequests()
// 允许跨域请求中的Preflight请求
.requestMatchers(CorsUtils::isPreFlightRequest).permitAll()
// 允许swagger文档接口匿名访问
.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous()
;
// 配置其他请求
http.authorizeRequests()
.anyRequest()
// 注意:此处若设置为authenticated,则基于url的过滤器优先级高于方法体上的@PreAuthorize
// 如通知公告中的图片请求,会因为没有附加token导致未认证被拦截
.permitAll();
}
}
有几个关键的点:
requestMatchers(CorsUtils::isPreFlightRequest):这是一个请求匹配器,它使用CorsUtils.isPreFlightRequest()方法来检查请求是否是跨域请求中的预检请求(Preflight request)。预检请求是 CORS(跨源资源共享)的一部分,用于在实际请求之前检查服务器是否允许跨域请求。- 对于一些无需登录即可访问的页面,如swagger生成的接口文档,使用.antMatchers(“/swagger-ui.html”).anonymous()方式来允许匿名访问。
- 对于其他请求,使用 permitAll(),这里的其他请求,实际是没有在控制器类中使用@PreAuthorize标注的方法。
自定义权限访问注解
此外,为了方便在控制类方法上灵活设置各种场景下的权限控制,使用了几个自定义注解,如下:
/**
* 允许任意访问
* 已确认,方法上不配置权限表达式,起到的效果一样
* 但仍建议加上,以明确该方法允许访问。而不是忘记做权限控制
*
* @author wqliu
* @date 2023-03-06
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@PreAuthorize("permitAll()")
public @interface AllowAll {
}
/**
* 允许匿名用户访问
*
* @author wqliu
* @date 2023-03-06
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@PreAuthorize("isAnonymous()")
public @interface AllowAnonymous {
}
/**
* 允许已登录用户访问
*
* @author wqliu
* @date 2023-03-06
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@PreAuthorize("isAuthenticated()")
public @interface AllowAuthenticated {
}
/**
* 禁止任何访问
* 仅是预留,目前未有合适的场景使用,可能用于部分极其重要的控制器,仅限于内部使用
* 但内部使用往往是服务层调用,而不是控制器层
*
* @author wqliu
* @date 2023-03-06
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@PreAuthorize("denyAll()")
public @interface DenyAll {
}
开源平台资料
平台名称:一二三开发平台
简介: 企业级通用开发平台
设计资料:[csdn专栏]
开源地址:[Gitee]
开源协议:MIT
如果您在阅读本文时获得了帮助或受到了启发,希望您能够喜欢并收藏这篇文章,为它点赞~
请在评论区与我分享您的想法和心得,一起交流学习,不断进步,遇见更加优秀的自己!
986

被折叠的 条评论
为什么被折叠?



