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

目录

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

1、Spring Security 介绍

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

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

简单来讲,认证就是登陆,受权其实就是权限的鉴别,看用户是否具有相应请求的权限。github

2、整合SpringSecurity

在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页面了

在这里插入图片描述

3、自定义登陆页

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>

重启项目查看

在这里插入图片描述

4、动态获取菜单

目前咱们的项目仍是根据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();

再重启项目,就能够正常访问了。

5、改写菜单路由

以前菜单的路由咱们是写再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";
    }
}

再去相应页面改写下路由就能够

6、图形验证码

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

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中同步更新

相关文章
相关标签/搜索