javascript
Spring Security OAuth2.0认证授权
文章目錄
- 1、基本概念
- 1.1.什么是認證
- 1.2 什么是會話
- 1.3什么是授權
- 1.4授權的數據模型
- 1.4 RBAC
- 1.4.1 基于角色的訪問控制
- 2、基于Session的認證方式
- 3、整合案例
- 3.1 SpringMVC+Servlet3.0模擬認證、會話、授權
- 3.1.1引入依賴
- 3.1.2 Spring 容器配置ApplicationConfig
- 3.1.3 servletContext配置 WebConfig
- 3.1.4 加載 Spring容器SpringApplicationInitializer
- 3.1.5實現認證功能
- 3.1.6實現會話功能
- 3.1.7實現授權功能
- 3.2 Spring集成Security
- 3.3 SpringBoot集成Security
- 4、工作原理
- 4.1結構總覽
- 4.2認證流程
- AuthenticationProvider接口
- UserDetailsService接口
- PasswordEncoder接口
- 4.3授權流程
- AccessDecisionManager(訪問決策管理器)
- 授權決策
- 5、自定認證
- 5.1登錄
- 5.2會話
- 5.3退出
- 5.4授權
- web授權
- 方法授權
- 6、分布式系統認證方案
- 6.1什么是分布式系統
- 6.2 分布式認證需求
- 統一認證授權
- 應用接入認證
- 6.3 分布式認證方案
- 選型分析
- 1、基于session的認證方式
- 2、基于token的認證方式
- 技術方案
- 7、OAuth2.0 開放授權
- 7.1 OAuth2.0介紹
- 7.2 Spring Cloud Security OAuth2 的實現
- 7.2.1代碼略。。。。。。
- 7.2.2授權服務器配置
- 1)@EnableAuthorizationServer 注解
- 2)配置客戶端詳細信息
- 3)管理令牌服務
- 4)令牌訪問端點配置
- 5)配置授權端點的URL(Endpoint URLs)
- 6)令牌端點的安全約束
- 7.2.3.授權碼模式
- 7.2.4.簡化模式
- 7.2.5.密碼模式
- 7.2.6.客戶端模式
- 7.2.7.資源服務測試
- 資源服務器配置
- 驗證token
- 7.3 JWT令牌
- 什么是JWT?
- JWT令牌結構
- 8、Spring Security實現分布式系統授權
- 需求分析
- 網關
什么是認證、授權、會話。
Java Servlet為支持http會話做了哪些事兒。
基于session認證機制的運作流程。
基于token認證機制的運作流程。
理解Spring Security的工作原理,Spring Security結構總覽,認證流程和授權,中間涉及到哪些組件,這些組件分
別處理什么,如何自定義這些組件滿足個性需求。
OAuth2.0認證的四種模式?它們的大體流程是什么?
Spring cloud Security OAuth2包括哪些組件?職責?
分布式系統認證需要解決的問題?
代碼參考:ahcfl_leon/work-study
1、基本概念
1.1.什么是認證
系統為什么要認證?
認證是為了保護系統的隱私數據與資源,用戶的身份合法方可訪問該系統的資源。
認證 :用戶認證就是判斷一個用戶的身份是否合法的過程,用戶去訪問系統資源時系統要求驗證用戶的身份信 息,身份合法方可繼續訪問,不合法則拒絕訪問。常見的用戶身份認證方式有:用戶名密碼登錄,二維碼登錄,手 機短信登錄,指紋認證等方式。
1.2 什么是會話
用戶認證通過后,為了避免用戶的每次操作都進行認證可將用戶的信息保證在會話中。會話就是系統為了保持當前 用戶的登錄狀態所提供的機制,常見的有基于session方式、基于token方式等。
1)基于session的認證方式如下圖:
它的交互流程是,用戶認證成功后,在服務端生成用戶相關的數據保存在session(當前會話)中,發給客戶端的 sesssion_id 存放到 cookie 中,這樣用戶客戶端請求時帶上 session_id 就可以驗證服務器端是否存在 session 數據,以此完成用戶的合法校驗,當用戶退出系統或session過期銷毀時,客戶端的session_id也就無效了。
2)基于token方式如下圖:
它的交互流程是,用戶認證成功后,服務端生成一個token發給客戶端,客戶端可以放到 cookie 或 localStorage 等存儲中,每次請求時帶上 token,服務端收到token通過驗證后即可確認用戶身份。
總結:
基于session的認證方式由Servlet規范定制,服務端要存儲session信息需要占用內存資源,客戶端需要支持 cookie;
基于token的方式則一般不需要服務端存儲token,并且不限制客戶端的存儲方式。如今移動互聯網時代 更多類型的客戶端需要接入系統,系統多是采用前后端分離的架構進行實現,所以基于token的方式更適合。
1.3什么是授權
微信來舉例,微信登錄成功后用戶即可使用微信的功能,比如,發紅包、發朋友圈、添加好友等,沒有綁定 銀行卡的用戶是無法發送紅包的,綁定銀行卡的用戶才可以發紅包,發紅包功能、發朋友圈功能都是微信的資源即 功能資源,用戶擁有發紅包功能的權限才可以正常使用發送紅包功能,擁有發朋友圈功能的權限才可以使用發朋友 圈功能,這個根據用戶的權限來控制用戶使用資源的過程就是授權。
為什么要授權?
認證是為了保證用戶身份的合法性,授權則是為了更細粒度的對隱私數據進行劃分,授權是在認證通過后發生的, 控制不同的用戶能夠訪問不同的資源。
授權:
授權是用戶認證通過根據用戶的權限來控制用戶訪問資源的過程,擁有資源的訪問權限則正常訪問,沒有權限則拒絕訪問。
1.4授權的數據模型
如何進行授權即如何對用戶訪問資源進行控制,首先需要學習授權相關的數據模型。
授權可簡單理解為Who對What(which)進行How操作,包括如下:
Who,即主體(Subject),主體一般是指用戶,也可以是程序,需要訪問系統中的資源。
What,即資源 (Resource),如系統菜單、頁面、按鈕、代碼方法、系統商品信息、系統訂單信息等。系統菜單、頁面、按鈕、代碼方法都屬于系統功能資源,對于web系統每個功能資源通常對應一個URL;系統商品信息、系統訂單信息都屬于實體資源(數據資源),實體資源由資源類型和資源實例組成,比如商品信息為資源類型,商品編號 為001的商品為資源實例。
How,權限/許可(Permission),規定了用戶對資源的操作許可,權限離開資源沒有意義, 如用戶查詢權限、用戶添加權限、某個代碼方法的調用權限、編號為001的用戶的修改權限等,通過權限可知用戶 對哪些資源都有哪些操作許可。
主體、資源、權限相關的數據模型如下:
主體(用戶id、賬號、密碼、…)
資源(資源id、資源名稱、訪問地址、…)
權限(權限id、權限標識、權限名稱、資源id、…)
角色(角色id、角色名稱、…)
角色和權限關系(角色id、權限id、…)
主體(用戶)和角色關系(用戶id、角色id、…)
主體(用戶)、資源、權限關系如下圖:
通常企業開發中將資源和權限表合并為一張權限表,如下:
資源(資源id、資源名稱、訪問地址、…)
權限(權限id、權限標識、權限名稱、資源id、…)
合并為:
權限(權限id、權限標識、權限名稱、資源名稱、資源訪問地址、…)
修改后數據模型之間的關系如下圖:
1.4 RBAC
如何實現授權?業界通常基于RBAC實現授權。
1.4.1 基于角色的訪問控制
RBAC基于角色的訪問控制(Role-Based Access Control)是按角色進行授權,比如:主體的角色為總經理可以查 詢企業運營報表,查詢員工工資信息等,訪問控制流程如下:
根據上邊的例子發現,當需要修改角色的權限時就需要修改授權的相關代碼,系統可擴展性差。
1.4.2 基于資源的訪問控制
RBAC基于資源的訪問控制(Resource-Based Access Control)是按資源(或權限)進行授權,比如:用戶必須 具有查詢工資權限才可以查詢員工工資信息等,訪問控制流程如下:
優點:系統設計時定義好查詢工資的權限標識,即使查詢工資所需要的角色變化為總經理和部門經理也不需要修改
授權代碼,系統可擴展性強。
2、基于Session的認證方式
認證流程
基于Session認證方式的流程是,用戶認證成功后,在服務端生成用戶相關的數據保存在session(當前會話),而發 給客戶端的 sesssion_id 存放到 cookie 中,這樣用客戶端請求時帶上 session_id 就可以驗證服務器端是否存在 session 數據,以此完成用戶的合法校驗。當用戶退出系統或session過期銷毀時,客戶端的session_id也就無效了。
基于Session的認證機制由Servlet規范定制,Servlet容器已實現,用戶通過HttpSession的操作方法即可實現,如 下是HttpSession相關的操作API。
3、整合案例
3.1 SpringMVC+Servlet3.0模擬認證、會話、授權
3.1.1引入依賴
1、由于是web工程,packaging設置為war
2、使用tomcat7-maven-plugin插件來運行工程
<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.1.5.RELEASE</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.0.1</version><scope>provided</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.8</version></dependency></dependencies>3.1.2 Spring 容器配置ApplicationConfig
在confifig包下定義ApplicationConfig.java,它對應web.xml中ContextLoaderListener的配置
@Configuration //相當于applicationContext.xml @ComponentScan(basePackages = "com.cfl",excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)}) public class ApplicationConfig {//在此配置 除了Controller的其它bean,比如:數據庫鏈接池、事務管理器、業務bean等。 }3.1.3 servletContext配置 WebConfig
本案例采用Servlet3.0無web.xml方式,的confifig包下定義WebConfifig.java,它對應s對應于DispatcherServlet配置。
@Configuration //就相當于springmvc.xml文件 @EnableWebMvc @ComponentScan(basePackages = "com.cfl",includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)}) public class WebConfig implements WebMvcConfigurer {//視頻解析器@Beanpublic InternalResourceViewResolver viewResolver(){InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();viewResolver.setPrefix("/WEB-INF/view/");viewResolver.setSuffix(".jsp");return viewResolver;}}3.1.4 加載 Spring容器SpringApplicationInitializer
在init包下定義Spring容器初始化類SpringApplicationInitializer,此類實現WebApplicationInitializer接口, Spring容器啟動時加載WebApplicationInitializer接口的所有實現類。
public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {//spring容器,相當于加載 applicationContext.xml@Overrideprotected Class<?>[] getRootConfigClasses() {return new Class[]{ApplicationConfig.class, WebSecurityConfig.class};}//servletContext,相當于加載springmvc.xml@Overrideprotected Class<?>[] getServletConfigClasses() {return new Class[]{WebConfig.class};}//url-mapping@Overrideprotected String[] getServletMappings() {return new String[]{"/"};} }SpringApplicationInitializer相當于web.xml,使用了servlet3.0開發則不需要再定義web.xml, ApplicationConfifig.class對應以下配置的application-context.xml,WebConfifig.class對應以下配置的spring- mvc.xml,web.xml的內容參考:
<web‐app><listener><listener‐class>org.springframework.web.context.ContextLoaderListener</listener‐class></listener><context‐param> <param‐name>contextConfigLocation</param‐name> <param‐value>/WEB‐INF/application‐context.xml</param‐value> </context‐param> <servlet><servlet‐name>springmvc</servlet‐name> <servlet‐class>org.springframework.web.servlet.DispatcherServlet</servlet‐class> <init‐param> <param‐name>contextConfigLocation</param‐name><param‐value>/WEB‐INF/spring‐mvc.xml</param‐value></init‐param> <load‐on‐startup>1</load‐on‐startup> </servlet> <servlet‐mapping> <servlet‐name>springmvc</servlet‐name> <url‐pattern>/</url‐pattern> </servlet‐mapping> </web‐app>3.1.5實現認證功能
在WebConfifig中新增如下配置,將/直接導向login.jsp頁面:
@Override // 視圖控制器public void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/").setViewName("login");}認證接口
用戶進入認證頁面,輸入賬號和密碼,點擊登錄,請求/login進行身份認證。
(1)定義認證接口,此接口用于對傳來的用戶名、密碼校驗,若成功則返回該用戶的詳細信息,否則拋出錯誤異常:
認證服務: public interface AuthenticationService {/*** 用戶認證* @param authenticationRequest 用戶認證請求,賬號和密碼* @return 認證成功的用戶信息*/UserDto authentication(AuthenticationRequest authenticationRequest); }認證請求結構: @Data public class AuthenticationRequest {//認證請求參數,賬號、密碼。。/*** 用戶名*/private String username;/*** 密碼*/private String password;}認證成功后返回的用戶詳細信息,也就是當前登錄用戶的信息: @Data @AllArgsConstructor public class UserDto {public static final String SESSION_USER_KEY = "_user";//用戶身份信息private String id;private String username;private String password;private String fullname;private String mobile;/*** 用戶權限*/private Set<String> authorities;}(2)認證實現類,根據用戶名查找用戶信息,并校驗密碼,這里模擬了兩個用戶:
package com.cfl.service;import com.cfl.model.AuthenticationRequest; import com.cfl.model.UserDto; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils;import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set;/*** @author Administrator* @version 1.0**/ @Service public class AuthenticationServiceImpl implements AuthenticationService{/*** 用戶認證,校驗用戶身份信息是否合法** @param authenticationRequest 用戶認證請求,賬號和密碼* @return 認證成功的用戶信息*/@Overridepublic UserDto authentication(AuthenticationRequest authenticationRequest) {//校驗參數是否為空if(authenticationRequest == null|| StringUtils.isEmpty(authenticationRequest.getUsername())|| StringUtils.isEmpty(authenticationRequest.getPassword())){throw new RuntimeException("賬號和密碼為空");}//根據賬號去查詢數據庫,這里測試程序采用模擬方法UserDto user = getUserDto(authenticationRequest.getUsername());//判斷用戶是否為空if(user == null){throw new RuntimeException("查詢不到該用戶");}//校驗密碼if(!authenticationRequest.getPassword().equals(user.getPassword())){throw new RuntimeException("賬號或密碼錯誤");}//認證通過,返回用戶身份信息return user;}//根據賬號查詢用戶信息private UserDto getUserDto(String userName){return userMap.get(userName);}//用戶信息private Map<String,UserDto> userMap = new HashMap<>();{Set<String> authorities1 = new HashSet<>();authorities1.add("p1");//這個p1我們人為讓它和/r/r1對應Set<String> authorities2 = new HashSet<>();authorities2.add("p2");//這個p2我們人為讓它和/r/r2對應userMap.put("zhangsan",new UserDto("1010","zhangsan","123","張三","133443",authorities1));userMap.put("lisi",new UserDto("1011","lisi","456","李四","144553",authorities2));} }(3)登錄Controller,對/login請求處理,它調用AuthenticationService完成認證并返回登錄結果提示信息:
@RestController public class LoginController {@AutowiredAuthenticationService authenticationService;@RequestMapping(value = "/login",produces = "text/plain;charset=utf-8")public String login(AuthenticationRequest authenticationRequest, HttpSession session){UserDetails userDetails = authenticationService.authentication(authenticationRequest); return userDetails.getFullname() + " 登錄成功";} }啟動項目,訪問路徑地址,進行測試
3.1.6實現會話功能
會話是指用戶登入系統后,系統會記住該用戶的登錄狀態,他可以在系統連續操作直到退出系統的過程。
認證的目的是對系統資源的保護,每次對資源的訪問,系統必須得知道是誰在訪問資源,才能對該請求進行合法性 攔截。因此,在認證成功后,一般會把認證成功的用戶信息放入Session中,在后續的請求中,系統能夠從Session 中獲取到當前用戶,用這樣的方式來實現會話機制。
增加會話控制 :首先在UserDto中定義一個SESSION_USER_KEY,作為Session中存放登錄用戶信息的key。
public static final String SESSION_USER_KEY = "_user";然后修改LoginController,認證成功后,將用戶信息放入當前會話。并增加用戶登出方法,登出時將session置為失效。
@RequestMapping(value = "/login",produces = "text/plain;charset=utf-8")public String login(AuthenticationRequest authenticationRequest, HttpSession session){UserDto userDto = authenticationService.authentication(authenticationRequest);//存入sessionsession.setAttribute(UserDto.SESSION_USER_KEY,userDto);return userDto.getUsername() +"登錄成功";}@GetMapping(value = "/logout",produces = {"text/plain;charset=UTF-8"})public String logout(HttpSession session){session.invalidate();return "退出成功";}啟動項目,訪問路徑地址,進行測試
3.1.7實現授權功能
現在我們已經完成了用戶身份憑證的校驗以及登錄的狀態保持,并且我們也知道了如何獲取當前登錄用戶(從 Session中獲取)的信息,接下來,用戶訪問系統需要經過授權,即需要完成如下功能:
匿名用戶(未登錄用戶)訪問攔截:禁止匿名用戶訪問某些資源。
登錄用戶訪問攔截:根據用戶的權限決定是否能訪問某些資源。
(1)增加權限數據
為了實現這樣的功能,我們需要在UserDto里增加權限屬性,用于表示該登錄用戶所擁有的權限,同時修改UserDto的構造方法。
并在AuthenticationServiceImpl中為模擬用戶初始化權限,其中張三給了p1權限,李四給了p2權限。
(2)增加測試資源
我們想實現針對不同的用戶能訪問不同的資源,前提是得有多個資源,因此在LoginController中增加測試資源2。
如上代碼所示
(3)實現授權攔截器
在interceptor包下定義SimpleAuthenticationInterceptor攔截器,實現授權攔截:
1、校驗用戶是否登錄
2、校驗用戶是否擁有操作權限
@Component public class SimpleAuthenticationInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {Object o = request.getSession().getAttribute(UserDto.SESSION_USER_KEY);if (Objects.isNull(o)) {writeContent(response,"請登錄");}UserDto userDto = (UserDto) o;String requestURI = request.getRequestURI();if( userDto.getAuthorities().contains("p1") && requestURI.contains("/r/r1")){return true;}if( userDto.getAuthorities().contains("p2") && requestURI.contains("/r/r2")){return true;}writeContent(response,"沒有權限,拒絕訪問");return false;}private void writeContent(HttpServletResponse response, String msg) {response.setContentType("text/html;charset=utf-8");try {PrintWriter writer = response.getWriter();writer.print(msg);writer.close();} catch (IOException e) {System.out.println("e = " + e);}} }**在WebConfifig中配置攔截器,匹配/r/的資源為受保護的系統資源,訪問該資源的請求進入 SimpleAuthenticationInterceptor攔器。
@Configuration//就相當于springmvc.xml文件 @EnableWebMvc @ComponentScan(basePackages = "com.cfl",includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)}) public class WebConfig implements WebMvcConfigurer {@AutowiredSimpleAuthenticationInterceptor simpleAuthenticationInterceptor; // 自定義攔截器@Bean // 視圖解析器public InternalResourceViewResolver viewResolver(){InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();viewResolver.setPrefix("/WEB-INF/view/");viewResolver.setSuffix(".jsp");return viewResolver;}@Override // 視圖控制器public void addViewControllers(ViewControllerRegistry registry) {registry.addViewController("/").setViewName("login");}@Override // 攔截器public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(simpleAuthenticationInterceptor).addPathPatterns("/r/**");} }測試
未登錄情況下,/r/r1與/r/r2均提示 “請先登錄”。
張三登錄情況下,由于張三有p1權限,因此可以訪問/r/r1,張三沒有p2權限,訪問/r/r2時提示 “權限不足 “。
李四登錄情況下,由于李四有p2權限,因此可以訪問/r/r2,李四沒有p1權限,訪問/r/r1時提示 “權限不足 “。
小結
基于Session的認證方式是一種常見的認證方式,至今還有非常多的系統在使用。我們在此小節使用Spring mvc技 術對它進行簡單實現,旨在讓大家更清晰實在的了解用戶認證、授權以及會話的功能意義及實現套路,也就是它們 分別干了哪些事兒?大概需要怎么做?
而在正式生產項目中,我們往往會考慮使用第三方安全框架(如 spring security,shiro等安全框架)來實現認證 授權功能,因為這樣做能一定程度提高生產力,提高軟件標準化程度,另外往往這些框架的可擴展性考慮的非常全 面。但是缺點也非常明顯,這些通用化組件為了提高支持范圍會增加很多可能我們不需要的功能,結構上也會比較 抽象,如果我們不夠了解它,一旦出現問題,將會很難定位。
3.2 Spring集成Security
Spring Security是一個能夠為基于Spring的企業應用系統提供聲明式的安全訪問控制解決方案的安全框架。由于它 是Spring生態系統中的一員,因此它伴隨著整個Spring生態系統不斷修正、升級,在spring boot項目中加入spring security更是十分簡單,使用Spring Security 減少了為企業系統安全控制編寫大量重復代碼的工作。
代碼步驟略
<dependencies><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-web</artifactId><version>5.1.4.RELEASE</version></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-config</artifactId><version>5.1.4.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-webmvc</artifactId><version>5.1.5.RELEASE</version></dependency><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.0.1</version><scope>provided</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.8</version></dependency></dependencies>小結
通過快速上手,使用Spring Security實現了認證和授權,Spring Security提供了基于賬號和密碼的認證方式, 通過安全配置即可實現請求攔截,授權功能,Spring Security能完成的不僅僅是這些。
3.3 SpringBoot集成Security
Spring Boot是一套Spring的快速開發框架,基于Spring 4.0設計,使用Spring Boot開發可以避免一些繁瑣的工程 搭建和配置,同時它集成了大量的常用框架,快速導入依賴包,避免依賴包的沖突。基本上常用的開發框架都支持 Spring Boot開發,例如:MyBatis、Dubbo等,Spring 家族更是如此,例如:Spring cloud、Spring mvc、 Spring security等,使用Spring Boot開發可以大大得高生產率,所以Spring Boot的使用率非常高。
學習如何通過Spring Boot開發Spring Security應用,Spring Boot提供spring-boot-starter-security用于開發Spring Security應用。
引入依賴
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.1.3.RELEASE</version></parent><dependencies><!-- 以下是>spring boot依賴--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- 以下是>spring security依賴--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><!-- 以下是jsp依賴--><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><scope>provided</scope></dependency><!--jsp頁面使用jstl標簽 --><dependency><groupId>javax.servlet</groupId><artifactId>jstl</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId><scope>provided</scope></dependency><!--用于編譯jsp --><dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-jasper</artifactId><scope>provided</scope></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.0</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.47</version></dependency></dependencies>SpringBoot工程啟動會自動掃描啟動類所在包下的所有Bean,加載到spring容器。
Spring Boot配置文件 :在resources下添加application.properties
server.port=8080 server.servlet.context-path=/security-springboot spring.application.name = security-springboot# 視頻解析器配置在application.properties中 spring.mvc.view.prefix=/WEB-INF/view/ spring.mvc.view.suffix=.jspspring.datasource.url=jdbc:mysql://localhost:3306/test spring.datasource.username= spring.datasource.password= spring.datasource.driver-class-name=com.mysql.jdbc.DriverServlet Context配置:對于WebConfig, 由于Spring boot starter自動裝配機制,無需使用@EnableWebMvc @ComponentScan
安全配置 :對于WebSecurityConfig,由于Spring boot starter自動裝配機制,無需使用@EnableWebSecurity
詳細代碼略
4、工作原理
4.1結構總覽
Spring Security所解決的問題就是安全訪問控制,而安全訪問控制功能其實就是對所有進入系統的請求進行攔截, 校驗每個請求是否能夠訪問它所期望的資源。
根據前邊知識的學習,可以通過Filter或AOP等技術來實現,Spring Security對Web資源的保護是靠Filter實現的,所以從這個Filter來入手,逐步深入Spring Security原理。
當初始化Spring Security時,會創建一個名為 SpringSecurityFilterChain 的Servlet過濾器,
類型為org.springframework.security.web.FilterChainProxy,它實現了javax.servlet.Filter,因此外部的請求會經過此 類,
下圖是Spring Security過慮器鏈結構圖:
FilterChainProxy是一個代理,真正起作用的是FilterChainProxy中SecurityFilterChain所包含的各個Filter,同時 這些Filter作為Bean被Spring管理,它們是Spring Security核心,各有各的職責,但他們并不直接處理用戶的認證,也不直接處理用戶的授權,而是把它們交給了認證管理器(AuthenticationManager)和決策管理器 (AccessDecisionManager)進行處理,
下圖是FilterChainProxy相關類的UML圖示:
spring Security功能的實現主要是由一系列過濾器鏈相互配合完成。
下面介紹過濾器鏈中主要的幾個過濾器及其作用:
SecurityContextPersistenceFilter 這個Filter是整個攔截過程的入口和出口(也就是第一個和最后一個攔截 器),會在請求開始時從配置好的 SecurityContextRepository 中獲取 SecurityContext,然后把它設置給 SecurityContextHolder。在請求完成后將 SecurityContextHolder 持有的 SecurityContext 再保存到配置好 的 SecurityContextRepository,同時清除 securityContextHolder 所持有的 SecurityContext;
UsernamePasswordAuthenticationFilter 用于處理來自表單提交的認證。該表單必須提供對應的用戶名和密 碼,其內部還有登錄成功或失敗后進行處理的 AuthenticationSuccessHandler 和 AuthenticationFailureHandler,這些都可以根據需求做相關改變;
FilterSecurityInterceptor 是用于保護web資源的,使用AccessDecisionManager對當前用戶進行授權訪問,前面已經詳細介紹過了;
ExceptionTranslationFilter 能夠捕獲來自 FilterChain 所有的異常,并進行處理。
但是它只會處理兩類異常: AuthenticationException 和 AccessDeniedException,其它的異常它會繼續拋出。
4.2認證流程
用戶提交用戶名、密碼被SecurityFilterChain中的 UsernamePasswordAuthenticationFilter 過濾器獲取到, 封裝為請求Authentication,通常情況下是UsernamePasswordAuthenticationToken這個實現類。
然后過濾器將Authentication提交至認證管理器(AuthenticationManager)進行認證
認證成功后, AuthenticationManager 身份管理器返回一個被填充滿了信息的(包括上面提到的權限信息, 身份信息,細節信息,但密碼通常會被移除) Authentication 實例。
SecurityContextHolder 安全上下文容器將第3步填充了信息的 Authentication ,通過SecurityContextHolder.getContext().setAuthentication(…)方法,設置到其中。 可以看出AuthenticationManager接口(認證管理器)是認證相關的核心接口,也是發起認證的出發點,它 的實現類為ProviderManager。而Spring Security支持多種認證方式,因此ProviderManager維護著一個 List 列表,存放多種認證方式,最終實際的認證工作是由 AuthenticationProvider完成的。咱們知道web表單的對應的AuthenticationProvider實現類為 DaoAuthenticationProvider,它的內部又維護著一個UserDetailsService負責UserDetails的獲取。最終 AuthenticationProvider將UserDetails填充至Authentication。
認證核心組件的大體關系如下:
AuthenticationProvider接口
通過前面的Spring Security認證流程我們得知,認證管理器(AuthenticationManager)委托 AuthenticationProvider完成認證工作。
AuthenticationProvider是一個接口,定義如下:
public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException;boolean supports(Class<?> var1); }authenticate()方法定義了認證的實現過程,它的參數是一個Authentication,里面包含了登錄用戶所提交的用 戶、密碼等。而返回值也是一個Authentication,這個Authentication則是在認證成功后,將用戶的權限及其他信 息重新組裝后生成。 Spring Security中維護著一個 List 列表,存放多種認證方式,不同的認證方式使用不 同的AuthenticationProvider。如使用用戶名密碼登錄時,使用AuthenticationProvider1,短信登錄時使用 AuthenticationProvider2等等這樣的例子很多。
每個AuthenticationProvider需要實現**supports()**方法來表明自己支持的認證方式,如我們使用表單方式認證,
在提交請求時Spring Security會生成UsernamePasswordAuthenticationToken,它是一個Authentication,里面 封裝著用戶提交的用戶名、密碼信息。而對應的,哪個AuthenticationProvider來處理它?
我們在DaoAuthenticationProvider的基類AbstractUserDetailsAuthenticationProvider發現以下代碼:
也就是說當web表單提交用戶名密碼時,Spring Security由DaoAuthenticationProvider處理。
最后,我們來看一下Authentication(認證信息)的結構,它是一個接口,我們之前提到的UsernamePasswordAuthenticationToken就是它的實現之一:
public interface Authentication extends Principal, Serializable { (1) Collection<? extends GrantedAuthority> getAuthorities(); (2) Object getCredentials(); (3) Object getDetails(); (4) Object getPrincipal(); (5) boolean isAuthenticated(); void setAuthenticated(boolean var1) throws IllegalArgumentException; }(1)Authentication是spring security包中的接口,直接繼承自Principal類,而Principal是位于 java.security 包中的。它是表示著一個抽象主體身份,任何主體都有一個名稱,因此包含一個getName()方法。
(2)getAuthorities(),權限信息列表,默認是GrantedAuthority接口的一些實現類,通常是代表權限信息的一系 列字符串。
(3)getCredentials(),憑證信息,用戶輸入的密碼字符串,在認證過后通常會被移除,用于保障安全。
(4)getDetails(),細節信息,web應用中的實現接口通常為 WebAuthenticationDetails,它記錄了訪問者的ip地 址和sessionId的值。
(5)getPrincipal(),身份信息,大部分情況下返回的是UserDetails接口的實現類,UserDetails代表用戶的詳細 信息,那從Authentication中取出來的UserDetails就是當前登錄用戶信息,它也是框架中的常用接口之一。
UserDetailsService接口
現在咱們現在知道DaoAuthenticationProvider處理了web表單的認證邏輯,認證成功后既得到一個 Authentication(UsernamePasswordAuthenticationToken實現),里面包含了身份信息(Principal)。這個身份 信息就是一個 Object,大多數情況下它可以被強轉為UserDetails對象。 DaoAuthenticationProvider中包含了一個UserDetailsService實例,它負責根據用戶名提取用戶信息 UserDetails(包含密碼),而后DaoAuthenticationProvider會去對比UserDetailsService提取的用戶密碼與用戶提交的密碼是否匹配作為認證成功的關鍵依據,因此可以通過將自定義的 UserDetailsService 公開為spring bean來定 義自定義身份驗證。
public interface UserDetailsService {UserDetails loadUserByUsername(String username) throws UsernameNotFoundException; }DaoAuthenticationProvider和UserDetailsService的職責搞混淆,其實UserDetailsService只負責從特定 的地方(通常是數據庫)加載用戶信息,僅此而已。而DaoAuthenticationProvider的職責更大,它完成完整的認 證流程,同時會把UserDetails填充Authentication。
上面一直提到UserDetails是用戶信息,咱們看一下它:
public interface UserDetails extends Serializable { Collection<? extends GrantedAuthority> getAuthorities();String getPassword(); String getUsername();boolean isAccountNonExpired();boolean isAccountNonLocked(); boolean isCredentialsNonExpired();boolean isEnabled(); }它和Authentication接口很類似,比如它們都擁有username,authorities。
Authentication的getCredentials()與 UserDetails中的getPassword()需要被區分對待,前者是用戶提交的密碼憑證,后者是用戶實際存儲的密碼,認證其實就是對這兩者的比對。Authentication中的getAuthorities()實際是由UserDetails的getAuthorities()傳遞而形成的。
還記得Authentication接口中的getDetails()方法嗎?其中的UserDetails用戶詳細信息便是經過了 AuthenticationProvider認證之后被填充的。
通過實現UserDetailsService和UserDetails,我們可以完成對用戶信息獲取方式以及用戶信息字段的擴展。
Spring Security提供的InMemoryUserDetailsManager(內存認證),JdbcUserDetailsManager(jdbc認證)就是UserDetailsService的實現類,主要區別無非就是從內存還是從數據庫加載用戶
自定義UserDetailsService
@Service public class SpringDataUserDetailsService implements UserDetailsService {@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {//登錄賬號System.out.println("username="+username); //根據賬號去數據庫查詢...//這里暫時使用靜態數據 UserDetails userDetails = User.withUsername(username).password("123").authorities("p1").build(); return userDetails; } }屏蔽安全配置類中UserDetailsService的定義
/* @Bean public UserDetailsService userDetailsService() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build()); manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());return manager; }*/重啟工程,請求認證,SpringDataUserDetailsService的loadUserByUsername方法被調用 ,查詢用戶信息。
PasswordEncoder接口
DaoAuthenticationProvider認證處理器通過UserDetailsService獲取到UserDetails后,它是如何與請求 Authentication中的密碼做對比呢? 在這里Spring Security為了適應多種多樣的加密類型,又做了抽象,DaoAuthenticationProvider通過 PasswordEncoder接口的matches方法進行密碼的對比,而具體的密碼對比細節取決于實現:
public interface PasswordEncoder { String encode(CharSequence var1); boolean matches(CharSequence var1, String var2);default boolean upgradeEncoding(String encodedPassword) {return false;} }而Spring Security提供很多內置的PasswordEncoder,能夠開箱即用,使用某種PasswordEncoder只需要進行如 下聲明即可,如下:
@Bean public PasswordEncoder passwordEncoder() { return NoOpPasswordEncoder.getInstance(); }**NoOpPasswordEncoder采用字符串匹配方法,不對密碼進行加密比較處理,**密碼比較流程如下:
1、用戶輸入密碼(明文 )
2、DaoAuthenticationProvider獲取UserDetails(其中存儲了用戶的正確密碼)
3、DaoAuthenticationProvider使用PasswordEncoder對輸入的密碼和正確的密碼進行校驗,密碼一致則校驗通過,否則校驗失敗。
NoOpPasswordEncoder的校驗規則拿 輸入的密碼和UserDetails中的正確密碼進行字符串比較,字符串內容一致 則校驗通過,否則 校驗失敗。 實際項目中推薦使用BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder等。
例如使用BCryptPasswordEncoder
1、配置BCryptPasswordEncoder 在安全配置類中定義
@Bean public PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder(); }測試認證失敗,提示:Encoded password does not look like BCrypt。
原因: 由于UserDetails中存儲的是原始密碼(比如:123),它不是BCrypt格式。
跟蹤 DaoAuthenticationProvider第33行代碼查看 userDetails中的內容 ,跟蹤第38行代碼查看 PasswordEncoder的類型。
@RunWith(SpringRunner.class) public class TestBCrypt { @Test public void test1(){//對原始密碼加密 String hashpw = BCrypt.hashpw("123",BCrypt.gensalt());System.out.println(hashpw); //校驗原始密碼和BCrypt密碼是否一致 boolean checkpw = BCrypt.checkpw("123", "$2a$10$NlBC84MVb7F95EXYTXwLneXgCca6/GipyWR5NHm8K0203bSQMLpvm");System.out.println(checkpw); } }實際項目中存儲在數據庫中的密碼并不是原始密碼,都是經過加密處理的密碼。
4.3授權流程
通過快速上手我們知道,Spring Security可以通過 http.authorizeRequests() 對web請求進行授權保護。Spring Security使用標準Filter建立了對web請求的攔截,最終實現對資源的授權訪問。
Spring Security的授權流程如下:
攔截請求,已認證用戶訪問受保護的web資源將被SecurityFilterChain中的 FilterSecurityInterceptor 的子 類攔截。
獲取資源訪問策略,FilterSecurityInterceptor會從 SecurityMetadataSource 的子類 DefaultFilterInvocationSecurityMetadataSource 獲取要訪問當前資源所需要的權限 Collection 。 SecurityMetadataSource其實就是讀取訪問策略的抽象,而讀取的內容,其實就是我們配置的訪問規則,讀取訪問策略如:
AccessDecisionManager(訪問決策管理器)
public interface AccessDecisionManager { /** 通過傳遞的參數來決定用戶是否有訪問對應受保護資源的權限 */void decide(Authentication authentication , Object object,Collection<ConfigAttribute> configAttributes ) throws AccessDeniedException, InsufficientAuthenticationException;//略.. }decide的參數:
authentication:要訪問資源的訪問者的身份
object:要訪問的受保護資源,web請求對應FilterInvocation
configAttributes:是受保護資源的訪問策略,通過SecurityMetadataSource獲取。
decide接口就是用來鑒定當前用戶是否有訪問對應受保護資源的權限。
授權決策
AccessDecisionManager采用投票的方式來確定是否能夠訪問受保護資源。
通過上圖可以看出,AccessDecisionManager中包含的一系列AccessDecisionVoter將會被用來對Authentication
是否有權訪問受保護對象進行投票,AccessDecisionManager根據投票結果,做出最終決策。
AccessDecisionVoter是一個接口,其中定義有三個方法,具體結構如下所示。
public interface AccessDecisionVoter<S> { int ACCESS_GRANTED = 1;int ACCESS_ABSTAIN = 0; int ACCESS_DENIED = ‐1; boolean supports(ConfigAttribute var1);boolean supports(Class<?> var1); int vote(Authentication var1, S var2, Collection<ConfigAttribute> var3); }vote()方法的返回結果會是AccessDecisionVoter中定義的三個常量之一。
ACCESS_GRANTED表示同意,
ACCESS_DENIED表示拒絕,
ACCESS_ABSTAIN表示棄權。
如果一個AccessDecisionVoter不能判定當前 Authentication是否擁有訪問對應受保護對象的權限,則其vote()方法的返回值應當為棄權ACCESS_ABSTAIN。 Spring Security內置了三個基于投票的AccessDecisionManager實現類如下,它們分別是 :
AffirmativeBased、ConsensusBased、UnanimousBased
AffirmativeBased的邏輯:
(1)只要有AccessDecisionVoter的投票為ACCESS_GRANTED則同意用戶進行訪問;
(2)如果全部棄權也表示通過;
(3)如果沒有一個人投贊成票,但是有人投反對票,則將拋出AccessDeniedException。
Spring security默認使用的是AffirmativeBased。
ConsensusBased的邏輯:
(1)如果贊成票多于反對票則表示通過。
(2)反過來,如果反對票多于贊成票則將拋出AccessDeniedException。
(3)如果贊成票與反對票相同且不等于0,并且屬性allowIfEqualGrantedDeniedDecisions的值為true,則表 示通過,否則將拋出異常AccessDeniedException。參數allowIfEqualGrantedDeniedDecisions的值默認為true。
(4)如果所有的AccessDecisionVoter都棄權了,則將視參數allowIfAllAbstainDecisions的值而定,如果該值 為true則表示通過,否則將拋出異常AccessDeniedException。參數allowIfAllAbstainDecisions的值默認為false。
UnanimousBased的邏輯:
與另外兩種實現有點不一樣,另外兩種會一次性把受保護對象的配置屬性全部傳遞 給AccessDecisionVoter進行投票,而UnanimousBased會一次只傳遞一個ConfigAttribute給 AccessDecisionVoter進行投票。
這也就意味著如果我們的AccessDecisionVoter的邏輯是只要傳遞進來的 ConfigAttribute中有一個能夠匹配則投贊成票,但是放到UnanimousBased中其投票結果就不一定是贊成了。
具體來說是這樣的:
(1)如果受保護對象配置的某一個ConfigAttribute被任意的AccessDecisionVoter反對了,則將拋出 AccessDeniedException。
(2)如果沒有反對票,但是有贊成票,則表示通過。
(3)如果全部棄權了,則將視參數allowIfAllAbstainDecisions的值而定,true則通過,false則拋出AccessDeniedException。
Spring Security也內置一些投票者實現類如RoleVoter、AuthenticatedVoter和WebExpressionVoter等可以查閱資料進行學習。
5、自定認證
Spring Security提供了非常好的認證擴展方法,比如:快速上手中將用戶信息存儲到內存中,實際開發中用戶信息 通常在數據庫,Spring security可以實現從數據庫讀取用戶信息,Spring security還支持多種授權方法。
5.1登錄
在快速上手中,你可能會想知道登錄頁面從哪里來的?因為我們并沒有提供任何的HTML或JSP文件。Spring Security的默認配置沒有明確設定一個登錄頁面的URL,因此Spring Security會根據啟用的功能自動生成一個登錄 頁面URL,并使用默認URL處理登錄的提交內容,登錄后跳轉的到默認URL等等。盡管自動生成的登錄頁面很方便快速啟動和運行,但大多數應用程序都希望定義自己的登錄頁面。
配置認證頁面 :在WebConfifig.java中配置認證頁面地址:
//默認Url根路徑跳轉到/login,此url為spring security提供 @Overridepublic void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("redirect:/login‐view");registry.addViewController("/login‐view").setViewName("login"); }安全配置 :在WebSecurityConfig中配置表章登錄信息
//配置安全攔截機制 @Override protected void configure(HttpSecurity http) throws Exception {http .authorizeRequests() .antMatchers("/r/**").authenticated() .anyRequest().permitAll() .and() .formLogin() // 允許表單登錄.loginPage("/login‐view") //指定我們自己的登錄頁,spring security以重定向方式跳轉到/login-view.loginProcessingUrl("/login") //指定登錄處理的URL,也就是用戶名、密碼表單提交的目的路徑.successForwardUrl("/login‐success") //指定登錄成功后的跳轉URL.permitAll(); //允許 任意用戶訪問基于表單登錄的所有的URL }測試 :當用戶沒有認證時訪問系統的資源會重定向到login-view頁面
問題解決:
spring security為防止CSRF(Cross-site request forgery跨站請求偽造)的發生,限制了除了get以外的大多數方 法。
解決方法1: 屏蔽CSRF控制,即spring security不再限制CSRF。 配置WebSecurityConfig
@Override protected void configure(HttpSecurity http) throws Exception {http.csrf().disable() //屏蔽CSRF控制,即spring security不再限制CSRF...... }解決方法2:
在login.jsp頁面添加一個token,spring security會驗證token,如果token合法則可以繼續請求。 修改login.jsp
<form action="login" method="post"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>...... </form>連接數據庫認證
定義dataSource 在application.properties配置
spring.datasource.url=jdbc:mysql://localhost:3306/user_db spring.datasource.username=root spring.datasource.password= spring.datasource.driver‐class‐name=com.mysql.jdbc.DriverUserDao
@Repository public class UserDao {@AutowiredJdbcTemplate jdbcTemplate;//根據賬號查詢用戶信息public UserDto getUserByUsername(String username){String sql = "select id,username,password,fullname,mobile from t_user where username = ?";//連接數據庫查詢用戶List<UserDto> list = jdbcTemplate.query(sql, new Object[]{username}, new BeanPropertyRowMapper<>(UserDto.class));if(list !=null && list.size()==1){return list.get(0);}return null;}}定義UserDetailService
@Service public class SpringDataUserDetailsService implements UserDetailsService { @Autowired UserDao userDao; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { //登錄賬號 System.out.println("username="+username);//根據賬號去數據庫查詢... UserDto user = userDao.getUserByUsername(username); if(user == null){return null;}//這里暫時使用靜態數據UserDetails userDetails = User.withUsername(user.getFullname()).password(user.getPassword()).authorities("p1").build(); return userDetails; } }測試 :輸入賬號和密碼請求認證,跟蹤代碼。
使用BCryptPasswordEncoder
使用BCryptPasswordEncoder需要完成如下工作:
在安全配置類中定義BCryptPasswordEncoder
@Bean public PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder(); }UserDetails中的密碼存儲BCrypt格式 ,前邊實現了從數據庫查詢用戶信息,所以數據庫中的密碼應該存儲BCrypt格式
5.2會話
用戶認證通過后,為了避免用戶的每次操作都進行認證可將用戶的信息保存在會話中。spring security提供會話管理,認證通過后將身份信息放入SecurityContextHolder上下文,SecurityContext與當前線程進行綁定,方便獲取用戶身份。
獲取用戶身份
@RestController public class LoginController {}會話控制
我們可以通過以下選項準確控制會話何時創建以及Spring Security如何與之交互:
| always | 如果沒有session存在就創建一個 |
| ifRequired | 如果需要就創建一個Session(默認)登錄時 |
| never | SpringSecurity 將不會創建Session,但是如果應用中其他地方創建了Session,那么Spring |
| stateless | SpringSecurity將絕對不會創建Session,也不使用Session |
通過以下配置方式對該選項進行配置:
@Override protected void configure(HttpSecurity http) throws Exception {http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) }默認情況下,Spring Security會為每個登錄成功的用戶會新建一個Session,就是ifRequired 。
若選用never,則指示Spring Security對登錄成功的用戶不創建Session了,但若你的應用程序在某地方新建了 session,那么Spring Security會用它的。
若使用stateless,則說明Spring Security對登錄成功的用戶不會創建Session了,你的應用程序也不會允許新建 session。并且它會暗示不使用cookie,所以每個請求都需要重新進行身份驗證。這種無狀態架構適用于REST API 及其無狀態認證機制。
會話超時
可以再sevlet容器中設置Session的超時時間,如下設置Session有效期為3600s;
spring boot 配置文件:
server.servlet.session.timeout=3600ssession超時之后,可以通過Spring Security 設置跳轉的路徑。
http.sessionManagement() .expiredUrl("/login‐view?error=EXPIRED_SESSION").invalidSessionUrl("/login‐view?error=INVALID_SESSION");// expired指session過期,invalidSession指傳入的sessionid無效。安全會話cookie
我們可以使用httpOnly和secure標簽來保護我們的會話cookie:
httpOnly:如果為true,那么瀏覽器腳本將無法訪問cookie
secure:如果為true,則cookie將僅通過HTTPS連接發送
spring boot 配置文件:
server.servlet.session.cookie.http‐only=true server.servlet.session.cookie.secure=true5.3退出
Spring security默認實現了logout退出,訪問/logout
點擊“Log Out”退出 成功。 退出后訪問其它url判斷是否成功退出。
這里也可以自定義退出成功的頁面: 在WebSecurityConfifig的protected void confifigure(HttpSecurity http)中配置:
.and() .logout() .logoutUrl("/logout") .logoutSuccessUrl("/login‐view?logout");當退出操作出發時,將發生:
使HTTP Session 無效
清除 SecurityContextHolder
跳轉到 /login-view?logout
但是,類似于配置登錄功能,可以進一步自定義退出功能
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests()//... .and() .logout() //提供系統退出支持,使用 WebSecurityConfigurerAdapter 會自動被應用.logoutUrl("/logout") //設置觸發退出操作的URL (默認是 /logout ).logoutSuccessUrl("/login‐view?logout") //退出之后跳轉的URL。默認是 /login?logout 。.logoutSuccessHandler(logoutSuccessHandler) //定制的 LogoutSuccessHandler ,用于實現用戶退出成功時的處理。如果指定了這個選項那么 logoutSuccessUrl() 的設置會被忽略。.addLogoutHandler(logoutHandler)//添加一個 LogoutHandler ,用于實現用戶退出時的清理工作.默認 SecurityContextLogoutHandler 會被添加 為最后一個 LogoutHandler 。.invalidateHttpSession(true); //指定是否在退出時讓 HttpSession 無效。 默認設置為 true。 }注意:
如果讓logout在GET請求下生效,必須關閉防止CSRF攻擊csrf().disable()。如果開啟了CSRF,必須使用 post方式請求/logout
logoutHandler:
一般來說, LogoutHandler 的實現類被用來執行必要的清理,因而他們不應該拋出異常。
下面是Spring Security提供的一些實現:
PersistentTokenBasedRememberMeServices 基于持久化token的RememberMe功能的相關清理
TokenBasedRememberMeService 基于token的RememberMe功能的相關清理
CookieClearingLogoutHandler 退出時Cookie的相關清理
CsrfLogoutHandler 負責在退出時移除csrfToken
SecurityContextLogoutHandler 退出時SecurityContext的相關清理
鏈式API提供了調用相應的 LogoutHandler 實現的快捷方式,比如deleteCookies()
5.4授權
授權的方式包括 web授權和方法授權,web授權是通過 url攔截進行授權,方法授權是通過 方法攔截進行授權。
他們都會調用accessDecisionManager進行授權決策,若為web授權則攔截器為FilterSecurityInterceptor;
若為方 法授權則攔截器為MethodSecurityInterceptor。如果同時通過web授權和方法授權則先執行web授權,再執行方法授權,
最后決策通過,則允許訪問資源,否則將禁止訪問。
類關系如下:
web授權
在上面例子中我們完成了認證攔截,并對/r/**下的某些資源進行簡單的授權保護,但是我們想進行靈活的授權控 制該怎么做呢?
通過給 http.authorizeRequests() 添加多個子節點來定制需求到我們的URL,如下代碼:
@Override protected void configure(HttpSecurity http) throws Exception {http .authorizeRequests() (1) .antMatchers("/r/r1").hasAuthority("p1") (2) .antMatchers("/r/r2").hasAuthority("p2") (3).antMatchers("/r/r3").access("hasAuthority('p1') and hasAuthority('p2')") (4) .antMatchers("/r/**").authenticated() (5).anyRequest().permitAll() (6) .and() .formLogin() // ... }(1) http.authorizeRequests() 方法有多個子節點,每個macher按照他們的聲明順序執行。
(2)指定"/r/r1"URL,擁有p1權限能夠訪問
(3)指定"/r/r2"URL,擁有p2權限能夠訪問
(4)指定了"/r/r3"URL,同時擁有p1和p2權限才能夠訪問
(5)指定了除了r1、r2、r3之外"/r/**"資源,同時通過身份認證就能夠訪問,這里使用SpEL(Spring Expression Language)表式
(6)剩余的尚未匹配的資源,不做保護
注意:
規則的順序是重要的,更具體的規則應該先寫現在以/ admin開始的所有內容都需要具有ADMIN角色的身份驗證用 戶,
即使是/ admin / login路徑(因為/ admin / login已經被/ admin / **規則匹配,因此第二個規則被忽略)
.antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/admin/login").permitAll()因此,登錄頁面的規則應該在/ admin / **規則之前.例如
.antMatchers("/admin/login").permitAll() .antMatchers("/admin/**").hasRole("ADMIN")保護URL常用的方法有:
authenticated() 保護URL,需要用戶登錄
permitAll() 指定URL無需保護,一般應用與靜態資源文件
hasRole(String role) 限制單個角色訪問,角色將被增加 “ROLE_” .所以”ADMIN” 將和 “ROLE_ADMIN”進行比較.
hasAuthority(String authority) 限制單個權限訪問
**hasAnyRole(String… roles)**允許多個角色訪問.
hasAnyAuthority(String… authorities) 允許多個權限訪問.
access(String attribute) 該方法使用 SpEL表達式, 所以可以創建復雜的限制.
hasIpAddress(String ipaddressExpression) 限制IP地址或子網
方法授權
現在我們已經掌握了使用如何使用 http.authorizeRequests() 對web資源進行授權保護,從Spring Security2.0版 本開始,它支持服務層方法的安全性的支持。
@PreAuthorize,@PostAuthorize, @Secured三類注解。 我們可以在任何 @Configuration 實例上使用 @EnableGlobalMethodSecurity 注釋來啟用基于注解的安全性。
以下內容將啟用Spring Security的 @Secured 注釋。
@EnableGlobalMethodSecurity(securedEnabled = true) public class MethodSecurityConfig {// ...}然后向方法(在類或接口上)添加注解就會限制對該方法的訪問。 Spring Security的原生注釋支持為該方法定義了 一組屬性。 這些將被傳遞給AccessDecisionManager以供它作出實際的決定:
public interface BankService { @Secured("IS_AUTHENTICATED_ANONYMOUSLY")public Account readAccount(Long id);@Secured("IS_AUTHENTICATED_ANONYMOUSLY") public Account[] findAccounts(); @Secured("ROLE_TELLER") public Account post(Account account, double amount); }以上配置標明readAccount、fifindAccounts方法可匿名訪問,底層使用WebExpressionVoter投票器,可從AffirmativeBased第23行代碼跟蹤。post方法需要有TELLER角色才能訪問,底層使用RoleVoter投票器。
使用如下代碼可啟用prePost注解的支持
@EnableGlobalMethodSecurity(prePostEnabled = true) public class MethodSecurityConfig { // ... }相應Java代碼如下:
public interface BankService {@PreAuthorize("isAnonymous()") public Account readAccount(Long id); @PreAuthorize("isAnonymous()") public Account[] findAccounts(); @PreAuthorize("hasAuthority('p_transfer') and hasAuthority('p_read_account')")public Account post(Account account, double amount); }以上配置標明readAccount、fifindAccounts方法可匿名訪問,
post方法需要同時擁有p_transfer和p_read_account 權限才能訪問,
底層使用WebExpressionVoter投票器,可從AffirmativeBased第23行代碼跟蹤。
6、分布式系統認證方案
6.1什么是分布式系統
分布式系統具體如下基本特點:
1、分布性:每個部分都可以獨立部署,服務之間交互通過網絡進行通信,比如:訂單服務、商品服務。
2、伸縮性:每個部分都可以集群方式部署,并可針對部分結點進行硬件及軟件擴容,具有一定的伸縮能力。
3、共享性:每個部分都可以作為共享資源對外提供服務,多個部分可能有操作共享資源的情況。
4、開放性:每個部分根據需求都可以對外發布共享資源的訪問接口,并可允許第三方系統訪問。
6.2 分布式認證需求
分布式系統的每個服務都會有認證、授權的需求,如果每個服務都實現一套認證授權邏輯會非常冗余,
考慮分布式系統共享性的特點,需要由獨立的認證服務處理系統認證授權的請求;
考慮分布式系統開放性的特點,不僅對系統內部服務提供認證,對第三方系統也要提供認證。
分布式認證的需求總結如下:
統一認證授權
提供獨立的認證服務,統一處理認證授權。無論是不同類型的用戶,還是不同種類的客戶端(web端,H5、APP),均采用一致的認證、權限、會話機制,實現統一認證授權。
要實現統一則認證方式必須可擴展,支持各種認證需求,比如:用戶名密碼認證、短信驗證碼、二維碼、人臉識別 等認證方式,并可以非常靈活的切換。
應用接入認證
應提供擴展和開放能力,提供安全的系統對接機制,并可開放部分API給接入第三方使用,一方應用(內部 系統服 務)和三方應用(第三方應用)均采用統一機制接入。
6.3 分布式認證方案
選型分析
1、基于session的認證方式
在分布式的環境下,基于session的認證會出現一個問題,每個應用服務都需要在session中存儲用戶身份信息,通
過負載均衡將本地的請求分配到另一個應用服務需要將session信息帶過去,否則會重新認證。
這個時候,通常的做法有下面幾種:
Session****復制:多臺應用服務器之間同步session,使session保持一致,對外透明。
Session****黏貼:當用戶訪問集群中某臺服務器后,強制指定后續所有請求均落到此機器上。
Session****集中存儲:將Session存入分布式緩存中,所有服務器應用實例統一從分布式緩存中存取Session。
總體來講,基于session認證的認證方式,可以更好的在服務端對會話進行控制,且安全性較高。但是,session機
制方式基于cookie,在復雜多樣的移動客戶端上不能有效的使用,并且無法跨域,另外隨著系統的擴展需提高
session的復制、黏貼及存儲的容錯性。
2、基于token的認證方式
基于token的認證方式,服務端不用存儲認證數據,易維護擴展性強, 客戶端可以把token 存在任意地方,并且可
以實現web和app統一認證機制。
其缺點也很明顯,token由于自包含信息,因此一般數據量較大,而且每次請求 都需要傳遞,因此比較占帶寬。另外,token的簽名驗簽操作也會給cpu帶來額外的處理負擔。
技術方案
根據 選型的分析,決定采用基于token的認證方式,它的優點是:
1、適合統一認證的機制,客戶端、一方應用、三方應用都遵循一致的認證機制。
2、token認證方式對第三方應用接入更適合,因為它更開放,可使用當前有流行的開放協議Oauth2.0、JWT等。
3、一般情況服務端無需存儲會話信息,減輕了服務端的壓力。
分布式系統認證技術方案見下圖:
流程描述:
(1)用戶通過接入方(應用)登錄,接入方采取OAuth2.0方式在統一認證服務(UAA)中認證。
(2)認證服務(UAA)調用驗證該用戶的身份是否合法,并獲取用戶權限信息。
(3)認證服務(UAA)獲取接入方權限信息,并驗證接入方是否合法。
(4)若登錄用戶以及接入方都合法,認證服務生成jwt令牌返回給接入方,其中jwt中包含了用戶權限及接入方權限。
(5)后續,接入方攜帶jwt令牌對API網關內的微服務資源進行訪問。
(6)API網關對令牌解析、并驗證接入方的權限是否能夠訪問本次請求的微服務。
(7)如果接入方的權限沒問題,API網關將原請求header中附加解析后的明文Token,并將請求轉發至微服務。
(8)微服務收到請求,明文token中包含登錄用戶的身份和權限信息。因此后續微服務自己可以干兩件事:
? 1,用 戶授權攔截(看當前用戶是否有權訪問該資源)
? 2,將用戶信息存儲進當前線程上下文(有利于后續業務邏輯隨時 獲取當前用戶信息)
流程所涉及到UAA服務、API網關這三個組件職責如下:
1)統一認證服務(UAA
它承載了OAuth2.0接入方認證、登入用戶的認證、授權以及生成令牌的職責,完成實際的用戶認證、授權功能。
2)API網關
作為系統的唯一入口,API網關為接入方提供定制的API集合,它可能還具有其它職責,如身份驗證、監控、負載均 衡、緩存等。
API網關方式的核心要點是,所有的接入方和消費端都通過統一的網關接入微服務,在網關層處理所有的非業務功能。
7、OAuth2.0 開放授權
7.1 OAuth2.0介紹
OAuth(開放授權)是一個開放標準,允許用戶授權第三方應用訪問他們存儲在另外的服務提供者上的信息,而不 需要將用戶名和密碼提供給第三方應用或分享他們數據的所有內容。OAuth2.0是OAuth協議的延續版本,但不向 后兼容OAuth 1.0即完全廢止了OAuth1.0。很多大公司如Google,Yahoo,Microsoft等都提供了OAUTH認證服 務,這些都足以說明OAUTH標準逐漸成為開放資源授權的標準。 Oauth協議目前發展到2.0版本,1.0版本過于復雜,2.0版本已得到廣泛應用。
參考:https://baike.baidu.com/item/oAuth/7153134?fr=aladdin
Oauth協議:https://tools.ietf.org/html/rfc6749
OAauth2.0包括以下角色:
1、客戶端
本身不存儲資源,需要通過資源擁有者的授權去請求資源服務器的資源,
比如:Android客戶端、Web客戶端(瀏 覽器端)、微信客戶端等。
2、資源擁有者
通常為用戶,也可以是應用程序,即該資源的擁有者。
3、授權服務器(也稱認證服務器)
用于服務提供商對資源擁有的身份進行認證、對訪問資源進行授權,認證成功后會給客戶端發放令牌 (access_token),
作為客戶端訪問資源服務器的憑據。本例為微信的認證服務器。
4、資源服務器
存儲資源的服務器,本例子為微信存儲的用戶信息。
現在還有一個問題,服務提供商能允許隨便一個客戶端就接入到它的授權服務器嗎?
答案是否定的,服務提供商會給準入的接入方一個身份,用于接入時的憑據:
client_id:客戶端標識 client_secret:客戶端秘鑰
因此,準確來說,授權服務器對兩種OAuth2.0中的兩個角色進行認證授權,分別是資源擁有者、客戶端。
7.2 Spring Cloud Security OAuth2 的實現
Spring-Security-OAuth2是對OAuth2的一種實現,并且跟我們之前學習的Spring Security相輔相成,與Spring Cloud體系的集成也非常便利,學習它,最終使用它來實現我們設計的分布式認證授權解決方案。
OAuth2.0的服務提供方涵蓋兩個服務,即授權服務 (Authorization Server,也叫認證服務) 和資源服務 (Resource Server),使用 Spring Security OAuth2 的時候你可以選擇把它們在同一個應用程序中實現,也可以選擇建立使用 同一個授權服務的多個資源服務。
授權服務 **(Authorization Server)**應包含對接入端以及登入用戶的合法性進行驗證并頒發token等功能,
對令牌的請求端點由 Spring MVC 控制器進行實現,下面是配置一個認證服務必須要實現的endpoints:
AuthorizationEndpoint 服務于認證請求。默認 URL: /oauth/authorize 。
TokenEndpoint 服務于訪問令牌的請求。默認 URL: /oauth/token 。
資源服務 (Resource Server),應包含對資源的保護功能,對非法請求進行攔截,對請求中token進行解析鑒 權等,
下面的過濾器用于實現 OAuth 2.0 資源服務:
OAuth2AuthenticationProcessingFilter用來對請求給出的身份令牌解析鑒權。
7.2.1代碼略。。。。。。
7.2.2授權服務器配置
1)@EnableAuthorizationServer 注解
@EnableAuthorizationServer 注解用來注釋 AuthorizationServerConfigurerAdapter的繼承類配置OAuth2.0 授權服務器的參數。
AuthorizationServerConfigurerAdapter要求配置以下幾個類,這幾個類是由Spring創建的獨立的配置對象,
它們會被Spring傳入AuthorizationServerConfigurer中進行配置。
public class AuthorizationServerConfigurerAdapter implements AuthorizationServerConfigurer { public AuthorizationServerConfigurerAdapter() {} public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {}public void configure(ClientDetailsServiceConfigurer clients) throws Exception {}public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {} }ClientDetailsServiceConfifigurer:用來配置客戶端詳情服務(ClientDetailsService),客戶端詳情信息在 這里進行初始化,你能夠把客戶端詳情信息寫死在這里或者是通過數據庫來存儲調取詳情信息。
AuthorizationServerEndpointsConfifigurer:用來配置令牌(token)的訪問端點和令牌服務(token services)。
AuthorizationServerSecurityConfifigurer:用來配置令牌端點的安全約束.
2)配置客戶端詳細信息
ClientDetailsServiceConfigurer 能夠使用內存或者JDBC來實現客戶端詳情服務(ClientDetailsService),
ClientDetailsService負責查找ClientDetails,而ClientDetails有幾個重要的屬性如下列表:
clientId:(必須的)用來標識客戶的Id。
secret:(需要值得信任的客戶端)客戶端安全碼,如果有的話。
scope:用來限制客戶端的訪問范圍,如果為空(默認)的話,那么客戶端擁有全部的訪問范圍。
authorizedGrantTypes:此客戶端可以使用的授權類型,默認為空。
authorities:此客戶端可以使用的權限(基于Spring Security authorities)。
客戶端詳情(Client Details)能夠在應用程序運行的時候進行更新,可以通過訪問底層的存儲服務(例如將客戶 端詳情存儲在一個關系數據庫的表中,
就可以使用 JdbcClientDetailsService)或者通過自己實現 ClientRegistrationService接口(同時你也可以實現 ClientDetailsService 口)來進行管理。
3)管理令牌服務
AuthorizationServerTokenServices 接口定義了一些操作使得你可以對令牌進行一些必要的管理,令牌可以被用來加載身份信息,里面包含了這個令牌的相關權限。
自己可以創建 AuthorizationServerTokenServices 這個接口的實現,則需要繼承 DefaultTokenServices 這個類, 里面包含了一些有用實現,你可以使用它來修改令牌的格式和令牌的存儲。默認的,當它嘗試創建一個令牌的時候,是使用隨機值來進行填充的,除了持久化令牌是委托一個 TokenStore 接口來實現以外,這個類幾乎幫你做了 所有的事情。并且 TokenStore 這個接口有一個默認的實現,它就是 InMemoryTokenStore ,如其命名,所有的令牌是被保存在了內存中。除了使用這個類以外,你還可以使用一些其他的預定義實現,下面有幾個版本,它們都 實現了TokenStore接口:
**InMemoryTokenStore:**這個版本的實現是被默認采用的,它可以完美的工作在單服務器上(即訪問并發量 壓力不大的情況下,并且它在失敗的時候不會進行備份),大多數的項目都可以使用這個版本的實現來進行 嘗試,你可以在開發的時候使用它來進行管理,因為不會被保存到磁盤中,所以更易于調試。
**JdbcTokenStore:**這是一個基于JDBC的實現版本,令牌會被保存進關系型數據庫。使用這個版本的實現時,你可以在不同的服務器之間共享令牌信息,使用這個版本的時候請注意把"spring-jdbc"這個依賴加入到你的 classpath當中。
**JwtTokenStore:**這個版本的全稱是 JSON Web Token(JWT),它可以把令牌相關的數據進行編碼(因此對 于后端服務來說,它不需要進行存儲,這將是一個重大優勢),但是它有一個缺點,那就是撤銷一個已經授權令牌將會非常困難,所以它通常用來處理一個生命周期較短的令牌以及撤銷刷新令牌(refresh_token)。 另外一個缺點就是這個令牌占用的空間會比較大,如果你加入了比較多用戶憑證息。JwtTokenStore 不會保存任何數據,但是它在轉換令牌值以及授權信息方面與 DefaultTokenServices 所扮演的角色是一樣的。
4)令牌訪問端點配置
AuthorizationServerEndpointsConfifigurer 這個對象的實例可以完成令牌服務以及令牌endpoint配置。
配置授權類型(Grant Types),AuthorizationServerEndpointsConfifigurer 通過設定以下屬性決定支持的授權類型(Grant Types):
authenticationManager:
認證管理器,當你選擇了資源所有者密碼(password)授權類型的時候,請設置這個屬性注入一個AuthenticationManager 對象。
userDetailsService:
如果你設置了這個屬性的話,那說明你有一個自己的 UserDetailsService 接口的實現, 或者你可以把這個東西設置到全局域上面去(例如 GlobalAuthenticationManagerConfifigurer 這個配置對象),當你設置了這個之后,那么 “refresh_token” 即刷新令牌授權類型模式的流程中就會包含一個檢查,用來確保這個賬號是否仍然有效,假如說你禁用了這個賬戶的話。
authorizationCodeServices:
這個屬性是用來設置授權碼服務的(AuthorizationCodeServices 的實例對象),主要用于 “authorization_code” 授權碼類型模式。
implicitGrantService:
這個屬性用于設置隱式授權模式,用來管理隱式授權模式的狀態。
tokenGranter:
當你設置了這個東西(即 TokenGranter 接口實現),那么授權將會交由你來完全掌控,并且會忽略掉上面的這幾個屬性,這個屬性一般是用作拓展用途的,即標準的四種授權模式已經滿足不了你的需求的時候,才會考慮使用這個。
5)配置授權端點的URL(Endpoint URLs)
AuthorizationServerEndpointsConfigurer 這個配置對象有一個叫做 pathMapping() 的方法用來配置端點URL鏈接,它有兩個參數:
第一個參數:String 類型的,這個端點URL的默認鏈接。
第二個參數:String 類型的,你要進行替代的URL鏈接。
以上的參數都將以 “/” 字符為開始的字符串,框架的默認URL鏈接如下列表,可以作為這個 pathMapping() 方法的第一個參數:
/oauth/authorize:授權端點。
/oauth/token:令牌端點。
/oauth/confirm_access:用戶確認授權提交端點。
/oauth/error:授權服務錯誤信息端點。
/oauth/check_token:用于資源服務訪問的令牌解析端點。
/oauth/token_key:提供公有密匙的端點,如果你使用JWT令牌的話。
**注意:**授權端點這個URL應該被Spring Security保護起來只供授權用戶訪問.
6)令牌端點的安全約束
AuthorizationServerSecurityConfifigurer:用來配置令牌端點(Token Endpoint)的安全約束,在
AuthorizationServer中配置如下
@Override public void configure(AuthorizationServerSecurityConfigurer security){ security .tokenKeyAccess("permitAll()") (1) // tokenkey這個endpoint當使用JwtToken且使用非對稱加密時,資源服務用于獲取公鑰而開放的,這里指這個endpoint完全公開。 .checkTokenAccess("permitAll()") (2) // checkToken這個endpoint完全公開 .allowFormAuthenticationForClients() (3) // 允許表單認證 ; }授權服務配置總結:授權服務配置分成三大塊,可以關聯記憶。
既然要完成認證,它首先得知道客戶端信息從哪兒讀取,因此要進行客戶端詳情配置。
既然要頒發token,那必須得定義token的相關endpoint,以及token如何存取,以及客戶端支持哪些類型的 token。
既然暴露除了一些endpoint,那對這些endpoint可以定義一些安全上的約束等
7.2.3.授權碼模式
1)資源擁有者打開客戶端,客戶端要求資源擁有者給予授權,它將瀏覽器被重定向到授權服務器,
? 重定向時會附加客戶端的身份信息。如:
/uaa/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com參數列表如下:
client_id:客戶端準入標識。
response_type:授權碼模式固定為code。
scope:客戶端權限。
redirect_uri:跳轉uri,當授權碼申請成功后會跳轉到此地址,并在后邊帶上code參數(授權碼)。
2)瀏覽器出現向授權服務器授權頁面,之后將用戶同意授權。
3)授權服務器將授權碼(AuthorizationCode)轉經瀏覽器發送給client(通過redirect_uri)。
4)客戶端拿著授權碼向授權服務器索要訪問access_token,請求如下:
/uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=authorization_code&code=5PgfcD&redirect_uri=http://w ww.baidu.com參數列表如下
client_id:客戶端準入標識。
client_secret:客戶端秘鑰。
grant_type:授權類型,填寫authorization_code,表示授權碼模式
code:授權碼,就是剛剛獲取的授權碼,注意:授權碼只使用一次就無效了,需要重新申請。
redirect_uri:申請授權碼時的跳轉url,一定和申請授權碼時用的redirect_uri一致。
5)授權服務器返回令牌(access_token)
這種模式是四種模式中最安全的一種模式。一般用于client是Web服務器端應用或第三方的原生App調用資源服務 的時候。因為在這種模式中access_token不會經過瀏覽器或移動端的App,而是直接從服務端去交換,這樣就最大 限度的減小了令牌泄漏的風險。
7.2.4.簡化模式
1)資源擁有者打開客戶端,客戶端要求資源擁有者給予授權,它將瀏覽器被重定向到授權服務器,
? 重定向時會附加客戶端的身份信息。如:
/uaa/oauth/authorize?client_id=c1&response_type=code&scope=all&redirect_uri=http://www.baidu.com參數描述同授權碼模式 ,注意response_type=token,說明是簡化模式。
2)瀏覽器出現向授權服務器授權頁面,之后將用戶同意授權。
3)授權服務器將授權碼將令牌(access_token)以Hash的形式存放在重定向uri的fargment中發送給瀏覽器。
**注:**fragment 主要是用來標識 URI 所標識資源里的某個資源,在 URI 的末尾通過 (#)作為 fragment 的開頭, 其中 # 不屬于 fragment 的值。如https://domain/index#L18這個 URI 中 L18 就是 fragment 的值。只需要知道js通過響應瀏覽器地址欄變化的方式能獲取到fragment 就行了。
一般來說,簡化模式用于沒有服務器端的第三方單頁面應用,因為沒有服務器端就無法接收授權碼。
7.2.5.密碼模式
1)資源擁有者將用戶名、密碼發送給客戶端
2)客戶端拿著資源擁有者的用戶名、密碼向授權服務器請求令牌(access_token),請求如下:
/uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=password&username=shangsan&password=123參數列表如下:
client_id:客戶端準入標識。
client_secret:客戶端秘鑰。
grant_type:授權類型,填寫password表示密碼模式
username:資源擁有者用戶名。
password:資源擁有者密碼。
3)授權服務器將令牌(access_token)發送給client
這種模式十分簡單,但是卻意味著直接將用戶敏感信息泄漏給了client,因此這就說明這種模式只能用于client是我們自己開發的情況下。因此密碼模式一般用于我們自己開發的,第一方原生App或第一方單頁面應用。
7.2.6.客戶端模式
1)客戶端向授權服務器發送自己的身份信息,并請求令牌(access_token)
2)確認客戶端身份無誤后,將令牌(access_token)發送給client,請求如下:
/uaa/oauth/token?client_id=c1&client_secret=secret&grant_type=client_credentials參數列表如下:
client_id:客戶端準入標識。
client_secret:客戶端秘鑰。
grant_type:授權類型,填寫client_credentials表示客戶端模式
這種模式是最方便但最不安全的模式。因此這就要求我們對client完全的信任,而client本身也是安全的。因此這種模式一般用來提供給我們完全信任的服務器端服務。比如,合作方系統對接,拉取一組用戶信息。
7.2.7.資源服務測試
資源服務器配置
@EnableResourceServer 注解到一個 @Confifiguration 配置類上,
并且必須使用 ResourceServerConfigurer 這個配置對象來進行配置(可以選擇繼承自 ResourceServerConfifigurerAdapter 然后覆寫其中的方法,參數就是這個 對象的實例),下面是一些可以配置的屬性:
ResourceServerSecurityConfifigurer中主要包括:
tokenServices:ResourceServerTokenServices 類的實例,用來實現令牌服務。
tokenStore:TokenStore類的實例,指定令牌如何訪問,與tokenServices配置可選
resourceId:這個資源服務的ID,這個屬性是可選的,但是推薦設置并在授權服務中進行驗證。 其他的拓展屬性例如 tokenExtractor 令牌提取器用來提取請求中的令牌。
HttpSecurity配置這個與Spring Security類似:
請求匹配器,用來設置需要進行保護的資源路徑,默認的情況下是保護資源服務的全部路徑。? 通過http.authorizeRequests()來設置受保護資源的訪問規則
? 其他的自定義權限保護規則通過 HttpSecurity 來進行配置。
@EnableResourceServer 注解自動增加了一個類型為 OAuth2AuthenticationProcessingFilter 的過濾器鏈
編寫ResouceServerConfig類 略。。。
驗證token
ResourceServerTokenServices 是組成授權服務的另一半,如果你的授權服務和資源服務在同一個應用程序上的話,你可以使用 DefaultTokenServices ,這樣的話,你就不用考慮關于實現所有必要的接口的一致性問題。如果你的資源服務器是分離開的,那么你就必須要確保能夠有匹配授權服務提供的 ResourceServerTokenServices,它 知道如何對令牌進行解碼。
令牌解析方法:
使用 DefaultTokenServices 在資源服務器本地配置令牌存儲、解碼、解析方式 使用RemoteTokenServices 資源服務器通過 HTTP 請求來解碼令牌,每次都請求授權服務器端點 /oauth/check_token使用授權服務的 /oauth/check_token 端點你需要在授權服務將這個端點暴露出去,以便資源服務可以進行訪問,
7.3 JWT令牌
通過上邊的測試我們發現,當資源服務和授權服務不在一起時資源服務使用RemoteTokenServices 遠程請求授權服務驗證token,
如果訪問量較大將會影響系統的性能 。
解決上邊問題:
令牌采用JWT格式即可解決上邊的問題,用戶認證通過會得到一個JWT令牌,
JWT令牌中已經包括了用戶相關的信 息,客戶端只需要攜帶JWT訪問資源服務,資源服務根據事先約定的算法自行完成令牌校驗,無需每次都請求認證服務完成授權。
什么是JWT?
JSON Web Token(JWT)是一個開放的行業標準(RFC 7519),它定義了一種簡介的、自包含的協議格式,用于 在通信雙方傳遞json對象,傳遞的信息經過數字簽名可以被驗證和信任。JWT可以使用HMAC算法或使用RSA的公 鑰/私鑰對來簽名,防止被篡改。
官網:https://jwt.io/
標準:https://tools.ietf.org/html/rfc7519
JWT令牌的優點:
1)jwt基于json,非常方便解析。
2)可以在令牌中自定義豐富的內容,易擴展。
3)通過非對稱加密算法及數字簽名技術,JWT防止篡改,安全性高。
4)資源服務使用JWT可不依賴認證服務即可完成授權。
缺點:
1)JWT令牌較長,占存儲空間比較大。
JWT令牌結構
通過學習JWT令牌結構為自定義jwt令牌打好基礎。
JWT令牌由三部分組成,每部分中間使用點 . 分隔,比如:xxxxx.yyyyy.zzzzz
base64UrlEncode(header):jwt令牌的第一部分。
base64UrlEncode(payload):jwt令牌的第二部分。
secret:簽名所使用的密鑰。jwt令牌的第三部分 。
Header
頭部包括令牌的類型(即JWT)及使用的哈希算法(如HMAC SHA256或RSA)
一個例子如下: 下邊是Header部分的內容
{ "alg": "HS256", "typ": "JWT" }將上邊的內容使用Base64Url編碼,得到一個字符串就是JWT令牌的第一部分
Payload
第二部分是負載,內容也是一個json對象,它是存放有效信息的地方,它可以存放jwt提供的現成字段,比 如:
iss(簽發者) exp(過期時間戳) sub(面向的用戶)等,也可自定義字段。
此部分不建議存放敏感信息,因為此部分可以解碼還原原始內容。
最后將第二部分負載使用Base64Url編碼,得到一個字符串就是JWT令牌的第二部分。
{ "sub": "1234567890", "name": "456", "admin": true }Signature
第三部分是簽名,此部分用于防止jwt內容被篡改。
這個部分使用base64url將前兩部分進行編碼,編碼后使用點 . 連接組成字符串,最后使用header中聲明簽名算法進行簽名。
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)8、Spring Security實現分布式系統授權
d336a09ff60a21bcd2950960b34d999e
需求分析
1、UAA認證服務負責認證授權。
2、所有請求經過 網關到達微服務
3、網關負責鑒權客戶端以及請求轉發
4、網關將token解析后傳給微服務,微服務進行授權。
所有微服務的請求都經過網關,網關從注冊中心讀取微服務的地址,將請求轉發至微服務。
網關
網關整合 OAuth2.0 有兩種思路,一種是認證服務器生成jwt令牌, 所有請求統一在網關層驗證,判斷權限等操作;
另一種是由各資源服務處理,網關只做請求轉發。
我們選用第一種。我們把API網關作為OAuth2.0的資源服務器角色,實現接入客戶端權限攔截、令牌解析并轉發當
前登錄用戶信息(jsonToken)給微服務,這樣下游微服務就不需要關心令牌格式解析以及OAuth2.0相關機制了。
API網關在認證授權體系里主要負責兩件事:
(1)作為OAuth2.0的資源服務器角色,實現接入方權限攔截。
(2)令牌解析并轉發當前登錄用戶信息(明文token)給微服務
微服務拿到明文token(明文token中包含登錄用戶的身份和權限信息)后也需要做兩件事:
(1)用戶授權攔截(看當前用戶是否有權訪問該資源)
(2)將用戶信息存儲進當前線程上下文(有利于后續業務邏輯隨時獲取當前用戶信息)
其他代碼略。。。。。。。。
總結
以上是生活随笔為你收集整理的Spring Security OAuth2.0认证授权的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: git 不小心删除本地文件恢复
- 下一篇: 50 道 经典 Spring 面试题