9.4 認證用戶
最簡單的Spring Security配置的話,那么就能無償?shù)氐玫揭粋€登錄頁。實際上,在重寫configure(HttpSecurity)之前,我們都能使用一個簡單卻功能完備的登錄頁。但是,一旦重寫了configure(HttpSecurity)方法,就失去了這個簡單的登錄頁面。
不過,把這個功能找回來也很容易。我們所需要做的就是在configure(HttpSecurity)方法中,調(diào)用formLogin(),如下面的程序清單所示。 請注意,和前面一樣,這里調(diào)用add()方法來將不同的配置指令連接在一起。 如果我們訪問應(yīng)用的“/login”鏈接或者導(dǎo)航到需要認證的頁面,那么將會在瀏覽器中展現(xiàn)登錄頁面。
// 啟用默認的登錄頁
httpSecurity.formLogin()
.and()
.authorizeRequests()
.antMatchers("/spitter/me").hasRole("SPITTER")
.anyRequest().permitAll()
.and()
.requiresChannel()
.antMatchers("/spitter/from")
.requiresSecure();
9.4.1 添加自定義的登錄頁
需要注意的一個關(guān)鍵點是<form>提交到了什么地方。同時還需要注意username和password輸入域,在你的登錄頁中,需要同樣的輸入域。最后,假設(shè)沒有禁用CSRF的話,還需要保證包含了值為CSRF token的“_csrf”輸入域。
<input type="hidden" name="_csrf" value="6984_hsd2_wrwqc_wrqd" />
在Thymeleaf模板中,包含了username和password輸入域,就像默認的登錄頁一樣,它也提交到了相對于上下文的“/login”頁面上。因為這是一個Thymeleaf模板,因此隱藏的“_csrf”域?qū)詣犹砑拥奖韱沃?/p>
?
<form method="POST" style="box-sizing: border-box; margin-top: 0px; margin-bottom: 0px;">
9.4.2 啟用HTTP Basic認證</form>
?
參考 HTTP Basic 認證 https://blog.csdn.net/yhb241/article/details/80646485
對于應(yīng)用程序的人類用戶來說,基于表單的認證是比較理想的。但是在第16章中,將會看到如何將我們Web應(yīng)用的頁面轉(zhuǎn)化為RESTful API。當應(yīng)用程序的使用者是另外一個應(yīng)用程序的話,使用表單來提示登錄的方式就不太適合了。
HTTP Basic認證(HTTP Basic Authentication)會直接通過HTTP請求本身,對要訪問應(yīng)用程序的用戶進行認證。你可能在以前見過HTTP Basic認證。當在Web瀏覽器中使用時,它將向用戶彈出一個簡單的模態(tài)對話框。
但這只是Web瀏覽器的顯示方式。本質(zhì)上,這是一個HTTP 401響應(yīng),表明必須要在請求中包含一個用戶名和密碼。在REST客戶端向它使用的服務(wù)進行認證的場景中,這種方式比較適合。
如果要啟用HTTP Basic認證的話,只需在configure()方法所傳入的HttpSecurity對象上調(diào)用httpBasic()即可。另外,還可以通過調(diào)用realmName()方法指定域。如下是在Spring Security中啟用HTTP Basic認證的典型配置:
httpSecurity.formLogin()
.and()
.httpBasic()
.realmName("Spittr")
在httpBasic()方法中,并沒有太多的可配置項,甚至不需要什么額外配置。HTTP Basic認證要么開啟要么關(guān)閉。所以,與其進一步研究這個話題,還不如看看如何通過Remember-me功能實現(xiàn)用戶的自動認證。
9.4.3 啟用Remember-me功能
對于應(yīng)用程序來講,能夠?qū)τ脩暨M行認證是非常重要的。但是站在用戶的角度來講,如果應(yīng)用程序不用每次都提示他們登錄是更好的。這就是為什么許多站點提供了Remember-me功能,你只要登錄過一次,應(yīng)用就會記住你,當再次回到應(yīng)用的時候你就不需要登錄了。
Spring Security使得為應(yīng)用添加Remember-me功能變得非常容易。為了啟用這項功能,只需在configure()方法所傳入的HttpSecurity對象上調(diào)用rememberMe()即可。
httpSecurity.formLogin()
.loginPage("/login")
.and()
.rememberMe()
.tokenValiditySeconds(2439800)
.key("spittrKey")
....
在這里,我們通過一點特殊的配置就可以啟用Remember-me功能。默認情況下,這個功能是通過在cookie中存儲一個token完成的,這個token最多兩周內(nèi)有效。但是,在這里,我們指定這個token最多四周內(nèi)有效2,419,200秒)。
存儲在cookie中的token包含用戶名、密碼、過期時間和一個私鑰——在寫入cookie前都進行了MD5哈希。默認情況下,私鑰的名為SpringSecured,但在這里我們將其設(shè)置為spitterKey,使它專門用于Spittr應(yīng)用。
如此簡單。既然Remember-me功能已經(jīng)啟用,我們需要有一種方式來讓用戶表明他們希望應(yīng)用程序能夠記住他們。為了實現(xiàn)這一點,登錄請求必須包含一個名為remember-me的參數(shù)。在登錄表單中,增加一個簡單復(fù)選框就可以完成這件事情:
<input id="remember_me" name="remember-me" type="checkbox"/>
<label for="remember_me" class="inline">Remember me</label>
在應(yīng)用中,與登錄同等重要的功能就是退出。如果你啟用Remember-me功能的話,更是如此,否則的話,用戶將永遠登錄在這個系統(tǒng)中。我們下面將看一下如何添加退 出功能。
9.4.4 退出
其實,按照我們的配置,退出功能已經(jīng)啟用了,不需要再做其他的配置了。我們需要的只是一個使用該功能的鏈接。退出功能是通過Servlet容器中的Filter實現(xiàn)的(默認情況下),這個Filter會攔截針對“/logout”的請求。
因此,為應(yīng)用添加退出功能只需添加如下的鏈接即可(如下以Thymeleaf代碼片段的形式進行了展現(xiàn)):
<a th:href="@{/logout}">Logout</a>
當用戶點擊這個鏈接的時候,會發(fā)起對“/logout”的請求,這個請求會被Spring Security的LogoutFilter所處理。用戶會退出應(yīng)用,所有的Remember-me token都會被清除掉。
在退出完成后,用戶瀏覽器將會重定向到“/login?logout”,從而允許用戶進行再次登錄。
如果你希望用戶被重定向到其他的頁面,如應(yīng)用的首頁,那么可以在configure()中進行如下的配置
httpSecurity.formLogin()
.loginPage("/login")
.and()
.logout()
.logoutSuccessUrl("/")
logout()提供了配置退出行為的方法。在本例中,調(diào)用logoutSuccessUrl()表明在退出成功之后,瀏覽器需要重定 向到“/”。
除了logoutSuccessUrl()方法以外,你可能還希望重寫默認的LogoutFilter攔截路徑。我們可以通過調(diào)用logoutUrl()方法實現(xiàn)這一功能:
.logout()
.logoutSuccessUrl("/")
.logoutUrl("/signout")
如何在發(fā)起請求的時候保護Web應(yīng)用。這假設(shè)安全性主要涉及阻止用戶訪問沒有權(quán)限的URL。
但是,如果我們能夠不給用戶顯示其無權(quán)訪問的連接,那么這也是一個很好的思路。
9.5 保護視圖
當為瀏覽器渲染HTML內(nèi)容時,你可能希望視圖中能夠反映安全限制和相關(guān)的信息。一個簡單的樣例就是渲染用戶的基本信息(比如顯示“您已經(jīng)以……身份登錄”)。或者你想根據(jù)用戶被授予了什么權(quán)限,有條件地渲染特定的視圖元素。 在第6章,我們看到了在Spring MVC應(yīng)用中渲染視圖的兩個最重要的可選方案:JSP和Thymeleaf。不管你使用哪種方案,都有辦法在視圖上實現(xiàn)安全性。Spring Security本身提供了一個JSP標簽庫,而Thymeleaf通過特定的方言實現(xiàn)了與Spring Security的集成。 讓我們看一下如何將Spring Security用到視圖中,就從Spring Security的JSP標簽庫開始吧。
9.5.2 使用Thymeleaf的Spring Security方言
Thymeleaf的安全方言提供了條件化渲染和顯示認證細節(jié)的能力。
為了使用安全方言,我們需要確保Thymeleaf Extras Spring Security已經(jīng)位于應(yīng)用的類路徑下。 然后,還需要在配置中使用SpringTemplateEngine來注冊SpringSecurityDialect。
程序清單9.10所展現(xiàn)的@Bean方法聲明了SpringTemplateEngine bean,其中就包含了SpringSecurityDialect。
@Bean
public SpringTemplateEngine templateEngine(TemplateResolver templateResolver) {
SpringTemplateEngine templateEngine = new SpringTemplateEngine();
templateEngine.setTemplateResolver(templateResolver);
// 注冊安全方言
templateEngine.addDialect(new SpringStandardDialect());
return templateEngine;
}
安全方言注冊完成之后,我們就可以在Thymeleaf模板中使用它的屬性了。首先,需要在使用這些屬性的模板中聲明安全命名空間:
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
...
</html>
標準的Thymeleaf方法依舊與之前一樣,使用th前綴,安全方言則設(shè)置為使用sec前綴。
這樣我們就能在任意合適的地方使用Thymeleaf屬性了。比如,假設(shè)我們想要為認證用戶渲染“Hello”文本。如下的Thymeleaf模板代碼片段就能完成這項任務(wù):
<div sec:authorize="isAuthenticated()">
Hello <span sec:authentication="name">someone</span>
</div>
好像沒有效果。。。。
9.6 小結(jié)
對于許多應(yīng)用而言,安全性都是非常重要的切面。Spring Security提供了一種簡單、靈活且強大的機制來保護我們的應(yīng)用程序。
借助于一系列Servlet Filter,Spring Security能夠控制對Web資源的訪問,包括Spring MVC控制器。借助于Spring Security的Java配置模型,我們不必直接處理Filter,能夠非常簡潔地聲明Web安全性功能。
當認證用戶時,Spring Security提供了多種選項。我們探討了如何基于內(nèi)存用戶庫、關(guān)系型數(shù)據(jù)庫和LDAP目錄服務(wù)器來配置認證功能。如果這些可選方案無法滿足認證需求的話,我們還學(xué)習(xí)了如何創(chuàng)建和配置自定義的用戶服務(wù)。
附:參考內(nèi)容:
手工配置springboot + spring security + thymeleaf + thymeleaf-extras-springsecurity https://my.oschina.net/kitos/blog/1632381
Spring-Security自定義登錄頁&inMemoryAuthentication驗證 https://www.cnblogs.com/MrSi/p/7993875.html>
springboot 構(gòu)建 security https://docs.spring.io/spring-security/site/docs/5.0.13.BUILD-SNAPSHOT/reference/htmlsingle/
初識 Spring Security https://www.w3cschool.cn/springsecurity/
參考項目 springboot整合 https://github.com/wean2016/springsecurity
JWT的Java使用 https://blog.csdn.net/qq_37636695/article/details/79265711
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9 eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.
TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
=================================================================
實戰(zhàn)代碼
基礎(chǔ)的自定義頁面認證
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
?
/**
* @description: 繼承AbstractSecurityWebApplicationInitializer會自動注冊DelegatingFilterProxy
* 等價于xml配置 springSecurityFilterChain
* @version: 1.0
* @data: 2019-04-19 11:52
*/
public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}
package com.web.spittr.config.security;
?
import com.web.spittr.data.SpittleRepository;
import lombok.extern.slf4j.Slf4j;
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.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.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
?
import javax.sql.DataSource;
?
?
@Configuration
@EnableWebMvcSecurity
//@EnableWebSecurity
@Slf4j
public class SecurityConfig extends WebSecurityConfigurerAdapter {
?
@Autowired
SpittleRepository spittleRepository;
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 啟用默認的登錄頁
httpSecurity
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/spittle/")
.failureUrl("/spittle/login?error=true")
.and()
.logout()
.logoutSuccessUrl("/login")
.logoutUrl("/spittle/login?logout=true")
.and()
.rememberMe()
.tokenValiditySeconds(2439800)
.key("spittrKey")
.and()
.authorizeRequests()
.antMatchers("/spittle/user/*").hasRole("USER")
.antMatchers("/spittle/admin/*").hasRole("ADMIN")
.anyRequest()
.permitAll()
;
httpSecurity.csrf().disable();
}
?
?
/**
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws AuthenticationException {
System.out.println("加載Security。。。讀取權(quán)限");
// 啟用內(nèi)存用戶儲存
auth.inMemoryAuthentication()
.passwordEncoder(NoOpPasswordEncoder.getInstance())
.withUser("user").password("1").roles("USER").and()
.withUser("admin").password("1").roles("USER","ADMIN");
}
?
?
?
?
}
<form method="post" action="/login" >
<table>
<tr>
<td>User:</td>
<td> <input name="username" type="text" value="" /> </td>
</tr>
<tr>
<td>Password:</td>
<td> <input name="password" type="password" /> </td>
</tr>
?
<tr>
<td><input id="remember_me" name="remember-me" type="checkbox"/></td>
<td><label for="remember_me" class="inline">Remember me</label></td>
</tr>
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
<tr>
<td colspan="2">
<input name="submit" type="submit" value="Login" />
<input name="reset" type="reset" value="Reset" />
</td>
</tr>
</table>
?
</form>
未完待續(xù)
基礎(chǔ)的認證雖然完成了,但是沒有從數(shù)據(jù)讀取user信息,需要繼續(xù)來驗證