后,進(jìn)入金三銀四招聘旺季,相信不少參加測(cè)試相關(guān)崗位面試的時(shí)候,會(huì)遇到設(shè)計(jì)測(cè)試的題目。用戶登錄測(cè)試屬于老生常談的問(wèn)題了,如果面試時(shí)能先概述一下你的設(shè)計(jì)思路、考慮的維度,再開(kāi)始逐個(gè)寫(xiě)出用例的設(shè)計(jì),應(yīng)該會(huì)更有條理。
給你一個(gè)“用戶登錄”功能你會(huì)如何測(cè)試它?
設(shè)計(jì)測(cè)試用例,通常主要有 2 個(gè)大類(lèi):功能性需求和非功能性需求。
一、功能需求
功能需求其實(shí)就是軟件本身需要實(shí)現(xiàn)的具體功能,通常它們是這個(gè)功能的最直接體現(xiàn)。
在設(shè)計(jì)這種用例的時(shí)候,我們基本會(huì)用【等價(jià)類(lèi)】和【邊界值】這兩種方法。
1. 用戶名和密碼都為空,驗(yàn)證是否登錄失敗,并提示信息(“用戶名為空”)
2. 用戶名為空或密碼為空,驗(yàn)證是否登錄失敗,并提示信息(“用戶名/密碼為空”)
3. 輸入已注冊(cè)的用戶名和正確的密碼,驗(yàn)證是否登錄成功
4. 登錄成功后,鏈接能否跳轉(zhuǎn)至正確的頁(yè)面
5. 輸入已注冊(cè)的用戶名和錯(cuò)誤的密碼,驗(yàn)證是否登錄失敗,并提示信息(“密碼錯(cuò)誤”)
6. 輸入未注冊(cè)的用戶名和隨意密碼,驗(yàn)證是否登錄失敗,并提示信息(“用戶名錯(cuò)誤”)
7. 如果登錄功能啟用驗(yàn)證碼功能,在用戶名和密碼正確的前提下,輸入正確的驗(yàn)證碼,驗(yàn)證是否登錄成功
8. 如果登錄功能啟用驗(yàn)證碼功能,在用戶名和密碼正確的前提下,輸入錯(cuò)誤的驗(yàn)證碼,驗(yàn)證是否登錄失敗,并提示信息正確(“驗(yàn)證碼錯(cuò)誤”)
9. 如果登錄功能啟用驗(yàn)證碼功能,考慮驗(yàn)證碼的辨認(rèn)難易程度,是否可以點(diǎn)擊驗(yàn)證碼圖片(“換一個(gè)”),更換驗(yàn)證碼
10. 記住用戶名和密碼的功能是否正確,且登錄失敗后不記錄密碼
11. 必填項(xiàng)為空時(shí),功能是否正確
不合法的用戶名:空白的用戶名,不正確的用戶名,使用了字符大于用戶名的限制;正常用戶名不允許的特殊字符;系統(tǒng)的保留字段;
不合法的密碼:空密碼;錯(cuò)誤的密碼;字符大于密碼的限制;正常密碼不允許的特殊字符;系統(tǒng)的保留字段。
二、非功能性需求
很多時(shí)候,僅做了功能性需求的測(cè)試覆蓋還是不夠的,因?yàn)檫€存在一些其他"隱藏"的需求,比如:界面UI、安全性、性能、兼容性。這些往往是決定軟件質(zhì)量的關(guān)鍵因素。
這些需求往往不容易優(yōu)先想到,需要仔細(xì)深入場(chǎng)景、設(shè)身處地的考慮才能很好地構(gòu)思出來(lái)。
1、界面測(cè)試(UI Test):
1. 布局是否美觀、合理
2. 控件是否美觀、對(duì)齊
3. 界面設(shè)計(jì)風(fēng)格是否統(tǒng)一
4. 界面文字無(wú)錯(cuò)別字
2、安全性測(cè)試
考慮登錄的安全性,可能需要用到以下測(cè)試用例:
1. 用戶名和密碼的后臺(tái)存儲(chǔ)是否加密
2. 用戶名和密碼是否加密后,發(fā)給web服務(wù)器
3. 用戶名和密碼的驗(yàn)證,應(yīng)該通過(guò)服務(wù)器驗(yàn)證,而不單單在客戶端用javascript驗(yàn)證
4. 登錄成功后生成的cookie,是否是httponly(否則容易被腳本盜取)
5. 密碼框內(nèi)輸入的密碼是否可以在頁(yè)面源碼模式下被查看
6. 密碼是否具有有效期,有效期到期后,是否提示修改密碼
7. 未登錄的情況下,在瀏覽器中直接輸入登錄后的URL地址,是否會(huì)重定向到登錄界面
8. 用戶名和密碼輸入框中分別輸入典型的“SQL注入攻擊”字符串,驗(yàn)證系統(tǒng)的返回頁(yè)面(防止SQL注入攻擊)
9. 用戶名和密碼輸入框中分別輸入典型的“XSS跨站腳本攻擊”字符串,驗(yàn)證系統(tǒng)行為是否被篡改(防止XSS攻擊)
10. 連續(xù)多次登錄失敗后,系統(tǒng)是否會(huì)阻止后續(xù)的嘗試,錯(cuò)誤登錄的次數(shù)限制,以應(yīng)對(duì)暴力破解(防止暴力破解)
11. 考慮很多用戶在同一終端上的登陸
12. 考慮同一用戶在不同終端登錄的互斥性
13. 同一用戶在同一終端的多種瀏覽器上登錄,驗(yàn)證登錄功能的互斥性
3、 性能壓力測(cè)試
考慮到性能,可能還需要增加以下的測(cè)試用例:
1. 單用戶登錄的響應(yīng)時(shí)間是否小于 3 秒
2. 單用戶登錄時(shí),后臺(tái)請(qǐng)求數(shù)量是否過(guò)多
3. 高并發(fā)場(chǎng)景下用戶登錄的響應(yīng)時(shí)間是否小于 5 秒
4. 高并發(fā)場(chǎng)景下服務(wù)端的監(jiān)控指標(biāo)是否符合預(yù)期
5. 高集合點(diǎn)并發(fā)場(chǎng)景下,是否存在資源死鎖和不合理的資源等待
6. 長(zhǎng)時(shí)間大量用戶連續(xù)登錄和登出,服務(wù)器端是否存在內(nèi)存泄漏
4、兼容性測(cè)試
1. 不同瀏覽器下,驗(yàn)證登錄頁(yè)面的顯示以及功能正確性
2. 相同瀏覽器的不同版本下,驗(yàn)證登錄頁(yè)面的顯示以及功能正確性
3. 不同移動(dòng)終端的不同瀏覽器下,驗(yàn)證登錄頁(yè)面的顯示以及功能正確性
4. 不同分辨率的界面下,驗(yàn)證登錄頁(yè)面的顯示以及功能正確性
5. 不同平臺(tái)(Windows、Mac)、不同移動(dòng)設(shè)備(iPhone、Andriod),登錄界面的顯示及功能是否正確
6. 不同語(yǔ)言環(huán)境下,登錄界面的顯示及功能是否正確 (本地化測(cè)試 Localization Test)
5、弱網(wǎng)測(cè)試
1、網(wǎng)絡(luò)延遲或者弱網(wǎng)或者切換網(wǎng)絡(luò)、斷網(wǎng)是否登錄正確
2、未激活或者凍結(jié)的用戶登陸
3、登錄的日志是否記錄正確
4、密碼強(qiáng)弱進(jìn)行校驗(yàn)
5、有沒(méi)有對(duì)登陸設(shè)備和地區(qū)進(jìn)行檢測(cè)
三、測(cè)試的不可窮盡性
通常在實(shí)際工作中,測(cè)試由于受限于時(shí)間成本和經(jīng)濟(jì)成本,是不可能去窮盡所有可能的組合的,而是采用基于風(fēng)險(xiǎn)驅(qū)動(dòng)的模式,有所側(cè)重地選擇測(cè)試范圍和設(shè)計(jì)測(cè)試用例,以尋求缺陷風(fēng)險(xiǎn)和研發(fā)成本之間的平衡。
對(duì)于高質(zhì)量的軟件測(cè)試,用例設(shè)計(jì)不僅需要考慮明確的功能性需求,還要涉及兼容性、安全性和性能等一系列的非功能性需求,這些非功能性需求對(duì)軟件系統(tǒng)的質(zhì)量有著舉足輕重的作用。
載自: java大師
博客系統(tǒng)訪問(wèn):
采用的是layui-admin框架,文中的驗(yàn)證碼內(nèi)容,請(qǐng)參考作者之前的驗(yàn)證碼功能
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<title>ds博客</title>
<div th:replace="common/link::header"></div>
<link rel="stylesheet" th:href="@{/static/layuiadmin/style/login.css}" media="all">
</head>
<body>
<div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login" style="display: none;">
<div class="layadmin-user-login-main">
<div class="layadmin-user-login-box layadmin-user-login-header">
<h2>ds博客</h2>
<p>后臺(tái)登錄</p>
</div>
<div class="layadmin-user-login-box layadmin-user-login-body layui-form">
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-username" for="LAY-user-login-username"></label>
<input type="text" name="userName" value="test" id="LAY-user-login-username" lay-verify="required" placeholder="用戶名" class="layui-input">
</div>
<div class="layui-form-item">
<label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-password"></label>
<input type="password" name="passWord" value="test" id="LAY-user-login-password" lay-verify="required" placeholder="密碼" class="layui-input">
</div>
<div class="layui-form-item">
<div class="layui-row">
<div class="layui-col-xs7">
<label class="layadmin-user-login-icon layui-icon layui-icon-vercode"></label>
<input type="text" name="code" lay-verify="required" placeholder="圖形驗(yàn)證碼" class="layui-input">
</div>
<div class="layui-col-xs5">
<div style="margin-left: 10px;">
<img id="codeImg" class="layadmin-user-login-codeimg">
</div>
</div>
</div>
</div>
<div class="layui-form-item" style="margin-bottom: 20px;">
<input type="checkbox" name="remember-me" lay-skin="primary" title="記住密碼">
</div>
<div class="layui-form-item">
<button class="layui-btn layui-btn-fluid layui-bg-blue" lay-submit lay-filter="login">登 錄</button>
</div>
</div>
</div>
<!-- <div class="layui-trans layadmin-user-login-footer">-->
<!-- <p>版權(quán)所有 ? 2022 <a href="#" target="_blank">濟(jì)南高新開(kāi)發(fā)區(qū)微本地軟件開(kāi)發(fā)工作室</a> 魯ICP備20002306號(hào)-1</p>-->
<!-- </div>-->
</div>
<div th:replace="common/script::footer"></div>
<script th:inline="javascript">
layui.config({
base: '/static/layuiadmin/' //靜態(tài)資源所在路徑
}).extend({
index: 'lib/index' //主入口模塊
}).use(['index', 'user'], function(){
let $=layui.$,
form=layui.form;
// 初始化
getImgCode();
form.render();
//提交
form.on('submit(login)', function(obj) {
// 打開(kāi)loading
let loading=layer.load(0, {
shade: false,
time: 2 * 1000
});
// 禁止重復(fù)點(diǎn)擊按鈕
$('.layui-btn').attr("disabled",true);
//請(qǐng)求登入接口
$.ajax({
type: 'POST',
url: ctx + '/login',
data: obj.field,
dataType: 'json',
success: function(result) {
if (result.code===200) {
layer.msg('登入成功', {
icon: 1
,time: 1000
}, function(){
window.location.href='/';
});
} else {
layer.msg(result.message);
// 刷新驗(yàn)證碼
getImgCode();
// 關(guān)閉loading
layer.close(loading);
// 開(kāi)啟點(diǎn)擊事件
$('.layui-btn').attr("disabled", false);
}
}
});
});
$("#codeImg").on('click', function() {
// 添加驗(yàn)證碼
getImgCode();
});
$(document).keydown(function (e) {
if (e.keyCode===13) {
$('.layui-btn').click();
}
});
// 解決session過(guò)期跳轉(zhuǎn)到登錄頁(yè)并跳出iframe框架
$(document).ready(function () {
if (window !=top) {
top.location.href=location.href;
}
});
});
/**
* 獲取驗(yàn)證碼
*/
function getImgCode() {
let url=ctx + '/getImgCode';
let xhr=new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType="blob";
xhr.onload=function() {
if (this.status===200) {
let blob=this.response;
document.getElementById("codeImg").src=window.URL.createObjectURL(blob);
}
}
xhr.send();
}
</script>
</body>
</html>
通過(guò)springsecurity的.loginProcessingUrl(“/login”)處理,處理邏輯如下:
.loginProcessingUrl("/login") 用于指定處理登錄操作的URL地址,而具體的驗(yàn)證邏輯是由 Spring Security 提供的認(rèn)證過(guò)濾器鏈負(fù)責(zé)的。在Spring Security中,主要的認(rèn)證過(guò)程是由UsernamePasswordAuthenticationFilter來(lái)完成的。
當(dāng)用戶提交登錄表單,請(qǐng)求到達(dá).loginProcessingUrl("/login")配置的URL時(shí),UsernamePasswordAuthenticationFilter會(huì)攔截這個(gè)請(qǐng)求,然后進(jìn)行以下主要步驟:
這個(gè)整個(gè)過(guò)程是由 Spring Security 提供的默認(rèn)配置完成的,通常情況下,開(kāi)發(fā)者只需要配置好認(rèn)證管理器、用戶信息服務(wù)(UserDetailsService),以及成功和失敗的處理器,Spring Security 就會(huì)負(fù)責(zé)處理登錄驗(yàn)證的整個(gè)流程。
package com.ds.core.config;
import com.ds.blog.system.service.SysUserService;
import com.ds.core.security.CustomAccessDeniedHandler;
import com.ds.core.security.DefaultAuthenticationFailureHandler;
import com.ds.core.security.DefaultAuthenticationSuccessHandler;
import com.ds.core.security.filter.ValidateCodeFilter;
import net.bytebuddy.asm.Advice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
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;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
// 放過(guò)
.antMatchers("/loginPage", "/getImgCode").permitAll()
.antMatchers("/**/*.jpg", "/**/*.png", "/**/*.gif", "/**/*.jpeg").permitAll()
// 剩下的所有的地址都是需要在認(rèn)證狀態(tài)下才可以訪問(wèn)
.anyRequest().authenticated()
.and()
// 過(guò)濾登錄驗(yàn)證碼
.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
// 配置登錄功能
.formLogin()
.usernameParameter("userName")
.passwordParameter("passWord")
// 指定指定要的登錄頁(yè)面
.loginPage("/loginPage")
// 處理認(rèn)證路徑的請(qǐng)求
.loginProcessingUrl("/login")
.successHandler(defaultAuthenticationSuccessHandler)
.failureHandler(defaultAuthenticationFailureHandler)
.and()
.exceptionHandling()
.accessDeniedHandler(accessDeniedHandler)
.and()
// 登出
.logout()
.invalidateHttpSession(true)
.deleteCookies("remember-me")
.logoutUrl("/logout")
.logoutSuccessUrl("/loginPage")
.and()
.rememberMe()
// 有效期7天
.tokenValiditySeconds(3600 * 24 * 7)
// 開(kāi)啟記住我功能
.rememberMeParameter("remember-me")
.and()
//禁用csrf
.csrf().disable()
// header response的X-Frame-Options屬性設(shè)置為SAMEORIGIN
.headers().frameOptions().sameOrigin()
.and()
// 配置session管理
.sessionManagement()
//session失效默認(rèn)的跳轉(zhuǎn)地址
.invalidSessionUrl("/loginPage");
}
}
創(chuàng)建監(jiān)聽(tīng)器,在登錄成功的時(shí)候記錄登錄日志。
總的來(lái)說(shuō),這段代碼的作用是在用戶成功登錄后,通過(guò)監(jiān)聽(tīng) Spring Security 的認(rèn)證成功事件,記錄用戶的登錄日志信息,包括登錄賬號(hào)、登錄IP和登錄成功的備注。這樣可以實(shí)現(xiàn)登錄成功后的自定義操作,例如記錄登錄日志等。
@Slf4j
@Component
public class AuthenticationSuccessListener implements ApplicationListener<AuthenticationSuccessEvent> {
@Autowired
private SysLoginLogService sysLoginLogService;
@Override
public void onApplicationEvent(AuthenticationSuccessEvent authenticationSuccessEvent) {
// 登錄賬號(hào)
User user=(User) authenticationSuccessEvent.getAuthentication().getPrincipal();
// 請(qǐng)求IP
String ip=ServletUtil.getClientIP(((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(), "");
SysLoginLog sysLoginLog=new SysLoginLog();
sysLoginLog.setAccount(user.getUsername());
sysLoginLog.setLoginIp(ip);
sysLoginLog.setRemark("登錄成功");
sysLoginLogService.save(sysLoginLog);
}
}
創(chuàng)建監(jiān)聽(tīng)器,在登錄失敗的時(shí)候記錄異常登錄日志。
@Slf4j
@Component
public class AuthenticationFailureListener implements ApplicationListener<AbstractAuthenticationFailureEvent> {
@Autowired
private SysLoginLogService sysLoginLogService;
@Override
public void onApplicationEvent(AbstractAuthenticationFailureEvent abstractAuthenticationFailureEvent) {
// 登錄賬號(hào)
String username=abstractAuthenticationFailureEvent.getAuthentication().getPrincipal().toString();
// 登錄失敗原因
String message ;
if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureBadCredentialsEvent) {
//提供的憑據(jù)是錯(cuò)誤的,用戶名或者密碼錯(cuò)誤
message="提供的憑據(jù)是錯(cuò)誤的,用戶名或者密碼錯(cuò)誤";
} else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureCredentialsExpiredEvent) {
//驗(yàn)證通過(guò),但是密碼過(guò)期
message="驗(yàn)證通過(guò),但是密碼過(guò)期";
} else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureDisabledEvent) {
//驗(yàn)證過(guò)了但是賬戶被禁用
message="驗(yàn)證過(guò)了但是賬戶被禁用";
} else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureExpiredEvent) {
//驗(yàn)證通過(guò)了,但是賬號(hào)已經(jīng)過(guò)期
message="驗(yàn)證通過(guò)了,但是賬號(hào)已經(jīng)過(guò)期";
} else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureLockedEvent) {
//賬戶被鎖定
message="賬戶被鎖定";
} else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureProviderNotFoundEvent) {
//配置錯(cuò)誤,沒(méi)有合適的AuthenticationProvider來(lái)處理登錄驗(yàn)證
message="配置錯(cuò)誤";
} else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureProxyUntrustedEvent) {
// 代理不受信任,用于Oauth、CAS這類(lèi)三方驗(yàn)證的情形,多屬于配置錯(cuò)誤
message="代理不受信任";
} else if (abstractAuthenticationFailureEvent instanceof AuthenticationFailureServiceExceptionEvent) {
// 其他任何在AuthenticationManager中內(nèi)部發(fā)生的異常都會(huì)被封裝成此類(lèi)
message="內(nèi)部發(fā)生的異常";
} else {
message="其他未知錯(cuò)誤";
}
// 請(qǐng)求IP
String ip=ServletUtil.getClientIP(((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest(), "");
SysLoginLog sysLoginLog=new SysLoginLog();
sysLoginLog.setAccount(username);
sysLoginLog.setLoginIp(ip);
sysLoginLog.setRemark(message);
sysLoginLogService.save(sysLoginLog);
}
}
下面是一個(gè)認(rèn)證成功處理器,登錄成功后,會(huì)返回響應(yīng)的信息給前端
@Component
@Slf4j
public class DefaultAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
log.info("-----login in success----");
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
PrintWriter writer=response.getWriter();
writer.write(JSON.toJSONString(Result.success()));
writer.flush();
}
}
.successHandler(defaultAuthenticationSuccessHandler)
.failureHandler(defaultAuthenticationFailureHandler)
下面是一個(gè)認(rèn)證成功處理器,登錄成功后,會(huì)返回響應(yīng)的信息給前端
@Component
@Slf4j
public class DefaultAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
log.info("login in failure : " + exception.getMessage());
response.setContentType("application/json;charset=utf-8");
response.setCharacterEncoding("utf-8");
PrintWriter writer=response.getWriter();
String message;
if (exception instanceof BadCredentialsException) {
message="用戶名或密碼錯(cuò)誤,請(qǐng)重試。";
writer.write(JSON.toJSONString(Result.failure(message)));
}else{
writer.write(JSON.toJSONString(Result.failure(exception.getMessage())));
}
writer.flush();
}
.successHandler(defaultAuthenticationSuccessHandler)
.failureHandler(defaultAuthenticationFailureHandler)
返回200,就代表成功,跳轉(zhuǎn)到/請(qǐng)求,進(jìn)去index或者main頁(yè)面
if (result.code===200) {
layer.msg('登入成功', {
icon: 1
,time: 1000
}, function(){
window.location.href='/';
});
} else {
AuthenticationSuccessEvent 是 Spring Security 中用于表示用戶認(rèn)證成功的事件。判斷登錄成功的主要依據(jù)是在認(rèn)證過(guò)程中,用戶提供的憑據(jù)(通常是用戶名和密碼)與系統(tǒng)中存儲(chǔ)的憑據(jù)匹配。以下是判斷登錄成功的基本流程:
在上述流程中,認(rèn)證成功的判斷主要是在 AuthenticationProvider 中完成的。DaoAuthenticationProvider 會(huì)檢查用戶提供的密碼與數(shù)據(jù)庫(kù)中存儲(chǔ)的密碼是否匹配。如果匹配,就認(rèn)為認(rèn)證成功。當(dāng)認(rèn)證成功后,后續(xù)的處理流程包括 AuthenticationSuccessHandler 的執(zhí)行和 AuthenticationSuccessEvent 的發(fā)布。你可以通過(guò)監(jiān)聽(tīng) AuthenticationSuccessEvent 事件來(lái)執(zhí)行一些額外的自定義邏輯,例如記錄登錄日志。
解Spring Security的formLogin登錄認(rèn)證模式
在本專(zhuān)欄之前的文章中,已經(jīng)給大家介紹過(guò)Spring Security的HttpBasic模式,該模式比較簡(jiǎn)單,只是進(jìn)行了通過(guò)攜帶Http的Header進(jìn)行簡(jiǎn)單的登錄驗(yàn)證,而且沒(méi)有定制的登錄頁(yè)面,所以使用場(chǎng)景比較窄。
對(duì)于一個(gè)完整的應(yīng)用系統(tǒng),與登錄驗(yàn)證相關(guān)的頁(yè)面都是高度定制化的,非常美觀而且提供多種登錄方式。這就需要Spring Security支持我們自己定制登錄頁(yè)面,也就是本文給大家介紹的formLogin模式登錄認(rèn)證模式。
準(zhǔn)備工作
需求
以上就是本文介紹formLogin模式需要進(jìn)行的準(zhǔn)備工作及需求,下面我們就來(lái)實(shí)現(xiàn)其中的核心的登錄驗(yàn)證邏輯,準(zhǔn)備工作非常簡(jiǎn)單請(qǐng)自行實(shí)現(xiàn)。(新建spring boot應(yīng)用,登錄頁(yè)面、首頁(yè)、四個(gè)業(yè)務(wù)頁(yè)面都寫(xiě)成非常簡(jiǎn)單的html即可,不用寫(xiě)實(shí)際業(yè)務(wù)和樣式。)
formLogin模式的三要素:
一般來(lái)說(shuō),使用權(quán)限認(rèn)證框架的的業(yè)務(wù)系統(tǒng)登錄驗(yàn)證邏輯是固定的,而資源訪問(wèn)控制規(guī)則和用戶信息是從數(shù)據(jù)庫(kù)或其他存儲(chǔ)介質(zhì)靈活加載的。但本文所有的用戶、資源、權(quán)限信息都是代碼配置寫(xiě)死的,旨在為大家介紹formLogin認(rèn)證模式,如何從數(shù)據(jù)庫(kù)加載權(quán)限認(rèn)證相關(guān)信息我還會(huì)結(jié)合RBAC權(quán)限模型再寫(xiě)文章的。
首先,我們要繼承WebSecurityConfigurerAdapter ,重寫(xiě)configure(HttpSecurity http) 方法,該方法用來(lái)配置登錄驗(yàn)證邏輯。請(qǐng)注意看下文代碼中的注釋信息。
實(shí)現(xiàn)formLogin模式基礎(chǔ)配置(點(diǎn)擊可放大)
上面的代碼分為兩部分:
這時(shí)候,我們通過(guò)瀏覽器訪問(wèn),隨便測(cè)試一個(gè)沒(méi)有訪問(wèn)權(quán)限的資源,都會(huì)跳轉(zhuǎn)到login.html頁(yè)面。
在上文中,我們配置了登錄驗(yàn)證及資源訪問(wèn)的權(quán)限規(guī)則,我們還沒(méi)有具體的用戶,下面我們就來(lái)配置具體的用戶。重寫(xiě)WebSecurityConfigurerAdapter的 configure(AuthenticationManagerBuilder auth)方法
實(shí)現(xiàn)資源訪問(wèn)限制的需求(點(diǎn)擊可放大)
這樣,我們就實(shí)現(xiàn)了文首提出的普通用戶只能訪問(wèn)biz1(業(yè)務(wù)一)和biz2(業(yè)務(wù)二)資源的需求。那么管理員用戶可以訪問(wèn)所有的資源的配置方式,你會(huì)不會(huì)呢?同樣的配方、同樣的方式、自己可以嘗試一下哦!
在我們的實(shí)際開(kāi)發(fā)中,登錄頁(yè)面login.html和控制層Controller登錄驗(yàn)證'/login'都必須無(wú)條件的開(kāi)放。除此之外,一些靜態(tài)資源如css、js文件通常也都不需要驗(yàn)證權(quán)限,我們需要將它們的訪問(wèn)權(quán)限也開(kāi)放出來(lái)。下面就是實(shí)現(xiàn)的方法:重寫(xiě)WebSecurityConfigurerAdapter類(lèi)的configure(WebSecurity web) 方法
靜態(tài)資源訪問(wèn)(點(diǎn)擊可放大)
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。