前言
這章主要通過SpringSecurity來實作對權限的控制,權限粒度是到每個方法。
一、token驗證
第四章登入我們擷取到了token,每次請求的時候都必須驗證這個token是否合法、是否過期,是以我們需要一個攔截器來攔截每一次的請求;這裡我們可以通過繼承OncePerRequestFilter來實作我們對token的驗證;當然并不是所有請求都需要攔截,是以還需要一個白名單,來配置不需要被攔截的請求。
@Slf4j
@Configuration
@ConfigurationProperties(prefix = "security.white")
public class PermitUrlProperties {
@Getter
@Setter
private List<String> urls = new ArrayList<>();
}
yml配置:
security:
white:
urls:
- /login
- /logout
JwtAuthenticationTokenFilter
/**
* token攔截驗證
*/
@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private JWTUtil jwtUtil;
@Autowired
private PermitUrlProperties permitUrlProperties;
@Override
protected void initFilterBean() throws ServletException {
System.out.println("JwtAuthenticationTokenFilter初始化...");
}
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
String requestUrl = httpServletRequest.getRequestURI();
log.info("請求url:{}", requestUrl);
// 白名單url放過
if (filterWhiteUrl(requestUrl)) {
filterChain.doFilter(httpServletRequest, httpServletResponse);
return;
}
String authToken = httpServletRequest.getHeader(SecurityConstants.AUTHORIZATION);
if (StrUtil.isBlank(authToken)) {
Result<String> result = Result.fail();
result.setMsg("未登入");
ResponseUtil.response(httpServletResponse, result);
return;
}
boolean checkToken = jwtUtil.checkToken(authToken);
if (checkToken) {
Result<String> result = Result.fail();
result.setMsg("會話已過期,請重新登入");
httpServletResponse.setStatus(HttpStatus.HTTP_UNAUTHORIZED);
ResponseUtil.response(httpServletResponse, result);
return;
}
if (SecurityContextHolder.getContext().getAuthentication() == null) {
//Context中的認證為空,進行token驗證
Claims claims = jwtUtil.getClaimsFromToken(authToken);
//從jwt中恢複使用者資訊和權限
String id = claims.get(JWTUtil.ID, String.class);
String orgId = claims.get(JWTUtil.ORGID, String.class);
String username = claims.get(JWTUtil.USERNAME, String.class);
String authorities = claims.get(JWTUtil.AUTHORITIES, String.class);
List<String> list = JSON.parseObject(authorities, new TypeReference<List<String>>() {
});
JwtUser jwtUser = new JwtUser(id, orgId, username, "", AuthorityUtils.createAuthorityList(list.toArray(new String[0])));
//如username不為空,并且能夠在資料庫中查到
JwtAuthenticationToken jwtAuthenticationToken =
new JwtAuthenticationToken(jwtUser.getAuthorities(), jwtUser, null);
//将authentication放入SecurityContextHolder中
SecurityContextHolder.getContext().setAuthentication(jwtAuthenticationToken);
}
filterChain.doFilter(httpServletRequest, httpServletResponse);
}
/**
* 過濾表名單的url
*
* @param url
* @return
*/
private boolean filterWhiteUrl(String url) {
List<String> whiteList = permitUrlProperties.getUrls();
if (CollectionUtil.isNotEmpty(whiteList)) {
PathMatcher matcher = new AntPathMatcher();
for (String releaseUrl : whiteList) {
boolean match = matcher.match(releaseUrl, url);
if (match) {
return true;
}
}
}
return false;
}
}
更新下SpringSecurityConfigurer,将JwtAuthenticationTokenFilter加入配置中,部分代碼如下:
http.addFilterAfter(jwtAuthenticationTokenFilter(),UsernamePasswordAuthenticationFilter.class);
@Bean
JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter() {
return new JwtAuthenticationTokenFilter();
}
經過一系列的編譯調試後,啟動項目驗證:
1、擷取token
2、不帶token通路首頁
3、帶token通路首頁
4、token錯誤和過期通路首頁
二、權限驗證
1、開啟全局安全配置
在SpringSecurityConfigurer上加上@EnableGlobalMethodSecurity(prePostEnabled = true)就可以了;他會解鎖 @PreAuthorize 和 @PostAuthorize 兩個注解,@PreAuthorize 會在方法執行前進行驗證, @PostAuthorize 會在方法執行後進行驗證。
2、标記需要校驗的方法
我們在IndexController上面加上權限校驗,即@PreAuthorize("hasAuthority('sys:index')")
3、自定義未授權處理器
實作AccessDeniedHandler的handle接口即可
JwtAccessDeniedHandler
/**
* 未授權通路處理
*/
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
Result<String> result = Result.fail(e.getMessage());
httpServletResponse.setStatus(HttpStatus.HTTP_FORBIDDEN);
ResponseUtil.response(httpServletResponse, result);
}
}
把這個加到SpringSecurityConfigurer裡面,新增代碼如下:
.exceptionHandling((execption) -> execption
// 未授權異常處理
.accessDeniedHandler(new JwtAccessDeniedHandler()));
測試未授權
測試已授權
在JwtUserDetailsServiceImpl的權限清單中加入我們剛剛加的權限标記sys:index
重新登入,擷取新的token,并請求首頁,發現能夠正常通路
3、通過資料庫配置權限
前面都是寫死的權限,實際項目都是從資料庫中查詢的,這個項目我們采用RBAC 基于角色的通路控制,将所有權限都賦給角色,将角色賦給具體的使用者。
3.1、表設計
使用者表sys_user,用來存放使用者名、密碼等基礎資訊
CREATE TABLE `sys_user` (
`id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '主鍵ID',
`username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '使用者名',
`password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '電話',
`avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '頭像',
`org_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '機構ID',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時間',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT '1' COMMENT '1-正常,0-鎖定',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT '1' COMMENT '邏輯删除标記(1:顯示;0:删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `uk_username`(`username`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '使用者表' ROW_FORMAT = Dynamic;
組織機構表sys_org
CREATE TABLE `sys_org` (
`id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`parent_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`sort` int NULL DEFAULT 1 COMMENT '排序',
`type` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '機構類型',
`code` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '機構編碼',
`name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '機構名稱',
`phone` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '電話',
`email` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '郵箱',
`address` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '位址',
`remarks` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '備注',
`del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '1' COMMENT '邏輯删除标記(1:顯示;0:删除)',
`status` char(2) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '1:正常,0:鎖定',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
`update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時間',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci COMMENT = '機構管理' ROW_FORMAT = Dynamic;
菜單表sys_menu,存放對應的菜單和權限辨別
CREATE TABLE `sys_menu` (
`id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜單ID',
`title` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜單名稱',
`permission` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '權限辨別',
`parent_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '父菜單ID',
`sort` int NOT NULL DEFAULT 0 COMMENT '排序值',
`type` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '菜單類型 (0菜單 1按鈕)',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新時間',
`del_flag` char(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '1' COMMENT '邏輯删除标記(1:顯示;0:删除)',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '菜單權限表' ROW_FORMAT = Dynamic;
角色表sys_role
CREATE TABLE `sys_role` (
`id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '主鍵',
`role_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '角色名',
`role_code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL COMMENT '角色編碼',
`role_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT NULL COMMENT '角色描述',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
`update_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時間',
`del_flag` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NULL DEFAULT '1' COMMENT '邏輯删除标記(1:顯示;0:删除)',
PRIMARY KEY (`id`) USING BTREE,
UNIQUE INDEX `role_id_role_code`(`role_code`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_bin COMMENT = '系統角色表' ROW_FORMAT = Dynamic;
角色菜單關系表sys_role_menu,一個角色擁有哪些菜單的權限
CREATE TABLE `sys_role_menu` (
`role_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色ID',
`menu_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '菜單ID',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
PRIMARY KEY (`role_id`, `menu_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '角色菜單表' ROW_FORMAT = Dynamic;
使用者角色表sys_user_role,一個使用者擁有哪些角色
CREATE TABLE `sys_user_role` (
`user_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '使用者ID',
`role_id` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '角色ID',
`create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立時間',
PRIMARY KEY (`user_id`, `role_id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '使用者角色表' ROW_FORMAT = Dynamic;
使用者和角色是一對多的關系,角色和菜單也是一對多的關系
3.2、建立實體和實作CRUD
寫這些類其實是一個重複的工作,把這個項目寫完了,一定要做一個代碼生成器,一個一個地敲太費時費力了!!
4、測試驗證
4.1、初始化資料
之前我們開啟了權限驗證,現在初始化資料的時候先關一下;隻需要注釋掉SpringSecurityConfigurer上的@EnableGlobalMethodSecurity(prePostEnabled = true)這個注即可。
接口文檔:從零開始手打一個權限管理系統
初始化菜單資料
取消注釋,發送登入請求,可以發現權限資訊已經全部寫進去了,大家會發現新生成的token會比之前大很多,因為寫入了權限資訊,具體代碼可看JWTUtil的createToken方法
測試通路沒有權限的首頁
測試有權限的使用者新增
看看能不能登入
到這裡,這個系統的基本功能大部分都完成了,接下來我将繼續完善和優化細節!!!
目前版本:1.0.4
[代碼倉庫](https://gitee.com/ailot/study)
三、 體驗位址(http://test.ailot.vip)
背景資料庫隻給了部分權限,報錯屬于正常! 想學的老鐵給點點關注吧!!! 後期會開源前後端所有代碼!!!
我是阿咕噜,一個從網際網路慢慢上岸的程式員,如果喜歡我的文章,記得幫忙點個贊喲,謝謝!