写在后面
上一回咱们简略剖析了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>
对于配置文件的几点阐明:
- login-page:配置自定义的认证页面;
- login-processing-url:配置认证解决的url;
- default-target-url:配置认证胜利之后跳转的页面;
- authentication-failure-url:配置认证失败之后跳转的页面;
- logout-url:配置登记解决门路;
- logout-success-url:配置胜利登记后跳转的页面;
- username-parameter:配置前端表单向后盾提交用户账户的参数名,默认值为username,能够不配置;
- password-parameter:配置前端表单向后盾提交用户凭证的参数名,默认值为password。
- 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="width: 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="width: auto;"> <el-input v-model="user.username" placeholder="username"></el-input> </el-form-item> <el-form-item style="width: auto;"> <el-input v-model="user.password" placeholder="password"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="onSubmit" style="width: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
为了保障系统安全,就义一些设计上的完满,在我看来是很有必要的。置信会有更好的计划!以上只是我集体的一些见解。欢送大家提出不一样的想法。