SpringSecurity权限管理系统实战—四、整合SpringSecurity(上)
目录
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 应用的安全性包括两部分:
- 用户认证(Authentication)
- 用户授权(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);
重启项目,这时需要我们输入正确的验证码后才能进行登录
剩下的一些我们下一节再来完成
本系列gitee和github中同步更新
- 视频教程-springboot2.0企业中台实战之权限统一管理与应用统一授权 (dubbo分布式系统实战)-Java...
- thinkphp整合系列之rbac的升级版auth权限管理系统demo
- SVN+Spring+SpringMVC+MyBatis+Mysql+Maven+Myeclipse整合开发实战:酒店管理系统
- Asp.Net Core 项目实战之权限管理系统(8) 功能菜单的动态加载
- SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例
- SpringBoot整合mybatis、shiro、redis实现基于数据库的细粒度动态权限管理系统实例
- 基于SpringSecurity3.x, JasperReport5.x等技术实现仿金蝶权限管理的企业信息管理系统
- SSM框架整合(企业权限管理系统)
- 视频教程-基于Servlet+JDBC+Bootstrap+MySQL+AJAX权限管理系统实战教程-Java
- Asp.Net Core 项目实战之权限管理系统(4) 依赖注入、仓储、服务的多项目分层实现
- [权限管理系统(四)]-spring boot +spring security短信认证+redis整合
- SQL注入实战---利用“dbo”获得SQL管理权限和系统权限
- SQL注入实战---利用“dbo”获得SQL管理权限和系统权限
- 权限管理系统(SSM整合)
- Asp.Net Core 项目实战之权限管理系统(8) 功能菜单的动态加载
- SpringBoot2.0 整合 SpringSecurity 框架,实现用户权限安全管理
- Asp.Net Core 项目实战之权限管理系统(2) 功能及实体设计
- Asp.Net Core 项目实战之权限管理系统(7) 组织机构、角色、用户权限
- SQL注入实战---利用“dbo”获得SQL管理权限和系统权限
- 接单,开发,学习神器--基于SpringSecurity的后台权限管理系统