朋也的博客 » 首页 » 文章

springsecurity6.x+gradle+kotlin+thymeleaf学习笔记 - 8.动态URL权限控制

作者:朋也
日期:2024-08-07
类别:springsecurity6.x学习笔记 


版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证

上篇创建的权限表auth中有个字段url,对应权限名name,目前只在SecurityConfig.kt里使用代码写死了一个配置

authorizeRequests {
    // 配置 /home 路径需要 ADMIN 权限
    authorize("/home", hasAuthority("user:delete"))
    // 配置所有请求地址都需要登录认证才能访问
    authorize("/**", authenticated)
}

这篇来将 auth 表里的 url 字段利用起来,实现权限动态控制,即数据库里对权限做更新而无需修改代码

准备Controller

将前面写的 /home的controller修改一下地址,改成 /user/list

@GetMapping("/user/list")
fun home(model: Model): Any? {
    model.addAttribute("user", SecurityContextHolder.getContext().authentication.principal)
    return "home"
}

配置authorizeHttpRequests

通过构造方法将 AuthService注入进来

@Configuration
@EnableWebSecurity
class SecurityConfig(
    private val myUserDetailsService: MyUserDetailsService,
    private val authService: AuthService
) {}

在HttpSecurity的权限配置里,authorizeRequestsauthorizeHttpRequests是不能并存的。前面配置的权限使用的是authorizeRequests,要想自定义决策逻辑,就需要用到 authorizeHttpRequests了,所以先将authorizeRequests相关代码注释掉,再添加上 authorizeHttpRequests相关代码

http {
//    authorizeRequests {
//        authorize("/captcha", permitAll)
//        // 配置 /home 路径需要 ADMIN 权限
//        authorize("/home", hasAuthority("user:delete"))
//        // 配置所有请求地址都需要登录认证才能访问
//        authorize("/**", authenticated)
//    }
    authorizeHttpRequests {
    	// 系统的报错页面,直接放行
        authorize("/error", permitAll)
        authorize(anyRequest) { authentication, context ->
            // 获取请求地址
            val path = context.request.servletPath
            // 获取当前认证用户权限列表
            val authorities = authentication.get().authorities
            // 临时变量:用来记录是否经过url决策
            var isDecision:Boolean = false
            // 临时变量:用来记录url决策结果
            var decision:Boolean = false
            // 查询数据库里所有的权限
            var auths = authService.list()
            // 过滤掉url字段是空的权限和请求地址与数据库里配置的url不匹配的权限
            auths = auths.filter { it.url != null && Pattern.compile(it.url!!).matcher(path).find() }
            if (auths.isNotEmpty()) {
                // 对权限列表进行2次过滤,取权限名的值在当前认证用户权限列表里的数据
                auths = auths.filter { authorities.contains(SimpleGrantedAuthority(it.name)) }
                // 不管二次过滤还有没有值,都表示经过了url决策,所以isDecision设为true
                isDecision = true
                // 缓存一下url决策的结果
                decision = auths.isNotEmpty()
                AuthorizationDecision(decision)
            }
            if (!isDecision) {
                // 如果没有经过url决策,则判断是不是匿名用户,是的话就去登录,不是的话,说明这个地址没有配置权限只需登录认证即可
                AuthorizationDecision(authentication.get() !is AnonymousAuthenticationToken)
            } else {
                // 上面两个临时变量isDecision, decision就是为了在这用的
                AuthorizationDecision(decision)
            }
        }
    }
    formLogin {}
    logout {}
    csrf { disable() }
}
return http.build()

测试

启动服务,直接访问 /user/list 地址,会跳转到登录,说明匿名用户会首先跳转登录的逻辑是正常的

然后登录成功后回到 /user/list 页面,因为 zhangsan 只有首页权限而没有这个页面的权限。所以跳转到403报错页面,说明从数据库中读取权限进行决策的逻辑也是正常的。

总结