SpringBoot整合Spring Security安全框架 动态权限管理(二)

在之前的《SpringBoot整合Spring Security安全框架(一)》中用户权限使用的是@PreAuthorize硬编码,无法动态的进行权限的配置,本节基于扩展access()的SpEL表达式实现URL动态权限。

SpringBoot整合Spring Security安全框架(一)https://blog.csdn.net/weixin_43424932/article/details/104433071

获取登录用户

为了方便看到当前登录的是哪个用户,在原有基础上先做如下修改。同时顺便去掉之前的@PreAuthorize注解。
修改controller中的首页接口index,使用security的上下文环境获取登录用户信息,关键是这句话SecurityContextHolder.getContext().getAuthentication().getPrincipal()。然后将用户名返回到thymeleaf模板。

package com.security.demo.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class HelloController {

    @GetMapping("/helloAdmin")
    @ResponseBody
    public String helloAdmin() {
        return "I am Admin";
    }

    @GetMapping("/helloUser")
    @ResponseBody
    public String helloUser() {
        return "I am User";
    }

    @GetMapping("/login")
    public String login() {
        return "login";
    }

    @GetMapping({"/index","/"})
    public String index(Model model) {
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        //anonymousUser表示匿名用户
        if("anonymousUser".equals(principal)) {
            model.addAttribute("name","当前未登录");
        }else {
            User user = (User)principal;
            model.addAttribute("name","Welcome "+user.getUsername());
        }
        return "index";
    }
}

修改首页页面

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" >
<head>
    <title>HelloWorld</title>
</head>
<body>
<h1>HelloWorld!
    <label th:text="${name}"></label>
    !
</h1>
<p><a th:href="@{/helloUser}">I am User</a></p>
<p><a th:href="@{/helloAdmin}">I am Admin</a></p>

<form th:action="@{/logout}" method="post">
    <input type="submit" value="退出登录"/>
</form>
</body>
</html>

运行项目请求http://localhost:8080。出现下面的页面,效果显而易见。
在这里插入图片描述
登录user用户,admin用户效果也是一样的,这里就不展示了。
在这里插入图片描述

扩展access()的SpEL表达式实现URL动态权限

编辑代码

引入lombok包简化代码。感觉之前粘贴上去代码太多了。

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.4</version>
</dependency>

新建Permission 实体类。

package com.security.demo.bean;

import lombok.Data;

import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;

@Entity
@Data
public class Permission {
    @Id @GeneratedValue
    private long id;//主键.
    private String url;//授权链

    // 角色 - 权限是多对多的关系
    @ManyToMany(fetch=FetchType.EAGER)
    @JoinTable(name="RolePermission",joinColumns= {@JoinColumn(name="permission_id")} , inverseJoinColumns= {@JoinColumn(name="role_id")})
    private List<Role> roles;
}

修改Role 实体类。

package com.security.demo.bean;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
@Data
public class Role {
    @Id @GeneratedValue
    private long rid;//主键.
    private String name;//角色名称.
    private String description;//角色描述.
}

修改UserInfo 实体类。

package com.security.demo.bean;

import lombok.Data;

import java.util.List;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;

@Entity
@Data
public class UserInfo {

    @Id @GeneratedValue
    private long uid;//主键.
    private String username;//用户名.
    private String password;//密码.

    //用户--角色:多对多的关系.
    @ManyToMany(fetch=FetchType.EAGER)//立即从数据库中进行加载数据;
    @JoinTable(name = "UserRole", joinColumns = { @JoinColumn(name = "uid") }, inverseJoinColumns ={@JoinColumn(name = "role_id") })
    private List<Role> roles;
}

修改WebSecurityConfig中重写的configure方法。这句是关键.access("@authService.canAccess(request,authentication)")

@Override
protected void configure(HttpSecurity http) throws Exception {
    // 定义哪些URL需要被保护、哪些不需要被保护
    http.authorizeRequests()
            // 设置所有人都可以访问登录页面
            .antMatchers("/login").permitAll()
            .antMatchers("/index","/").permitAll()
            // 任何请求,登录后需要权限验证才能访问
            .anyRequest().access("@authService.canAccess(request,authentication)")
            .and()
            .formLogin().loginPage("/login")
    ;
}

新建AuthService 类。在这里面实现权限分配。

package com.security.demo.config;

import com.security.demo.bean.Permission;
import com.security.demo.bean.Role;
import com.security.demo.repository.PermissionReporitory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.util.*;

@Component
public class AuthService {

    @Autowired
    private PermissionReporitory permissionReporitory;

    /**
     * 权限校验
     */
    public boolean canAccess(HttpServletRequest request, Authentication authentication) {
        boolean b = false;
        System.out.println(authentication);

        //判断是否登录
        Object principal = authentication.getPrincipal();
        if(principal == null || "anonymousUser".equals(principal)) {
            return b;
        }

        Map<String,Collection<ConfigAttribute>> map =  getPermissionMap();

        //String uri = request.getRequestURI();
        //Collection<ConfigAttribute> configAttributes = map.get(uri);
               
        Collection<ConfigAttribute> configAttributes = null;
        String resUrl;
        //URL规则匹配.//可支持通配符
        AntPathRequestMatcher matcher;
        for(Iterator<String> it  = map.keySet().iterator();it.hasNext();) {
            resUrl = it.next();
            matcher = new AntPathRequestMatcher(resUrl);
            if(matcher.matches(request)) {
                configAttributes =  map.get(resUrl);
                break;
            }
        }

        if(configAttributes == null || configAttributes.size() ==0) {
            return b;
        }

        String needRole = null;
        for (ConfigAttribute configAttribute : configAttributes) {
            //拥有当前权限的角色
            needRole = configAttribute.getAttribute();
            System.out.println("needRole = " + needRole);

            String hasRole=null;
            for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) {
                //当前用户拥有的角色
                hasRole=grantedAuthority.getAuthority();
                System.err.println("hasRole = " + hasRole);
                if (needRole.equals(hasRole)) {
                    b = true;
                    break;
                }
            }
        }
        return b;
    }

    /**
     * 获取权限集合
     */
    private Map<String, Collection<ConfigAttribute>> getPermissionMap() {
        Map<String, Collection<ConfigAttribute>> permissionMap = new HashMap<>();
        Collection<ConfigAttribute> collection;
        ConfigAttribute cfg;

        List<Permission> permissions = permissionReporitory.findAll();
        for(Permission p:permissions) {
            collection = new ArrayList<>();
            for(Role r:p.getRoles()) {
                cfg = new SecurityConfig("ROLE_"+r.getName());
                collection.add(cfg);
            }
            permissionMap.put(p.getUrl(),collection);
        }
        System.out.println(permissionMap);
        return permissionMap;
    }
}

测试和分析

原理分析

运行项目,jpa会为我们自动生成权限信息表和权限角色关联表,多对多的关系。当前手动添加两条权限信息,权限1为/helloAdmin,权限2为/helloUser。
在这里插入图片描述
当前自己手动填写权限和角色关系。当前表示角色1拥有权限1和权限2,角色2只拥有权限2。
在这里插入图片描述
在来看看用户表,当前拥有用户1/admin,用户2/user。
在这里插入图片描述
角色表,当前拥有角色1/admin,角色2/user。
在这里插入图片描述
五个表连起来看,就是admin用户拥有角色1/admin,所以admin用户拥有权限1/helloAdmin和权限2/helloUser。而user用户拥有角色2/user,所以user用户只拥有权限2/helloUser。
在这里插入图片描述
可以看出本次采用的是RBAC模式建立数据库。RBAC 是基于角色的访问控制(Role-Based Access Control )在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。这样管理都是层级相互依赖的,权限赋予给角色,而把角色又赋予用户,这样的权限设计很清楚,管理起来很方便。表也就比较简单。

测试效果展示

当前登录admin。
在这里插入图片描述
admin用户可以访问helloUser和helloAdmin两个接口。
在这里插入图片描述
在这里插入图片描述
当前登录user用户。
在这里插入图片描述
user用户只能访问helloUser接口。
在这里插入图片描述
在这里插入图片描述
至此,加上上一篇文章,SpringBoot整合Spring Security安全框架的动态权限管理就全部实现了。
完整项目资源链接https://download.csdn.net/download/weixin_43424932/12182847

发布了82 篇原创文章 · 获赞 9 · 访问量 6177

猜你喜欢

转载自blog.csdn.net/weixin_43424932/article/details/104443087