SpringSecurity权限管理系统实战—1、项目简介和开发环境准备
SpringSecurity权限管理系统实战—2、日志、接口文档等实现
SpringSecurity权限管理系统实战—3、主要页面及接口实现
SpringSecurity权限管理系统实战—4、整合SpringSecurity(上)
SpringSecurity权限管理系统实战—5、整合SpringSecurity(下)
SpringSecurity权限管理系统实战—6、SpringSecurity整合jwt
SpringSecurity权限管理系统实战—7、处理一些问题
SpringSecurity权限管理系统实战—8、AOP 记录用户日志、异常日志css
这几天的时间去弄博客了,这个项目就被搁在一边了。
在以前我是用wordpress来搭的博客,用的阿里云的学生机,就卡的不行,体验极差,也没有发布过多少内容。后来又想着本身写一个博客系统,后台部分已经开发了大半,懒癌犯了,就一直搁置了(图片上的全部能点击的接口都实现了)。如今回过去一看,接口十分混乱,冗余。可能不会再用来做为本身的博客了(随便再写写,作个毕设项目吧)html
而后又想着用静态博客,绕来绕去后,最终选用了vuepress来搭建静态博客,部署的时候又顺带着复习了下git的知识(平时idea插件用的搞得我git命令都忘得差很少了)。如今的博客是根据vuepress-theme-roco主题魔改的,给张照片感觉下前端
已经部署到github pages。能够访问www.codermy.cn查看。 目前尚未备案成功,还没有配置cdn,因此可能会加载有点慢。国内也能够访问 witmy.gitee.io 查看。vue
Spring Security 是Spring项目之中的一个安全模块,能够很是方便与spring项目集成。自从有了 Spring Boot 以后,Spring Boot 对于 Spring Security 提供了 自动化配置方案,能够零配置使用 Spring Security。java
其实Spring Security 最先不叫 Spring Security ,叫 Acegi Security,后来才发展成为Spring的子项目。因为SpringBoot的大火,让Spring系列的技术都获得了很是多的关注度,SpringSecurity一样也沾了一把光。jquery
通常来讲,Web 应用的安全性包括两部分:git
简单来讲,认证就是登陆,受权其实就是权限的鉴别,看用户是否具有相应请求的权限。github
在SpringBoot中想要使用SpringSecurity,只要添加SpringSecurity的依赖便可web
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
这个依赖在最初给的pom中已经有了,不过给注释了,取消掉就能够,其他什么都不用作,启动项目。spring
启动完成后,咱们访问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>(); // 一、获取第一级节点 for (MenuIndexDto menu : list) { if(menu.getParentId() == 0) { result.add(menu); } } // 二、递归获取子节点 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);
重启项目,这时须要咱们输入正确的验证码后才能进行登陆
剩下的一些咱们下一节再来完成
本系列gitee和github中同步更新