目录

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. Python如何向SQLServer存储二进制图片

    需求是需要用python往 SqlServer中的image类型字段中插入二进制图片 核心代码,研究好几个小时的代码: 安装pywin32,adodbapi image_url = "图片链 ...

  2. POJ2774 --后缀树解法

    POJ2774 Long Long Message --后缀树解法 原题链接 题意明确说明求两字符串的最长连续公共子串,可用字符串hash或者后缀数据结构来做 关于后缀树 后缀树的原理较为简单,但 \ ...

  3. JMS资源文件下载列表

    网关程序(Gateway) https://files.cnblogs.com/files/IWings/Gateway.zip 网关裁判程序(GatewayReferee) https://file ...

  4. 重学c#系列——c# 托管和非托管资源与代码相关(四)

    前言 这是续第三节. 概况垃圾回收与我们写代码的关系: 强引用和弱引用 针对共享 Web 承载优化 垃圾回收和性能 应用程序域资源监视 正文 强引用和弱引用 垃圾回收器不能回收仍在引用的对象的内存-- ...

  5. SUCTF2019-web Easyweb

    <?php function get_the_flag(){ // webadmin will remove your upload file every 20 min!!!! $userdir ...

  6. sed 指定行范围匹配

    sed -n '5,10{/pattern/p}' file sed是一个非交互性性文本编辑器,它编辑文件或标准输入 导出的文件拷贝.标准输入可能是来自键盘.文件重定向.字符串或变量,或者是一个管道文 ...

  7. 数据结构C语言实现----顺序查找

     建立上图的一个txt文件: 1004 TOM 1001002 lily 951001 ann 931003 lucy 98 用一个c程序读入这个表一个结构体数组中: 结构体如下: //学生数据结构体 ...

  8. Python os.utime() 方法

    概述 os.utime() 方法用于设置指定路径文件最后的修改和访问时间.高佣联盟 www.cgewang.com 在Unix,Windows中有效. 语法 utime()方法语法格式如下: os.u ...

  9. vjudge CountTables/2018雅礼集训 方阵 dp 斯特林反演

    LINK:CountTables 神题! 首先单独考虑行不同的情况 设\(f_i\)表示此时有i列且 行都不同. 那么显然有 \(f_i=(c^i)^\underline{n}\) 考虑设\(g_i\ ...

  10. MR程序的几种提交运行模式

    本地模式运行 1-在windows的eclipse里面直接运行main方法 将会将job提交给本地执行器localjobrunner 输入输出数据可以放在本地路径下 输入输出数据放在HDFS中:(hd ...