目录
前言
一、RBAC模型
二、实战应用
1. 建立用户、角色、资源实体类
2. 数据层查询角色资源
3. 业务层实现,调用数据层查询接口
4. SystemController控制器菜单获取方法
前言
本篇文章接SSM项目集成Spring Security 4.X版本(使用spring-security.xml 配置文件方式)-CSDN博客https://blog.csdn.net/u011529483/article/details/135699004?spm=1001.2014.3001.5501
文章之后,代码在同一个示例工程中。如有必要请参考此文。
一、RBAC模型
本文获取菜单资源是基于角色分配的方式获得的。即什么用户是什么样的角色,什么角色可以访问什么资源。也就是RBAC模型(Role-Based Access Control:基于角色的访问控制)。RBAC模型的3个基础组成部分便是:用户、角色和权限。
- User(用户):用户有唯一的ID,分配到不同的角色上
- Role(角色):角色有唯一的ID,角色分配有不同的权限(资源)
- Permission(权限):资源有唯一的ID,系统的访问权限(资源)
- 用户-角色映射:用户和角色建立对应关系。用户表ID对应角色表ID
- 角色-权限映射:角色和权限(资源)建立对应关系。角色表ID对应资源表ID
二、实战应用
1. 建立用户、角色、资源实体类
package com.wqbr.domain;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
/**
* 系统用户,封装用户数据,实现 UserDetails 接口
* @author lv
* @date 2024年1月11日
*/
public class SysUser implements UserDetails {
private static final long serialVersionUID = 1L;
private String id;
private String username; //从UserDetails的重写方法中返回
private String password; //从UserDetails的重写方法中返回
private Date addtime;
private boolean accountnonexpired; //账户是否过期,从UserDetails的重写方法中返回
private boolean accountnonlocked; //账户是否锁定,从UserDetails的重写方法中返回
private boolean credentialsnonexpired; //密码是否过期,从UserDetails的重写方法中返回
private boolean enabled; //账户是否可用,从UserDetails的重写方法中返回
// 储存用户拥有的所有权限
private List<GrantedAuthority> authorities = new ArrayList<>(); //从UserDetails的重写方法中返回
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
public Date getAddtime() {
return addtime;
}
public void setAddtime(Date addtime) {
this.addtime = addtime;
}
// 返回用户权限,上面声明了权限集合对象 authorities
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
public void setAuthorities(List<GrantedAuthority> authorities) {
this.authorities = authorities;
}
// 返回用户密码,上面声明了属性 password
@Override
public String getPassword() {
return password;
}
// 返回用户名,上面声明了属性 username
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return accountnonexpired;
}
public void setAccountnonexpired(boolean accountnonexpired) {
this.accountnonexpired = accountnonexpired;
}
@Override
public boolean isAccountNonLocked() {
return accountnonlocked;
}
public void setAccountnonlocked(boolean accountnonlocked) {
this.accountnonlocked = accountnonlocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsnonexpired;
}
public void setCredentialsnonexpired(boolean credentialsnonexpired) {
this.credentialsnonexpired = credentialsnonexpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
}
package com.wqbr.domain;
/**
* 系统角色
* @author lv
* @date 2024年1月16日
*/
public class SysRole {
private String id;
private String available;
private String description;
private String role;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getAvailable() {
return available;
}
public void setAvailable(String available) {
this.available = available;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
}
package com.wqbr.domain;
/**
* 系统资源
* @author lv
* @date 2024年1月16日
*/
public class SysPermission {
private String id;
private String available;
private String name;
private String parent_id;
private String parent_ids;
private String permission;
private String resource_type;
private String url;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getAvailable() {
return available;
}
public void setAvailable(String available) {
this.available = available;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getParent_id() {
return parent_id;
}
public void setParent_id(String parent_id) {
this.parent_id = parent_id;
}
public String getParent_ids() {
return parent_ids;
}
public void setParent_ids(String parent_ids) {
this.parent_ids = parent_ids;
}
public String getPermission() {
return permission;
}
public void setPermission(String permission) {
this.permission = permission;
}
public String getResource_type() {
return resource_type;
}
public void setResource_type(String resource_type) {
this.resource_type = resource_type;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
关于这3个表的建表语句,数据插入语句请参考此文(数据库oracle)spirng框架之spring security(二)insert 语句补充-CSDN博客
2. 数据层查询角色资源
定义SystemDao接口类的查询方法
/**
* 查询当前用户拥有的资源
*/
public List<SysPermission> findPermissionByUsername(String username);
SystemDao.xml(mybatis的mapper文件)查询语句
<!--查询当前用户拥有的资源-->
<select id="findPermissionByUsername" parameterType="String" resultType="com.wqbr.domain.SysPermission">
select d.*
from sys_user a, sys_user_role b, sys_role_permission c, sys_permission d
where a.id = b.user_id and b.role_id = c.role_id and c.permission_id = d.id
and a.username = #{username}
</select>
3. 业务层实现,调用数据层查询接口
package com.wqbr.service;
import com.wqbr.domain.SysPermission;
import java.util.List;
/**
* 系统服务接口
* @author lv
* @date 2024年1月11日
*/
public interface SystemService {
/**
* 查询当前用户拥有的资源
*/
public List<SysPermission> findPermissionByUsername(String username);
}
package com.wqbr.service.impl;
import com.wqbr.domain.SysPermission;
import com.wqbr.persistence.SystemDao;
import com.wqbr.service.SystemService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 系统服务接口实现
* @author lv
* @date 2024年1月11日
*/
@Service
public class SystemServiceImpl implements SystemService {
@Autowired
private SystemDao systemDao;
@Override
public List<SysPermission> findPermissionByUsername(String username) {
return systemDao.findPermissionByUsername(username);
}
}
4. SystemController控制器菜单获取方法
先新建一个菜单数据装载实体类Menus
package com.wqbr.domain;
/**
* 定义的实体类,保存菜单数据
*/
public class Menus {
private long id;
private String name;
private Long parentId;
private Long parentIds;
private String url;
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Long getParentId() {
return parentId;
}
public void setParentId(Long parentId) {
this.parentId = parentId;
}
public Long getParentIds() {
return parentIds;
}
public void setParentIds(Long parentIds) {
this.parentIds = parentIds;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
SystemController类中的方法
@GetMapping("/findMenu")
public ModelAndView findMenus(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
ModelAndView model = new ModelAndView("main/menu");
SysUser user = (SysUser) authentication.getPrincipal();
String username=user.getUsername();
if(username!=null){
List<Menus> listMenu = new ArrayList<>();
List<SysPermission> pList = systemService.findPermissionByUsername(username);
System.out.println("=-----=大小为:"+pList.size());
for (SysPermission permission : pList) {
if (permission.getResource_type().equals("menu")) {
Menus menu = new Menus();
menu.setId(Long.parseLong(permission.getId()));
menu.setName(permission.getName());
menu.setParentId(Long.parseLong(permission.getParent_id()));
menu.setParentIds(Long.parseLong(permission.getParent_ids()));
menu.setUrl(permission.getUrl());
listMenu.add(menu);
}
}
//request.setAttribute("listMenus", listMenu);
model.addObject("listMenu",listMenu);
// for (Menus menus : listMenu) {
// if (menus.getParentId() == 10000) { //10000为数据库中的值
// System.out.println("==" + menus.getName() + "[" + menus.getUrl() + "]");
// for (Menus menusch : listMenu) {
// if (menus.getId() == menusch.getParentId()) {
// System.out.println("---------" + menusch.getName() + "[" + menusch.getUrl() + "]");
// }
// }
// }
// }
}
return model;
}
到此已经可以运行项目,登录成功后在浏览器地址栏调用控制器的 /findMenu 请求在控制台打印出菜单结构。
5. menu.jsp菜单页面实现
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
<html>
<head>
<title>菜单列表</title>
</head>
<body>
<div><h2>导航菜单</h2></div>
<!-- 菜单开始 -->
<div>
<h4>列表大小:${listMenu.size()}</h4>
<c:choose>
<c:when test="${listMenu.size()>0}">
<dl>
<c:forEach var="lstMenu" items="${listMenu}" varStatus="status">
<c:if test="${lstMenu.parentId eq 10000}">
<dt style="height: 28px;">
<span>■</span>
<span onclick="alert('■父菜单${status.index}')">${lstMenu.name}</span>:
<span>${lstMenu.url}</span>
</dt>
<c:forEach var="menuChild" items="${listMenu}">
<c:if test="${menuChild.parentId eq lstMenu.id}">
<dd style="height: 25px;">
<span>●</span>
<span onclick="alert('●子菜单${status.index}')">${menuChild.name}</span>:
<span>${menuChild.url}</span>
</dd>
</c:if>
</c:forEach>
</c:if>
</c:forEach>
</dl>
</c:when>
<c:otherwise>
<p>没有查询到菜单列表!</p>
</c:otherwise>
</c:choose>
</div>
<!-- 菜单结束 -->
</body>
</html>
菜单页面实现结束,现在运行项目查看页面效果。
登录成功后浏览器输入:http://localhost:8080/wqdemotwo_war/system/findMenu
用户表中有两个用户:
zhangsan用户登录成功后的菜单资源: