• Spring Security自定义认证页面(动态网页解决方案+静态网页解决方案)练气中期圆满


    写在前面

    上一回我们简单分析了spring security拦截器链的加载流程,我们还有一些简单的问题没有解决。如何自定义登录页面?如何通过数据库获取用户权限信息?
    今天主要解决如何配置自定义认证页面的问题。因为现在前后端分离,无状态、restful接口设计比较火,因此在思考静态网页如何获取spring security的CRSF Token.这个问题我在文末提出了我的见解,但似乎也不是很好的解决方案,很期待大家的宝贵建议!

    Spring Security配置自定义认证页面步骤

    第一步:在spring security的配置文件中指定自定义登录页面的信息

    <!--静态资源不拦截-->
        <security:http pattern="/assets/**" security="none"/>
    <!--设置可以用spring的el表达式配置Spring Security并自动生成对应配置组件(过滤器)-->
        <security:http auto-config="true" use-expressions="true">
            <!--使用spring的el表达式来指定项目所有资源访问都必须有ROLE_USER或ROLE_ADMIN角色-->
            <security:intercept-url pattern="/**" access="hasAnyRole('ROLE_USER','ROLE_ADMIN')"/>
    
            <!--        使这个页面能匿名访问-->
            <security:intercept-url pattern="/login.html" access="permitAll()"/>
            <security:form-login
                    login-page="/login.html"
                    login-processing-url="/login"
                    default-target-url="/pages/index.html"
                    username-parameter="username"
                    password-parameter="password"
                    authentication-failure-url="/failure.html"/>
            <security:logout
                    logout-url="/logout"
                    logout-success-url=":/login.html"/>
        </security:http>
    

    关于配置文件的几点说明:

    1. login-page:配置自定义的认证页面;
    2. login-processing-url:配置认证处理的url;
    3. default-target-url:配置认证成功之后跳转的页面;
    4. authentication-failure-url:配置认证失败之后跳转的页面;
    5. logout-url:配置注销处理路径;
    6. logout-success-url:配置成功注销后跳转的页面;
    7. username-parameter:配置前端表单向后台提交用户账户的参数名,默认值为username,可以不配置;
    8. password-parameter:配置前端表单向后台提交用户凭证的参数名,默认值为password。
    9. security="none"表示该路径下的资源访问不拦截(这个和匿名访问的配置是有区别的)

    第二步:编写一个前端登录页面login.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>登录</title>
    </head>
    <!-- 引入样式 -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui@2.13.2/lib/theme-chalk/index.css">
    <style>
        * {margin: 0; padding: 0; box-sizing: border-box;}
        .form {position: relative;}
        .son {position: relative; top:50%;margin-top:-50px;left:50%;margin-left:-50px}
    </style>
    <body>
    <div id="app">
    <el-container>
        <el-header></el-header>
        <el-main style="margin-top: 10%">
    
            <div id="form" style=" 310px;height:280px;margin: auto;background-color: #71d3bd">
    
    
                <el-form id="son" ref="form" :model="user"  style="margin: auto;padding-left: 10px;padding-right: 10px">
                    <el-form-item>
                        <h3 style="text-align: center">欢迎登录</h3>
                    </el-form-item>
                    <el-form-item  style=" auto;">
                        <el-input v-model="user.username" placeholder="username"></el-input>
                    </el-form-item>
                    <el-form-item  style=" auto;">
                        <el-input v-model="user.password" placeholder="password"></el-input>
                    </el-form-item>
                    <el-form-item>
                        <el-button type="primary" @click="onSubmit" style="100%;">登录</el-button>
                    </el-form-item>
                </el-form>
            </div>
        </el-main>
    </el-container>
    </div>
    
    
    <!--axios-->
    <script src="https://cdn.bootcss.com/axios/0.19.0/axios.min.js"></script>
    <!--vue-->
    <script src="https://unpkg.com/vue@2.6.11/dist/vue.js"></script>
    <!-- 引入组件库 -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script>
    
    <script>
    
        new Vue({
            el: "#app",
            data: function (){
                return {
                    user:{
                        username:"",
                        password:""
                    }
                }
            },
            methods:{
             onSubmit(){
                 axios.post("/login",this.user)
                }
            }
    
        })
    </script>
    </body>
    </html>
    

    第三步,启动tomcat,访问http://127.0.0.1:8081/

    我们可以发现,我们被重定向到了登录页面。接下来我们填写username和password点击登录(在前面的配置文件中定义了)

    我们发现结果并不符合我们的预期。我们得到了403(没有权限)错误信息。

    why?

    我们对比一下我们写的登录页面和spring security给出的页面有啥不一样

    <!DOCTYPE html>
    <html lang="en"><head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <meta name="description" content="">
        <meta name="author" content="">
        <title>Please sign in</title>
        <link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M" crossorigin="anonymous">
        <link href="https://getbootstrap.com/docs/4.0/examples/signin/signin.css" rel="stylesheet" crossorigin="anonymous">
      <style>@media print {#ghostery-purple-box {display:none !important}}</style></head>
      <body>
         <div class="container">
          <form class="form-signin" method="post" action="/login">
            <h2 class="form-signin-heading">Please sign in</h2>
            <p>
              <label for="username" class="sr-only">Username</label>
              <input type="text" id="username" name="username" class="form-control" placeholder="Username" required="" autofocus="">
            </p>
            <p>
              <label for="password" class="sr-only">Password</label>
              <input type="password" id="password" name="password" class="form-control" placeholder="Password" required="">
            </p>
    <input name="_csrf" type="hidden" value="0aef3732-50d3-455a-8404-43d5ed9f2939">
            <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
          </form>
    </div>
    </body></html>
    

    对比发现系统给出的和我们写的认证页面在表单中多出了下面这段

    <input name="_csrf" type="hidden" value="0aef3732-50d3-455a-8404-43d5ed9f2939">
    

    也就是说我们少了一个参数信息,这参数信息就是我们前篇文章讲到的spring security预防csrf攻击的内容。显然它是通过校验token去预防的。

    解释一下如何通过检验token的方式预防csrf攻击:

    ​ 简单来说就是赵六你去账房领银两办事(获取系统服务)前先向王员外(可以是系统专门发放令牌的一个接口)索要一个令牌(token),你领银两的时候带上王员外给你的令牌,这账房管事的李老先生(一个拦截器)查看了你的令牌后才能给你钱,不然就不给你钱。你拿钱了之后呢令牌就被收走了(token过期策略),下次再想拿钱就得找王员外再要一个新的令牌。

    接下来就让我们找李老先生拿银两去,哦,不对,是找王员外拿鸡毛令箭去。。。

    第四步:配置csrf攻击防护机制

    从Spring Security 4.0开始,默认情况下使用XML配置启用CSRF保护。如果要禁用CSRF保护,可以在下面看到相应的XML配置。

    <http>
        <!-- ... -->
        <csrf disabled="true"/>
    </http>
    

    由于spring security的csrf令牌是存储再HttpSession中的,因此在动态网页中,可以很方便的获取到csrf token信息。

    <jsp:root xmlns:jsp="http://java.sun.com/JSP/Page"
        xmlns:c="http://java.sun.com/jsp/jstl/core"
        xmlns:form="http://www.springframework.org/tags/form" version="2.0">
        <jsp:directive.page language="java" contentType="text/html" />
    <html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
        <!-- ... -->
    
        <c:url var="logoutUrl" value="/logout"/>
        <form:form action="${logoutUrl}"
            method="post">
        <input type="submit"
            value="Log out" />
        <input type="hidden"
            name="${_csrf.parameterName}"
            value="${_csrf.token}"/>
        </form:form>
    
        <!-- ... -->
    </html>
    </jsp:root>
    

    如果使用的是静态网页,例如前后端分离、restful接口设计和无状态session的情况下,或许可以考虑如下设计:

    ​ 将csef token暴露出去,前端通过ajax请求获取token,并在请求服务时带上token。当然,这里暴露出去的token应该是只能在内部暴露,使得静态页面得到token后,再将网页响应给用户。

    获取token

    登录

    静态网页获取csrf token的部分代码

    /**
     * @author 赖柄沣 bingfengdev@aliyun.com
     * @version 1.0
     * @date 2020/8/21 12:29
     */
    @RestController
    @RequestMapping("/csrf")
    public class CsrfTokenCtrl {
    
        @GetMapping(value = "/getToken")
        public HashMap<String, String> getToken(HttpServletRequest request ){
            /**
             * 在这里可以先过滤掉一些非法的请求,只允许内部静态网页服务器请求
             */
            
            CsrfToken token = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
            HashMap<String, String> map = new HashMap<>();
            map.put("csrf_header",token.getHeaderName());
            map.put("csrf",token.getToken());
            return map;
        }
    
    }
    

    End
    为了保证系统安全,牺牲一些设计上的完美,在我看来是很有必要的。相信会有更好的方案!以上只是我个人的一些见解。欢迎大家提出不一样的想法。

  • 相关阅读:
    C# get和set
    动手学pytorch-优化算法
    动手学pytorch-梯度下降
    动手学pytorch-凸优化
    动手学pytorch-Batch Norm
    动手学pytorch-经典卷积神经网络模型
    动手学pytorch-卷积神经网络基础
    动手学pytorch-Transformer代码实现
    动手学pytorch-机器翻译
    动手学pytorch-循环神经网络进阶
  • 原文地址:https://www.cnblogs.com/bingfengdev/p/13543882.html
Copyright © 2020-2023  润新知