SpringSecurity之授权
1. 写在前面的话
此文并非教程, 而是个人学习SpringSecurity时的记录以及一些踩坑解决
如果能帮到大家, 那就让人非常开心了
另外, SpringSecurity的授权分为web授权和方法授权, 本文只说明了web授权, 方法授权实际上就是SpringSecurity控制对方法和接口的访问, 需要学习的小伙伴可以自行学习, 笔者在今后的工作中如果用到的话也会回来添加相关内容
好了, 让我们看看笔者的一些学习心得吧
2. web授权
个人认为授权较之认证简单了许多, 大概是本人在认证把该踩的坑都踩了一遍吧...
首先, 我们需要建立数据库
1. 建库
我们需要以下的几张表
角色表
权限表
用户表(这个在认证中已经建立了)
角色权限关联表 (此处为管理员有1和2两个权限)
用户角色关联表
- 关联表是为了方便拓展, 正常的业务都是这样的, 存在一对多和多对多的关系
2. 添加查询权限的接口
权限的信息也是存放在UserDetails中的, 因此我们也要实现通过用户id查询用户对应权限的功能
建立对应的实体类(此处省略)
添加接口以及其实现类
dao
//根据用户id查询用户权限 List<PermissionDTO> getPermissionByUserId(String userId);
service接口
List<PermissionDTO> getPermissionByUserId(String userId);
接口实现类
@Override public List<PermissionDTO> getPermissionByUserId(String userId) { return userMapper.getPermissionByUserId(userId); }
在xml中添加查询的sql
<!--根据用户id查询用户权限--> <select id="getPermissionByUserId" parameterType="string" resultType="permissionDTO"> select * from t_permission where `id` in ( select permission_id from t_role_permission where role_id = ( select role_id from t_user_role where user_id = #{userId} ) ) </select>
- 注意, 这里我们使用了子查询, 同时, 由于会查出多条结果, 我们要酌情考虑是否要使用 in 语句, 将多个结果放在select语句的条件中
3. 前端页面的编写
我们使用的是 layui的默认后端模板
模板的定义
先编写一个后台模板文件
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <title>layout 后台大布局 - Layui</title> <link rel="stylesheet" th:href="@{css/layui.css}"> </head> <body class="layui-layout-body"> <div class="layui-layout layui-layout-admin"> <div class="layui-header"> <div class="layui-logo">layui 后台布局</div> <!-- 头部区域(可配合layui已有的水平导航) --> <ul class="layui-nav layui-layout-left"> <li class="layui-nav-item"><a href="">控制台</a></li> <li class="layui-nav-item"><a href="">商品管理</a></li> <li class="layui-nav-item"><a href="">用户</a></li> <li class="layui-nav-item"> <a href="javascript:;">其它系统</a> <dl class="layui-nav-child"> <dd><a href="">邮件管理</a></dd> <dd><a href="">消息管理</a></dd> <dd><a href="">授权管理</a></dd> </dl> </li> </ul> <ul class="layui-nav layui-layout-right"> <li class="layui-nav-item"> <a href="javascript:;"> <img src="http://t.cn/RCzsdCq" class="layui-nav-img"> <span sec:authentication="principal.username"></span> </a> <dl class="layui-nav-child"> <dd><a href="">基本资料</a></dd> <dd><a href="">安全设置</a></dd> </dl> </li> <li class="layui-nav-item"><a id="logout" href="javascript:void(0);" onclick="logout()">退了</a></li> </ul> </div> <div class="layui-side layui-bg-black"> <div class="layui-side-scroll"> <!-- 左侧导航区域(可配合layui已有的垂直导航) --> <ul class="layui-nav layui-nav-tree" lay-filter="test"> <li class="layui-nav-item layui-nav-itemed"> <a class="" href="javascript:;">所有商品</a> <dl class="layui-nav-child"> <dd><a href="javascript:;">列表一</a></dd> <dd><a href="javascript:;">列表二</a></dd> <dd><a href="javascript:;">列表三</a></dd> <dd><a href="">超链接</a></dd> </dl> </li> <li class="layui-nav-item"> <a href="javascript:;">解决方案</a> <dl class="layui-nav-child"> <dd><a href="javascript:;">列表一</a></dd> <dd><a href="javascript:;">列表二</a></dd> <dd><a href="">超链接</a></dd> </dl> </li> <li class="layui-nav-item"><a href="">云市场</a></li> <li class="layui-nav-item"><a href="">发布商品</a></li> </ul> </div> </div> <!-- <div class="layui-body">--> <!-- <!– 内容主体区域 –>--> <!-- <div style="padding: 15px;">内容主体区域</div>--> <!-- </div>--> <div class="layui-footer"> <!-- 底部固定区域 --> © layui.com - 底部固定区域 </div> </div> <script type="text/javascript" th:src="@{js/jquery.min.js}"></script> <script type="text/javascript" th:src="@{js/jquery-ui.min.js}"></script> <script type="text/javascript" th:src="@{js/jquery.mockjax.js}"></script> <script th:src="@{layui.js}"></script> <script> //JavaScript代码区域 layui.use('element', function () { var element = layui.element; }); function logout() { layui.use('layer', function () { //退出登录 layer.confirm('确定要退出么?', {icon: 3, title: '提示'}, function (index) { //do something let url = '/logout'; $.ajax({ url: url, type: "post", dataType: "json", contentType: "application/json;charset=utf-8", success: function (data) { alert("进入success---"); let code = data.code; let url = data.url; let msg = data.msg; if (code == 203) { alert(msg); window.location.href = url; } else { alert("未知错误!"); } }, error: function (xhr, textStatus, errorThrown) { alert("进入error---"); alert("状态码:" + xhr.status); alert("状态:" + xhr.readyState); //当前状态,0-未初始化,1-正在载入,2-已经载入,3-数据进行交互,4-完成。 alert("错误信息:" + xhr.statusText); alert("返回响应信息:" + xhr.responseText);//这里是详细的信息 alert("请求状态:" + textStatus); alert(errorThrown); alert("请求失败"); } }); layer.close(index); }); }); } </script> </body> </html>
- 注意
- 我们为了获得SpringSecurity中的用户名, 使用了SpringSecurity的thymeleaf方言, 可以实现细粒度的控制(依据权限是否显示某些标签)
- 引入命名空间, 这样就有代码提示了(不引入其实也无所谓~) xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
- 注意
测试用页面
为了测试权限, 我们定义了三个按钮, 分别对应三个权限(我们在下一节的SpringSecurity配置类中可以看到)
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>页面</title> </head> <body class="layui-layout-body"> <div th:include="content/layout"></div> <div class="layui-layout layui-layout-admin"> <div class="layui-body"> <!-- 内容主体区域 --> <div style="padding: 15px;">成功应用模板!</div> <div class="layui-btn-container"> <button type="button" class="layui-btn" onclick="btnClick1()">按钮一</button> <button type="button" class="layui-btn" onclick="btnClick2()">按钮二</button> <button type="button" class="layui-btn" onclick="btnClick3()">按钮三</button> </div> </div> </div> <script> $.ajaxSetup({ type: "post", dataType: "json", contentType: "application/json;charset=utf-8", success: function (data) { let code = data.code; let url = data.url; let msg = data.msg; if (code == 204) { alert(msg); window.location.href = url; } else { alert("未知错误!"); } }, error: function (xhr, textStatus, errorThrown) { alert("进入error---"); alert("状态码:" + xhr.status); alert("状态:" + xhr.readyState); //当前状态,0-未初始化,1-正在载入,2-已经载入,3-数据进行交互,4-完成。 alert("错误信息:" + xhr.statusText); alert("返回响应信息:" + xhr.responseText);//这里是详细的信息 alert("请求状态:" + textStatus); alert(errorThrown); alert("请求失败"); } }); function btnClick1() { let url = "/toR1"; $.ajax({ url: url, type: "post", dataType: "json", contentType: "application/json;charset=utf-8", }); } function btnClick2() { let url = "/toR2"; $.ajax({ url: url, type: "post", dataType: "json", contentType: "application/json;charset=utf-8", }); } function btnClick3() { let url = "/toR3"; $.ajax({ url: url, type: "post", dataType: "json", contentType: "application/json;charset=utf-8", }); } </script> </body> </html>
4. SpringSecurity配置
//授权
http
.authorizeRequests()
.antMatchers("/r/r1").hasAnyAuthority("p1")
.antMatchers("/r/r2").hasAnyAuthority("p2")
.antMatchers("/r/r3").access("hasAuthority('p1') and hasAuthority('p2')")
.antMatchers("/r/**").authenticated().anyRequest().permitAll();
在配置类中配置授权的规则
注意
- Authority和Role都是角色管理, 区别是Role会加一个 ROLE_ 前缀, 具体的区别可以在网上自行查看, 一般来说, 我们使用Authority就行了
5. Controller配置
- RestController处理AJAX
package com.wang.spring_security_framework.controller;
import com.alibaba.fastjson.JSON;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class RestfulJumpController {
@RequestMapping("/toR1")
public String toR1Page() {
String url = "/r/r1";
String code = "204";
String msg = "即将跳转!";
Map<String, String> resultMap = new HashMap<>();
resultMap.put("url", url);
resultMap.put("code", code);
resultMap.put("msg", msg);
return JSON.toJSONString(resultMap);
}
@RequestMapping("/toR2")
public String toR2Page() {
String url = "/r/r2";
String code = "204";
String msg = "即将跳转!";
Map<String, String> resultMap = new HashMap<>();
resultMap.put("url", url);
resultMap.put("code", code);
resultMap.put("msg", msg);
return JSON.toJSONString(resultMap);
}
@RequestMapping("/toR3")
public String toR3Page() {
String url = "/r/r3";
String code = "204";
String msg = "即将跳转!";
Map<String, String> resultMap = new HashMap<>();
resultMap.put("url", url);
resultMap.put("code", code);
resultMap.put("msg", msg);
return JSON.toJSONString(resultMap);
}
}
我们用RestController处理了Ajax的跳转请求, 并返回了JSON信息, 让前端进行url跳转
- 测试的授权Controller
这里的Controller用于显示对应的资源目录下的一些内容, 其中我们从会话中获取了当前登录的用户名
package com.wang.spring_security_framework.controller;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/r")
public class AuthorizeTestController {
//从会话中获取当前登录用户名
private String getUserName(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//未登录, 返回null
if(!authentication.isAuthenticated()) {
return null;
}
Object principal = authentication.getPrincipal();
String username;
if (principal instanceof UserDetails) {
username = ((UserDetails) principal).getUsername();
} else {
username = principal.toString();
}
return username;
}
@RequestMapping("/r1")
public String R1() {
return getUserName() + "访问资源1";
}
@RequestMapping("/r2")
public String R2() {
return getUserName() + "访问资源2";
}
@RequestMapping("/r3")
public String R3() {
return getUserName() + "访问资源3";
}
}
3. 结语
本篇看起来很简单, 事实上也确实很简单......
主要需要理解的是数据库的建立以及关联表的处理, 其中的SQL语句才是最难搞的
访问没有授权的页面, 会爆出 405 错误, 我们可以自定义错误页面来处理这种错误(此处就不叙述了, 属于SpringBoot的内容~)
欢迎小伙伴批评与交流~