SpringBoot Security 整合thymeleaf模板自定義登錄頁面奕枝,按需提示錯誤信息

使用SpringBoot Security進(jìn)行登錄驗(yàn)證棺榔,可以結(jié)合具體的業(yè)務(wù)需求來使用。在
SpringBoot Security前后端分離隘道,登錄退出等返回json
一文中症歇,描述了前后端分離的情況下,如何進(jìn)行登錄驗(yàn)證和提示錯誤信息的√饭#現(xiàn)在針對自定義的登錄頁面忘晤,能夠精確地提示錯誤信息,做一個簡單的演示demo激捏。

本文使用的SpringBoot版本是2.1.4.RELEASE设塔,下面直接進(jìn)入使用階段。

第一步远舅,在pom.xml中引入架包
<!-- security架包 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

加上這個架包壹置,重啟項(xiàng)目后,整個項(xiàng)目就配置了登錄攔截和驗(yàn)證表谊。

第二步钞护,重啟項(xiàng)目,會在控制臺下看到自動生成的登錄密碼爆办,默認(rèn)的用戶名是admin

圖-1

第三步难咕,打開瀏覽器窗口,對登錄頁面進(jìn)行探究

圖-2

不輸入用戶名和密碼,直接點(diǎn)擊登錄時余佃,會有提示信息暮刃,輸入框的顏色還會變紅。查看源碼爆土,可以發(fā)現(xiàn)椭懊,架包默認(rèn)的登錄頁面提交方式為表單提交,method為post步势,并且默認(rèn)是開啟csrf的氧猬,在表單里自動生成了一個隱藏域,防止跨域提交坏瘩,確保請求的安全性盅抚。


圖-2

輸入錯誤的用戶名或密碼,可以看到頁面進(jìn)行了跳轉(zhuǎn)倔矾,跳轉(zhuǎn)后的頁面又回到了登錄頁妄均,只是url地址后面多了一個參數(shù),頁面提示錯誤信息哪自。


圖-3

從頁面源碼丰包,我們可以獲得以下幾個方面的信息:

  1. 自帶的登錄頁面使用post方式提交;
  2. 用戶名密碼錯誤時壤巷,頁面會進(jìn)行重定向邑彪,重定向到登錄頁面,并展示錯誤信息隙笆。

如果頁面是我們自己自定義的锌蓄,如果要使用默認(rèn)的過濾器獲取登錄信息,則必須使用post方式進(jìn)行提交撑柔,如果使用ajax json的方式進(jìn)行提交瘸爽,則獲取不到參數(shù)。

接下來自定義一個登錄頁面铅忿,為了快速構(gòu)建登錄頁面剪决,這里使用了thymeleaf模板。

第一步檀训,在配置文件中柑潦,引入thymeleaf架包
<!-- 導(dǎo)入Spring Boot的thymeleaf依賴-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
第二步,在resouce的templates下建立登錄頁面login.html
<!DOCTYPE HTML>
<!-- thymeleaf模板必須引入 -->
<!DOCTYPE HTML>
<!-- thymeleaf模板必須引入 -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>SpringBoot模版渲染</title>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
<form th:action="@{/login}" method="post" >
    <input th:text="用戶名" type="text" name="username" />
    <input th:text="密碼" type="password" name="password" />
    <button type="submit" >提交</button>
    <!-- ${session?.SPRING_SECURITY_LAST_EXCEPTION?.message} security自帶的錯誤提示信息 -->
    <p th:if="${param.error}" th:text="${session?.SPRING_SECURITY_LAST_EXCEPTION?.message}" ></p>
</form>
</body>
</html>
第三步峻凫,添加security的配置文件渗鬼,為了debug登錄情況,對UserDetails荧琼、MyPasswordEncoder譬胎、UserDetailsService三個接口進(jìn)行了實(shí)現(xiàn)差牛,并在配置文件中進(jìn)行了配置
package com.example.demo.security;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.csrf.CsrfFilter;
import org.springframework.web.filter.CharacterEncodingFilter;

/**
 * SpringSecurity的配置
 * @author 程就人生
 * @date 2019年5月26日
 */
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Autowired
    private UserDetailsService myCustomUserService;

    @Autowired
    private MyPasswordEncoder myPasswordEncoder;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        
        http.cors().and().csrf().disable();
        
        http
            //使用form表單post方式進(jìn)行登錄
            .formLogin()
            //登錄頁面為自定義的登錄頁面
            .loginPage("/login")
            //設(shè)置登錄成功跳轉(zhuǎn)頁面,error=true控制頁面錯誤信息的展示
            .successForwardUrl("/index").failureUrl("/login?error=true")
            .permitAll()
            .and()
            //允許不登陸就可以訪問的方法堰乔,多個用逗號分隔
            .authorizeRequests().antMatchers("/test").permitAll()
            //其他的需要授權(quán)后訪問
            .anyRequest().authenticated();        
        
            //session管理,失效后跳轉(zhuǎn)  
            http.sessionManagement().invalidSessionUrl("/login"); 
            //單用戶登錄偏化,如果有一個登錄了,同一個用戶在其他地方登錄將前一個剔除下線 
            //http.sessionManagement().maximumSessions(1).expiredSessionStrategy(expiredSessionStrategy()); 
            //單用戶登錄镐侯,如果有一個登錄了侦讨,同一個用戶在其他地方不能登錄 
            http.sessionManagement().maximumSessions(1).maxSessionsPreventsLogin(true); 
            //退出時情況cookies
            http.logout().deleteCookies("JESSIONID"); 
            //解決中文亂碼問題 
            CharacterEncodingFilter filter = new CharacterEncodingFilter(); 
            filter.setEncoding("UTF-8"); filter.setForceEncoding(true); 
            //
            http.addFilterBefore(filter,CsrfFilter.class); 
    }
    
//  @Bean
//  public SessionInformationExpiredStrategy expiredSessionStrategy() {
//      return new ExpiredSessionStrategy();
//  }
    
    @Bean
    public DaoAuthenticationProvider daoAuthenticationProvider() {
        DaoAuthenticationProvider bean = new DaoAuthenticationProvider();
        //返回錯誤信息提示,而不是Bad Credential
        bean.setHideUserNotFoundExceptions(true);
        //覆蓋UserDetailsService類
        bean.setUserDetailsService(myCustomUserService);
        //覆蓋默認(rèn)的密碼驗(yàn)證類
        bean.setPasswordEncoder(myPasswordEncoder); 
        return bean;
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.authenticationProvider(this.daoAuthenticationProvider());
    }
}

在這個配置中苟翻,對登錄頁面進(jìn)行了設(shè)置韵卤,設(shè)置使用自定義的登錄頁面,在Controller需要添加對應(yīng)的頁面渲染袜瞬。

package com.example.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
/**
 * 
 * @author 程就人生
 * @date 2019年6月8日
 */
@RestController
public class IndexController {
    
    @RequestMapping("/index")
    public ModelAndView index(){
        return new ModelAndView("/index");
    }
    
    @RequestMapping("/test")
    public Object test(){
        return "test";
    }
    
    /**
     * 自定義登錄頁面
     * @param error 錯誤信息顯示標(biāo)識
     * @return
     *
     */
    @GetMapping("/login")
    public ModelAndView login(String error){
         ModelAndView modelAndView = new ModelAndView("/login");
         modelAndView.addObject("error", error);
        return modelAndView;
    }   
}
package com.example.demo.security;

import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

/**
 * 登錄專用類,用戶登陸時怜俐,通過這里查詢數(shù)據(jù)庫
 * 自定義類身堡,實(shí)現(xiàn)了UserDetailsService接口邓尤,用戶登錄時調(diào)用的第一類
 * @author 程就人生
 * @date 2019年5月26日
 */
@Component
public class MyCustomUserService implements UserDetailsService {

    /**
     * 登陸驗(yàn)證時,通過username獲取用戶的所有權(quán)限信息
     * 并返回UserDetails放到spring的全局緩存SecurityContextHolder中贴谎,以供授權(quán)器使用
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //在這里可以自己調(diào)用數(shù)據(jù)庫汞扎,對username進(jìn)行查詢逢并,看看在數(shù)據(jù)庫中是否存在
        MyUserDetails myUserDetail = new MyUserDetails();
        if(StringUtils.isEmpty(username)){
            throw new RuntimeException("用戶名不能為空灭必!");
        }
        if(!username.equals("admin")){
            throw new RuntimeException("用戶名不存在虽界!");
        }
        myUserDetail.setUsername(username);
        myUserDetail.setPassword("123456");
        return myUserDetail;
    }
}
package com.example.demo.security;

import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * 自定義的密碼加密方法瘦馍,實(shí)現(xiàn)了PasswordEncoder接口
 * @author 程就人生
 * @date 2019年5月26日
 */
@Component
public class MyPasswordEncoder implements PasswordEncoder {

    @Override
    public String encode(CharSequence charSequence) {       
        //加密方法可以根據(jù)自己的需要修改
        return charSequence.toString();
    }

    @Override
    public boolean matches(CharSequence charSequence, String s) {
        return encode(charSequence).equals(s);
    }
}
package com.example.demo.security;

import java.util.Collection;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

/**
 * 實(shí)現(xiàn)了UserDetails接口民假,只留必需的屬性烹植,也可添加自己需要的屬性
 * @author 程就人生
 * @date 2019年5月26日
 */
public class MyUserDetails implements UserDetails {

    private static final long serialVersionUID = 1L;

    //登錄用戶名
    private String username;
    //登錄密碼
    private String password;

    private Collection<? extends GrantedAuthority> authorities;

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

第四步橡羞,重新啟動項(xiàng)目担租,看一下登錄頁面的效果
圖-4

一個很丑的登錄頁面溯香,這不是重點(diǎn)鲫构。重點(diǎn)是,登錄名和密碼正確時玫坛,頁面可以正確的跳轉(zhuǎn)结笨,輸入錯誤時,可以在登錄頁面進(jìn)行信息提示湿镀。

在MyCustomUserService類中炕吸,我們設(shè)置了用戶名為admin,密碼為123456勉痴;輸入其他的用戶名稱時赫模,提示用戶不存在;不輸入用戶名稱蒸矛,提示用戶不能為空瀑罗;密碼不是123456時扫外,提示密碼錯誤;輸入admin廓脆,123456時筛谚,頁面前往index頁面,下面進(jìn)行驗(yàn)證停忿。

第一步驾讲,測試不輸入用戶名,測試結(jié)果ok
圖-5
第二步席赂,測試輸入錯誤的用戶名吮铭,測試結(jié)果ok
圖-6
第三步,測試輸入正確的用戶名和錯誤的密碼颅停,測試結(jié)果ok
圖-7
第四步谓晌,測試輸入正確的用戶名和密碼,測試結(jié)果ok
圖-8
第五步癞揉,在index.html頁面中加個退出按鈕纸肉,測試一下退出操作,這里為了簡單喊熟,寫了一個表單
<!DOCTYPE HTML>
<!-- thymeleaf模板必須引入 -->
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>SpringBoot模版渲染</title>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
</head>
<body>
index
<form th:action="@{/logout}" method="get" >
<button type="submit" >退出</button>
</form>
</body>
</html>
第六步柏肪,不登錄,訪問index時芥牌,是直接跳往登錄頁面的烦味,登錄后直接跳往index頁面,測試結(jié)果ok
圖-9

圖-10
有幫助壁拉,微信掃一掃谬俄,關(guān)注一下
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市弃理,隨后出現(xiàn)的幾起案子溃论,更是在濱河造成了極大的恐慌,老刑警劉巖案铺,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蔬芥,死亡現(xiàn)場離奇詭異,居然都是意外死亡控汉,警方通過查閱死者的電腦和手機(jī)笔诵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來姑子,“玉大人乎婿,你說我怎么就攤上這事〗钟樱” “怎么了谢翎?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵捍靠,是天一觀的道長。 經(jīng)常有香客問我森逮,道長榨婆,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任褒侧,我火速辦了婚禮良风,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闷供。我一直安慰自己烟央,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布歪脏。 她就那樣靜靜地躺著疑俭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪婿失。 梳的紋絲不亂的頭發(fā)上钞艇,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天,我揣著相機(jī)與錄音移怯,去河邊找鬼香璃。 笑死这难,一個胖子當(dāng)著我的面吹牛舟误,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播姻乓,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼嵌溢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了蹋岩?” 一聲冷哼從身側(cè)響起赖草,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎剪个,沒想到半個月后秧骑,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡扣囊,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年乎折,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片侵歇。...
    茶點(diǎn)故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡骂澄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惕虑,到底是詐尸還是另有隱情坟冲,我是刑警寧澤磨镶,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站健提,受9級特大地震影響琳猫,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜私痹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一沸移、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧侄榴,春花似錦雹锣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至桦山,卻和暖如春攒射,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背恒水。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工会放, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人钉凌。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓咧最,卻偏偏與公主長得像,于是被迫代替她去往敵國和親御雕。 傳聞我的和親對象是個殘疾皇子矢沿,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評論 2 361

推薦閱讀更多精彩內(nèi)容

  • 吃貨地圖產(chǎn)品需求文檔 V1.0-2015/03/30 1概述 1.1產(chǎn)品概述及目標(biāo) 概述:“吃貨地圖”是一款基于i...
    michaelshan閱讀 5,860評論 1 46
  • Web網(wǎng)站測試流程和方法(轉(zhuǎn)載) 1測試流程與方法 1.1測試流程 進(jìn)行正式測試之前,應(yīng)先確定如何開展測試酸纲,不可盲...
    夏了夏夏夏天閱讀 1,300評論 0 0
  • 一捣鲸、Python簡介和環(huán)境搭建以及pip的安裝 4課時實(shí)驗(yàn)課主要內(nèi)容 【Python簡介】: Python 是一個...
    _小老虎_閱讀 5,754評論 0 10
  • 引用地址:http://www.51testing.com/html/29/n-3958829.html 功能測試...
    小胖5920閱讀 1,564評論 0 1
  • ORA-00001: 違反唯一約束條件 (.) 錯誤說明:當(dāng)在唯一索引所對應(yīng)的列上鍵入重復(fù)值時,會觸發(fā)此異常闽坡。 O...
    我想起個好名字閱讀 5,343評論 0 9