如何使用Spring Session 與 Spring security 完成網(wǎng)站登錄改造,相信很多沒(méi)有經(jīng)驗(yàn)的人對(duì)此束手無(wú)策,為此本文總結(jié)了問(wèn)題出現(xiàn)的原因和解決方法,通過(guò)這篇文章希望你能解決這個(gè)問(wèn)題。
10年的巴彥網(wǎng)站建設(shè)經(jīng)驗(yàn),針對(duì)設(shè)計(jì)、前端、開發(fā)、售后、文案、推廣等六對(duì)一服務(wù),響應(yīng)快,48小時(shí)及時(shí)工作處理。全網(wǎng)整合營(yíng)銷推廣的優(yōu)勢(shì)是能夠根據(jù)用戶設(shè)備顯示端的尺寸不同,自動(dòng)調(diào)整巴彥建站的顯示方式,使網(wǎng)站能夠適用不同顯示終端,在瀏覽器中調(diào)整網(wǎng)站的寬度,無(wú)論在任何一種瀏覽器上瀏覽網(wǎng)站,都能展現(xiàn)優(yōu)雅布局與設(shè)計(jì),從而大程度地提升瀏覽體驗(yàn)。創(chuàng)新互聯(lián)建站從事“巴彥網(wǎng)站設(shè)計(jì)”,“巴彥網(wǎng)站推廣”以來(lái),每個(gè)客戶項(xiàng)目都認(rèn)真落實(shí)執(zhí)行。
上次小黑在文章中介紹了四種分布式一致性 Session 的實(shí)現(xiàn)方式,在這四種中最常用的就是后端集中存儲(chǔ)方案,這樣即使 web 應(yīng)用重啟或者擴(kuò)容,Session 都沒(méi)有丟失的風(fēng)險(xiǎn)。
今天我們就使用這種方式對(duì) Session 存儲(chǔ)方式進(jìn)行改造,將其統(tǒng)一存儲(chǔ)到 redis 中。
我們先來(lái)想一下,如果我們不依靠任何框架,自己如何實(shí)現(xiàn)后端 Session集中存儲(chǔ)。
這里我們假設(shè)我們的網(wǎng)站除了某些頁(yè)面,比如首頁(yè)可以直接訪問(wèn)以外,其他任何頁(yè)面都需要登錄之后才能訪問(wèn)。
如果需要實(shí)現(xiàn)這個(gè)需求,這就需要我們對(duì)每個(gè)請(qǐng)求都進(jìn)行鑒權(quán),鑒權(quán)目的是為了判斷用戶是否登錄,判斷用戶角色。
如果用戶沒(méi)有登錄,我們需要將請(qǐng)求強(qiáng)制跳轉(zhuǎn)到登錄頁(yè)面進(jìn)行登錄。
用戶登錄之后,我們需要將登錄獲取到的用戶信息存儲(chǔ)到 Session中,這樣后面請(qǐng)求鑒權(quán)只需要判斷 Session中是否存在即可。
知道整個(gè)流程之后,其實(shí)實(shí)現(xiàn)原理就不是很難了。
我們可以使用類似 AOP的原理,在每個(gè)請(qǐng)求進(jìn)來(lái)之后,都先判斷 Session 中是否存在用戶信息,如果不存在就跳轉(zhuǎn)到登錄頁(yè)。
整個(gè)流程如下所示:
我們可以利用 Servelt Filter實(shí)現(xiàn)上述流程,不過(guò)上述整套流程,Spring 已經(jīng)幫我們實(shí)現(xiàn)了,那我們就不用重復(fù)造輪子了。
我們可以使用 Spring-Session與 Spring-security實(shí)現(xiàn)上述網(wǎng)站的流程。
Spring-Session是 Spring 提供一套管理用戶 Session的實(shí)現(xiàn)方案,使用 Spring-Session之后,默認(rèn) WEB 容器,比如 Tomcat,產(chǎn)生的 Session將會(huì)被 Spring-Session接管。
除此之外,Spring-Session還提供幾種常見后端存儲(chǔ)實(shí)現(xiàn)方案,比如 Redis,數(shù)據(jù)庫(kù)等。
有了 Spring-Session之后,它只是幫我們解決了 Session后端集中存儲(chǔ)。但是上述流程中我們還需要登錄授權(quán),而這一塊我們可以使用 Spring-security來(lái)實(shí)現(xiàn)。
Spring-security可以維護(hù)統(tǒng)一的登錄授權(quán)方式,同時(shí)它可以結(jié)合 Spring-Session一起使用。用戶登錄授權(quán)之后,獲取的用戶信息可以自動(dòng)存儲(chǔ)到 Spring-Session中。
好了,不說(shuō)廢話了,我們來(lái)看下實(shí)現(xiàn)代碼。
下述使用 Spring Boot 實(shí)現(xiàn), Spring-Boot 版本為:2.3.2.RELEASE
首先我們引入 Spring Session 依賴,這里我們使用 Redis 集中存儲(chǔ) Session 信息,所以我們需要下述依賴即可。
<dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>
如果不是 Spring Boot 項(xiàng)目,那主要需要引入如下依賴:
<dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-redis</artifactId> <version>2.3.0.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-core</artifactId> <version>2.3.0.RELEASE</version> </dependency>
引入依賴之后,我們首先需要在 application.properties
增加 Session 相關(guān)的配置:
## Session 存儲(chǔ)方式 spring.session.store-type=redis ## Session 過(guò)期時(shí)間,默認(rèn)單位為 s server.servlet.session.timeout=600 ## Session 存儲(chǔ)到 Redis 鍵的前綴 spring.session.redis.namespace=test:spring:session ## Redis 相關(guān)配置 spring.redis.host=127.0.0.1 spring.redis.password=**** spring.redis.port=6379
配置完成之后,Spring Session 就會(huì)開始管理 Session 信息,下面我們來(lái)測(cè)試一下:
@ResponseBody @GetMapping("/hello") public String hello() { return "Hello World"; }
當(dāng)我們?cè)L問(wèn)上面地址之后,訪問(wèn) Redis ,可以看到存儲(chǔ)的 Session 信息。
推薦大家一個(gè) Redis 客戶端「Another Redis DeskTop Manager」,這個(gè)客戶端 UI 頁(yè)面非常漂亮,操作也很方便,下載地址:
https://github.com/qishibo/anotherredisdesktopmanager/releases
默認(rèn)情況下,Session 默認(rèn)使用HttpSession 序列化方式,這種值看起來(lái)不夠直觀。我們可以將其修改成 json 序列化方式,存儲(chǔ)到 redis 中。
@Configuration public class HttpSessionConfig implements BeanClassLoaderAware { private ClassLoader loader; @Bean public RedisSerializer<Object> springSessionDefaultRedisSerializer() { return new GenericJackson2JsonRedisSerializer(objectMapper()); } /** * Customized {@link ObjectMapper} to add mix-in for class that doesn't have default * constructors * * @return the {@link ObjectMapper} to use */ private ObjectMapper objectMapper() { ObjectMapper mapper = new ObjectMapper(); mapper.registerModules(SecurityJackson2Modules.getModules(this.loader)); return mapper; } @Override public void setBeanClassLoader(ClassLoader classLoader) { this.loader = classLoader; } }
修改之后 Redis 鍵值如下所示:
ps:這里 Redis 鍵值含義,下次分析源碼的時(shí)候,再做分析。
Spring Session 還存在一個(gè) @EnableRedisHttpSession,我們可以在這個(gè)注解上配置 Spring Session 相關(guān)配置。
@EnableRedisHttpSession(redisNamespace = "test:session")
需要注意的是,如果使用這個(gè)注解,將會(huì)導(dǎo)致 application.properties
Session 相關(guān)配置失效,也就是說(shuō) Spring Session 將會(huì)直接使用注解上的配置。
這里小黑比較推薦大家使用配置文件的方式。
好了,Spring Session 到這里我們就接入完成了。
上面我們集成了 Spring Session,完成 Session 統(tǒng)一 Redis 存儲(chǔ)。接下來(lái)主要需要實(shí)現(xiàn)請(qǐng)求的登陸鑒權(quán)。
這一步我們使用 Spring security 實(shí)現(xiàn)統(tǒng)一的登陸鑒權(quán)服務(wù),同樣的框架的還有 Shiro,這里我們就使用 Spring 全家桶。
首先我們需要依賴的相應(yīng)的依賴:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
引入上面的依賴之后,應(yīng)用啟動(dòng)之后將會(huì)生成一個(gè)隨機(jī)密碼,然后所有的請(qǐng)求將會(huì)跳轉(zhuǎn)到一個(gè) Spring security 的頁(yè)面。
這里我們需要實(shí)現(xiàn)自己業(yè)務(wù)的登陸頁(yè),所以我們需要自定義登錄校驗(yàn)邏輯。
在 Spring security 我們只需要實(shí)現(xiàn) UserDetailsService
接口,重寫 loadUserByUsername
方法邏輯。
@Service public class UserServiceImpl implements UserDetailsService { @Autowired PasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { // 簡(jiǎn)單起見,直接內(nèi)部校驗(yàn) String uname = "admin"; String passwd = "1234qwer"; // 如果是正式項(xiàng)目,我們需要從數(shù)據(jù)庫(kù)數(shù)據(jù)數(shù)據(jù),然后再校驗(yàn),形式如下: // User user = userDAO.query(username); if (!username.equals(uname)) { throw new UsernameNotFoundException(username); } // 封裝成 Spring security 定義的 User 對(duì)象 return User.builder() .username(username) .passwordEncoder(s -> passwordEncoder.encode(passwd)) .authorities(new SimpleGrantedAuthority("user")) .build(); } }
上面代碼實(shí)現(xiàn),這里主要在內(nèi)存固定用戶名與密碼,真實(shí)環(huán)境下,我們需要修改成從數(shù)據(jù)庫(kù)查詢用戶信息。
接著我們需要把 UserServiceImpl
配置到 Spring security
中。
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired UserServiceImpl userService; @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 使用自定義用戶服務(wù)校驗(yàn)登錄信息 * * @param auth * @throws Exception */ @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 用戶登錄信息校驗(yàn)使用自定義 userService // 還需要注意密碼加密與驗(yàn)證需要使用同一種方式 auth.userDetailsService(userService).passwordEncoder(passwordEncoder()); } }
上面的配置中,密碼部分我們使用 BCrypt
算法加密,這里需要注意,加密與解密需要使用同一種方式。
接著我們需要實(shí)現(xiàn)一個(gè)自定義的登陸頁(yè)面,這里就懶得自己寫了,直接使用 spring-session-data-redis 頁(yè)面。
<!DOCTYPE html> <html xmlns:th="https://www.thymeleaf.org" xmlns:layout="https://github.com/ultraq/thymeleaf-layout-dialect" layout:decorate="~{layout}"> <head> <title>Login</title> </head> <body> <div layout:fragment="content"> <!-- 自定義登錄的請(qǐng)求 --> <form name="f" th:action="@{/auth/login}" method="post"> <fieldset> <legend>Please Login -</legend> <div th:if="${param.error}" class="alert alert-error">Invalid username and password.</div> <div th:if="${param.logout}" class="alert alert-success">You have been logged out.</div> <label for="username">Username</label> <input type="text" id="username" name="username"/> <label for="password">Password</label> <input type="password" id="password" name="password"/> <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/> <label>remember me: </label> <input type="checkbox" name="remember-me"/> <div class="form-actions"> <button type="submit" class="btn">Log in</button> </div> </fieldset> </form> </div> </body> </html>
這里需要注意一點(diǎn),這里 form表單的請(qǐng)求地址使用 /auth/login
,我們需要在下面配置中修改,默認(rèn)情況下登錄請(qǐng)求的地址需要為 /login
。
接著我們?cè)谏厦娴?SecurityConfig
類增加相應(yīng)配置方法:
/** * 自定義處理登錄處理 * * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests((authorize) -> authorize .requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll() // 靜態(tài)資源,比如 css,js 無(wú)需登錄鑒權(quán) .anyRequest().permitAll() // 其他頁(yè)面需要登錄鑒權(quán) ).formLogin((formLogin) -> formLogin // 自定義登錄頁(yè)面 .loginPage("/login") // 登錄頁(yè) .loginProcessingUrl("/auth/login") // 自定義登錄請(qǐng)求地址 .permitAll()// 登錄頁(yè)當(dāng)然無(wú)需鑒權(quán)了,不然不就套娃了嗎? ).logout(LogoutConfigurer::permitAll // 登出頁(yè)面 ).rememberMe(rememberMe -> rememberMe .rememberMeCookieName("test-remember") // 自定義記住我 cookie 名 .key("test") // 鹽值 .tokenValiditySeconds(3600 * 12)) // 記住我,本地生成 cookie 包含用戶信息 ; }
這個(gè)方法可能比較長(zhǎng),重點(diǎn)解釋一下:
authorizeRequests
方法內(nèi)需要指定那些頁(yè)面需要鑒權(quán),這里我們指定靜態(tài)資源無(wú)需登錄鑒權(quán),其他請(qǐng)求我們都需要登錄鑒權(quán)
formLogin
方法內(nèi)修改默認(rèn)的登錄頁(yè)面地址,以及登錄的請(qǐng)求地址。
logout
在這里面可以配置登出的相關(guān)配置。
rememberMe
開啟這個(gè)功能之后,當(dāng)內(nèi)部 Session 過(guò)期之后,用戶還可以根據(jù)用戶瀏覽器中的 Cookie 信息實(shí)現(xiàn)免登錄的功能。
最后我們需要配置一些頁(yè)面的跳轉(zhuǎn)地址:
@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { // 首頁(yè) registry.addViewController("/").setViewName("home"); // 登錄之后跳轉(zhuǎn)到 home 頁(yè) registry.addViewController("/login").setViewName("login"); } }
到此為止,我們已經(jīng)集成 Spring-Session與 Spring-security完成完整的網(wǎng)站的登錄鑒權(quán)功能。從這個(gè)例子可以看到,引入這個(gè)兩個(gè)框架之后,我們只需要按照 Spring 規(guī)范開發(fā)即可,其他復(fù)雜實(shí)現(xiàn)原理我們都不需要自己實(shí)現(xiàn)了,這樣真的很方便。
看完上述內(nèi)容,你們掌握如何使用Spring Session 與 Spring security 完成網(wǎng)站登錄改造的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道,感謝各位的閱讀!
網(wǎng)頁(yè)題目:如何使用SpringSession與Springsecurity完成網(wǎng)站登錄改造
本文路徑:http://sd-ha.com/article8/jgspip.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站制作、搜索引擎優(yōu)化、虛擬主機(jī)、網(wǎng)站建設(shè)、全網(wǎng)營(yíng)銷推廣、企業(yè)建站
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來(lái)源: 創(chuàng)新互聯(lián)