Spring Security 安全框架應(yīng)用原理解析
Spring Security 簡介
背景分析
企業(yè)中數(shù)據(jù)是最重要的資源,對于這些數(shù)據(jù)而言,有些可以直接匿名訪問,有些只能登錄以后才能訪問,還有一些你登錄成功以后,權(quán)限不夠也不能訪問.總之這些規(guī)則都是保護(hù)系統(tǒng)資源不被破壞的一種手段.幾乎每個(gè)系統(tǒng)中都需要這樣的措施對數(shù)據(jù)(資源)進(jìn)行保護(hù).我們通常會通過軟件技術(shù)對這樣業(yè)務(wù)進(jìn)行具體的設(shè)計(jì)和實(shí)現(xiàn).早期沒有統(tǒng)一的標(biāo)準(zhǔn),每個(gè)系統(tǒng)都有自己獨(dú)立的設(shè)計(jì)實(shí)現(xiàn),但是對于這個(gè)業(yè)務(wù)又是一個(gè)共性,后續(xù)市場上就基于共享做了具體的落地實(shí)現(xiàn),例如Spring Security,Apache shiro誕生了.
認(rèn)證授權(quán)分析
用戶在進(jìn)行資源訪問時(shí),要求系統(tǒng)要對用戶進(jìn)行權(quán)限控制,其具體流程如圖所示:
Spring Security 概述
Spring Security 是一個(gè)企業(yè)級安全框架,由spring官方推出,它對軟件系統(tǒng)中的認(rèn)證,授權(quán),加密等功能進(jìn)行封裝,并在springboot技術(shù)推出以后,配置方面做了很大的簡化.市場上現(xiàn)在的分布式架構(gòu)下的安全控制正在逐步的轉(zhuǎn)向Spring Security.
Spring Security 基本架構(gòu)
Spring Security 在企業(yè)中實(shí)現(xiàn)認(rèn)證和授權(quán)業(yè)務(wù)時(shí),底層構(gòu)建了大量的過濾器.
其中:
綠色部分為認(rèn)證過濾器,需要我們自己配置,也可以配置過個(gè)認(rèn)證過濾器.也可以使用Spring Security提供的默認(rèn)認(rèn)證過濾器.黃色部分為授權(quán)過濾器.Spring Security就是通過這些過濾器然后調(diào)用相關(guān)對象一起完成認(rèn)證和授權(quán)操作.
Spring Security 快速入門
創(chuàng)建工程
添加項(xiàng)目依賴
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <artifactId>spring-boot-starter-parent</artifactId> <groupId>org.springframework.boot</groupId> <version>2.3.2.RELEASE</version> </parent> <groupId>com.cy</groupId> <artifactId>02-jt-spring-security</artifactId> <version>1.0-SNAPSHOT</version> <properties> <maven.compiler.source>8</maven.compiler.source> <maven.compiler.target>8</maven.compiler.target> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency> </dependencies> </project>
創(chuàng)建配置文件
在resources目錄下創(chuàng)建application.yml文件,并指定服務(wù)端口
server: port: 8080
創(chuàng)建項(xiàng)目啟動類
package com.cy.jt; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringSecurityApplication { public static void main(String[] args) { SpringApplication.run( SpringSecurityApplication.class, args); } }
運(yùn)行啟動類訪問測試
第一步:檢查控制輸出,是否自動生成了一個(gè)密碼,例如:
Using generated security password: 360123aa-df93-4cd9-bab4-5212af421d2c
第二步:打開瀏覽器輸入http://localhost:8080,然后呈現(xiàn)登錄頁面,例如:
在登錄窗口中輸入用戶名user(系統(tǒng)默認(rèn)),密碼(服務(wù)啟動時(shí),控制臺默認(rèn)輸出的
密碼),然后點(diǎn)擊Sign in進(jìn)行登錄,登錄成功默認(rèn)會出現(xiàn),如下界面:
定義登錄成功頁面
在項(xiàng)目的resources目錄下創(chuàng)建static目錄,并在此目錄創(chuàng)建一個(gè)index.html文件,例如:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>Login Ok</h1> </body> </html>
啟動服務(wù),再次進(jìn)行登錄訪問測試,登錄成功以后系統(tǒng)默認(rèn)會跳轉(zhuǎn)到index.html頁面,例如
配置登錄密碼
第一步:編寫一個(gè)方法(可以在啟動類中調(diào)用執(zhí)行),對一個(gè)名文進(jìn)行加密,例如:
static void encodePwd(){ BCryptPasswordEncoder encoder=new BCryptPasswordEncoder(); String password="123456";//明文 String newPwd=encoder.encode("123456"); System.out.println(newPwd);//$2a$10$fahHJIe3SJm3KcyiPPQ2d.a2qR029gB3qKHrKanQ87u.KbtZ6Phr. }
第二步:將用戶和密碼在在springboot工程的application.yml文件中進(jìn)行配置,例如:
spring: security: user: name: jack #password: 123456 #這種寫法,密碼太簡單了 password: '{bcrypt}$2a$10$fahHJIe3SJm3KcyiPPQ2d.a2qR029gB3qKHrKanQ87u.KbtZ6Phr.'
其中,{bcrypt}指定了密碼加密時(shí)使用的算法
第三步:啟動服務(wù),重新進(jìn)行登錄測試.
SpringSecurity 認(rèn)證邏輯實(shí)現(xiàn)
自定義登陸邏輯
SpringSecurity支持通過配置文件的方式定義用戶信息(賬號密碼和角色等),但這種方式有明顯的缺點(diǎn),那就是系統(tǒng)上線后,用戶信息的變更比較麻煩。因此SpringSecurity還支持通過實(shí)現(xiàn)UserDetailsService接口的方式來提供用戶認(rèn)證授權(quán)信息,其應(yīng)用過程如下:
第一步:定義security配置類,例如:
/** * 由@Configuration注解描述的類為spring中的配置類,配置類會在spring * 工程啟動時(shí)優(yōu)先加載,在配置類中通常會對第三方資源進(jìn)行初始配置. */ @Configuration public class SecurityConfig { /** * 定義SpringSecurity密碼加密對象 * @Bean 注解通常會在@Configuration注解描述的類中描述方法, * 用于告訴spring框架這個(gè)方法的返回值會交給spring管理,并spring * 管理的這個(gè)對象起個(gè)默認(rèn)的名字,這個(gè)名字與方法名相同,當(dāng)然也可以通過 * @Bean注解起名字 */ @Bean //對象名默認(rèn)為方法名 //@Bean("bcryptPasswordEncoder")//bean對象名字為bcryptPasswordEncoder public BCryptPasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } }
第二步:定義UserDetailService接口實(shí)現(xiàn)類,自定義登陸邏輯,代碼如下:
UserDetailService為SpringSecurity官方提供的登錄邏輯處理對象,我們自己可以實(shí)現(xiàn)此接口,然后在對應(yīng)的方法中進(jìn)行登錄邏輯的編寫即可.
package com.cy.jt.security.service; @Service public class UserDetailServiceImpl implements UserDetailsService { @Autowired private BCryptPasswordEncoder passwordEncoder; /** * 當(dāng)我們執(zhí)行登錄操作時(shí),底層會通過過濾器等對象,調(diào)用這個(gè)方法. * @param username 這個(gè)參數(shù)為頁面輸出的用戶名 * @return 一般是從數(shù)據(jù)庫基于用戶名查詢到的用戶信息 * @throws UsernameNotFoundException */ @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //1.基于用戶名從數(shù)據(jù)庫查詢用戶信息 //User user=userMapper.selectUserByUsername(username); if(!"jack".equals(username))//假設(shè)這是從數(shù)據(jù)庫查詢的信息 throw new UsernameNotFoundException("user not exists"); //2.將用戶信息封裝到UserDetails對象中并返回 //假設(shè)這個(gè)密碼是從數(shù)據(jù)庫查詢出來的 String encodedPwd=passwordEncoder.encode("123456"); //假設(shè)這個(gè)權(quán)限信息也是從數(shù)據(jù)庫查詢到的 //假如分配權(quán)限的方式是角色,編寫字符串時(shí)用"ROLE_"做前綴 List<GrantedAuthority> grantedAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList( "ROLE_admin,ROLE_normal,sys:res:retrieve,sys:res:create"); //這個(gè)user是SpringSecurity提供的UserDetails接口的實(shí)現(xiàn),用于封裝用戶信息 //后續(xù)我們也可以基于需要自己構(gòu)建UserDetails接口的實(shí)現(xiàn) User user=new User(username,encodedPwd,grantedAuthorities); return user; } }
說明,這里的User對象會交給SpringSecurity框架,框架提取出密碼信息,然后與用戶輸入的密碼進(jìn)行匹配校驗(yàn).
第三步:啟動服務(wù)進(jìn)行登陸,訪問測試。
自定義登陸頁面
第一步:定義登陸頁面(直接在static目錄下創(chuàng)建即可),關(guān)鍵代碼如下:
<!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 rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> </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="cc1471a5-3246-43ff-bef7-31d714273899" /> <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button> </form> </div> </body> </html>
注意:請求的url暫時(shí)為”/login”,請求方式必須為post方式,請求的參數(shù)暫時(shí)必須為username,password。這些規(guī)則默認(rèn)在UsernamePasswordAuthenticationFilter中進(jìn)行了定義。
第二步:修改安全配置類,讓其實(shí)現(xiàn)接口,并重寫相關(guān)config方法,進(jìn)行登陸設(shè)計(jì),代碼如下:
@Configuration public class SecutiryConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { //super.configure(http); //關(guān)閉跨域攻擊,不關(guān)閉容易出錯(cuò) http.csrf().disable(); //自定義登陸表單 http.formLogin() //設(shè)置登陸頁面 .loginPage("/login.html") //設(shè)置登陸請求處理地址(對應(yīng)form表單中的action),登陸時(shí)會訪問UserDetailService對象 .loginProcessingUrl("/login") //設(shè)置請求用戶名參數(shù)為username(默認(rèn)就是username,可以自己修改,需要與表單同步) .usernameParameter("username") //請求請求密碼參數(shù)為password(默認(rèn)就是password,可以自己修改,需要與表單同步) .passwordParameter("password") //設(shè)置登陸成功跳轉(zhuǎn)頁面(默認(rèn)為/index.html) .defaultSuccessUrl("/index.html") //登陸失敗訪問的頁面(默認(rèn)為/login.html?error) .failureUrl("/login.html?error"); //認(rèn)證設(shè)計(jì) http.authorizeRequests() //設(shè)置要放行的咨詢 .antMatchers("/login.html").permitAll() //設(shè)置需要認(rèn)證的請求(除了上面的要放行,其它都要進(jìn)行認(rèn)證) .anyRequest().authenticated(); } }
登陸成功和失敗處理器
現(xiàn)在的很多系統(tǒng)都采用的是前后端分離設(shè)計(jì),我們登陸成功以后可能會跳轉(zhuǎn)到前端系統(tǒng)的某個(gè)地址,或者返回一個(gè)json數(shù)據(jù),我們可以自己定義登錄成功的處理操作,例如:
定義登陸成功處理器:
方案1:可以直接執(zhí)行重定向的處理器,例如
package com.cy.jt.auth.config.authentication; public class RedirectAuthenticationSuccessHandler implements AuthenticationSuccessHandler { //定義要跳轉(zhuǎn)的url private String redirectUrl; public RedirectAuthenticationSuccessHandler(String redirectUrl){ this.redirectUrl=redirectUrl; } @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { httpServletResponse.sendRedirect(redirectUrl); } }
方案2:可以直接返回JSON數(shù)據(jù)的處理器,例如:
package com.cy.jt.security.config.handler; /**處理登錄失敗 * 0)Default-默認(rèn) * 1)Authentication-認(rèn)證 * 2)Failure-失敗 * 3)Handler-處理器 * */ public class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { //1.設(shè)置響應(yīng)數(shù)據(jù)的編碼 httpServletResponse.setCharacterEncoding("utf-8"); //2.告訴客戶端響應(yīng)數(shù)據(jù)的類型,以及客戶端以怎樣的編碼進(jìn)行顯示 httpServletResponse.setContentType("application/json;charset=utf-8"); //3.獲取一個(gè)輸出流對象 PrintWriter out=httpServletResponse.getWriter(); //4.向客戶端輸出一個(gè)json格式字符串 //4.1構(gòu)建一個(gè)map對象 Map<String,Object> map=new HashMap<>(); map.put("state","500"); map.put("msg","username or password error"); //4.2基于jackson中的ObjectMapper對象將一個(gè)對象轉(zhuǎn)換為json格式字符串 String jsonStr= new ObjectMapper().writeValueAsString(map); out.println(jsonStr); out.flush(); } }
定義登陸失敗處理器:
方案1:登陸失敗重定向到頁面,例如
package com.cy.jt.auth.config.authentication; public class RedirectAuthenticationFailureSuccessHandler implements AuthenticationFailureHandler { private String redirectUrl; public RedirectAuthenticationFailureSuccessHandler(String redirectUrl){ this.redirectUrl=redirectUrl; } @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { httpServletResponse.sendRedirect(redirectUrl); } }
方案2:定義登陸失敗處理器,例如:
package com.cy.jt.security.config.handler; /**處理登錄失敗 * 0)Default-默認(rèn) * 1)Authentication-認(rèn)證 * 2)Failure-失敗 * 3)Handler-處理器 * */ public class DefaultAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure( HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { //1.設(shè)置響應(yīng)數(shù)據(jù)的編碼 httpServletResponse.setCharacterEncoding("utf-8"); //2.告訴客戶端響應(yīng)數(shù)據(jù)的類型,以及客戶端以怎樣的編碼進(jìn)行顯示 httpServletResponse.setContentType("application/json;charset=utf-8"); //3.獲取一個(gè)輸出流對象 PrintWriter out=httpServletResponse.getWriter(); //4.向客戶端輸出一個(gè)json格式字符串 //4.1構(gòu)建一個(gè)map對象 Map<String,Object> map=new HashMap<>(); map.put("state","500"); map.put("msg","username or password error"); //4.2基于jackson中的ObjectMapper對象將一個(gè)對象轉(zhuǎn)換為json格式字符串 String jsonStr= new ObjectMapper().writeValueAsString(map); out.println(jsonStr); out.flush(); } }
修改配置類,設(shè)置登陸成功與失敗處理器。
@Configuration public class SecutiryConfig extends WebSecurityConfigurerAdapter { @Bean public PasswordEncoder passwordEncoder(){ return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity http) throws Exception { //super.configure(http); //關(guān)閉跨域攻擊,不關(guān)閉容易出錯(cuò) http.csrf().disable(); //自定義登陸表單 http.formLogin() //設(shè)置登陸頁面 .loginPage("/login.html") //設(shè)置登陸請求處理地址(對應(yīng)form表單中的action),登陸時(shí)會訪問UserDetailService對象 .loginProcessingUrl("/login") //設(shè)置請求用戶名參數(shù)為username(默認(rèn)就是username,可以自己修改,需要與表單同步) .usernameParameter("username") //請求請求密碼參數(shù)為password(默認(rèn)就是password,可以自己修改,需要與表單同步) .passwordParameter("password") //設(shè)置登陸成功跳轉(zhuǎn)頁面(默認(rèn)為/index.html) .successHandler(new RedirectAuthenticationSuccessHandler("你的url")) //登陸失敗訪問的頁面(默認(rèn)為/login.html?error) .failureHandler(new RedirectAuthenticationFailureHandler("你的url")) //認(rèn)證設(shè)計(jì) http.authorizeRequests() //設(shè)置要放行的咨詢 .antMatchers("/login.html").permitAll() //設(shè)置需要認(rèn)證的請求(除了上面的要放行,其它都要進(jìn)行認(rèn)證) .anyRequest().authenticated(); } }
第四步:啟動服務(wù)進(jìn)行訪問測試(分別用正確和錯(cuò)誤的賬號進(jìn)行測試)。
放行靜態(tài)資源
在SecurityManager配置類中的configure(HttpSecurity http)方法中我們可以通過對anMatchers方法定義要放行靜態(tài)資源,例如:
.authorizeRequests() //設(shè)置請求的授權(quán) .antMatchers( //配置下列路徑的授權(quán) "/index.html", "/js/*", "/css/*", "/img/**", "/bower_components/**", "/login.html" ).permitAll() //設(shè)置上述所有路徑不需要登錄就能訪問(放行)
其中:
- “*”用于匹配0個(gè)或多個(gè)字符
- “**”用于匹配0個(gè)或多個(gè)目錄及字符
登出設(shè)計(jì)及實(shí)現(xiàn)
在SecurityManager配置類中的configure(HttpSecurity http)方法中,添加登出配置,例如
http.logout() //開始設(shè)置登出信息 .logoutUrl("/logout") //登出路徑 .logoutSuccessUrl("/login.html?logout");//設(shè)置登出后顯示的頁面
SpringSecurity授權(quán)邏輯實(shí)現(xiàn)
修改授權(quán)配置類
在權(quán)限配置類上添加啟用全局方法訪問控制注解,例如:
package com.cy.auth.config; //這個(gè)配置類是配置Spring-Security的, //prePostEnabled= true表示啟動權(quán)限管理功能 @EnableGlobalMethodSecurity(prePostEnabled = true) @Configuration public class SpringSecurityConfigurer extends WebSecurityConfigurerAdapter { …… }
定義資源Controller
定義一個(gè)ResourceController類,作為資源訪問對象,例如
package com.cy.jt.auth.controller; @RestController public class ResourceController { @PreAuthorize("hasAuthority('sys:res:create')") @RequestMapping("/doCreate") public String doCreate(){ return "add resource"; } @PreAuthorize("hasAuthority('sys:res:update')") @RequestMapping("doUpdate") public String doUpdate(){ return "update resource"; } @PreAuthorize("hasAuthority('sys:res:delete')") @RequestMapping("/doDelete") public String doDelete(){ return "delete resource"; } @PreAuthorize("hasAuthority('sys:res:retrieve')") @RequestMapping("/doRetrieve") public String doRetrieve(){ return "retrieve resource"; } }
其中,@PreAuthorize注解描述方法時(shí),用于告訴系統(tǒng)訪問此方法時(shí)需要進(jìn)行權(quán)限檢測。需要具備指定權(quán)限才可以訪問。例如:
- @PreAuthorize(“hasAuthority('sys:res:delete”) 需要具備sys:res:delete權(quán)限
- @PreAuthorize(“hasRole(‘a(chǎn)dmin')”) 需要具備admin角色 啟動服務(wù)訪問測試
使用不同用戶進(jìn)行登陸,然后執(zhí)行資源訪問,假如沒有權(quán)限,則會看到響應(yīng)狀態(tài)嗎403,如圖所示:
Spring認(rèn)證和授權(quán)異常處理
異常類型
對于SpringSecurity框架而言,在實(shí)現(xiàn)認(rèn)證和授權(quán)業(yè)務(wù)時(shí),可能出現(xiàn)如下兩大類型異常:
1)AuthenticationException (用戶還沒有認(rèn)證就去訪問某個(gè)需要認(rèn)證才可訪問的方法時(shí),可能出現(xiàn)的異常,這個(gè)異常通常對應(yīng)的狀態(tài)碼401)
2)AccessDeniedException (用戶認(rèn)證以后,在訪問一些沒有權(quán)限的資源時(shí),可能會出現(xiàn)的異常,這個(gè)異常通常對應(yīng)的狀態(tài)嗎為403)
異常處理規(guī)范
SpringSecurity框架給了默認(rèn)的異常處理方式,當(dāng)默認(rèn)的異常處理方式不滿足我們實(shí)際業(yè)務(wù)需求時(shí),此時(shí)我們就要自己定義異常處理邏輯,編寫邏輯時(shí)需要遵循如下規(guī)范:
1)AuthenticationEntryPoint:統(tǒng)一處理 AuthenticationException 異常
2)AccessDeniedHandler:統(tǒng)一處理 AccessDeniedException 異常.
自定義異常處理對象
處理沒有認(rèn)證的訪問異常
package com.cy.jt.config; public class DefaultAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { //設(shè)置響應(yīng)數(shù)據(jù)的編碼 response.setCharacterEncoding("utf-8"); //告訴瀏覽器要響應(yīng)的內(nèi)容類型,以及編碼 response.setContentType("application/json;charset=utf-8"); Map<String,Object> map=new HashMap<>(); map.put("state",401); map.put("message","請先登錄"); PrintWriter out=response.getWriter(); out.println(new ObjectMapper().writeValueAsString(map)); out.flush(); out.close(); } }
處理沒有權(quán)限時(shí)拋出的異常
package com.cy.jt.config; public class DefaultAccessDeniedExceptionHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException { //設(shè)置響應(yīng)數(shù)據(jù)的編碼 response.setCharacterEncoding("utf-8"); //告訴瀏覽器要響應(yīng)的內(nèi)容類型,以及編碼 response.setContentType("application/json;charset=utf-8"); Map<String,Object> map=new HashMap<>(); map.put("state",403); map.put("message","沒有此資源的訪問權(quán)限"); PrintWriter out=response.getWriter(); out.println(new ObjectMapper().writeValueAsString(map)); out.flush(); out.close(); } }
配置異常處理對象
在配置類SecurityConfig中添加自定義異常處理對象,代碼如下
http.exceptionHandling() .authenticationEntryPoint(new DefaultAuthenticationEntryPoint()) .accessDeniedHandler(new DefaultAccessDeniedExceptionHandler());
配置完成后,重啟服務(wù)進(jìn)行訪問測試分析.
系統(tǒng)會話狀態(tài)分析與實(shí)踐 何為會話狀態(tài)
客戶端與服務(wù)端通訊過程中產(chǎn)生的狀態(tài)信息(類似會議記錄),稱之為會話狀態(tài).
會話狀態(tài)如何存儲
客戶端瀏覽器與服務(wù)端通訊時(shí)使用的是http協(xié)議,這個(gè)協(xié)議本身是無狀態(tài)協(xié)議,也就是說通過此協(xié)議,無法存儲會話狀態(tài),此時(shí)在服務(wù)端與客戶端就采用了一種Cookie與Session方式記錄會話狀態(tài).
有狀態(tài)的會話技術(shù)分析
- Cookie 技術(shù)
Cookie是由服務(wù)端創(chuàng)建但在客戶端存儲會話狀態(tài)的一個(gè)對象,此對象分為兩種類型,一種為會話Cookie,一種為持久Cookie,瀏覽器在訪問具體的某個(gè)域名時(shí)會攜帶這個(gè)域的有效Cookie到服務(wù)端.
- 會話Cookie: 瀏覽器關(guān)閉Cookie生命周期結(jié)束(一般默認(rèn)都是會話Cookie)
- 持久Cookie: 持久Cookie是在Cookie對象創(chuàng)建時(shí)指定了生命周期,例如一周時(shí)間,即便瀏覽器關(guān)閉,持久Cookie依舊有效.
- Session技術(shù)
Session技術(shù)由服務(wù)端創(chuàng)建,并在服務(wù)端存儲會話狀態(tài)的一個(gè)對象,當(dāng)Session對象創(chuàng)建時(shí),還會創(chuàng)建一個(gè)會話Cookie對象,并且通過這個(gè)會話Cookie將SessionId寫到客戶端,客戶端下次訪問服務(wù)端會攜帶這個(gè)會話Cookie,并且通過JsessionId找到Session對象,進(jìn)而獲取Session對象中存儲的數(shù)據(jù).Cookie默認(rèn)的生命周期為30分鐘.
在SpringSecurity中獲取用戶的認(rèn)證信息,就可以通過如下方式進(jìn)行實(shí)現(xiàn):
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
無狀態(tài)的會話技術(shù)分析
有狀態(tài)的會話實(shí)現(xiàn),在分布式架構(gòu)中可能會存在很多問題,例如瀏覽器默認(rèn)不支持?jǐn)y帶其它域的Cookie信息進(jìn)行資源訪問,同時(shí)服務(wù)端的Session默認(rèn)不能共享,當(dāng)然我們有一種方式可以將session持久化到到一些數(shù)據(jù)庫,例如Redis,下次請求到其它服務(wù)器(例如tomcat)時(shí),可以直接從redis中獲取登錄信息,但是假如并發(fā)比較大,數(shù)據(jù)庫的訪問壓力就會劇增,壓力太大有可能會導(dǎo)致系統(tǒng)宕機(jī).所以現(xiàn)在還有一種方案就是將用戶的登錄狀態(tài)信息都存儲在客戶端,服務(wù)端不記錄任何狀態(tài),服務(wù)端只負(fù)責(zé)對客戶端傳遞過來的狀態(tài)信息進(jìn)行解析,基于此方式進(jìn)行用戶登錄狀態(tài)的判斷,這樣的會話過程稱之為無狀態(tài)會話.
總結(jié)(Summary)
重難點(diǎn)分析
- SpringSecurity 產(chǎn)生背景?
- SpringSecurity 快速入門?(依賴,配置,登錄認(rèn)證,密碼的加密-啟動生成,配置文件)
- SpringSecurity 認(rèn)證邏輯分析及實(shí)踐?(認(rèn)證方式-用戶名和密碼,登錄頁
- 面,SecurityConfig.UserServiceDetail,成功,失敗,放行)
- SpringSecurity 授權(quán)邏輯分析及實(shí)現(xiàn)?(為什么,授權(quán)步驟,用到的注解)
FAQ 分析
- 如何理解認(rèn)證?(判定用戶身份的合法性)
- 如何校驗(yàn)用戶身份的合法性?(用戶密碼,指紋,刷臉,刷身份證,…)
- 如何進(jìn)行身份認(rèn)證?(自己寫認(rèn)證邏輯,借助框架去寫認(rèn)證邏輯-尊重框架規(guī)則)
- 市場上的認(rèn)證和授權(quán)框架有哪些?(SpringSecurity,Shiro)
- 為什么會選擇SpringSecurity?(功能強(qiáng)大,SpringBoot誕生后在配置方面做了大量的簡化)
- SpringSecurity中的加密方式你用的什么?(Bcrypt,底層基于隨機(jī)鹽方式對密碼進(jìn)行hash不可逆加密,更加安全,缺陷是慢)
- SpringSecurity中你用過哪些API?(BcryptPasswordEncoder,UserDetailService,UserDetail,User,
- AuthenticationSuccessHandler,AuthenticationFailureHandler,…)
- 為什么要進(jìn)行權(quán)限控制?(防止非法用戶破壞數(shù)據(jù))
- SpringSecurity進(jìn)行權(quán)限控制的步驟(@EnableGlobalMethodSecurity,@PreAuthorize)
- SpringSecurity在進(jìn)行認(rèn)證和授權(quán)時(shí)可能出現(xiàn)的異常?
- SpringSecurity在未認(rèn)證和未授權(quán)的前提下訪問授權(quán)資源時(shí),出現(xiàn)的異常如何處理?
- 作業(yè):用戶登錄成功以后,用戶信息默認(rèn)存在哪里了? (Session)
- 作業(yè):用戶登錄成功以后,如何獲取我們登錄的用戶信息?(這個(gè)用戶的用戶名,這個(gè)用戶的權(quán)限)
Bug 分析
- 依賴下載不完整
- 響應(yīng)json數(shù)據(jù)時(shí)出現(xiàn)文件下載
到此這篇關(guān)于Spring Security 安全框架應(yīng)用的文章就介紹到這了,更多相關(guān)Spring Security框架內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot+MySQL+Jpa實(shí)現(xiàn)對數(shù)據(jù)庫的增刪改查和分頁詳解
這篇文章主要介紹了SpringBoot+MySQL+Jpa實(shí)現(xiàn)對數(shù)據(jù)庫的增刪改查和分頁詳解,需要的朋友可以參考下2020-02-02Jvm調(diào)優(yōu)和SpringBoot項(xiàng)目優(yōu)化的詳細(xì)教程
這篇文章主要介紹了Jvm調(diào)優(yōu)和SpringBoot項(xiàng)目優(yōu)化,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-09-09解讀Spring接口方法加@Transactional失效的原因
這篇文章主要介紹了Spring接口方法加@Transactional失效的原因解讀,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03百度翻譯API使用詳細(xì)教程(前端vue+后端springboot)
這篇文章主要給大家介紹了關(guān)于百度翻譯API使用的相關(guān)資料,百度翻譯API是百度面向開發(fā)者推出的免費(fèi)翻譯服務(wù)開放接口,任何第三方應(yīng)用或網(wǎng)站都可以通過使用百度翻譯API為用戶提供實(shí)時(shí)優(yōu)質(zhì)的多語言翻譯服務(wù),需要的朋友可以參考下2024-02-02java基于線程池和反射機(jī)制實(shí)現(xiàn)定時(shí)任務(wù)完整實(shí)例
這篇文章主要介紹了java基于線程池和反射機(jī)制實(shí)現(xiàn)定時(shí)任務(wù)的方法,以完整實(shí)例形式較為詳細(xì)的分析了Java定時(shí)任務(wù)的功能原理與實(shí)現(xiàn)技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-11-11SpringBoot集成Druid實(shí)現(xiàn)多數(shù)據(jù)源的兩種方式
這篇文章主要介紹了SpringBoot集成Druid實(shí)現(xiàn)多數(shù)據(jù)源的兩種方式,集成com.baomidou的方式和基于AOP手動實(shí)現(xiàn)多數(shù)據(jù)源原生的方式,文中通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-03-03springboot2.1.7去除json返回字段中為null的字段
這篇文章主要介紹了springboot2.1.7去除json返回字段中為null的字段,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12