zoukankan      html  css  js  c++  java
  • 初窥SpringSecurity安全框架

    @

    作为一名开发怎能不知道大名顶顶的安全框架呢?市面上流行的安全框架有:shiro和springSecurity。那么你经常用哪个框架做安全访问控制呢?因为SpringBoot集成了SpringSecurity,所以我们这次来聊聊它

    概念

    Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

    SpringSecurity官网:官网

    对应依赖

    <dependency>
        <groupId>org.springframework.boot</groupId> 
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    

    SpringSecurity包含的模块:

    继续,我们可以看到这么一幅图,可以知道它是基于servlet过滤器实现的:

    http配置相关:

    用户密码验证:

    创建项目

    1.创建新的项目,选择thymeleaf和SpringWeb依赖
    2.新增静态资源(代码省略,主要是各角色页面,需要此部分代码可私我)
    3.添加路由控制页面跳转

    package com.springstudy.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class RouterController {
        @RequestMapping({"/","/index"})
        public String index(){
            return "index";
        }
    
        @RequestMapping("/toLogin")
        public String toLogin(){
            return "views/login";
        }
    
        // 通过restful 实现复用
        @RequestMapping("/level1/{id}")
        public String level1(@PathVariable("id") int id){
            return "views/level1/"+id;
        }
    
        @RequestMapping("/level2/{id}")
        public String level12(@PathVariable("id") int id){
            return "views/level2/"+id;
        }
    
        @RequestMapping("/level3/{id}")
        public String level3(@PathVariable("id") int id){
            return "views/level3/"+id;
        }
    }
    

    4.启动项目查看页面效果(默认登陆用户名是user,登陆密码在控制台)

    后面我们要实现对应的权限控制效果!

    自定义登陆用户和密码

    老是这样登不行啊,我么来自定义配置一下登陆用户名和密码

    # 清除thymeleaf缓存 这使得我们改动html代码不用重启项目 build一下即可生效
    spring.thymeleaf.cache=false
    # 用户名
    spring.security.user.name=admin
    # 密码
    spring.security.user.password=123456
    

    新增SecurityConfig配置类

    package com.springstudy.config;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    
    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        // 可以再这里导入userService相关配置
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            // 不同用户访问内容不同! 这里,过滤器,登录注销规则,安全配置,OAuth2配置
            // 我们平时只需要配置一些基本的规则即可!
    
            // 首页是允许所有人访问的!
            // 定制授权规则: 那些请求,哪些人可以访问!
            http.authorizeRequests()
                    .antMatchers("/").permitAll()
                    .antMatchers("/level1/**").hasRole("guest")
                    .antMatchers("/level2/**").hasRole("vip")
                    .antMatchers("/level3/**").hasRole("svip");
    
            // 自定义登录页 配置后默认登陆页将失效
            // login跳转到登录页  /login?error 登录失败
            http.formLogin()
                    .usernameParameter("username")
                    .passwordParameter("password")
                    .loginPage("/toLogin")
                    .loginProcessingUrl("/login"); // 登陆表单提交请求!
    
            // 如果注销404,因为 Security 默认是防止 csrf 跨站伪请求!
            // http.csrf().disable(); // 可能会让我们系统不安全
    
            // 注销 开启默认的注销功能!
            http.logout().logoutSuccessUrl("/"); // 注销成功后跳转至首页!
    
            // 自定义的登录页需要配置 rememberMe 的参数名,就可以绑定到我们前端的!
            // 记住我功能
            http.rememberMe().rememberMeParameter("remember");
        }
    
        // 定义用户的认证规则 (角色,密码....)
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            // 定义user1 uer2 user3 三个用户的角色权限
            // 这里我们一般在用户角色表里存储用户的角色信息
            // 密码没有加密会报500错误,这里我们只定义了用户的密码加密,但是没有定义用户的认证规则的加密方式
            auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
                    // 一个人可以拥有多个角色!
                    .withUser("user1")
                    .password(new BCryptPasswordEncoder().encode("123456"))
                    .roles("guest")
                    .and()
                    .withUser("user2")
                    .password(new BCryptPasswordEncoder().encode("123456"))
                    .roles("guest","vip")
                    .and()
                    .withUser("user3")
                    .password(new BCryptPasswordEncoder().encode("123456"))
                    .roles("guest","vip","svip");
        }
    }
    

    修改前台配置

    <!DOCTYPE html>
    <html lang="en"
          xmlns:th="http://www.thymeleaf.org"
          xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
        <title>首页</title>
        <!--semantic-ui-->
        <link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
        <link th:href="@{/test/css/qinstyle.css}" rel="stylesheet">
    </head>
    <body>
    
    <!--主容器-->
    <div class="ui container">
    
        <div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
            <div class="ui secondary menu">
                <a class="item"  th:href="@{/index}">首页</a>
                <!--登录注销-->
                <div class="right menu">
                     <!--核心类:Authentication-->
                    <!-- 如果未登录就显示登陆按钮 -->
                    <div sec:authorize="!isAuthenticated()">
                        <a class="item" th:href="@{/toLogin}">
                            <i class="address card icon"></i> 登录
                        </a>
                    </div>
    
                    <!-- 如果已登录,显示用户的信息 -->
                    <div sec:authorize="isAuthenticated()">
                        <a class="item">
                            <!--<i class="address card icon"></i>-->
                            用户名:<span sec:authentication="principal.username"></span> &nbsp;
                            角色:<span sec:authentication="principal.authorities"></span>
                        </a>
                    </div>
    
                    <div sec:authorize="isAuthenticated()">
                        <!-- 将注销请求也改成post提交即可! -->
                        <form th:action="@{/logout}" method="post">
    <!--                        <button type="submit" class="def-log-out">注销</button>-->
                            <a class="item" th:href="@{/logout}" style="text-decoration:underline;">
                                注销
                            </a>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    
        <div class="ui segment" style="text-align: center">
            <h3>Spring Security Study</h3>
        </div>
    
        <div>
            <br>
            <div class="ui three column stackable grid">
    <!--            <div sec:authorize="hasRole('vip1')">-->
                    <div class="column" sec:authorize="hasRole('guest')">
                        <div class="ui raised segment">
                            <div class="ui">
                                <div class="content">
                                    <h5 class="content">Level 1</h5>
                                    <hr>
                                    <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                                    <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                                    <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
                                </div>
                            </div>
                        </div>
                    </div>
    <!--            <div sec:authorize="hasRole('vip2')">-->
                    <div class="column" sec:authorize="hasRole('vip')">
                        <div class="ui raised segment">
                            <div class="ui">
                                <div class="content">
                                    <h5 class="content">Level 2</h5>
                                    <hr>
                                    <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
                                    <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
                                    <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
                                </div>
                            </div>
                        </div>
                    </div>
    <!--            <div sec:authorize="hasRole('vip3')">-->
                    <div class="column" sec:authorize="hasRole('svip')">
                        <div class="ui raised segment">
                            <div class="ui">
                                <div class="content">
                                    <h5 class="content">Level 3</h5>
                                    <hr>
                                    <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
                                    <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
                                    <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
                                </div>
                            </div>
                        </div>
                    </div>
            </div>
        </div>
    </div>
    <script th:src="@{/test/js/jquery-3.1.1.min.js}"></script>
    <script th:src="@{/test/js/semantic.min.js}"></script>
    </body>
    </html>
    

    重启项目验证

    可以看到3个用户,且不同的模块,实现了不同的权限控制。
    注:在我们配置注销时,可以看到springSecurity已经帮我们配置好了

    当然我们实际的处理方式是不同的角色配置不同的菜单,不会这么控制!这里只是举例说明Security权限控制以及thymeleaf权限控制语法。Sercurity还是登陆鉴权用的多!

    登陆页配置:记住我

    功能:用户没有登录的时候,就只显示导航栏!如果登录了,只显示自己权限能够看到的东西 根据不同的权限,前端展示不同的功能!

    springSecrity 和 thymealef 结合:

    添加依赖:

    <!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras- springsecurity5 -->
    <dependency>
        <groupId>org.thymeleaf.extras</groupId> 
        <artifactId>thymeleaf-extras-springsecurity5</artifactId> 
        <version>3.0.4.RELEASE</version>
    </dependency>
    

    前端代码:

    <input type="checkbox" name="remember"> 记住我
    

    后端代码:

     // 自定义的登录页需要配置 rememberMe 的参数名,就可以绑定到我们前端的! 
     // 记住我功能 
     http.rememberMe().rememberMeParameter("remember");
    
     // 定制登陆页
     http.formLogin()
        .usernameParameter("username") // 前端用户名提交参数 
        .passwordParameter("password") // 前端密码提交参数 
        .loginPage("/toLogin") // 登录页面 
        .loginProcessingUrl("/login"); // 登陆表单提交请求!
    

    启动测试看用户信息是否存在cookie,默认过期时间15天:

    可以看到对应有个remember-me的cookie,至于这个key为何是remember-me可以去看源码,一切都配置都能去源码中找到答案!删除cookie后刷新页面看看是否自动退出了!

    退出的问题

    点击退出发现报错了

    这是为什么呢?
    原来SpringSecurity禁止了get方式的退出,以防止 csrf 跨站伪请求!

    // 如果注销404,因为 Security 默认是防止 csrf 跨站伪请求!
    // http.csrf().disable(); // 可能会让我们系统不安全
    

    那我们把退出操作改成表单提交的post方式请求即可;

    修改index.html注销代码:

    <form th:action="@{/logout}" method="post">
        <button type="submit" class="def-log-out">注销</button>
        <!-- <a class="item" th:href="@{/logout}" style="text-decoration:underline;">
            注销
        </a> -->
    </form>
    

    build 后再次点击注销可以验证下!

    余路那么长,还是得带着虔诚上路...
  • 相关阅读:
    这是阿里技术专家对 SRE 和稳定性保障的理解
    阿里四年技术 TL 的得失总结:如何做好技术 Team Leader
    深度 | 阿里云蒋江伟:什么是真正的云原生?
    亲历者说 | 完整记录一年多考拉海购的云原生之路
    Seata RPC 模块的重构之路
    对容器镜像的思考和讨论
    20 行代码:Serverless 架构下用 Python 轻松搞定图像分类和预测
    怎么提升写代码的能力
    云原生 DevOps 的 5 步升级路径
    dubbo-go 白话文 | 从零搭建 dubbogo 和 dubbo 的简单用例
  • 原文地址:https://www.cnblogs.com/itiaotiao/p/12736426.html
Copyright © 2011-2022 走看看