目录

SpringSecurity权限管理系统实战—一、项目简介和开发环境准备

SpringSecurity权限管理系统实战—二、日志、接口文档等实现

SpringSecurity权限管理系统实战—三、主要页面及接口实现

SpringSecurity权限管理系统实战—四、整合SpringSecurity(上)

SpringSecurity权限管理系统实战—五、整合SpringSecurity(下)

SpringSecurity权限管理系统实战—六、SpringSecurity整合jwt

SpringSecurity权限管理系统实战—七、处理一些问题

SpringSecurity权限管理系统实战—八、AOP 记录用户日志、异常日志

前言

这几天的时间去弄博客了,这个项目就被搁在一边了。

在之前我是用wordpress来搭的博客,用的阿里云的学生机,就卡的不行,体验极差,也没有发布过多少内容。后来又想着自己写一个博客系统,后台部分已经开发了大半,懒癌犯了,就一直搁置了(图片上的所有能点击的接口都实现了)。现在回过去一看,接口十分混乱,冗余。可能不会再用来作为自己的博客了(随便再写写,做个毕设项目吧)

然后又想着用静态博客,绕来绕去后,最终选用了vuepress来搭建静态博客,部署的时候又顺带着复习了下git的知识(平时idea插件用的搞得我git命令都忘得差不多了)。现在的博客是根据vuepress-theme-roco主题魔改的,给张照片感受下

已经部署到github pages。可以访问www.codermy.cn查看。 目前还没有备案成功,尚未配置cdn,所以可能会加载有点慢。国内也可以访问 witmy.gitee.io 查看。

一、Spring Security 介绍

Spring Security 是Spring项目之中的一个安全模块,可以非常方便与spring项目集成。自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了 自动化配置方案,可以零配置使用 Spring Security。

其实Spring Security 最早不叫 Spring Security ,叫 Acegi Security,后来才发展成为Spring的子项目。由于SpringBoot的大火,让Spring系列的技术都得到了非常多的关注度,SpringSecurity同样也沾了一把光。

一般来说,Web 应用的安全性包括两部分:

  1. 用户认证(Authentication)
  2. 用户授权(Authorization)

简单来说,认证就是登录,授权其实就是权限的鉴别,看用户是否具备相应请求的权限。

二、整合SpringSecurity

在SpringBoot中想要使用SpringSecurity,只要添加SpringSecurity的依赖即可

		<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

这个依赖在最初给的pom中已经有了,不过给注释了,取消掉就可以,其余什么都不用做,启动项目。

启动完成后,我们访问http://localhost:8080或者其中的任何接口,都会重定向到登录页面。

SpringSecurity默认的用户名是user,密码则在启动项目时会打印在控制台上。

Using generated security password: 21d26148-7f1e-403a-9041-1bc62a034871

21d26148-7f1e-403a-9041-1bc62a034871就是密码,每次启动都会分配不一样的密码。SpringSecurity同样支持自定义密码,只要在application.yml中简单配置一下即可

spring:
security:
user:
name: admin
password: 123456

输入用户名密码,登录后就能访问index页面了

三、自定义登录页

SpringSecurity默认的登录页在SpringBoot2.0之后已经做过升级了,以前的更丑,就是一个没有样式的form表单。现在这个虽然好看了不少,但是感觉还是单调了些。

那么我们需要新建一个SpringSecurityConfig类继承WebSecurityConfigurerAdapter

@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { @Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/PearAdmin/**");//放行静态资源
}
/**
* anyRequest | 匹配所有请求路径
* access | SpringEl表达式结果为true时可以访问
* anonymous | 匿名可以访问
* denyAll | 用户不能访问
* fullyAuthenticated | 用户完全认证可以访问(非remember-me下自动登录)
* hasAnyAuthority | 如果有参数,参数表示权限,则其中任何一个权限可以访问
* hasAnyRole | 如果有参数,参数表示角色,则其中任何一个角色可以访问
* hasAuthority | 如果有参数,参数表示权限,则其权限可以访问
* hasIpAddress | 如果有参数,参数表示IP地址,如果用户IP和参数匹配,则可以访问
* hasRole | 如果有参数,参数表示角色,则其角色可以访问
* permitAll | 用户可以任意访问
* rememberMe | 允许通过remember-me登录的用户访问
* authenticated | 用户登录后可访问
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")//登录页面
.loginProcessingUrl("/login")//登录接口
.permitAll()
.and()
.csrf().disable();//关闭csrf
}
}

把login.html移动到static目录下,不要忘记把form表单的action替换成/login

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="utf-8">
<title></title>
<link rel="stylesheet" href="/PearAdmin/admin/css/pearForm.css" />
<link rel="stylesheet" href="/PearAdmin/component/layui/css/layui.css" />
<link rel="stylesheet" href="/PearAdmin/admin/css/pearButton.css" />
<link rel="stylesheet" href="/PearAdmin/assets/login.css" />
</head>
<body background="PearAdmin/admin/images/background.svg" >
<form class="layui-form" action="/login" method="post">
<div class="layui-form-item">
<img class="logo" src="PearAdmin/admin/images/logo.png" />
<div class="title">M-S-P Admin</div>
<div class="desc">
Spring Security 权 限 管 理 系 统 实 战
</div>
</div>
<div class="layui-form-item">
<input id="username" name="username" placeholder="用户名 : " type="text" hover class="layui-input" />
</div>
<div class="layui-form-item">
<input d="password" name="password" placeholder="密 码 : " type="password" hover class="layui-input" />
</div>
<div class="layui-form-item">
<input type="checkbox" name="" title="记住密码" lay-skin="primary" checked>
</div>
<div class="layui-form-item">
<button style="background-color: #5FB878!important;" class="pear-btn pear-btn-primary login">
登 入
</button>
</div>
</form>
<script src="/PearAdmin/component/layui/layui.js" charset="utf-8"></script>
<script>
layui.use(['form', 'element','jquery'], function() {
var form = layui.form;
var element = layui.element;
var $ = layui.jquery; $("body").on("click",".login",function(){
location.href="index"
})
})
</script>
</body>
</html>

重启项目查看

四、动态获取菜单

目前我们的项目还是根据PeaAdmin的menu.json来获取的菜单。这明显不行,没有权限的用户登录后点来点去,发现什么都用不了,这对用户体验来说非常差。所有要根据用户的id来动态的生成菜单。

首先看一下menu.json的格式。

之后的返回的json格式也要像这样才能被正确解析。

新建一个MenuIndexDto用于封装数据

@Data
public class MenuIndexDto implements Serializable {
private Integer id;
private Integer parentId;
private String title;
private String icon;
private Integer type;
private String href;
private List<MenuIndexDto> children;
}

MenuDao中新增通过用户id查询菜单的方法

 	@Select("SELECT DISTINCT sp.id,sp.parent_id,sp.name,sp.icon,sp.url,sp.type  " +
"FROM my_role_user sru " +
"INNER JOIN my_role_menu srp ON srp.role_id = sru.role_id " +
"LEFT JOIN my_menu sp ON srp.menu_id = sp.id " +
"WHERE " +
"sru.user_id = #{userId}")
@Result(property = "title",column = "name")
@Result(property = "href",column = "url")
List<MenuIndexDto> listByUserId(@Param("userId")Integer userId);

MenuService

List<MenuIndexDto> getMenu(Integer userId);

MenuServiceImpl

@Override
public List<MenuIndexDto> getMenu(Integer userId) {
List<MenuIndexDto> list = menuDao.listByUserId(userId);
List<MenuIndexDto> result = TreeUtil.parseMenuTree(list);
return result;
}

这里我写了一个工具方法,用于转换返回格式。TreeUtil添加如下方法

public static List<MenuIndexDto> parseMenuTree(List<MenuIndexDto> list){
List<MenuIndexDto> result = new ArrayList<MenuIndexDto>();
// 1、获取第一级节点
for (MenuIndexDto menu : list) {
if(menu.getParentId() == 0) {
result.add(menu);
}
}
// 2、递归获取子节点
for (MenuIndexDto parent : result) {
parent = recursiveTree(parent, list);
}
return result;
} public static MenuIndexDto recursiveTree(MenuIndexDto parent, List<MenuIndexDto> list) {
List<MenuIndexDto>children = new ArrayList<>();
for (MenuIndexDto menu : list) {
if (Objects.equals(parent.getId(), menu.getParentId())) {
children.add(menu);
}
parent.setChildren(children);
}
return parent;
}

MenuController添加如下方法

 	@GetMapping(value = "/index")
@ResponseBody
@ApiOperation(value = "通过用户id获取菜单")
public List<MenuIndexDto> getMenu(Integer userId) {
return menuService.getMenu(userId);
}

在index.html文件中把菜单数据加载地址 先换成/api/menu/index/?userId=1(这里先写死,之后自定义SpringSecurity的userdetail时再改)

启动项目,查看效果

这里显示拒绝链接是因为SpringSecurity默认拒绝frame中访问。这里我们可以写一个SuccessHandler设置Header,或者在SpringSecurityConfig重写的configure方法中添加如下配置

http.headers().frameOptions().sameOrigin();

再重启项目,就可以正常访问了。

五、改写菜单路由

之前菜单的路由我们是写再HelloController中的,现在我们规定下格式。新建AdminController

@Controller
@RequestMapping("/api")
@Api(tags = "系统:菜单路由")
public class AdminController {
@Autowired
private MenuService menuService; @GetMapping(value = "/index")
@ResponseBody
@ApiOperation(value = "通过用户id获取菜单")
public List<MenuIndexDto> getMenu(Integer userId) {
return menuService.getMenu(userId);
} @GetMapping("/console")
public String console(){
return "console/console1";
} @GetMapping("/403")
public String error403(){
return "error/403";
} @GetMapping("/404")
public String error404(){
return "error/404";
} @GetMapping("/500")
public String error500(){
return "error/500";
}
@GetMapping("/admin")
public String admin(){
return "index";
}
}

再去相应页面改写下路由就可以

六、图形验证码

验证码主要是防止机器大规模注册,机器暴力破解数据密码等危害。

EasyCaptcha是一个Java图形验证码生成工具,可生成的类型有如下几种

首先引入maven

<dependencies>
<dependency>
<groupId>com.github.whvcse</groupId>
<artifactId>easy-captcha</artifactId>
<version>1.6.2</version>
</dependency>
</dependencies>

新建一个CaptchaController

@Controller
public class CaptchaController { @RequestMapping("/captcha")
public void captcha(HttpServletRequest request, HttpServletResponse response) throws Exception {
CaptchaUtil.out(request, response);
}
}

再login.html 密码所在的div后面添加如下代码(这里我添加了一下css格式,具体不贴了,自己操作吧)

<div class="layui-form-item">
<input id="captcha" name="captcha" placeholder="验 证 码:" type="text" hover class="layui-verify" style="border: 1px solid #dcdfe6;">
<img src="/captcha" width="130px" height="44px" onclick="this.src=this.src+'?'+Math.random()" title="点击刷新"/>
</div>

重启项目来看一下

目前只是让验证码在前端绘制了出来,我们如果想要使用,还需要自定义一个过滤器

新建VerifyCodeFilter继承OncePerRequestFilter

@Component
public class VerifyCodeFilter extends OncePerRequestFilter {
private String defaultFilterProcessUrl = "/login";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
if ("POST".equalsIgnoreCase(request.getMethod()) && defaultFilterProcessUrl.equals(request.getServletPath())) {
// 登录请求校验验证码,非登录请求不用校验
HttpSession session = request.getSession();
String requestCaptcha = request.getParameter("captcha");
String genCaptcha = (String) request.getSession().getAttribute("captcha");//验证码的信息存放在seesion种,具体看EasyCaptcha官方解释
if (StringUtils.isEmpty(requestCaptcha)){
session.removeAttribute("captcha");//删除缓存里的验证码信息
throw new AuthenticationServiceException("验证码不能为空!");
}
if (!genCaptcha.toLowerCase().equals(requestCaptcha.toLowerCase())) {
session.removeAttribute("captcha");
throw new AuthenticationServiceException("验证码错误!");
}
}
chain.doFilter(request, response);
}
}

最后在SpringSecurity种配置该过滤器

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private VerifyCodeFilter verifyCodeFilter; @Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/PearAdmin/**");//放行静态资源
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().frameOptions().sameOrigin();
http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);
http.authorizeRequests()
.antMatchers("/captcha").permitAll()//任何人都能访问这个请求
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")//登录页面 不设限访问
.loginProcessingUrl("/login")//拦截的请求
.successForwardUrl("/api/admin")
.permitAll()
.and()
.csrf().disable();//关闭csrf
}
}

http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class);

重启项目,这时需要我们输入正确的验证码后才能进行登录

剩下的一些我们下一节再来完成



本系列giteegithub中同步更新

SpringSecurity权限管理系统实战—四、整合SpringSecurity(上)的更多相关文章

  1. SpringSecurity权限管理系统实战—六、SpringSecurity整合jwt

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  2. SpringSecurity权限管理系统实战—一、项目简介和开发环境准备

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  3. SpringSecurity权限管理系统实战—二、日志、接口文档等实现

    系列目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战 ...

  4. SpringSecurity权限管理系统实战—七、处理一些问题

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  5. SpringSecurity权限管理系统实战—八、AOP 记录用户、异常日志

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  6. SpringSecurity权限管理系统实战—九、数据权限的配置

    目录 SpringSecurity权限管理系统实战-一.项目简介和开发环境准备 SpringSecurity权限管理系统实战-二.日志.接口文档等实现 SpringSecurity权限管理系统实战-三 ...

  7. SpringSecurity权限管理系统实战—五、整合SpringSecurity(下)

    系列目录 前言 上篇文章SpringSecurity整合了一半,这次把另一半整完,所以本篇的序号接着上一篇. 七.自定义用户信息 前面我们登录都是用的指定的用户名和密码或者是springsecurit ...

  8. SpringSecurity权限管理系统实战—三、主要页面及接口实现

    系列目录 前言 后端五分钟,前端半小时.. 每次写js都头疼. 自己写前端是不可能的,这辈子不可能自己写前端的,只能找找别人的模板才能维持的了生存这样子.github,gitee上的模板又多,帮助文档 ...

  9. 权限管理系统(四):RBAC权限模型分类介绍

    RBAC是Role-BasedAccess Control的英文缩写,意思是基于角色的访问控制.RBAC认为权限授权实际上是Who.What.How的问题.在RBAC模型中,who.what.how构 ...

随机推荐

  1. 7.20试机测 T3 阶乘之和 暴力AC题解

    7.20试机测  T3 阶乘之和 暴力AC题解 题外话:此乃本蒟蒻发表的第一篇题解,大家多多关照,支持一下,谢谢 题面 3.阶乘之和(sum.pas/in/out) 问题描述: 给定一个非负整数 n, ...

  2. JMS微服务开发示例(一)Hello world

    网关部署 1.在网关服务器上,安装.net core 3.1运行环境: 2.到 https://www.cnblogs.com/IWings/p/13354541.html 下载Gateway.zip ...

  3. Oracle连接报错之IO异常(The Network Adapter could not establish the connection)

    简单介绍:自己封装oracle jdbc的一些常用功能jar包,自己本机玩没啥问题,给别人玩儿,发现总是抛异常 IO异常(The Network Adapter could not establish ...

  4. Java基础之函数

    函数(方法)的定义: 函数就是定义在类中的具有特定功能的一段独立的小程序. 为什么有函数:为了提高代码的复用性,对独立代码进行抽取,把抽取部分代码部分,定义成一个独立的功能,方便日后使用.Java中对 ...

  5. 学python不是一个入门教程就行,学之前你必须知道这些

    第一 学习方向:python应用方向很多,必须明确学习方向想 全栈开发:就是web应用开发,是用来写网站后台的数据分析:就是做大数据分析的,数据量级起码是千万级别的,做的是大数据分析网络爬虫:其实属于 ...

  6. 前端学习(十五):了解 Javascript

    进击のpython ***** 前端学习--了解JavaScript Javascript是一种运行在浏览器中的解释型的编程语言 还记得我们在说python的时候提过解释型和编译型的区别 在解释型语言 ...

  7. FPGA内部IP核DDS

    项目当中需要正弦信号与余弦信号,首先想到了DDS芯片,例如AD9833.AD9834.由于还需要用FPGA   做一些数据处理,后来干脆直接用FPGA 内部的DDSIP核,同时根据IP核内部的相位累加 ...

  8. PHP fwrite() 函数

    定义和用法 fwrite() 函数将内容写入一个打开的文件中. 函数会在到达指定长度或读到文件末尾(EOF)时(以先到者为准),停止运行. 如果函数成功执行,则返回写入的字节数.如果失败,则返回 FA ...

  9. 7.9 NOI模拟赛 C.走路 背包 dp 特异性

    (啊啊啊 什么考试的时候突然降智这题目硬生生没想出来. 容易发现是先走到某个地方 然后再走回来的 然后在倒着走的路径上选择一些点使得最后的得到的最多. 设\(f_{i,j}\)表示到达i这个点选择的价 ...

  10. luogu P6583 回首过去 简单数论变换 简单容斥

    LINK:回首过去 考试的时候没推出来 原因:状态真的很差 以及 数论方面的 我甚至连除数分块都给忘了. 手玩几个数据 可以发现 \(\frac{x}{y}\)满足题目中的条件当且仅当 这个是一个既约 ...