您的位置:首页 > 编程语言 > Java开发

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

2020-08-18 15:08 1471 查看

目录

SpringSecurity权限管理系统实战—一、项目简介和开发环境准备
SpringSecurity权限管理系统实战—二、日志、接口文档等实现
SpringSecurity权限管理系统实战—三、主要页面及接口实现
SpringSecurity权限管理系统实战—四、整合SpringSecurity(上)
SpringSecurity权限管理系统实战—五、整合SpringSecurity(下)
SpringSecurity权限管理系统实战—六、SpringSecurity整合jwt
SpringSecurity权限管理系统实战—七、处理一些问题
SpringSecurity权限管理系统实战—八、AOP 记录用户日志、异常日志

前言

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

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

已经部署到github pages。可以访问https://www.cnblogs.com/codermy/p/www.codermy.cn查看。 目前还没有备案成功,尚未配置cdn,所以可能会加载有点慢。国内也可以访问 https://www.cnblogs.com/codermy/p/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-ite
56c
m">
<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>

重启项目查看

25ec

四、动态获取菜单

目前我们的项目还是根据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;
@Autowi
14ff
red
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中同步更新

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐